[CORD-3022] Adding an option to provide an extra configuration

Change-Id: Iaef5d7bc6041b76f2896f9fcf91a0072e78ad0c2
diff --git a/lib/xos-config/tests/confs/extend_conf.yaml b/lib/xos-config/tests/confs/extend_conf.yaml
new file mode 100644
index 0000000..dd888e3
--- /dev/null
+++ b/lib/xos-config/tests/confs/extend_conf.yaml
@@ -0,0 +1,17 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+
+xos_dir: /opt/xos
\ No newline at end of file
diff --git a/lib/xos-config/tests/confs/override_conf.yaml b/lib/xos-config/tests/confs/override_conf.yaml
new file mode 100644
index 0000000..683b8da
--- /dev/null
+++ b/lib/xos-config/tests/confs/override_conf.yaml
@@ -0,0 +1,21 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+
+
+database:
+  # as per xos-config-schema.yaml these are all required
+  name: xos
+  username: test
+  password: overridden_password
\ No newline at end of file
diff --git a/lib/xos-config/tests/confs/sample_conf.yaml b/lib/xos-config/tests/confs/sample_conf.yaml
index 3e08f03..e177fbb 100644
--- a/lib/xos-config/tests/confs/sample_conf.yaml
+++ b/lib/xos-config/tests/confs/sample_conf.yaml
@@ -23,5 +23,4 @@
   level: info
   channels:
     - file
-    - console
-xos_dir: /opt/xos
\ No newline at end of file
+    - console
\ No newline at end of file
diff --git a/lib/xos-config/tests/test_config.py b/lib/xos-config/tests/test_config.py
index 1684472..ee80b52 100644
--- a/lib/xos-config/tests/test_config.py
+++ b/lib/xos-config/tests/test_config.py
@@ -24,6 +24,8 @@
 yaml_not_valid = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/confs/yaml_not_valid.yaml")
 invalid_format = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/confs/invalid_format.yaml")
 sample_conf = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/confs/sample_conf.yaml")
+override_conf = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/confs/override_conf.yaml")
+extend_conf = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/confs/extend_conf.yaml")
 
 small_schema = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/schemas/small_schema.yaml")
 
@@ -158,7 +160,7 @@
 
     def test_get_missing_param(self):
         """
-        [XOS-Config] Should raise reading a missing param
+        [XOS-Config] Should return None reading a missing param
         """
         Config.init(sample_conf)
         res = Config.get("foo")
@@ -178,13 +180,33 @@
             "password": "safe"
         })
 
-    def _test_get_child_level(self):
+    def test_get_child_level(self):
         """
         [XOS-Config] Should return a child level param
         """
         Config.init(sample_conf)
-        res = Config.get("nested.parameter.for")
-        self.assertEqual(res, "testing")
+        res = Config.get("database.name")
+        self.assertEqual(res, "xos")
+
+    def test_config_override(self):
+        """
+        [XOS-Config] If an override is provided for the config, it should return the overridden value
+        """
+        Config.init(sample_conf, 'xos-config-schema.yaml', override_conf)
+        res = Config.get("logging.level")
+        self.assertEqual(res, "info")
+        res = Config.get("database.password")
+        self.assertEqual(res, "overridden_password")
+
+    def test_config_extend(self):
+        """
+        [XOS-Config] If an override is provided for the config, it should return the overridden value (also if not defined in the base one)
+        """
+        Config.init(sample_conf, 'xos-config-schema.yaml', extend_conf)
+        res = Config.get("xos_dir")
+        self.assertEqual(res, "/opt/xos")
+        res = Config.get("database.password")
+        self.assertEqual(res, "safe")
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/lib/xos-config/xosconfig/config.py b/lib/xos-config/xosconfig/config.py
index 4e6af9e..ebc696f 100644
--- a/lib/xos-config/xosconfig/config.py
+++ b/lib/xos-config/xosconfig/config.py
@@ -20,6 +20,9 @@
 import requests
 import default
 from pykwalify.core import Core as PyKwalify
+import pykwalify
+
+pykwalify.init_logging(1)
 
 DEFAULT_CONFIG_FILE = "/opt/xos/xos_config.yaml"
 DEFAULT_CONFIG_SCHEMA = 'xos-config-schema.yaml'
@@ -27,9 +30,7 @@
 CONFIG_FILE = None
 CONFIG = {}
 
-GLOBAL_CONFIG_FILE = DEFAULT_CONFIG_FILE
-GLOBAL_CONFIG_SCHEMA = DEFAULT_CONFIG_SCHEMA
-GLOBAL_CONFIG = {}
+OVERRIDE_CONFIG = {}
 
 class Config:
     """
@@ -37,7 +38,7 @@
     """
 
     @staticmethod
