Init commit for standalone enodebd
Change-Id: I88eeef5135dd7ba8551ddd9fb6a0695f5325337b
diff --git a/configuration/__init__.py b/configuration/__init__.py
new file mode 100644
index 0000000..b78acae
--- /dev/null
+++ b/configuration/__init__.py
@@ -0,0 +1,15 @@
+import importlib
+import logging
+
+from configuration.exceptions import LoadConfigError
+from configuration.service_configs import load_service_config
+
+# Import all mconfig-providing modules so for the protobuf symbol database
+try:
+ mconfig_modules = load_service_config('magmad').get('mconfig_modules', [])
+ for mod in mconfig_modules:
+ logging.info('Importing mconfig module %s', mod)
+ importlib.import_module(mod)
+except LoadConfigError:
+ logging.error('Could not load magmad yml config for mconfig modules')
+ importlib.import_module('orc8r.protos.mconfig.mconfigs_pb2')
diff --git a/configuration/environment.py b/configuration/environment.py
new file mode 100644
index 0000000..c2b6722
--- /dev/null
+++ b/configuration/environment.py
@@ -0,0 +1,28 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import os
+
+
+def is_dev_mode() -> bool:
+ """
+ Returns whether the environment is set for dev mode
+ """
+ return os.environ.get('MAGMA_DEV_MODE') == '1'
+
+
+def is_docker_network_mode() -> bool:
+ """
+ Returns whether the environment is set for dev mode
+ """
+ return os.environ.get('DOCKER_NETWORK_MODE') == '1'
diff --git a/configuration/events.py b/configuration/events.py
new file mode 100644
index 0000000..308f1be
--- /dev/null
+++ b/configuration/events.py
@@ -0,0 +1,38 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import snowflake
+from eventd.eventd_client import log_event
+from orc8r.protos.eventd_pb2 import Event
+
+
+def deleted_stored_mconfig():
+ log_event(
+ Event(
+ stream_name="magmad",
+ event_type="deleted_stored_mconfig",
+ tag=snowflake.snowflake(),
+ value="{}",
+ ),
+ )
+
+
+def updated_stored_mconfig():
+ log_event(
+ Event(
+ stream_name="magmad",
+ event_type="updated_stored_mconfig",
+ tag=snowflake.snowflake(),
+ value="{}",
+ ),
+ )
diff --git a/configuration/exceptions.py b/configuration/exceptions.py
new file mode 100644
index 0000000..79a8907
--- /dev/null
+++ b/configuration/exceptions.py
@@ -0,0 +1,16 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+
+class LoadConfigError(Exception):
+ pass
diff --git a/configuration/mconfig_managers.py b/configuration/mconfig_managers.py
new file mode 100644
index 0000000..bd39c4c
--- /dev/null
+++ b/configuration/mconfig_managers.py
@@ -0,0 +1,235 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+import abc
+import contextlib
+import json
+import os
+from typing import Any, Generic, TypeVar
+
+import configuration.events as magma_configuration_events
+from google.protobuf import json_format
+from common import serialization_utils
+from configuration.exceptions import LoadConfigError
+from configuration.mconfigs import (
+ filter_configs_by_key,
+ unpack_mconfig_any,
+)
+from orc8r.protos.mconfig_pb2 import GatewayConfigs, GatewayConfigsMetadata
+
+T = TypeVar('T')
+
+MCONFIG_DIR = './magma_configs'
+MCONFIG_OVERRIDE_DIR = './override_configs'
+DEFAULT_MCONFIG_DIR = os.environ.get('MAGMA_CONFIG_LOCATION', MCONFIG_DIR)
+
+
+def get_mconfig_manager():
+ """
+ Get the mconfig manager implementation that the system is configured to
+ use.
+
+ Returns: MconfigManager implementation
+ """
+ # This is stubbed out after deleting the streamed mconfig manager
+ return MconfigManagerImpl()
+
+
+def load_service_mconfig(service: str, mconfig_struct: Any) -> Any:
+ """
+ Utility function to load the mconfig for a specific service using the
+ configured mconfig manager.
+ """
+ return get_mconfig_manager().load_service_mconfig(service, mconfig_struct)
+
+
+def load_service_mconfig_as_json(service_name: str) -> Any:
+ """
+ Loads the managed configuration from its json file stored on disk.
+
+ Args:
+ service_name (str): name of the service to load the config for
+
+ Returns: Loaded config value for the service as parsed json struct, not
+ protobuf message struct
+ """
+ return get_mconfig_manager().load_service_mconfig_as_json(service_name)
+
+
+class MconfigManager(Generic[T]):
+ """
+ Interface for a class which handles loading and updating some cloud-
+ managed configuration (mconfig).
+ """
+
+ @abc.abstractmethod
+ def load_mconfig(self) -> T:
+ """
+ Load the managed configuration from its stored location.
+
+ Returns: Loaded mconfig
+ """
+ pass
+
+ @abc.abstractmethod
+ def load_service_mconfig(
+ self, service_name: str,
+ mconfig_struct: Any,
+ ) -> Any:
+ """
+ Load a specific service's managed configuration.
+
+ Args:
+ service_name (str): name of the service to load a config for
+ mconfig_struct (Any): protobuf message struct of the managed config
+ for the service
+
+ Returns: Loaded config value for the service
+ """
+ pass
+
+ @abc.abstractmethod
+ def load_mconfig_metadata(self) -> GatewayConfigsMetadata:
+ """
+ Load the metadata of the managed configuration.
+
+ Returns: Loaded mconfig metadata
+ """
+ pass
+
+ @abc.abstractmethod
+ def update_stored_mconfig(self, updated_value: str):
+ """
+ Update the stored mconfig to the provided serialized value
+
+ Args:
+ updated_value: Serialized value of new mconfig value to store
+ """
+ pass
+
+ @abc.abstractmethod
+ def deserialize_mconfig(
+ self, serialized_value: str,
+ allow_unknown_fields: bool = True,
+ ) -> T:
+ """
+ Deserialize the given string to the managed mconfig.
+
+ Args:
+ serialized_value:
+ Serialized value of a managed mconfig
+ allow_unknown_fields:
+ Set to true to suppress errors from parsing unknown fields
+
+ Returns: deserialized mconfig value
+ """
+ pass
+
+ @abc.abstractmethod
+ def delete_stored_mconfig(self):
+ """
+ Delete the stored mconfig file.
+ """
+ pass
+
+
+class MconfigManagerImpl(MconfigManager[GatewayConfigs]):
+ """
+ Legacy mconfig manager for non-offset mconfigs
+ """
+
+ MCONFIG_FILE_NAME = 'gateway.mconfig'
+ MCONFIG_PATH = os.path.join(MCONFIG_OVERRIDE_DIR, MCONFIG_FILE_NAME)
+
+ def load_mconfig(self) -> GatewayConfigs:
+ cfg_file_name = self._get_mconfig_file_path()
+ try:
+ with open(cfg_file_name, 'r', encoding='utf-8') as cfg_file:
+ mconfig_str = cfg_file.read()
+ return self.deserialize_mconfig(mconfig_str)
+ except (OSError, json.JSONDecodeError, json_format.ParseError) as e:
+ raise LoadConfigError('Error loading mconfig') from e
+
+ def load_service_mconfig(
+ self, service_name: str,
+ mconfig_struct: Any,
+ ) -> Any:
+ mconfig = self.load_mconfig()
+ if service_name not in mconfig.configs_by_key:
+ raise LoadConfigError(
+ "Service ({}) missing in mconfig".format(service_name),
+ )
+
+ service_mconfig = mconfig.configs_by_key[service_name]
+ return unpack_mconfig_any(service_mconfig, mconfig_struct)
+
+ def load_service_mconfig_as_json(self, service_name) -> Any:
+ cfg_file_name = self._get_mconfig_file_path()
+ with open(cfg_file_name, 'r', encoding='utf-8') as f:
+ json_mconfig = json.load(f)
+ service_configs = json_mconfig.get('configsByKey', {})
+ service_configs.update(json_mconfig.get('configs_by_key', {}))
+ if service_name not in service_configs:
+ raise LoadConfigError(
+ "Service ({}) missing in mconfig".format(service_name),
+ )
+
+ return service_configs[service_name]
+
+ def load_mconfig_metadata(self) -> GatewayConfigsMetadata:
+ mconfig = self.load_mconfig()
+ return mconfig.metadata
+
+ def deserialize_mconfig(
+ self, serialized_value: str,
+ allow_unknown_fields: bool = True,
+ ) -> GatewayConfigs:
+ # First parse as JSON in case there are types unrecognized by
+ # protobuf symbol database
+ json_mconfig = json.loads(serialized_value)
+ cfgs_by_key_json = json_mconfig.get('configs_by_key', {})
+ cfgs_by_key_json.update(json_mconfig.get('configsByKey', {}))
+ filtered_cfgs_by_key = filter_configs_by_key(cfgs_by_key_json)
+
+ # Set configs to filtered map, re-dump and parse
+ if 'configs_by_key' in json_mconfig:
+ json_mconfig.pop('configs_by_key')
+ json_mconfig['configsByKey'] = filtered_cfgs_by_key
+ json_mconfig_dumped = json.dumps(json_mconfig)
+
+ # Workaround for outdated protobuf library on sandcastle
+ if allow_unknown_fields:
+ return json_format.Parse(
+ json_mconfig_dumped,
+ GatewayConfigs(),
+ ignore_unknown_fields=True,
+ )
+ else:
+ return json_format.Parse(json_mconfig_dumped, GatewayConfigs())
+
+ def delete_stored_mconfig(self):
+ with contextlib.suppress(FileNotFoundError):
+ os.remove(self.MCONFIG_PATH)
+ magma_configuration_events.deleted_stored_mconfig()
+
+ def update_stored_mconfig(self, updated_value: str) -> GatewayConfigs:
+ parsed = json.loads(updated_value)
+ serialization_utils.write_to_file_atomically(
+ self.MCONFIG_PATH, json.dumps(parsed, indent=4, sort_keys=True),
+ )
+ magma_configuration_events.updated_stored_mconfig()
+
+ def _get_mconfig_file_path(self):
+ if os.path.isfile(self.MCONFIG_PATH):
+ return self.MCONFIG_PATH
+ else:
+ return os.path.join(DEFAULT_MCONFIG_DIR, self.MCONFIG_FILE_NAME)
diff --git a/configuration/mconfigs.py b/configuration/mconfigs.py
new file mode 100644
index 0000000..88862b5
--- /dev/null
+++ b/configuration/mconfigs.py
@@ -0,0 +1,65 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+from typing import Any as TAny
+from typing import Dict
+
+from google.protobuf.internal.well_known_types import Any
+from configuration import service_configs
+from configuration.exceptions import LoadConfigError
+
+
+def filter_configs_by_key(configs_by_key: Dict[str, TAny]) -> Dict[str, TAny]:
+ """
+ Given a JSON-deserialized map of mconfig protobuf Any's keyed by service
+ name, filter out any entires without a corresponding service or which have
+ values that aren't registered in the protobuf symbol database yet.
+
+ Args:
+ configs_by_key:
+ JSON-deserialized service mconfigs keyed by service name
+
+ Returns:
+ The input map without any services which currently don't exist.
+ """
+ magmad_cfg = service_configs.load_service_config('magmad')
+ services = magmad_cfg.get('magma_services', [])
+ services.append('magmad')
+ services += magmad_cfg.get('registered_dynamic_services', [])
+ services = set(services)
+
+ filtered_configs_by_key = {}
+ for srv, cfg in configs_by_key.items():
+ if srv not in services:
+ continue
+ filtered_configs_by_key[srv] = cfg
+ return filtered_configs_by_key
+
+
+def unpack_mconfig_any(mconfig_any: Any, mconfig_struct: TAny) -> TAny:
+ """
+ Unpack a protobuf Any type into a given an empty protobuf message struct
+ for a service.
+
+ Args:
+ mconfig_any: protobuf Any type to unpack
+ mconfig_struct: protobuf message struct
+
+ Returns: Concrete protobuf object that the provided Any wraps
+ """
+ unpacked = mconfig_any.Unpack(mconfig_struct)
+ if not unpacked:
+ raise LoadConfigError(
+ 'Cannot unpack Any type into message: %s' % mconfig_struct,
+ )
+ return mconfig_struct
diff --git a/configuration/service_configs.py b/configuration/service_configs.py
new file mode 100644
index 0000000..c60c340
--- /dev/null
+++ b/configuration/service_configs.py
@@ -0,0 +1,152 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import logging
+import os
+from typing import Optional # noqa: lint doesn't handle inline typehints
+from typing import Any, Dict
+
+import yaml
+from configuration.exceptions import LoadConfigError
+
+# Location of configs (both service config and mconfig)
+CONFIG_DIR = './magma_configs'
+CONFIG_OVERRIDE_DIR = './override_configs'
+
+
+def load_override_config(service_name: str) -> Optional[Any]:
+ """
+ Load override service configuration from the file in the override
+ directory.
+
+ Args:
+ service_name: service to pull configs for; name of config file
+
+ Returns: json-decoded value of the service config, None if it's not found
+
+ Raises:
+ LoadConfigError:
+ Unable to load config due to missing file or missing key
+ """
+ override_file_name = _override_file_name(service_name)
+ if os.path.isfile(override_file_name):
+ return _load_yaml_file(override_file_name)
+ return None
+
+
+def save_override_config(service_name: str, cfg: Any):
+ """
+ Write the configuration object to its corresponding file in the override
+ directory.
+
+ Args:
+ service_name: service to write config object to; name of config file
+ cfg: json-decoded value of the service config
+ """
+ override_file_name = _override_file_name(service_name)
+ os.makedirs(CONFIG_OVERRIDE_DIR, exist_ok=True)
+ with open(override_file_name, 'w', encoding='utf-8') as override_file:
+ yaml.dump(cfg, override_file, default_flow_style=False)
+
+
+def load_service_config(service_name: str) -> Any:
+ """
+ Load service configuration from file. Also check override directory,
+ and, if service file present there, override the values.
+
+ Args:
+ service_name: service to pull configs for; name of config file
+
+ Returns: json-decoded value of the service config
+
+ Raises:
+ LoadConfigError:
+ Unable to load config due to missing file or missing key
+ """
+ print(CONFIG_DIR, service_name)
+ cfg_file_name = os.path.join(CONFIG_DIR, '%s.yml' % service_name)
+ cfg = _load_yaml_file(cfg_file_name)
+
+ overrides = load_override_config(service_name)
+ if overrides is not None:
+ # Update the keys in the config if they are present in the override
+ cfg.update(overrides)
+ return cfg
+
+
+cached_service_configs = {} # type: Dict[str, Any]
+
+
+def get_service_config_value(service: str, param: str, default: Any) -> Any:
+ """
+ Get a config value for :service:, falling back to a :default: value.
+
+ Log error if the default config is returned.
+
+ Args:
+ service: name of service to get config for
+ param: config key to fetch the value for
+ default: default value to return on failure
+
+ Returns:
+ value of :param: in the config files for :service:
+ """
+ service_configs = cached_service_configs.get(service)
+ try:
+ service_configs = service_configs or load_service_config(service)
+ except LoadConfigError as e:
+ logging.error('Error retrieving config: %s', e)
+ return default
+
+ # Handle empty file
+ if not service_configs:
+ logging.error('Error retrieving config, file empty for: %s', service)
+ return default
+
+ cached_service_configs[service] = service_configs
+
+ config_value = service_configs.get(param)
+ if config_value is not None:
+ return config_value
+ else:
+ logging.error(
+ 'Error retrieving config for %s, key not found: %s',
+ service, param,
+ )
+ return default
+
+
+def _override_file_name(service_name: str) -> str:
+ return os.path.join(CONFIG_OVERRIDE_DIR, '%s.yml' % service_name)
+
+
+def _load_yaml_file(file_name: str) -> Any:
+ """
+ Load the yaml file and returns the python object.
+
+ Args:
+ file_name: name of the .yml file
+
+ Returns:
+ Contents of the yml file deserialized into a Python object
+
+ Raises:
+ LoadConfigError: on error
+ """
+
+ try:
+ with open(file_name, 'r', encoding='utf-8') as stream:
+ data = yaml.safe_load(stream)
+ return data
+ except (OSError, yaml.YAMLError) as e:
+ raise LoadConfigError('Error loading yml config') from e
diff --git a/configuration/tests/__init__.py b/configuration/tests/__init__.py
new file mode 100644
index 0000000..5c6cb64
--- /dev/null
+++ b/configuration/tests/__init__.py
@@ -0,0 +1,12 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
diff --git a/configuration/tests/mconfig_manager_impl_tests.py b/configuration/tests/mconfig_manager_impl_tests.py
new file mode 100644
index 0000000..4be0adc
--- /dev/null
+++ b/configuration/tests/mconfig_manager_impl_tests.py
@@ -0,0 +1,63 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import unittest
+from unittest import mock
+
+from google.protobuf import any_pb2
+from google.protobuf.json_format import MessageToJson
+from configuration import mconfig_managers
+from configuration.exceptions import LoadConfigError
+from orc8r.protos.mconfig import mconfigs_pb2
+
+
+class MconfigManagerImplTest(unittest.TestCase):
+ @mock.patch('magma.configuration.service_configs.load_service_config')
+ def test_load_mconfig(self, get_service_config_value_mock):
+ # Fixture mconfig has 1 unrecognized service, 1 unregistered type
+ magmad_fixture = mconfigs_pb2.MagmaD(
+ checkin_interval=10,
+ checkin_timeout=5,
+ autoupgrade_enabled=True,
+ autoupgrade_poll_interval=300,
+ package_version='1.0.0-0',
+ images=[],
+ tier_id='default',
+ feature_flags={'flag1': False},
+ )
+ magmad_fixture_any = any_pb2.Any()
+ magmad_fixture_any.Pack(magmad_fixture)
+ magmad_fixture_serialized = MessageToJson(magmad_fixture_any)
+ fixture = '''
+ {
+ "configs_by_key": {
+ "magmad": %s,
+ "foo": {
+ "@type": "type.googleapis.com/magma.mconfig.NotAType",
+ "value": "test1"
+ },
+ "not_a_service": {
+ "@type": "type.googleapis.com/magma.mconfig.MagmaD",
+ "value": "test2"
+ }
+ }
+ }
+ ''' % magmad_fixture_serialized
+ get_service_config_value_mock.return_value = {
+ 'magma_services': ['foo'],
+ }
+
+ with mock.patch('builtins.open', mock.mock_open(read_data=fixture)):
+ manager = mconfig_managers.MconfigManagerImpl()
+ with self.assertRaises(LoadConfigError):
+ manager.load_mconfig()
diff --git a/configuration/tests/mconfigs_tests.py b/configuration/tests/mconfigs_tests.py
new file mode 100644
index 0000000..be8ae0f
--- /dev/null
+++ b/configuration/tests/mconfigs_tests.py
@@ -0,0 +1,103 @@
+"""
+Copyright 2020 The Magma Authors.
+
+This source code is licensed under the BSD-style license found in the
+LICENSE file in the root directory of this source tree.
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import unittest
+from unittest import mock
+
+from google.protobuf.any_pb2 import Any
+from configuration import mconfigs
+from orc8r.protos.mconfig import mconfigs_pb2
+
+
+class MconfigsTest(unittest.TestCase):
+
+ @mock.patch('magma.configuration.service_configs.load_service_config')
+ def test_filter_configs_by_key(self, load_service_config_mock):
+ # All services present, but 1 type not
+ configs_by_key = {
+ 'magmad': {
+ '@type': 'type.googleapis.com/magma.mconfig.MagmaD',
+ 'value': 'world'.encode(),
+ },
+ 'directoryd': {
+ '@type': 'type.googleapis.com/magma.mconfig.DirectoryD',
+ 'value': 'hello'.encode(),
+ },
+ 'foo': {
+ '@type': 'type.googleapis.com/magma.mconfig.Foo',
+ 'value': 'test'.encode(),
+ },
+ }
+
+ # Directoryd not present
+ load_service_config_mock.return_value = {
+ 'magma_services': ['mme', 'foo'],
+ }
+ actual = mconfigs.filter_configs_by_key(configs_by_key)
+ expected = {
+ 'magmad': configs_by_key['magmad'],
+ 'foo': configs_by_key['foo'],
+ }
+ self.assertEqual(expected, actual)
+
+ # No services present
+ load_service_config_mock.return_value = {
+ 'magma_services': [],
+ }
+ actual = mconfigs.filter_configs_by_key(configs_by_key)
+ expected = {'magmad': configs_by_key['magmad']}
+ self.assertEqual(expected, actual)
+
+ # Directoryd service present as a dynamic service
+ load_service_config_mock.return_value = {
+ 'magma_services': [],
+ 'registered_dynamic_services': ['directoryd'],
+ }
+ actual = mconfigs.filter_configs_by_key(configs_by_key)
+ expected = {
+ 'magmad': configs_by_key['magmad'],
+ 'directoryd': configs_by_key['directoryd'],
+ }
+ self.assertEqual(expected, actual)
+
+ def test_unpack_mconfig_any(self):
+ magmad_mconfig = mconfigs_pb2.MagmaD(
+ checkin_interval=10,
+ checkin_timeout=5,
+ autoupgrade_enabled=True,
+ autoupgrade_poll_interval=300,
+ package_version='1.0.0-0',
+ images=[],
+ tier_id='default',
+ feature_flags={'flag1': False},
+ )
+ magmad_any = Any(
+ type_url='type.googleapis.com/magma.mconfig.MagmaD',
+ value=magmad_mconfig.SerializeToString(),
+ )
+ actual = mconfigs.unpack_mconfig_any(magmad_any, mconfigs_pb2.MagmaD())
+ self.assertEqual(magmad_mconfig, actual)
+
+ def test_unpack_mconfig_directoryd(self):
+ directoryd_mconfig = mconfigs_pb2.DirectoryD(
+ log_level=5,
+ )
+ magmad_any = Any(
+ type_url='type.googleapis.com/magma.mconfig.DirectoryD',
+ value=directoryd_mconfig.SerializeToString(),
+ )
+
+ actual = mconfigs.unpack_mconfig_any(
+ magmad_any, mconfigs_pb2.DirectoryD(),
+ )
+ self.assertEqual(directoryd_mconfig, actual)