1use std::{collections::HashMap, fmt, path::PathBuf};
62
63use crate::{
64 parse::parse_next,
65 v1::{self, Cgroup, CgroupPath},
66 Error, ErrorKind, Max, Result,
67};
68
69#[derive(Debug)]
71pub struct Subsystem {
72 path: CgroupPath,
73}
74
75#[derive(Debug, Default, Clone, PartialEq, Eq)]
79pub struct Resources {
80 pub max: HashMap<String, Limit>,
85}
86
87#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
89pub struct Limit {
90 pub hca_handle: Max,
92 pub hca_object: Max,
94}
95
96impl_cgroup! {
97 Subsystem, Rdma,
98
99 fn apply(&mut self, resources: &v1::Resources) -> Result<()> {
101 let max = &resources.rdma.max;
102
103 if max.is_empty() {
104 Ok(())
105 } else {
106 self.set_max(max.iter())
107 }
108 }
109}
110
111impl Subsystem {
112 gen_getter!(
113 rdma, "the current usage of RDMA/IB devices",
114 current, HashMap<String, Limit>, parse_limits
115 );
116
117 gen_getter!(
118 rdma, "the usage limits on RDMA/IB devices",
119 max : link, HashMap<String, Limit>, parse_limits
120 );
121
122 with_doc! { concat!(
123 gen_doc!(
124 sets; "rdma.max",
125 "usage limits on RDMA/IB devices"
126 : "The first element of the iterator item is device name,
127 and the second is limit for the device."
128 ),
129 gen_doc!(see; max),
130 gen_doc!(err_write; "rdma.max"),
131 gen_doc!(
132 eg_write;
133 rdma,
134 set_max,
135 [(
136 "mlx4_0",
137 rdma::Limit { hca_handle: 3.into(), hca_object: controlgroup::Max::Max }
138 )].iter()
139 )),
140 pub fn set_max<I, T, K>(&mut self, limits: I) -> Result<()>
141 where
142 I: Iterator<Item = T>,
143 T: crate::RefKv<K, Limit>,
144 K: fmt::Display,
145 {
146 use std::io::Write;
147
148 let mut file = self.open_file_write("rdma.max")?;
149 for lim in limits {
150 let (device, limit) = lim.ref_kv();
151
152 file.write_all(format!("{} {}", device, limit).as_bytes())?;
154 }
155
156 Ok(())
157 }
158 }
159}
160
161fn parse_limits(reader: impl std::io::Read) -> Result<HashMap<String, Limit>> {
162 use std::io::{BufRead, BufReader};
163
164 let mut result = HashMap::new();
165 let buf = BufReader::new(reader);
166
167 for line in buf.lines() {
168 let line = line?;
169 let mut entry = line.split_whitespace();
170
171 let device = entry.next().ok_or_else(|| Error::new(ErrorKind::Parse))?;
172
173 let (mut hca_handle, mut hca_object) = (None, None);
174 for e in entry.by_ref().take(2) {
175 let mut kv = e.split('=');
176
177 match kv.next() {
178 Some("hca_handle") => {
180 if hca_handle.is_some() {
181 bail_parse!();
182 }
183 hca_handle = Some(parse_next(kv)?);
184 }
185 Some("hca_object") => {
186 if hca_object.is_some() {
187 bail_parse!();
188 }
189 hca_object = Some(parse_next(kv)?);
190 }
191 _ => {
192 bail_parse!();
193 }
194 }
195 }
196
197 match (hca_handle, hca_object, entry.next()) {
198 (Some(hca_handle), Some(hca_object), None) => {
199 result.insert(
200 device.to_string(),
201 Limit {
202 hca_handle,
203 hca_object,
204 },
205 );
206 }
207 _ => {
208 bail_parse!();
209 }
210 }
211 }
212
213 Ok(result)
214}
215
216impl Into<v1::Resources> for Resources {
217 fn into(self) -> v1::Resources {
218 v1::Resources {
219 rdma: self,
220 ..v1::Resources::default()
221 }
222 }
223}
224
225impl fmt::Display for Limit {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 write!(
228 f,
229 "hca_handle={} hca_object={}",
230 self.hca_handle, self.hca_object
231 )
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use v1::SubsystemKind;
239
240 #[test]
241 #[ignore] fn test_subsystem_create_file_exists() -> Result<()> {
243 gen_subsystem_test!(Rdma, ["current", "max"])
244 }
245
246 #[test]
247 fn test_subsystem_apply() -> Result<()> {
248 Ok(())
251 }
252
253 #[test]
254 #[ignore] fn test_subsystem_current() -> Result<()> {
256 let mut cgroup = Subsystem::new(CgroupPath::new(SubsystemKind::Rdma, gen_cgroup_name!()));
257 cgroup.create()?;
258
259 let _ = cgroup.current()?;
260
261 cgroup.delete()
262 }
263
264 #[test]
265 #[ignore] fn test_subsystem_max() -> Result<()> {
267 let mut cgroup = Subsystem::new(CgroupPath::new(SubsystemKind::Rdma, gen_cgroup_name!()));
268 cgroup.create()?;
269
270 let mut limits = cgroup.max()?;
271 for (_, limit) in limits.iter_mut() {
272 limit.hca_handle = match limit.hca_handle {
273 Max::Max => Max::Limit(3),
274 Max::Limit(_) => Max::Max,
275 };
276
277 limit.hca_object = match limit.hca_object {
278 Max::Max => Max::Limit(3000),
279 Max::Limit(_) => Max::Max,
280 };
281 }
282
283 cgroup.set_max(limits.iter())?;
284 assert_eq!(cgroup.max()?, limits);
285
286 cgroup.delete()
287 }
288
289 #[test]
290 fn test_parse_limits() -> Result<()> {
291 const CONTENT_OK_0: &str = "\
292mlx4_0 hca_handle=2 hca_object=2000
293ocrdma1 hca_handle=3 hca_object=max
294";
295
296 const CONTENT_OK_1: &str = "\
297mlx4_0 hca_object=2000 hca_handle=2
298ocrdma1 hca_object=max hca_handle=3
299";
300
301 let expected = hashmap! {
302 (
303 "mlx4_0".to_string(),
304 Limit {
305 hca_handle: Max::Limit(2),
306 hca_object: Max::Limit(2000),
307 },
308 ),
309 (
310 "ocrdma1".to_string(),
311 Limit {
312 hca_handle: Max::Limit(3),
313 hca_object: Max::Max,
314 },
315 ),
316 };
317
318 assert_eq!(parse_limits(CONTENT_OK_0.as_bytes())?, expected);
319 assert_eq!(parse_limits(CONTENT_OK_1.as_bytes())?, expected);
320
321 assert!(parse_limits("".as_bytes())?.is_empty());
322
323 const CONTENT_NG_NOT_INT: &str = "\
324mlx4_0 hca_object=invalid hca_handle=2000
325";
326
327 const CONTENT_NG_INVALID_KEY: &str = "\
328mlx4_0 invalid=2
329";
330
331 const CONTENT_NG_MISSING_DATA: &str = "\
332mlx4_0 hca_object=2
333";
334
335 const CONTENT_NG_EXTRA_DATA: &str = "\
336mlx4_0 hca_object=2 hca_handle=2000 invalid
337";
338
339 for case in &[
340 CONTENT_NG_NOT_INT,
341 CONTENT_NG_INVALID_KEY,
342 CONTENT_NG_MISSING_DATA,
343 CONTENT_NG_EXTRA_DATA,
344 ] {
345 assert_eq!(
346 parse_limits(case.as_bytes()).unwrap_err().kind(),
347 ErrorKind::Parse
348 );
349 }
350
351 Ok(())
352 }
353}