blob: c60c340e276d805a318689723ba29a6e37e20fb3 [file] [log] [blame]
Wei-Yu Chen49950b92021-11-08 19:19:18 +08001"""
2Copyright 2020 The Magma Authors.
3
4This source code is licensed under the BSD-style license found in the
5LICENSE file in the root directory of this source tree.
6
7Unless required by applicable law or agreed to in writing, software
8distributed under the License is distributed on an "AS IS" BASIS,
9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10See the License for the specific language governing permissions and
11limitations under the License.
12"""
13
14import logging
15import os
16from typing import Optional # noqa: lint doesn't handle inline typehints
17from typing import Any, Dict
18
19import yaml
20from configuration.exceptions import LoadConfigError
21
22# Location of configs (both service config and mconfig)
23CONFIG_DIR = './magma_configs'
24CONFIG_OVERRIDE_DIR = './override_configs'
25
26
27def load_override_config(service_name: str) -> Optional[Any]:
28 """
29 Load override service configuration from the file in the override
30 directory.
31
32 Args:
33 service_name: service to pull configs for; name of config file
34
35 Returns: json-decoded value of the service config, None if it's not found
36
37 Raises:
38 LoadConfigError:
39 Unable to load config due to missing file or missing key
40 """
41 override_file_name = _override_file_name(service_name)
42 if os.path.isfile(override_file_name):
43 return _load_yaml_file(override_file_name)
44 return None
45
46
47def save_override_config(service_name: str, cfg: Any):
48 """
49 Write the configuration object to its corresponding file in the override
50 directory.
51
52 Args:
53 service_name: service to write config object to; name of config file
54 cfg: json-decoded value of the service config
55 """
56 override_file_name = _override_file_name(service_name)
57 os.makedirs(CONFIG_OVERRIDE_DIR, exist_ok=True)
58 with open(override_file_name, 'w', encoding='utf-8') as override_file:
59 yaml.dump(cfg, override_file, default_flow_style=False)
60
61
62def load_service_config(service_name: str) -> Any:
63 """
64 Load service configuration from file. Also check override directory,
65 and, if service file present there, override the values.
66
67 Args:
68 service_name: service to pull configs for; name of config file
69
70 Returns: json-decoded value of the service config
71
72 Raises:
73 LoadConfigError:
74 Unable to load config due to missing file or missing key
75 """
76 print(CONFIG_DIR, service_name)
77 cfg_file_name = os.path.join(CONFIG_DIR, '%s.yml' % service_name)
78 cfg = _load_yaml_file(cfg_file_name)
79
80 overrides = load_override_config(service_name)
81 if overrides is not None:
82 # Update the keys in the config if they are present in the override
83 cfg.update(overrides)
84 return cfg
85
86
87cached_service_configs = {} # type: Dict[str, Any]
88
89
90def get_service_config_value(service: str, param: str, default: Any) -> Any:
91 """
92 Get a config value for :service:, falling back to a :default: value.
93
94 Log error if the default config is returned.
95
96 Args:
97 service: name of service to get config for
98 param: config key to fetch the value for
99 default: default value to return on failure
100
101 Returns:
102 value of :param: in the config files for :service:
103 """
104 service_configs = cached_service_configs.get(service)
105 try:
106 service_configs = service_configs or load_service_config(service)
107 except LoadConfigError as e:
108 logging.error('Error retrieving config: %s', e)
109 return default
110
111 # Handle empty file
112 if not service_configs:
113 logging.error('Error retrieving config, file empty for: %s', service)
114 return default
115
116 cached_service_configs[service] = service_configs
117
118 config_value = service_configs.get(param)
119 if config_value is not None:
120 return config_value
121 else:
122 logging.error(
123 'Error retrieving config for %s, key not found: %s',
124 service, param,
125 )
126 return default
127
128
129def _override_file_name(service_name: str) -> str:
130 return os.path.join(CONFIG_OVERRIDE_DIR, '%s.yml' % service_name)
131
132
133def _load_yaml_file(file_name: str) -> Any:
134 """
135 Load the yaml file and returns the python object.
136
137 Args:
138 file_name: name of the .yml file
139
140 Returns:
141 Contents of the yml file deserialized into a Python object
142
143 Raises:
144 LoadConfigError: on error
145 """
146
147 try:
148 with open(file_name, 'r', encoding='utf-8') as stream:
149 data = yaml.safe_load(stream)
150 return data
151 except (OSError, yaml.YAMLError) as e:
152 raise LoadConfigError('Error loading yml config') from e