oci_spec/runtime/
process.rs

1use crate::{
2    error::OciSpecError,
3    runtime::{Capabilities, Capability},
4};
5use derive_builder::Builder;
6use getset::{CopyGetters, Getters, MutGetters, Setters};
7use regex::Regex;
8use serde::{de, Deserialize, Deserializer, Serialize};
9use std::path::PathBuf;
10use std::sync::OnceLock;
11use strum_macros::{Display as StrumDisplay, EnumString};
12
13#[derive(
14    Builder,
15    Clone,
16    CopyGetters,
17    Debug,
18    Deserialize,
19    Getters,
20    MutGetters,
21    Setters,
22    Eq,
23    PartialEq,
24    Serialize,
25)]
26#[serde(rename_all = "camelCase")]
27#[builder(
28    default,
29    pattern = "owned",
30    setter(into, strip_option),
31    build_fn(error = "OciSpecError")
32)]
33/// Process contains information to start a specific application inside the
34/// container.
35pub struct Process {
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    #[getset(get_copy = "pub", set = "pub")]
38    /// Terminal creates an interactive terminal for the container.
39    terminal: Option<bool>,
40
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    #[getset(get_copy = "pub", set = "pub")]
43    /// ConsoleSize specifies the size of the console.
44    console_size: Option<Box>,
45
46    #[getset(get_mut = "pub", get = "pub", set = "pub")]
47    /// User specifies user information for the process.
48    user: User,
49
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    #[getset(get = "pub", set = "pub")]
52    /// Args specifies the binary and arguments for the application to
53    /// execute.
54    args: Option<Vec<String>>,
55
56    #[serde(default, skip_serializing_if = "Option::is_none")]
57    #[getset(get_mut = "pub", get = "pub", set = "pub")]
58    /// CommandLine specifies the full command line for the application to
59    /// execute on Windows.
60    command_line: Option<String>,
61
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    #[getset(get_mut = "pub", get = "pub", set = "pub")]
64    /// Env populates the process environment for the process.
65    env: Option<Vec<String>>,
66
67    #[getset(get = "pub", set = "pub")]
68    /// Cwd is the current working directory for the process and must be
69    /// relative to the container's root.
70    cwd: PathBuf,
71
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    #[getset(get = "pub", set = "pub")]
74    /// Capabilities are Linux capabilities that are kept for the process.
75    capabilities: Option<LinuxCapabilities>,
76
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    #[getset(get = "pub", set = "pub")]
79    /// Rlimits specifies rlimit options to apply to the process.
80    rlimits: Option<Vec<PosixRlimit>>,
81
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    #[getset(get_copy = "pub", set = "pub")]
84    /// NoNewPrivileges controls whether additional privileges could be
85    /// gained by processes in the container.
86    no_new_privileges: Option<bool>,
87
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    #[getset(get = "pub", set = "pub")]
90    /// ApparmorProfile specifies the apparmor profile for the container.
91    apparmor_profile: Option<String>,
92
93    #[serde(skip_serializing_if = "Option::is_none")]
94    #[getset(get_copy = "pub", set = "pub")]
95    /// Specify an oom_score_adj for the container.
96    oom_score_adj: Option<i32>,
97
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    #[getset(get = "pub", set = "pub")]
100    /// SelinuxLabel specifies the selinux context that the container
101    /// process is run as.
102    selinux_label: Option<String>,
103
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    #[getset(get = "pub", set = "pub")]
106    /// IOPriority contains the I/O priority settings for the cgroup.
107    io_priority: Option<LinuxIOPriority>,
108
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    #[getset(get = "pub", set = "pub")]
111    /// Scheduler specifies the scheduling attributes for a process
112    scheduler: Option<Scheduler>,
113
114    #[serde(default, skip_serializing_if = "Option::is_none")]
115    #[getset(get = "pub", set = "pub")]
116    /// ExecCPUAffinity specifies the cpu affinity for a process
117    exec_cpu_affinity: Option<ExecCPUAffinity>,
118}
119
120// Default impl for processes in the container
121impl Default for Process {
122    fn default() -> Self {
123        Process {
124            // Don't create an interactive terminal for container by default
125            terminal: false.into(),
126            // Gives default console size of 0, 0
127            console_size: Default::default(),
128            // Gives process a uid and gid of 0 (root)
129            user: Default::default(),
130            // By default executes sh command, giving user shell
131            args: vec!["sh".to_string()].into(),
132            // Sets linux default enviroment for binaries and default xterm emulator
133            env: vec![
134                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".into(),
135                "TERM=xterm".into(),
136            ]
137            .into(),
138            // Sets cwd of process to the container root by default
139            cwd: "/".into(),
140            // By default does not allow process to gain additional privileges
141            no_new_privileges: true.into(),
142            // Empty String, no default apparmor
143            apparmor_profile: Default::default(),
144            // Empty String, no default selinux
145            selinux_label: Default::default(),
146            // Empty String, no default scheduler
147            scheduler: Default::default(),
148            // See impl Default for LinuxCapabilities
149            capabilities: Some(Default::default()),
150            // Sets the default maximum of 1024 files the process can open
151            // This is the same as the linux kernel default
152            rlimits: vec![PosixRlimit {
153                typ: PosixRlimitType::RlimitNofile,
154                hard: 1024,
155                soft: 1024,
156            }]
157            .into(),
158            oom_score_adj: None,
159            command_line: None,
160            // Empty IOPriority, no default iopriority
161            io_priority: Default::default(),
162            exec_cpu_affinity: Default::default(),
163        }
164    }
165}
166
167#[derive(
168    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,
169)]
170#[builder(
171    default,
172    pattern = "owned",
173    setter(into, strip_option),
174    build_fn(error = "OciSpecError")
175)]
176#[getset(get_copy = "pub", set = "pub")]
177/// Box specifies dimensions of a rectangle. Used for specifying the size of
178/// a console.
179pub struct Box {
180    #[serde(default)]
181    /// Height is the vertical dimension of a box.
182    height: u64,
183
184    #[serde(default)]
185    /// Width is the horizontal dimension of a box.
186    width: u64,
187}
188
189#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
190#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
191#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
192/// Available rlimit types (see <https://man7.org/linux/man-pages/man2/getrlimit.2.html>)
193pub enum PosixRlimitType {
194    /// Limit in seconds of the amount of CPU time that the process can consume.
195    RlimitCpu,
196
197    /// Maximum size in bytes of the files that the process creates.
198    RlimitFsize,
199
200    /// Maximum size of the process's data segment (init data, uninit data and
201    /// heap) in bytes.
202    RlimitData,
203
204    /// Maximum size of the proces stack in bytes.
205    RlimitStack,
206
207    /// Maximum size of a core dump file in bytes.
208    RlimitCore,
209
210    /// Limit on the process's resident set (the number of virtual pages
211    /// resident in RAM).
212    RlimitRss,
213
214    /// Limit on number of threads for the real uid calling processes.
215    RlimitNproc,
216
217    /// One greator than the maximum number of file descritors that one process
218    /// may open.
219    RlimitNofile,
220
221    /// Maximum number of bytes of memory that may be locked into RAM.
222    RlimitMemlock,
223
224    /// Maximum size of the process's virtual memory(address space) in bytes.
225    RlimitAs,
226
227    /// Limit on the number of locks and leases for the process.
228    RlimitLocks,
229
230    /// Limit on number of signals that may be queued for the process.
231    RlimitSigpending,
232
233    /// Limit on the number of bytes that can be allocated for POSIX message
234    /// queue.
235    RlimitMsgqueue,
236
237    /// Specifies a ceiling to which the process's nice value can be raised.
238    RlimitNice,
239
240    /// Specifies a ceiling on the real-time priority.
241    RlimitRtprio,
242
243    /// This is a limit (in microseconds) on the amount of CPU time that a
244    /// process scheduled under a real-time scheduling policy may consume
245    /// without making a blocking system call.
246    RlimitRttime,
247}
248
249impl Default for PosixRlimitType {
250    fn default() -> Self {
251        Self::RlimitCpu
252    }
253}
254
255#[derive(
256    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,
257)]
258#[builder(
259    default,
260    pattern = "owned",
261    setter(into, strip_option),
262    build_fn(error = "OciSpecError")
263)]
264#[getset(get_copy = "pub", set = "pub")]
265/// RLimit types and restrictions.
266pub struct PosixRlimit {
267    #[serde(rename = "type")]
268    /// Type of Rlimit to set
269    typ: PosixRlimitType,
270
271    #[serde(default)]
272    /// Hard limit for specified type
273    hard: u64,
274
275    #[serde(default)]
276    /// Soft limit for specified type
277    soft: u64,
278}
279
280#[derive(
281    Builder,
282    Clone,
283    CopyGetters,
284    Debug,
285    Default,
286    Deserialize,
287    Getters,
288    MutGetters,
289    Setters,
290    Eq,
291    PartialEq,
292    Serialize,
293)]
294#[serde(rename_all = "camelCase")]
295#[builder(
296    default,
297    pattern = "owned",
298    setter(into, strip_option),
299    build_fn(error = "OciSpecError")
300)]
301/// User id (uid) and group id (gid) tracks file permssions.
302pub struct User {
303    #[serde(default)]
304    #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
305    /// UID is the user id.
306    uid: u32,
307
308    #[serde(default)]
309    #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
310    /// GID is the group id.
311    gid: u32,
312
313    #[serde(default, skip_serializing_if = "Option::is_none")]
314    #[getset(get_mut = "pub", get_copy = "pub", set = "pub")]
315    /// Specifies the umask of the user.
316    umask: Option<u32>,
317
318    #[serde(default, skip_serializing_if = "Option::is_none")]
319    #[getset(get_mut = "pub", get = "pub", set = "pub")]
320    /// AdditionalGids are additional group ids set for the container's
321    /// process.
322    additional_gids: Option<Vec<u32>>,
323
324    #[serde(default, skip_serializing_if = "Option::is_none")]
325    #[getset(get_mut = "pub", get = "pub", set = "pub")]
326    /// Username is the user name.
327    username: Option<String>,
328}
329
330#[derive(Builder, Clone, Debug, Deserialize, Getters, Setters, Eq, PartialEq, Serialize)]
331#[builder(
332    default,
333    pattern = "owned",
334    setter(into, strip_option),
335    build_fn(error = "OciSpecError")
336)]
337#[getset(get = "pub", set = "pub")]
338/// LinuxCapabilities specifies the list of allowed capabilities that are
339/// kept for a process. <http://man7.org/linux/man-pages/man7/capabilities.7.html>
340pub struct LinuxCapabilities {
341    #[serde(default, skip_serializing_if = "Option::is_none")]
342    /// Bounding is the set of capabilities checked by the kernel.
343    bounding: Option<Capabilities>,
344
345    #[serde(default, skip_serializing_if = "Option::is_none")]
346    /// Effective is the set of capabilities checked by the kernel.
347    effective: Option<Capabilities>,
348
349    #[serde(default, skip_serializing_if = "Option::is_none")]
350    /// Inheritable is the capabilities preserved across execve.
351    inheritable: Option<Capabilities>,
352
353    #[serde(default, skip_serializing_if = "Option::is_none")]
354    /// Permitted is the limiting superset for effective capabilities.
355    permitted: Option<Capabilities>,
356
357    #[serde(default, skip_serializing_if = "Option::is_none")]
358    /// Ambient is the ambient set of capabilities that are kept.
359    ambient: Option<Capabilities>,
360}
361
362// Default container's linux capabilities:
363// CAP_AUDIT_WRITE gives container ability to write to linux audit logs,
364// CAP_KILL gives container ability to kill non root processes
365// CAP_NET_BIND_SERVICE allows container to bind to ports below 1024
366impl Default for LinuxCapabilities {
367    fn default() -> Self {
368        let audit_write = Capability::AuditWrite;
369        let cap_kill = Capability::Kill;
370        let net_bind = Capability::NetBindService;
371        let default_vec = vec![audit_write, cap_kill, net_bind]
372            .into_iter()
373            .collect::<Capabilities>();
374        LinuxCapabilities {
375            bounding: default_vec.clone().into(),
376            effective: default_vec.clone().into(),
377            inheritable: default_vec.clone().into(),
378            permitted: default_vec.clone().into(),
379            ambient: default_vec.into(),
380        }
381    }
382}
383
384#[derive(
385    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,
386)]
387#[builder(
388    default,
389    pattern = "owned",
390    setter(into, strip_option),
391    build_fn(error = "OciSpecError")
392)]
393#[getset(get_copy = "pub", set = "pub")]
394/// RLimit types and restrictions.
395pub struct LinuxIOPriority {
396    #[serde(default)]
397    /// Class represents an I/O scheduling class.
398    class: IOPriorityClass,
399
400    #[serde(default)]
401    /// Priority for the io operation
402    priority: i64,
403}
404
405#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
406#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
407#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
408/// IOPriorityClass represents an I/O scheduling class.
409pub enum IOPriorityClass {
410    /// This is the realtime io class. This scheduling class is given
411    /// higher priority than any other in the system, processes from this class are
412    /// given first access to the disk every time. Thus it needs to be used with some
413    /// care, one io RT process can starve the entire system. Within the RT class,
414    /// there are 8 levels of class data that determine exactly how much time this
415    /// process needs the disk for on each service. In the future this might change
416    /// to be more directly mappable to performance, by passing in a wanted data
417    /// rate instead
418    IoprioClassRt,
419    /// This is the best-effort scheduling class, which is the default
420    /// for any process that hasn't set a specific io priority. The class data
421    /// determines how much io bandwidth the process will get, it's directly mappable
422    /// to the cpu nice levels just more coarsely implemented. 0 is the highest
423    /// BE prio level, 7 is the lowest. The mapping between cpu nice level and io
424    /// nice level is determined as: io_nice = (cpu_nice + 20) / 5.
425    IoprioClassBe,
426    /// This is the idle scheduling class, processes running at this
427    /// level only get io time when no one else needs the disk. The idle class has no
428    /// class data, since it doesn't really apply here.
429    IoprioClassIdle,
430}
431
432impl Default for IOPriorityClass {
433    fn default() -> Self {
434        Self::IoprioClassBe
435    }
436}
437
438#[derive(Builder, Clone, Debug, Deserialize, Getters, Setters, Eq, PartialEq, Serialize)]
439#[builder(
440    default,
441    pattern = "owned",
442    setter(into, strip_option),
443    build_fn(error = "OciSpecError")
444)]
445#[getset(get = "pub", set = "pub")]
446/// Scheduler represents the scheduling attributes for a process. It is based on
447/// the Linux sched_setattr(2) syscall.
448pub struct Scheduler {
449    /// Policy represents the scheduling policy (e.g., SCHED_FIFO, SCHED_RR, SCHED_OTHER).
450    policy: LinuxSchedulerPolicy,
451
452    #[serde(default, skip_serializing_if = "Option::is_none")]
453    /// Nice is the nice value for the process, which affects its priority.
454    nice: Option<i32>,
455
456    #[serde(default, skip_serializing_if = "Option::is_none")]
457    /// Priority represents the static priority of the process.
458    priority: Option<i32>,
459
460    #[serde(default, skip_serializing_if = "Option::is_none")]
461    /// Flags is an array of scheduling flags.
462    flags: Option<Vec<LinuxSchedulerFlag>>,
463
464    // The following ones are used by the DEADLINE scheduler.
465    #[serde(default, skip_serializing_if = "Option::is_none")]
466    /// Runtime is the amount of time in nanoseconds during which the process
467    /// is allowed to run in a given period.
468    runtime: Option<u64>,
469
470    #[serde(default, skip_serializing_if = "Option::is_none")]
471    /// Deadline is the absolute deadline for the process to complete its execution.
472    deadline: Option<u64>,
473
474    #[serde(default, skip_serializing_if = "Option::is_none")]
475    /// Period is the length of the period in nanoseconds used for determining the process runtime.
476    period: Option<u64>,
477}
478
479/// Default scheduler is SCHED_OTHER with no priority.
480impl Default for Scheduler {
481    fn default() -> Self {
482        Self {
483            policy: LinuxSchedulerPolicy::default(),
484            nice: None,
485            priority: None,
486            flags: None,
487            runtime: None,
488            deadline: None,
489            period: None,
490        }
491    }
492}
493
494#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
495#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
496#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
497///  LinuxSchedulerPolicy represents different scheduling policies used with the Linux Scheduler
498pub enum LinuxSchedulerPolicy {
499    /// SchedOther is the default scheduling policy
500    SchedOther,
501    /// SchedFIFO is the First-In-First-Out scheduling policy
502    SchedFifo,
503    /// SchedRR is the Round-Robin scheduling policy
504    SchedRr,
505    /// SchedBatch is the Batch scheduling policy
506    SchedBatch,
507    /// SchedISO is the Isolation scheduling policy
508    SchedIso,
509    /// SchedIdle is the Idle scheduling policy
510    SchedIdle,
511    /// SchedDeadline is the Deadline scheduling policy
512    SchedDeadline,
513}
514
515/// Default LinuxSchedulerPolicy is SchedOther
516impl Default for LinuxSchedulerPolicy {
517    fn default() -> Self {
518        LinuxSchedulerPolicy::SchedOther
519    }
520}
521
522#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
523#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
524#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
525///  LinuxSchedulerFlag represents the flags used by the Linux Scheduler.
526pub enum LinuxSchedulerFlag {
527    /// SchedFlagResetOnFork represents the reset on fork scheduling flag
528    SchedResetOnFork,
529    /// SchedFlagReclaim represents the reclaim scheduling flag
530    SchedFlagReclaim,
531    /// SchedFlagDLOverrun represents the deadline overrun scheduling flag
532    SchedFlagDLOverrun,
533    /// SchedFlagKeepPolicy represents the keep policy scheduling flag
534    SchedFlagKeepPolicy,
535    /// SchedFlagKeepParams represents the keep parameters scheduling flag
536    SchedFlagKeepParams,
537    /// SchedFlagUtilClampMin represents the utilization clamp minimum scheduling flag
538    SchedFlagUtilClampMin,
539    /// SchedFlagUtilClampMin represents the utilization clamp maximum scheduling flag
540    SchedFlagUtilClampMax,
541}
542
543/// Default LinuxSchedulerFlag is SchedResetOnFork
544impl Default for LinuxSchedulerFlag {
545    fn default() -> Self {
546        LinuxSchedulerFlag::SchedResetOnFork
547    }
548}
549
550#[derive(
551    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,
552)]
553#[builder(
554    default,
555    pattern = "owned",
556    setter(into, strip_option),
557    build_fn(validate = "Self::validate", error = "OciSpecError")
558)]
559#[getset(get = "pub", set = "pub")]
560/// ExecCPUAffinity specifies CPU affinity used to execute the process.
561/// This setting is not applicable to the container's init process.
562pub struct ExecCPUAffinity {
563    #[serde(
564        default,
565        skip_serializing_if = "Option::is_none",
566        deserialize_with = "deserialize"
567    )]
568    /// cpu_affinity_initial is a list of CPUs a runtime parent process to be run on
569    /// initially, before the transition to container's cgroup.
570    /// This is a a comma-separated list, with dashes to represent ranges.
571    /// For example, `0-3,7` represents CPUs 0,1,2,3, and 7.
572    cpu_affinity_initial: Option<String>,
573
574    #[serde(
575        default,
576        skip_serializing_if = "Option::is_none",
577        deserialize_with = "deserialize"
578    )]
579    /// cpu_affinity_final is a list of CPUs the process will be run on after the transition
580    /// to container's cgroup. The format is the same as for `initial`. If omitted or empty,
581    /// runtime SHOULD NOT change process' CPU affinity after the process is moved to
582    /// container's cgroup, and the final affinity is determined by the Linux kernel.
583    cpu_affinity_final: Option<String>,
584}
585
586impl ExecCPUAffinityBuilder {
587    fn validate(&self) -> Result<(), OciSpecError> {
588        if let Some(Some(ref s)) = self.cpu_affinity_initial {
589            validate_cpu_affinity(s).map_err(|e| OciSpecError::Other(e.to_string()))?;
590        }
591
592        if let Some(Some(ref s)) = self.cpu_affinity_final {
593            validate_cpu_affinity(s).map_err(|e| OciSpecError::Other(e.to_string()))?;
594        }
595
596        Ok(())
597    }
598}
599
600fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
601where
602    D: Deserializer<'de>,
603{
604    let value: Option<String> = Option::deserialize(deserializer)?;
605
606    if let Some(ref s) = value {
607        validate_cpu_affinity(s).map_err(de::Error::custom)?;
608    }
609
610    Ok(value)
611}
612
613fn exec_cpu_affinity_regex() -> &'static Regex {
614    static EXEC_CPU_AFFINITY_REGEX: OnceLock<Regex> = OnceLock::new();
615    EXEC_CPU_AFFINITY_REGEX.get_or_init(|| {
616        Regex::new(r"^(\d+(-\d+)?)(,\d+(-\d+)?)*$")
617            .expect("Failed to create regex for execCPUAffinity")
618    })
619}
620
621fn validate_cpu_affinity(s: &str) -> Result<(), String> {
622    if !exec_cpu_affinity_regex().is_match(s) {
623        return Err(format!("Invalid execCPUAffinity format: {}", s));
624    }
625
626    Ok(())
627}
628
629#[cfg(test)]
630mod tests {
631    use super::*;
632    use serde_json::json;
633
634    // PosixRlimitType test cases
635    #[test]
636    fn posix_rlimit_type_enum_to_string() {
637        let type_a = PosixRlimitType::RlimitCpu;
638        assert_eq!(type_a.to_string(), "RLIMIT_CPU");
639
640        let type_b = PosixRlimitType::RlimitData;
641        assert_eq!(type_b.to_string(), "RLIMIT_DATA");
642
643        let type_c = PosixRlimitType::RlimitNofile;
644        assert_eq!(type_c.to_string(), "RLIMIT_NOFILE");
645    }
646
647    #[test]
648    fn posix_rlimit_type_string_to_enum() {
649        let posix_rlimit_type_str = "RLIMIT_CPU";
650        let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();
651        assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitCpu);
652
653        let posix_rlimit_type_str = "RLIMIT_DATA";
654        let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();
655        assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitData);
656
657        let posix_rlimit_type_str = "RLIMIT_NOFILE";
658        let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();
659        assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitNofile);
660
661        let invalid_posix_rlimit_type_str = "x";
662        let unknown_rlimit = invalid_posix_rlimit_type_str.parse::<PosixRlimitType>();
663        assert!(unknown_rlimit.is_err());
664    }
665
666    #[test]
667    fn exec_cpu_affinity_valid_initial_final() {
668        let json = json!({"cpu_affinity_initial": "0-3,7", "cpu_affinity_final": "4-6,8"});
669        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
670        assert!(result.is_ok());
671
672        let json = json!({"cpu_affinity_initial": "0-3", "cpu_affinity_final": "4-6"});
673        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
674        assert!(result.is_ok());
675
676        let json = json!({"cpu_affinity_initial": "0", "cpu_affinity_final": "4"});
677        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
678        assert!(result.is_ok());
679    }
680
681    #[test]
682    fn exec_cpu_affinity_invalid_initial() {
683        let json = json!({"cpu_affinity_initial": "0-3,,7", "cpu_affinity_final": "4-6,8"});
684        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
685        assert!(result.is_err());
686    }
687
688    #[test]
689    fn exec_cpu_affinity_invalid_final() {
690        let json = json!({"cpu_affinity_initial": "0-3,7", "cpu_affinity_final": "4-6.,8"});
691        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
692        assert!(result.is_err());
693    }
694
695    #[test]
696    fn exec_cpu_affinity_valid_final() {
697        let json = json!({"cpu_affinity_final": "0,1,2,3"});
698        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
699        assert!(result.is_ok());
700        assert!(result.unwrap().cpu_affinity_initial.is_none());
701    }
702
703    #[test]
704    fn exec_cpu_affinity_valid_initial() {
705        let json = json!({"cpu_affinity_initial": "0-1,2-5"});
706        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
707        assert!(result.is_ok());
708        assert!(result.unwrap().cpu_affinity_final.is_none());
709    }
710
711    #[test]
712    fn exec_cpu_affinity_empty() {
713        let json = json!({});
714        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);
715        assert!(result.is_ok());
716        let affinity = result.unwrap();
717        assert!(affinity.cpu_affinity_initial.is_none());
718        assert!(affinity.cpu_affinity_final.is_none());
719    }
720
721    #[test]
722    fn test_build_valid_input() {
723        let affinity = ExecCPUAffinityBuilder::default()
724            .cpu_affinity_initial("0-3,7,8,9,10".to_string())
725            .cpu_affinity_final("4-6,8".to_string())
726            .build();
727        assert!(affinity.is_ok());
728        let affinity = affinity.unwrap();
729        assert_eq!(
730            affinity.cpu_affinity_initial,
731            Some("0-3,7,8,9,10".to_string())
732        );
733        assert_eq!(affinity.cpu_affinity_final, Some("4-6,8".to_string()));
734    }
735
736    #[test]
737    fn test_build_invalid_initial() {
738        let affinity = ExecCPUAffinityBuilder::default()
739            .cpu_affinity_initial("0-3,i".to_string())
740            .cpu_affinity_final("4-6,8".to_string())
741            .build();
742        let err = affinity.unwrap_err();
743        assert_eq!(err.to_string(), "Invalid execCPUAffinity format: 0-3,i");
744
745        let affinity = ExecCPUAffinityBuilder::default()
746            .cpu_affinity_initial("-".to_string())
747            .cpu_affinity_final("4-6,8".to_string())
748            .build();
749        let err = affinity.unwrap_err();
750        assert_eq!(err.to_string(), "Invalid execCPUAffinity format: -");
751    }
752
753    #[test]
754    fn test_build_invalid_final() {
755        let affinity = ExecCPUAffinityBuilder::default()
756            .cpu_affinity_initial("0-3,7".to_string())
757            .cpu_affinity_final("0-l1".to_string())
758            .build();
759        let err = affinity.unwrap_err();
760        assert_eq!(err.to_string(), "Invalid execCPUAffinity format: 0-l1");
761
762        let affinity = ExecCPUAffinityBuilder::default()
763            .cpu_affinity_initial("0-3,7".to_string())
764            .cpu_affinity_final(",1,2".to_string())
765            .build();
766        let err = affinity.unwrap_err();
767        assert_eq!(err.to_string(), "Invalid execCPUAffinity format: ,1,2");
768    }
769
770    #[test]
771    fn test_build_empty() {
772        let affinity = ExecCPUAffinityBuilder::default().build();
773        let affinity = affinity.unwrap();
774        assert!(affinity.cpu_affinity_initial.is_none());
775        assert!(affinity.cpu_affinity_final.is_none());
776    }
777}