Skip to content

Commit

Permalink
settings: create oci defaults settings extension
Browse files Browse the repository at this point in the history
  • Loading branch information
mgsharm committed Jun 11, 2024
1 parent 4e1534f commit 903a43f
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 0 deletions.
13 changes: 13 additions & 0 deletions sources/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sources/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ members = [
"settings-extensions/motd",
"settings-extensions/network",
"settings-extensions/ntp",
"settings-extensions/oci-defaults",
"settings-extensions/oci-hooks",
"settings-extensions/pki",
"settings-extensions/updates",
Expand Down
20 changes: 20 additions & 0 deletions sources/settings-extensions/oci-defaults/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "settings-extension-oci-defaults"
version = "0.1.0"
authors = ["Gaurav Sharma <[email protected]>"]
license = "Apache-2.0 OR MIT"
edition = "2021"
publish = false

[dependencies]
env_logger = "0.10"
modeled-types = { path = "../../models/modeled-types", version = "0.1" }
model-derive = { path = "../../models/model-derive", version = "0.1" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"

[dependencies.bottlerocket-settings-sdk]
git = "https://github.com/bottlerocket-os/bottlerocket-settings-sdk"
tag = "bottlerocket-settings-sdk-v0.1.0-alpha.2"
version = "0.1.0-alpha"
13 changes: 13 additions & 0 deletions sources/settings-extensions/oci-defaults/oci-defaults.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[extension]
supported-versions = [
"v1"
]
default-version = "v1"

[v1]
[v1.validation.cross-validates]

[v1.templating]
helpers = []

[v1.generation.requires]
126 changes: 126 additions & 0 deletions sources/settings-extensions/oci-defaults/src/de.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use serde::de::Error;
use serde::{Deserialize, Deserializer};

/// This specifies that any non negative i64 integer, -1, and "unlimited"
/// are the valid resource-limits. The hard-limit set to "unlimited" or -1
/// and soft-limit set to "unlimited" or -1 are converted to u64::MAX in
/// the spec file for the container runtime which ultimately represents
/// unlimited for that resource
pub(crate) fn deserialize_limit<'de, D>(deserializer: D) -> Result<i64, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrInt64 {
String(String),
Int(i64),
}

match StringOrInt64::deserialize(deserializer)? {
StringOrInt64::String(s) => {
if s == "unlimited" {
Ok(-1)
} else {
Err(Error::custom(format!(
"Invalid rlimit {}, expected -1 to {} or \"unlimited\"",
s,
i64::MAX
)))
}
}
StringOrInt64::Int(i) => {
if (-1..=i64::MAX).contains(&i) {
Ok(i)
} else {
Err(Error::custom(format!(
"Invalid rlimit {}, expected -1 to {} or \"unlimited\"",
i,
i64::MAX
)))
}
}
}
}

#[cfg(test)]
mod oci_default_resource_limit_tests {
use crate::OciDefaultsResourceLimitV1;

#[test]
fn valid_any_integer_i_64() {
assert!(toml::from_str::<OciDefaultsResourceLimitV1>(
r#"
hard-limit = 200000
soft-limit = 10000
"#
)
.is_ok());
}

#[test]
fn valid_string_unlimited() {
assert!(toml::from_str::<OciDefaultsResourceLimitV1>(
r#"
hard-limit = 'unlimited'
soft-limit = 10000
"#
)
.is_ok());
}

#[test]
fn valid_integer_i_64_max() {
assert!(toml::from_str::<OciDefaultsResourceLimitV1>(
r#"
hard-limit = 9223372036854775807
soft-limit = 10000
"#
)
.is_ok());
}

#[test]
fn valid_integer_minus_one() {
assert!(toml::from_str::<OciDefaultsResourceLimitV1>(
r#"
hard-limit = -1
soft-limit = 10000
"#
)
.is_ok());
}

#[test]
fn invalid_integer_greater_than_i_64_max() {
assert!(toml::from_str::<OciDefaultsResourceLimitV1>(
r#"
hard-limit = 9223372036854775808
soft-limit = 10000
"#
)
.is_err());
}

#[test]
fn invalid_minus_2() {
assert!(toml::from_str::<OciDefaultsResourceLimitV1>(
r#"
hard-limit = -2
soft-limit = 10000
"#
)
.is_err());
}

#[test]
fn invalid_string_abc() {
assert!(toml::from_str::<OciDefaultsResourceLimitV1>(
r#"
hard-limit = 'abc'
soft-limit = 10000
"#
)
.is_err());
}
}
122 changes: 122 additions & 0 deletions sources/settings-extensions/oci-defaults/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/// Settings related to orchestrated containers for overriding the OCI runtime spec defaults
mod de;

