blob: 1fe670787e788c5a35c72823bc2861063bb7d183 [file] [log] [blame]
Matteo Scandolod2044a42017-08-07 16:08:28 -07001# Copyright 2017-present Open Networking Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15
Zack Williams9a42f872019-02-15 17:56:04 -070016from __future__ import absolute_import
17
Matteo Scandolo56879722017-05-17 21:39:54 -070018import os
19import sys
Zack Williams9a42f872019-02-15 17:56:04 -070020
Matteo Scandoloc59372a2018-06-11 15:17:40 -070021import pykwalify
Zack Williams9a42f872019-02-15 17:56:04 -070022import yaml
23from pykwalify.core import Core as PyKwalify
24
25from . import default
Matteo Scandoloc59372a2018-06-11 15:17:40 -070026
27pykwalify.init_logging(1)
Matteo Scandolo56879722017-05-17 21:39:54 -070028
Matteo Scandolo6bc017c2017-05-25 18:37:42 -070029DEFAULT_CONFIG_FILE = "/opt/xos/xos_config.yaml"
Zack Williams045b63d2019-01-22 16:30:57 -070030DEFAULT_CONFIG_SCHEMA = "xos-config-schema.yaml"
Matteo Scandolo56879722017-05-17 21:39:54 -070031INITIALIZED = False
Matteo Scandoloe0fc6852017-06-07 16:01:54 -070032CONFIG_FILE = None
Matteo Scandolo56879722017-05-17 21:39:54 -070033CONFIG = {}
34
Matteo Scandoloc59372a2018-06-11 15:17:40 -070035OVERRIDE_CONFIG = {}
Zack Williams37845ca2017-07-18 15:39:20 -070036
Zack Williams045b63d2019-01-22 16:30:57 -070037
Matteo Scandolo56879722017-05-17 21:39:54 -070038class Config:
39 """
40 XOS Configuration APIs
41 """
42
43 @staticmethod
Zack Williams045b63d2019-01-22 16:30:57 -070044 def init(
45 config_file=DEFAULT_CONFIG_FILE,
46 config_schema=DEFAULT_CONFIG_SCHEMA,
47 override_config_file=None,
48 ):
Matteo Scandolo1879ce72017-05-30 15:45:26 -070049
50 # make schema relative to this directory
51 # TODO give the possibility to specify an absolute path
52 config_schema = Config.get_abs_path(config_schema)
53
Matteo Scandolo56879722017-05-17 21:39:54 -070054 global INITIALIZED
55 global CONFIG
Matteo Scandoloe0fc6852017-06-07 16:01:54 -070056 global CONFIG_FILE
Zack Williams37845ca2017-07-18 15:39:20 -070057
Matteo Scandoloc59372a2018-06-11 15:17:40 -070058 global OVERRIDE_CONFIG
59 global OVERRIDE_CONFIG_FILE
60 global OVERRIDE_CONFIG_SCHEMA
Zack Williams37845ca2017-07-18 15:39:20 -070061
62 # Use same schema for both provided and global config by default
Matteo Scandoloc59372a2018-06-11 15:17:40 -070063 OVERRIDE_CONFIG_SCHEMA = config_schema
64 OVERRIDE_CONFIG_FILE = override_config_file
Zack Williams37845ca2017-07-18 15:39:20 -070065
Matteo Scandolo56879722017-05-17 21:39:54 -070066 # the config module can be initialized only one
67 if INITIALIZED:
Zack Williams045b63d2019-01-22 16:30:57 -070068 raise Exception("[XOS-Config] Module already initialized")
Matteo Scandolo56879722017-05-17 21:39:54 -070069 INITIALIZED = True
70
Matteo Scandolo1879ce72017-05-30 15:45:26 -070071 # if XOS_CONFIG_FILE is defined override the config_file
72 # FIXME shouldn't this stay in whatever module call this one? and then just pass the file to the init method
Zack Williams045b63d2019-01-22 16:30:57 -070073 if os.environ.get("XOS_CONFIG_FILE"):
74 config_file = os.environ["XOS_CONFIG_FILE"]
Matteo Scandolo1879ce72017-05-30 15:45:26 -070075
76 # if XOS_CONFIG_SCHEMA is defined override the config_schema
77 # FIXME shouldn't this stay in whatever module call this one? and then just pass the file to the init method
Zack Williams045b63d2019-01-22 16:30:57 -070078 if os.environ.get("XOS_CONFIG_SCHEMA"):
79 config_schema = Config.get_abs_path(os.environ["XOS_CONFIG_SCHEMA"])
Matteo Scandolo56879722017-05-17 21:39:54 -070080
Matteo Scandoloc59372a2018-06-11 15:17:40 -070081 # allow OVERRIDE_CONFIG_* to be overridden by env vars
Zack Williams045b63d2019-01-22 16:30:57 -070082 if os.environ.get("XOS_OVERRIDE_CONFIG_FILE"):
83 OVERRIDE_CONFIG_FILE = os.environ["XOS_OVERRIDE_CONFIG_FILE"]
84 if os.environ.get("XOS_OVERRIDE_CONFIG_SCHEMA"):
85 OVERRIDE_CONFIG_SCHEMA = Config.get_abs_path(
86 os.environ["XOS_OVERRIDE_CONFIG_SCHEMA"]
87 )
Zack Williams37845ca2017-07-18 15:39:20 -070088
Matteo Scandolo56879722017-05-17 21:39:54 -070089 # if a -C parameter is set in the cli override the config_file
90 # FIXME shouldn't this stay in whatever module call this one? and then just pass the file to the init method
91 if Config.get_cli_param(sys.argv):
Matteo Scandolo1879ce72017-05-30 15:45:26 -070092 config_schema = Config.get_cli_param(sys.argv)
Matteo Scandolo56879722017-05-17 21:39:54 -070093
Matteo Scandoloe0fc6852017-06-07 16:01:54 -070094 CONFIG_FILE = config_file
Matteo Scandolo1879ce72017-05-30 15:45:26 -070095 CONFIG = Config.read_config(config_file, config_schema)
Matteo Scandolo56879722017-05-17 21:39:54 -070096
Matteo Scandoloc59372a2018-06-11 15:17:40 -070097 # if an override is set
98 if OVERRIDE_CONFIG_FILE is not None:
Zack Williams045b63d2019-01-22 16:30:57 -070099 OVERRIDE_CONFIG = Config.read_config(
100 OVERRIDE_CONFIG_FILE, OVERRIDE_CONFIG_SCHEMA, True
101 )
Zack Williams37845ca2017-07-18 15:39:20 -0700102
Matteo Scandolo56879722017-05-17 21:39:54 -0700103 @staticmethod
Matteo Scandoloe0fc6852017-06-07 16:01:54 -0700104 def get_config_file():
105 return CONFIG_FILE
106
107 @staticmethod
Matteo Scandolo56879722017-05-17 21:39:54 -0700108 def clear():
109 global INITIALIZED
110 INITIALIZED = False
111
112 @staticmethod
Matteo Scandolo1879ce72017-05-30 15:45:26 -0700113 def get_abs_path(path):
114 if os.path.isabs(path):
115 return path
Zack Williams045b63d2019-01-22 16:30:57 -0700116 return os.path.dirname(os.path.realpath(__file__)) + "/" + path
Matteo Scandolo1879ce72017-05-30 15:45:26 -0700117
118 @staticmethod
119 def validate_config_format(config_file, config_schema):
120 schema = os.path.abspath(config_schema)
Matteo Scandolo56879722017-05-17 21:39:54 -0700121 c = PyKwalify(source_file=config_file, schema_files=[schema])
122 c.validate(raise_exception=True)
123
124 @staticmethod
125 def get_cli_param(args):
126 last = None
127 for arg in args:
Zack Williams045b63d2019-01-22 16:30:57 -0700128 if last == "-C":
Matteo Scandolo56879722017-05-17 21:39:54 -0700129 return arg
130 last = arg
131
132 @staticmethod
Zack Williams37845ca2017-07-18 15:39:20 -0700133 def read_config(config_file, config_schema, ignore_if_not_found=False):
Matteo Scandolo56879722017-05-17 21:39:54 -0700134 """
135 Read the configuration file and return a dictionary
136 :param config_file: string
137 :return: dict
138 """
Zack Williams37845ca2017-07-18 15:39:20 -0700139
Zack Williams045b63d2019-01-22 16:30:57 -0700140 if not os.path.exists(config_file) and ignore_if_not_found:
Zack Williams37845ca2017-07-18 15:39:20 -0700141 return {}
142
Matteo Scandolo56879722017-05-17 21:39:54 -0700143 if not os.path.exists(config_file):
Zack Williams045b63d2019-01-22 16:30:57 -0700144 raise Exception("[XOS-Config] Config file not found at: %s" % config_file)
Matteo Scandolo56879722017-05-17 21:39:54 -0700145
Matteo Scandolo1879ce72017-05-30 15:45:26 -0700146 if not os.path.exists(config_schema):
Zack Williams045b63d2019-01-22 16:30:57 -0700147 raise Exception(
148 "[XOS-Config] Config schema not found at: %s" % config_schema
149 )
Matteo Scandolo1879ce72017-05-30 15:45:26 -0700150
Matteo Scandolo56879722017-05-17 21:39:54 -0700151 try:
Matteo Scandolo1879ce72017-05-30 15:45:26 -0700152 Config.validate_config_format(config_file, config_schema)
Zack Williams045b63d2019-01-22 16:30:57 -0700153 except Exception as e:
Scott Baker1f6a32c2017-11-22 11:24:01 -0800154 try:
155 error_msg = e.msg
156 except AttributeError:
157 error_msg = str(e)
Zack Williams045b63d2019-01-22 16:30:57 -0700158 raise Exception("[XOS-Config] The config format is wrong: %s" % error_msg)
Matteo Scandolo56879722017-05-17 21:39:54 -0700159
Zack Williams045b63d2019-01-22 16:30:57 -0700160 with open(config_file, "r") as stream:
Matteo Scandolo56879722017-05-17 21:39:54 -0700161 return yaml.safe_load(stream)
162
163 @staticmethod
164 def get(query):
165 """
166 Read a parameter from the config
167 :param query: a dot separated selector for configuration options (eg: database.username)
168 :return: the requested parameter in any format the parameter is specified
169 """
170 global INITIALIZED
171 global CONFIG
Matteo Scandoloc59372a2018-06-11 15:17:40 -0700172 global OVERRIDE_CONFIG
173 global OVERRIDE_CONFIG_FILE
Matteo Scandolo56879722017-05-17 21:39:54 -0700174
175 if not INITIALIZED:
Zack Williams045b63d2019-01-22 16:30:57 -0700176 raise Exception("[XOS-Config] Module has not been initialized")
Matteo Scandolo56879722017-05-17 21:39:54 -0700177
178 val = Config.get_param(query, CONFIG)
Matteo Scandoloc59372a2018-06-11 15:17:40 -0700179 if OVERRIDE_CONFIG_FILE or not val:
180 # if we specified an override configuration, we should override the value
181 # we also look for the value in case it's missing
182 over_val = Config.get_param(query, OVERRIDE_CONFIG)
183 if over_val is not None:
184 val = over_val
Zack Williams37845ca2017-07-18 15:39:20 -0700185 if not val:
Matteo Scandolo56879722017-05-17 21:39:54 -0700186 val = Config.get_param(query, default.DEFAULT_VALUES)
187 if not val:
Matteo Scandolo1879ce72017-05-30 15:45:26 -0700188 # TODO if no val return none
189 # raise Exception('[XOS-Config] Config does not have a value (or a default) parameter %s' % query)
190 return None
Matteo Scandolo56879722017-05-17 21:39:54 -0700191 return val
192
193 @staticmethod
194 def get_param(query, config):
195 """
196 Search for a parameter in config's first level, other call get_nested_param
197 :param query: a dot separated selector for configuration options (eg: database.username)
198 :param config: the config source to read from (can be the config file or the defaults)
199 :return: the requested parameter in any format the parameter is specified
200 """
Zack Williams045b63d2019-01-22 16:30:57 -0700201 keys = query.split(".")
Matteo Scandolo56879722017-05-17 21:39:54 -0700202 if len(keys) == 1:
203 key = keys[0]
Zack Williams045b63d2019-01-22 16:30:57 -0700204 if key not in config:
Matteo Scandolo56879722017-05-17 21:39:54 -0700205 return None
206 return config[key]
207 else:
208 return Config.get_nested_param(keys, config)
209
210 @staticmethod
211 def get_nested_param(keys, config):
212 """
Zack Williams37845ca2017-07-18 15:39:20 -0700213
Matteo Scandolo56879722017-05-17 21:39:54 -0700214 :param keys: a list of descending selector
215 :param config: the config source to read from (can be the config file or the defaults)
216 :return: the requested parameter in any format the parameter is specified
217 """
218 param = config
219 for k in keys:
Zack Williams045b63d2019-01-22 16:30:57 -0700220 if k not in param:
Matteo Scandolo56879722017-05-17 21:39:54 -0700221 return None
222 param = param[k]
223 return param
224
Zack Williams045b63d2019-01-22 16:30:57 -0700225
226if __name__ == "__main__":
Zack Williams37845ca2017-07-18 15:39:20 -0700227 Config.init()