-    def init(config_file=DEFAULT_CONFIG_FILE, config_schema=DEFAULT_CONFIG_SCHEMA):
+    def init(config_file=DEFAULT_CONFIG_FILE, config_schema=DEFAULT_CONFIG_SCHEMA, override_config_file=None):
 
         # make schema relative to this directory
         # TODO give the possibility to specify an absolute path
@@ -47,12 +48,13 @@
         global CONFIG
         global CONFIG_FILE
 
-        global GLOBAL_CONFIG
-        global GLOBAL_CONFIG_FILE
-        global GLOBAL_CONFIG_SCHEMA
+        global OVERRIDE_CONFIG
+        global OVERRIDE_CONFIG_FILE
+        global OVERRIDE_CONFIG_SCHEMA
 
         # Use same schema for both provided and global config by default
-        GLOBAL_CONFIG_SCHEMA = config_schema
+        OVERRIDE_CONFIG_SCHEMA = config_schema
+        OVERRIDE_CONFIG_FILE = override_config_file
 
         # the config module can be initialized only one
         if INITIALIZED:
@@ -69,11 +71,11 @@
         if os.environ.get('XOS_CONFIG_SCHEMA'):
             config_schema = Config.get_abs_path(os.environ['XOS_CONFIG_SCHEMA'])
 
-        # allow GLOBAL_CONFIG_* to be overridden  by env vars
-        if os.environ.get('XOS_GLOBAL_CONFIG_FILE'):
-            GLOBAL_CONFIG_FILE = os.environ['XOS_GLOBAL_CONFIG_FILE']
-        if os.environ.get('XOS_GLOBAL_CONFIG_SCHEMA'):
-            GLOBAL_CONFIG_SCHEMA = Config.get_abs_path(os.environ['XOS_GLOBAL_CONFIG_SCHEMA'])
+        # allow OVERRIDE_CONFIG_* to be overridden  by env vars
+        if os.environ.get('XOS_OVERRIDE_CONFIG_FILE'):
+            OVERRIDE_CONFIG_FILE = os.environ['XOS_OVERRIDE_CONFIG_FILE']
+        if os.environ.get('XOS_OVERRIDE_CONFIG_SCHEMA'):
+            OVERRIDE_CONFIG_SCHEMA = Config.get_abs_path(os.environ['XOS_OVERRIDE_CONFIG_SCHEMA'])
 
         # if a -C parameter is set in the cli override the config_file
         # FIXME shouldn't this stay in whatever module call this one? and then just pass the file to the init method
@@ -84,8 +86,9 @@
         CONFIG_FILE = config_file
         CONFIG = Config.read_config(config_file, config_schema)
 
-        # Load global schema
-        GLOBAL_CONFIG = Config.read_config(GLOBAL_CONFIG_FILE, GLOBAL_CONFIG_SCHEMA, True)
+        # if an override is set
+        if OVERRIDE_CONFIG_FILE is not None:
+            OVERRIDE_CONFIG = Config.read_config(OVERRIDE_CONFIG_FILE, OVERRIDE_CONFIG_SCHEMA, True)
 
     @staticmethod
     def get_config_file():
@@ -154,14 +157,19 @@
         """
         global INITIALIZED
         global CONFIG
-        global GLOBAL_CONFIG
+        global OVERRIDE_CONFIG
+        global OVERRIDE_CONFIG_FILE
 
         if not INITIALIZED:
             raise Exception('[XOS-Config] Module has not been initialized')
 
         val = Config.get_param(query, CONFIG)
-        if not val:
-            val = Config.get_param(query, GLOBAL_CONFIG)
+        if OVERRIDE_CONFIG_FILE or not val:
+            # if we specified an override configuration, we should override the value
+            # we also look for the value in case it's missing
+            over_val = Config.get_param(query, OVERRIDE_CONFIG)
+            if over_val is not None:
+                val = over_val
         if not val:
             val = Config.get_param(query, default.DEFAULT_VALUES)
         if not val:
diff --git a/lib/xos-config/xosconfig/synchronizer-config-schema.yaml b/lib/xos-config/xosconfig/synchronizer-config-schema.yaml
index df73a25..37876ad 100644
--- a/lib/xos-config/xosconfig/synchronizer-config-schema.yaml
+++ b/lib/xos-config/xosconfig/synchronizer-config-schema.yaml
@@ -67,7 +67,7 @@
     type: str
   accessor:
     type: map
-    required: True
+    required: False
     map:
       endpoint:
         type: str