use crate::de::deserialize_limit;
use bottlerocket_settings_sdk::{GenerateResult, SettingsModel};
use model_derive::model;
use modeled_types::{OciDefaultsCapability, OciDefaultsResourceLimitType};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::convert::Infallible;

///// OCI defaults specifies the default values that will be used in cri-base-json.
#[model(impl_default = true)]
struct OciDefaultsV1 {
capabilities: HashMap<OciDefaultsCapability, bool>,
resource_limits: HashMap<OciDefaultsResourceLimitType, OciDefaultsResourceLimitV1>,
}

///// The hard and soft limit values for an OCI defaults resource limit.
#[model(add_option = false)]
#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, Ord, PartialOrd, PartialEq)]
struct OciDefaultsResourceLimitV1 {
#[serde(deserialize_with = "deserialize_limit")]
hard_limit: i64,
#[serde(deserialize_with = "deserialize_limit")]
soft_limit: i64,
}

type Result<T> = std::result::Result<T, Infallible>;

impl SettingsModel for OciDefaultsV1 {
type PartialKind = Self;
type ErrorKind = Infallible;

fn get_version() -> &'static str {
"v1"
}

fn set(_current_value: Option<Self>, _target: Self) -> Result<()> {
// Set anything that can be parsed as OciDefaultsV1.
Ok(())
}

fn generate(
existing_partial: Option<Self::PartialKind>,
_dependent_settings: Option<serde_json::Value>,
) -> Result<GenerateResult<Self::PartialKind, Self>> {
Ok(GenerateResult::Complete(
existing_partial.unwrap_or_default(),
))
}

fn validate(_value: Self, _validated_settings: Option<serde_json::Value>) -> Result<()> {
// OciDefaultsV1 is validated during deserialization.
Ok(())
}
}

#[cfg(test)]
mod test {
use super::*;
use serde_json::json;
use std::collections::HashMap;

#[test]
fn test_generate_oci_defaults() {
assert_eq!(
OciDefaultsV1::generate(None, None),
Ok(GenerateResult::Complete(OciDefaultsV1 {
capabilities: None,
resource_limits: None,
}))
)
}

#[test]
fn test_serde_oci_defaults() {
let test_json = json!({
"capabilities": {
"sys-admin": true,
"net-admin": false
},
"resource-limits": {
"max-cpu-time": {
"hard-limit": 1000,
"soft-limit": 500
}
}
});

let test_json_str = test_json.to_string();

let oci_defaults: OciDefaultsV1 = serde_json::from_str(&test_json_str).unwrap();

let mut expected_capabilities = HashMap::new();
expected_capabilities.insert(OciDefaultsCapability::SysAdmin, true);
expected_capabilities.insert(OciDefaultsCapability::NetAdmin, false);

let mut expected_resource_limits = HashMap::new();
expected_resource_limits.insert(
OciDefaultsResourceLimitType::MaxCpuTime,
OciDefaultsResourceLimitV1 {
hard_limit: 1000,
soft_limit: 500,
},
);

assert_eq!(
oci_defaults,
OciDefaultsV1 {
capabilities: Some(expected_capabilities),
resource_limits: Some(expected_resource_limits),
}
);

let serialized_json: serde_json::Value = serde_json::to_string(&oci_defaults)
.map(|s| serde_json::from_str(&s).unwrap())
.unwrap();

assert_eq!(serialized_json, test_json);
}
}
18 changes: 18 additions & 0 deletions sources/settings-extensions/oci-defaults/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use bottlerocket_settings_sdk::{BottlerocketSetting, NullMigratorExtensionBuilder};
use settings_extension_oci_defaults::OciDefaultsV1;
use std::process::ExitCode;

fn main() -> ExitCode {
env_logger::init();

match NullMigratorExtensionBuilder::with_name("oci-defaults")
.with_models(vec![BottlerocketSetting::<OciDefaultsV1>::model()])
.build()
{
Ok(extension) => extension.run(),
Err(e) => {
println!("{}", e);
ExitCode::FAILURE
}
}
}

0 comments on commit 903a43f

Please sign in to comment.