Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e69d60a8 authored by Dennis Shen's avatar Dennis Shen
Browse files

Allow mainline beta namespace config

When parsing flags, allow passing in a json configuration file about
mainline beta namespace. For each mainline beta namespace, the
configuration file specifies its owning container, and it exported mode
are supported for the namespace.

This configuration is used to determine the storage backend of a flag.
If it is a flag in mainline beta namespace and belongs to the owning
container, assign device config as storage backend.

Bug: b/406508083
Test: atest -c
Change-Id: I783d6c43392e102f92e46b874effbf5372cfeeee
parent 3ef8857c
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -45,6 +45,9 @@ rust_test_host {
        "libitertools",
    ],
    test_suites: ["general-tests"],
    data: [
        "tests/mainline_beta_namespaces.json"
    ]
}

// integration tests: general
+24 −0
Original line number Diff line number Diff line
{
    "namespaces": {
        "com_android_tethering": {
            "container": "com.android.tethering",
            "allow_exported": true
        },
        "com_android_networkstack": {
            "container": "com.android.networkstack",
            "allow_exported": false
        },
        "com_android_captiveportallogin": {
            "container": "com.android.captiveportallogin",
            "allow_exported": false
        },
        "com_android_healthfitness": {
            "container": "com.android.healthfitness",
            "allow_exported": true
        },
        "com_android_mediaprovider": {
            "container": "com.android.mediaprovider",
            "allow_exported": true
        }
    }
}
+47 −3
Original line number Diff line number Diff line
@@ -91,6 +91,24 @@ Allow the same flag to be present in multiple cache files; if duplicates are fou
a single instance.
"#;

const MAINLINE_BETA_NAMESPACE_CONFIG: &str = r#"
A json file to configure mainline beta namespaces. This option is internal to Google. The json
configuration should assume the following format:

{
    "namespaces": {
        "com_android_tethering": {
            "container": "com.android.tethering",
            "allow_exported": true
        },
        "com_android_mediaprovider": {
            "container": "com.android.mediaprovider",
            "allow_exported": true
        }
    }
}
"#;

/// Conventional prefix to mark response file
pub const RESPONSE_FILE_PREFIX: char = '@';

