Wei-Yu Chen | ad55cb8 | 2022-02-15 20:07:01 +0800 | [diff] [blame] | 1 | # SPDX-FileCopyrightText: 2020 The Magma Authors. |
| 2 | # SPDX-FileCopyrightText: 2022 Open Networking Foundation <support@opennetworking.org> |
| 3 | # |
| 4 | # SPDX-License-Identifier: BSD-3-Clause |
Wei-Yu Chen | 49950b9 | 2021-11-08 19:19:18 +0800 | [diff] [blame] | 5 | |
| 6 | import logging |
| 7 | import os |
| 8 | from typing import Optional # noqa: lint doesn't handle inline typehints |
| 9 | from typing import Any, Dict |
| 10 | |
| 11 | import yaml |
| 12 | from configuration.exceptions import LoadConfigError |
| 13 | |
| 14 | # Location of configs (both service config and mconfig) |
| 15 | CONFIG_DIR = './magma_configs' |
| 16 | CONFIG_OVERRIDE_DIR = './override_configs' |
Wei-Yu Chen | 5cbdfbb | 2021-12-02 01:10:21 +0800 | [diff] [blame] | 17 | ENB_COMMON_FILE = './magma_configs/acs_common.yml' |
| 18 | ENB_CONFIG_DIR = './magma_configs/serial_number' |
Wei-Yu Chen | 49950b9 | 2021-11-08 19:19:18 +0800 | [diff] [blame] | 19 | |
| 20 | def load_override_config(service_name: str) -> Optional[Any]: |
| 21 | """ |
| 22 | Load override service configuration from the file in the override |
| 23 | directory. |
| 24 | |
| 25 | Args: |
| 26 | service_name: service to pull configs for; name of config file |
| 27 | |
| 28 | Returns: json-decoded value of the service config, None if it's not found |
| 29 | |
| 30 | Raises: |
| 31 | LoadConfigError: |
| 32 | Unable to load config due to missing file or missing key |
| 33 | """ |
| 34 | override_file_name = _override_file_name(service_name) |
| 35 | if os.path.isfile(override_file_name): |
| 36 | return _load_yaml_file(override_file_name) |
| 37 | return None |
| 38 | |
| 39 | |
| 40 | def save_override_config(service_name: str, cfg: Any): |
| 41 | """ |
| 42 | Write the configuration object to its corresponding file in the override |
| 43 | directory. |
| 44 | |
| 45 | Args: |
| 46 | service_name: service to write config object to; name of config file |
| 47 | cfg: json-decoded value of the service config |
| 48 | """ |
| 49 | override_file_name = _override_file_name(service_name) |
| 50 | os.makedirs(CONFIG_OVERRIDE_DIR, exist_ok=True) |
| 51 | with open(override_file_name, 'w', encoding='utf-8') as override_file: |
| 52 | yaml.dump(cfg, override_file, default_flow_style=False) |
| 53 | |
| 54 | |
| 55 | def load_service_config(service_name: str) -> Any: |
| 56 | """ |
| 57 | Load service configuration from file. Also check override directory, |
| 58 | and, if service file present there, override the values. |
| 59 | |
| 60 | Args: |
| 61 | service_name: service to pull configs for; name of config file |
| 62 | |
| 63 | Returns: json-decoded value of the service config |
| 64 | |
| 65 | Raises: |
| 66 | LoadConfigError: |
| 67 | Unable to load config due to missing file or missing key |
| 68 | """ |
Wei-Yu Chen | 49950b9 | 2021-11-08 19:19:18 +0800 | [diff] [blame] | 69 | cfg_file_name = os.path.join(CONFIG_DIR, '%s.yml' % service_name) |
| 70 | cfg = _load_yaml_file(cfg_file_name) |
| 71 | |
| 72 | overrides = load_override_config(service_name) |
| 73 | if overrides is not None: |
| 74 | # Update the keys in the config if they are present in the override |
| 75 | cfg.update(overrides) |
| 76 | return cfg |
| 77 | |
Wei-Yu Chen | 5cbdfbb | 2021-12-02 01:10:21 +0800 | [diff] [blame] | 78 | def load_enb_config() -> Any: |
| 79 | """ |
| 80 | Load enb configurations from directory. |
| 81 | |
| 82 | Args: |
| 83 | None |
| 84 | |
| 85 | Returns: json-decoded value of the service config |
| 86 | """ |
| 87 | |
| 88 | ret = dict() |
Wei-Yu Chen | b91af85 | 2022-03-15 22:24:49 +0800 | [diff] [blame] | 89 | for fname in filter(lambda x: x.endswith(".yml"), os.listdir(ENB_CONFIG_DIR)): |
Wei-Yu Chen | 5cbdfbb | 2021-12-02 01:10:21 +0800 | [diff] [blame] | 90 | sn = fname.replace(".yml", "") |
| 91 | cfg_file_name = os.path.join(ENB_CONFIG_DIR, fname) |
Wei-Yu Chen | b91af85 | 2022-03-15 22:24:49 +0800 | [diff] [blame] | 92 | sn_yaml = _load_yaml_file(cfg_file_name) |
| 93 | |
| 94 | enb_cfg = dict() |
| 95 | for category in sn_yaml.values(): |
| 96 | for key, value in category.items(): |
| 97 | enb_cfg[key] = value |
| 98 | |
| 99 | ret[sn] = enb_cfg |
Wei-Yu Chen | 5cbdfbb | 2021-12-02 01:10:21 +0800 | [diff] [blame] | 100 | |
| 101 | return ret |
| 102 | |
| 103 | def load_common_config() -> Any: |
| 104 | """ |
| 105 | Load enb common configuration. |
| 106 | |
| 107 | Args: |
| 108 | None |
| 109 | |
| 110 | Returns: json-decoded value of the service config |
| 111 | """ |
| 112 | |
| 113 | return _load_yaml_file(ENB_COMMON_FILE) |
| 114 | |
Wei-Yu Chen | 49950b9 | 2021-11-08 19:19:18 +0800 | [diff] [blame] | 115 | |
| 116 | cached_service_configs = {} # type: Dict[str, Any] |
| 117 | |
| 118 | |
| 119 | def get_service_config_value(service: str, param: str, default: Any) -> Any: |
| 120 | """ |
| 121 | Get a config value for :service:, falling back to a :default: value. |
| 122 | |
| 123 | Log error if the default config is returned. |
| 124 | |
| 125 | Args: |
| 126 | service: name of service to get config for |
| 127 | param: config key to fetch the value for |
| 128 | default: default value to return on failure |
| 129 | |
| 130 | Returns: |
| 131 | value of :param: in the config files for :service: |
| 132 | """ |
| 133 | service_configs = cached_service_configs.get(service) |
| 134 | try: |
| 135 | service_configs = service_configs or load_service_config(service) |
| 136 | except LoadConfigError as e: |
| 137 | logging.error('Error retrieving config: %s', e) |
| 138 | return default |
| 139 | |
| 140 | # Handle empty file |
| 141 | if not service_configs: |
| 142 | logging.error('Error retrieving config, file empty for: %s', service) |
| 143 | return default |
| 144 | |
| 145 | cached_service_configs[service] = service_configs |
| 146 | |
| 147 | config_value = service_configs.get(param) |
| 148 | if config_value is not None: |
| 149 | return config_value |
| 150 | else: |
| 151 | logging.error( |
| 152 | 'Error retrieving config for %s, key not found: %s', |
| 153 | service, param, |
| 154 | ) |
| 155 | return default |
| 156 | |
| 157 | |
| 158 | def _override_file_name(service_name: str) -> str: |
| 159 | return os.path.join(CONFIG_OVERRIDE_DIR, '%s.yml' % service_name) |
| 160 | |
| 161 | |
| 162 | def _load_yaml_file(file_name: str) -> Any: |
| 163 | """ |
| 164 | Load the yaml file and returns the python object. |
| 165 | |
| 166 | Args: |
| 167 | file_name: name of the .yml file |
| 168 | |
| 169 | Returns: |
| 170 | Contents of the yml file deserialized into a Python object |
| 171 | |
| 172 | Raises: |
| 173 | LoadConfigError: on error |
| 174 | """ |
| 175 | |
| 176 | try: |
| 177 | with open(file_name, 'r', encoding='utf-8') as stream: |
| 178 | data = yaml.safe_load(stream) |
| 179 | return data |
| 180 | except (OSError, yaml.YAMLError) as e: |
| 181 | raise LoadConfigError('Error loading yml config') from e |