@@ -111,6 +129,7 @@ pub enum ParsedCommand {
        default_permission: aconfig_protos::ProtoFlagPermission,
        allow_read_write: bool,
        cache_out_path: String,
        mainline_beta_namespace_config: Option<PathBuf>,
    },
    CreateJavaLib {
        cache_path: String,
@@ -168,8 +187,11 @@ fn build_cli() -> Command {
                        .value_parser(clap::value_parser!(bool))
                        .default_value("true"),
                )
                .arg(Arg::new("cache").long("cache").required(true).help("Output cache file path."))
                .arg(
                    Arg::new("cache").long("cache").required(true).help("Output cache file path."),
                    Arg::new("mainline-beta-namespace-config")
                        .long("mainline-beta-namespace-config")
                        .long_help(MAINLINE_BETA_NAMESPACE_CONFIG.trim()),
                ),
        )
        .subcommand(
@@ -339,6 +361,17 @@ pub fn parse_args(
        Some(("create-cache", sub_matches)) => {
            let declarations = get_zero_or_more_string_paths_from_arg(sub_matches, "declarations");
            let values = get_zero_or_more_string_paths_from_arg(sub_matches, "values");
            let mainline_beta_namespace_config =
                match sub_matches.get_one::<String>("mainline-beta-namespace-config") {
                    Some(config) => {
                        if config.is_empty() {
                            None
                        } else {
                            Some(PathBuf::from(config))
                        }
                    }
                    None => None,
                };
            Ok(ParsedCommand::CreateCache {
                package: get_required_arg::<String>(sub_matches, "package")?.clone(),
                container: get_required_arg::<String>(sub_matches, "container")?.clone(),
@@ -350,6 +383,7 @@ pub fn parse_args(
                )?,
                allow_read_write: *get_required_arg::<bool>(sub_matches, "allow-read-write")?,
                cache_out_path: get_required_arg::<String>(sub_matches, "cache")?.clone(),
                mainline_beta_namespace_config,
            })
        }
        Some(("create-java-lib", sub_matches)) => Ok(ParsedCommand::CreateJavaLib {
@@ -452,7 +486,8 @@ mod tests {
             --values flag.val \
             --cache /output/cache.pb \
             --default-permission READ_WRITE \
             --allow-read-write true";
             --allow-read-write true \
             --mainline-beta-namespace-config /path/to/some/file.json";
        let input_args = create_os_command(command_string);
        let parsed = parse_args(input_args)?;

@@ -465,6 +500,7 @@ mod tests {
            default_permission,
            allow_read_write,
            cache_out_path,
            mainline_beta_namespace_config,
        } = parsed
        {
            assert_eq!(package, "com.test.cache");
@@ -476,6 +512,10 @@ mod tests {
            assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE);
            assert!(allow_read_write);
            assert_eq!(cache_out_path, "/output/cache.pb");
            assert_eq!(
                mainline_beta_namespace_config,
                Some(PathBuf::from("/path/to/some/file.json"))
            );
        }
        Ok(())
    }
@@ -661,6 +701,7 @@ mod tests {
            cache_out_path,
            default_permission,
            allow_read_write,
            mainline_beta_namespace_config,
        } = parsed
        {
            assert_eq!(package, "com.via.respfile");
@@ -672,6 +713,7 @@ mod tests {
            assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE);
            assert!(allow_read_write);
            assert_eq!(cache_out_path, "cache.pb");
            assert_eq!(mainline_beta_namespace_config, None);
        }

        Ok(())
@@ -705,6 +747,7 @@ mod tests {
            cache_out_path,
            default_permission,
            allow_read_write,
            mainline_beta_namespace_config,
        } = parsed
        {
            assert_eq!(package, "com.via.respfile");
@@ -716,6 +759,7 @@ mod tests {
            assert_eq!(default_permission, aconfig_protos::ProtoFlagPermission::READ_WRITE);
            assert!(allow_read_write);
            assert_eq!(cache_out_path, "cache.pb");
            assert_eq!(mainline_beta_namespace_config, None);
        }

        Ok(())
+182 −72
Original line number Diff line number Diff line
@@ -17,8 +17,9 @@
use anyhow::{anyhow, bail, ensure, Context, Result};
use convert_finalized_flags::FinalizedFlagMap;
use itertools::Itertools;
use lazy_static::lazy_static;
use protobuf::Message;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::fmt;
use std::hash::Hasher;
@@ -73,18 +74,50 @@ pub struct OutputFile {
pub const DEFAULT_FLAG_STATE: ProtoFlagState = ProtoFlagState::DISABLED;
pub const DEFAULT_FLAG_PERMISSION: ProtoFlagPermission = ProtoFlagPermission::READ_WRITE;

// A hash map from beta namespace name to the corresponding containers. This is used to
// determine if an RW flag is a mainline beta flag which is defined as:
// 1, defined inside a beta namespace
// 2, has the corresponding mainline module as container
// TODO(b/328657418): Populate the map with mainline beta namespaces
lazy_static! {
    static ref MAINLINE_BETA_NAMESPACES: HashMap<&'static str, &'static str> =
        vec![].into_iter().collect();
#[derive(Serialize, Deserialize, Debug)]
pub struct NamespaceSetting {
    pub container: String,
    pub allow_exported: bool,
}

fn assign_storage_backend(pf: &mut ProtoParsedFlag) -> Result<()> {
    let is_mainline_beta = MAINLINE_BETA_NAMESPACES.get(pf.namespace()) == Some(&pf.container());
#[derive(Serialize, Deserialize, Debug)]
pub struct MainlineBetaNamespaces {
    pub namespaces: HashMap<String, NamespaceSetting>,
}

#[allow(dead_code)]
impl MainlineBetaNamespaces {
    fn is_mainline_beta_flag(&self, pf: &ProtoParsedFlag) -> bool {
        match self.namespaces.get(pf.namespace()) {
            Some(setting) => setting.container == pf.container(),
            None => false,
        }
    }

    fn supports_exported_mode(&self, pf: &ProtoParsedFlag) -> bool {
        match self.namespaces.get(pf.namespace()) {
            Some(setting) => {
                if setting.container == pf.container() {
                    setting.allow_exported
                } else {
                    panic!("Should not be called on none mainline beta flag")
                }
            }
            None => {
                panic!("Should not be called on none mainline beta flag")
            }
        }
    }
}

fn assign_storage_backend(
    pf: &mut ProtoParsedFlag,
    beta_namespaces: &Option<MainlineBetaNamespaces>,
) -> Result<()> {
    let is_mainline_beta = match beta_namespaces {
        Some(namespaces) => namespaces.is_mainline_beta_flag(pf),
        None => false,
    };
    let is_read_only = pf.permission() == ProtoFlagPermission::READ_ONLY;
    let storage = if is_read_only {
        ProtoFlagStorageBackend::NONE
@@ -105,9 +138,18 @@ pub fn parse_flags(
    values: Vec<Input>,
    default_permission: ProtoFlagPermission,
    allow_read_write: bool,
    mainline_beta_namespace_config: Option<PathBuf>,
) -> Result<Vec<u8>> {
    let mut parsed_flags = ProtoParsedFlags::new();

    let beta_namespaces: Option<MainlineBetaNamespaces> = match mainline_beta_namespace_config {
        Some(file) => {
            let contents = std::fs::read_to_string(file)?;
            Some(serde_json::from_str(&contents)?)
        }
        None => None,
    };

    for mut input in declarations {
        let mut contents = String::new();
        input
@@ -131,6 +173,7 @@ pub fn parse_flags(
            container,
            flag_declarations.container()
        );

        for mut flag_declaration in flag_declarations.flag.into_iter() {
            aconfig_protos::flag_declaration::verify_fields(&flag_declaration)
                .with_context(|| input.error_context())?;
@@ -162,7 +205,7 @@ pub fn parse_flags(
            let purpose = flag_declaration.metadata.purpose();
            metadata.set_purpose(purpose);
            parsed_flag.metadata = Some(metadata).into();
            assign_storage_backend(&mut parsed_flag)?;
            assign_storage_backend(&mut parsed_flag, &beta_namespaces)?;

            // verify ParsedFlag looks reasonable
            aconfig_protos::parsed_flag::verify_fields(&parsed_flag)?;
@@ -211,7 +254,7 @@ pub fn parse_flags(
            parsed_flag.set_state(flag_value.state());
            if parsed_flag.permission() != flag_value.permission() {
                parsed_flag.set_permission(flag_value.permission());
                assign_storage_backend(parsed_flag)?;
                assign_storage_backend(parsed_flag, &beta_namespaces)?;
            }
            let mut tracepoint = ProtoTracepoint::new();
            tracepoint.set_source(input.source.clone());
@@ -590,6 +633,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_ONLY,
            true,
            None,
        )
        .unwrap();
        let parsed_flags =
@@ -624,6 +668,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
            None,
        )
        .unwrap_err();
        assert_eq!(
@@ -656,6 +701,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
            None,
        )
        .unwrap_err();
        assert_eq!(
@@ -685,6 +731,7 @@ mod tests {
            vec![],
            ProtoFlagPermission::READ_WRITE,
            false,
            None,
        )
        .unwrap_err();
        assert_eq!(
@@ -727,6 +774,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_ONLY,
            false,
            None,
        )
        .unwrap_err();
        assert_eq!(
@@ -769,6 +817,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_ONLY,
            false,
            None,
        )
        .unwrap();
        let parsed_flags =
@@ -814,6 +863,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
            None,
        )
        .unwrap_err();
        assert_eq!(
@@ -850,6 +900,7 @@ mod tests {
            value,
            ProtoFlagPermission::READ_ONLY,
            true,
            None,
        )
        .unwrap();
        let parsed_flags =
@@ -859,6 +910,43 @@ mod tests {
        assert_eq!(ProtoFlagPurpose::PURPOSE_FEATURE, parsed_flag.metadata.purpose());
    }

    fn get_parsed_flag_proto(
        container: &'static str,
        package: &'static str,
        decl: &'static str,
        val: Option<&'static str>,
        config: Option<PathBuf>,
    ) -> ProtoParsedFlag {
        let declaration =
            vec![Input { source: "memory".to_string(), reader: Box::new(decl.as_bytes()) }];

        let value: Vec<Input> = match val {
            Some(val_str) => {
                vec![Input { source: "memory".to_string(), reader: Box::new(val_str.as_bytes()) }]
            }
            None => {
                vec![]
            }
        };

        let flags_bytes = crate::commands::parse_flags(
            package,
            container,
            declaration,
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
            config,
        )
        .unwrap();

        let parsed_flags =
            aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();

        assert_eq!(1, parsed_flags.parsed_flag.len());
        parsed_flags.parsed_flag.first().unwrap().clone()
    }

    #[test]
    fn test_parse_flags_metadata_storage() {
        let metadata_flag = r#"
@@ -872,33 +960,14 @@ mod tests {
        }
        "#;

        // Case 1, regular RW flag without value file override
        let declaration = vec![Input {
            source: "memory".to_string(),
            reader: Box::new(metadata_flag.as_bytes()),
        }];
        let value: Vec<Input> = vec![];
        let config = Some(PathBuf::from("tests/mainline_beta_namespaces.json"));

        let flags_bytes = crate::commands::parse_flags(
            "com.first",
            "test",
            declaration,
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
        )
        .unwrap();
        let parsed_flags =
            aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
        assert_eq!(1, parsed_flags.parsed_flag.len());
        let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
        // Case 1, regular RW flag without value file override
        let parsed_flag =
            get_parsed_flag_proto("test", "com.first", metadata_flag, None, config.clone());
        assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage());

        // Case 2, regular RW flag with value file override to RO
        let declaration = vec![Input {
            source: "memory".to_string(),
            reader: Box::new(metadata_flag.as_bytes()),
        }];
        let first_flag_value = r#"
        flag_value {
            package: "com.first"
@@ -907,23 +976,13 @@ mod tests {
            permission: READ_ONLY
        }
        "#;
        let value = vec![Input {
            source: "memory".to_string(),
            reader: Box::new(first_flag_value.as_bytes()),
        }];
        let flags_bytes = crate::commands::parse_flags(
            "com.first",
        let parsed_flag = get_parsed_flag_proto(
            "test",
            declaration,
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
        )
        .unwrap();
        let parsed_flags =
            aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
        assert_eq!(1, parsed_flags.parsed_flag.len());
        let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
            "com.first",
            metadata_flag,
            Some(first_flag_value),
            config.clone(),
        );
        assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage());

        // Case 3, fixed read only flag
@@ -938,30 +997,81 @@ mod tests {
            is_fixed_read_only: true
        }
        "#;
        let declaration = vec![Input {
            source: "memory".to_string(),
            reader: Box::new(metadata_flag.as_bytes()),
        }];
        let value: Vec<Input> = vec![];

        let flags_bytes = crate::commands::parse_flags(
        let parsed_flag =
            get_parsed_flag_proto("test", "com.first", metadata_flag, None, config.clone());
        assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage());

        // Case 4, mainline beta namespace fixed read only flag
        let metadata_flag = r#"
        package: "com.first"
        container: "com.android.tethering"
        flag {
            name: "first"
            namespace: "com_android_tethering"
            description: "This is the description of this feature flag."
            bug: "123"
            is_fixed_read_only: true
        }
        "#;
        let parsed_flag = get_parsed_flag_proto(
            "com.android.tethering",
            "com.first",
            "test",
            declaration,
            value,
            ProtoFlagPermission::READ_WRITE,
            true,
        )
        .unwrap();
        let parsed_flags =
            aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
        assert_eq!(1, parsed_flags.parsed_flag.len());
        let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
            metadata_flag,
            None,
            config.clone(),
        );
        assert_eq!(ProtoFlagStorageBackend::NONE, parsed_flag.metadata.storage());

        // TODO case 4, mainline beta namespace fixed read only flag
        // TODO case 5, mainline beta namespace platform flag
        // TODO case 6, mainline beta namespace mainline flag
        // Case 5, mainline beta namespace platform flag
        let metadata_flag = r#"
        package: "com.first"
        container: "system"
        flag {
            name: "first"
            namespace: "com_android_tethering"
            description: "This is the description of this feature flag."
            bug: "123"
        }
        "#;
        let parsed_flag =
            get_parsed_flag_proto("system", "com.first", metadata_flag, None, config.clone());
        assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage());

        // Case 6, mainline beta namespace mainline flag
        let metadata_flag = r#"
        package: "com.first"
        container: "com.android.tethering"
        flag {
            name: "first"
            namespace: "com_android_tethering"
            description: "This is the description of this feature flag."
            bug: "123"
        }
        "#;
        let parsed_flag = get_parsed_flag_proto(
            "com.android.tethering",
            "com.first",
            metadata_flag,
            None,
            config.clone(),
        );
        assert_eq!(ProtoFlagStorageBackend::DEVICE_CONFIG, parsed_flag.metadata.storage());

        // Case 7, mainline beta namespace mainline flag but without config
        let metadata_flag = r#"
        package: "com.first"
        container: "com.android.tethering"
        flag {
            name: "first"
            namespace: "com_android_tethering"
            description: "This is the description of this feature flag."
            bug: "123"
        }
        "#;
        let parsed_flag =
            get_parsed_flag_proto("com.android.tethering", "com.first", metadata_flag, None, None);
        assert_eq!(ProtoFlagStorageBackend::ACONFIGD, parsed_flag.metadata.storage());
    }

    #[test]
+2 −0
Original line number Diff line number Diff line
@@ -113,6 +113,7 @@ fn main() -> Result<()> {
            default_permission,
            allow_read_write,
            cache_out_path,
            mainline_beta_namespace_config,
        } => {
            let output = commands::parse_flags(
                &package,
@@ -121,6 +122,7 @@ fn main() -> Result<()> {
                open_zero_or_more_files(&values)?,       // values
                default_permission,
                allow_read_write,
                mainline_beta_namespace_config,
            )
            .context("failed to create cache")?;
            write_output_to_file_or_stdout(&cache_out_path, &output)?;
Loading