[CORD-1273] Base structure for XOS-Config

Change-Id: I27f0b6664127ae2b26a1e7f489c70d5d32b73c3c
diff --git a/lib/xos-config/xosconfig/config.py b/lib/xos-config/xosconfig/config.py
new file mode 100644
index 0000000..e58477c
--- /dev/null
+++ b/lib/xos-config/xosconfig/config.py
@@ -0,0 +1,172 @@
+import os
+import sys
+import yaml
+import requests
+import default
+from pykwalify.core import Core as PyKwalify
+
+DEFAULT_CONFIG_FILE = "/opt/xos/config.yaml"
+INITIALIZED = False
+CONFIG = {}
+
+class Config:
+    """
+    XOS Configuration APIs
+    """
+
+    @staticmethod
+    def init(config_file=DEFAULT_CONFIG_FILE):
+        global INITIALIZED
+        global CONFIG
+        # the config module can be initialized only one
+        if INITIALIZED:
+            raise Exception('[XOS-Config] Module already initialized')
+        INITIALIZED = True
+
+        # if XOS-CONFIG is defined override the config_file
+        if os.environ.get('XOS-CONFIG'):
+            config_file = os.environ['XOS-CONFIG']
+
+        # 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
+        if Config.get_cli_param(sys.argv):
+            config_file = Config.get_cli_param(sys.argv)
+
+        CONFIG = Config.read_config(config_file)
+
+    @staticmethod
+    def clear():
+        global INITIALIZED
+        INITIALIZED = False
+
+    @staticmethod
+    def validate_config_format(config_file):
+        schema = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/config-schema.yaml')
+        c = PyKwalify(source_file=config_file, schema_files=[schema])
+        c.validate(raise_exception=True)
+
+    @staticmethod
+    def get_cli_param(args):
+        last = None
+        for arg in args:
+            if last == '-C':
+                return arg
+            last = arg
+
+    @staticmethod
+    def read_config(config_file):
+        """
+        Read the configuration file and return a dictionary
+        :param config_file: string
+        :return: dict
+        """
+        if not os.path.exists(config_file):
+            raise Exception('[XOS-Config] Config file not found at: %s' % config_file)
+
+        try:
+            Config.validate_config_format(config_file)
+        except Exception, e:
+            raise Exception('[XOS-Config] The config format is wrong: %s' % e.msg)
+
+        with open(config_file, 'r') as stream:
+            return yaml.safe_load(stream)
+
+    @staticmethod
+    def get(query):
+        """
+        Read a parameter from the config
+        :param query: a dot separated selector for configuration options (eg: database.username)
+        :return: the requested parameter in any format the parameter is specified
+        """
+        global INITIALIZED
+        global CONFIG
+
+        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, default.DEFAULT_VALUES)
+        if not val:
+            raise Exception('[XOS-Config] Config does not have a value (or a default) parameter %s' % query)
+        return val
+
+    @staticmethod
+    def get_param(query, config):
+        """
+        Search for a parameter in config's first level, other call get_nested_param
+        :param query: a dot separated selector for configuration options (eg: database.username)
+        :param config: the config source to read from (can be the config file or the defaults)
+        :return: the requested parameter in any format the parameter is specified
+        """
+        keys = query.split('.')
+        if len(keys) == 1:
+            key = keys[0]
+            if not config.has_key(key):
+                return None
+            return config[key]
+        else:
+            return Config.get_nested_param(keys, config)
+
+    @staticmethod
+    def get_nested_param(keys, config):
+        """
+        
+        :param keys: a list of descending selector
+        :param config: the config source to read from (can be the config file or the defaults)
+        :return: the requested parameter in any format the parameter is specified
+        """
+        param = config
+        for k in keys:
+            if not param.has_key(k):
+                return None
+            param = param[k]
+        return param
+
+    @staticmethod
+    def get_service_list():
+        """
+        Query registrator to get the list of services
+        NOTE: we assume that consul is a valid URL
+        :return: a list of service names 
+        """
+        service_dict = requests.get('http://consul:8500/v1/catalog/services').json()
+        service_list = []
+        for s in service_dict:
+            service_list.append(s)
+        return service_list
+
+    @staticmethod
+    def get_service_info(service_name):
+        """
+        Query registrator to get the details about a service
+        NOTE: we assume that consul is a valid URL
+        :param service_name: the name of the service, can be retrieved from get_service_list
+        :return: the informations about a service
+        """
+        response = requests.get('http://consul:8500/v1/catalog/service/%s' % service_name)
+        if not response.ok:
+            raise Exception('[XOS-Config] Registrator is down')
+        service = response.json()
+        if not service or len(service) == 0:
+            raise Exception('[XOS-Config] The service missing-service looking for does not exist')
+        return {
+            'name': service[0]['ServiceName'],
+            'url': service[0]['ServiceAddress'],
+            'port': service[0]['ServicePort']
+        }
+
+    @staticmethod
+    def get_service_endpoint(service_name):
+        """
+        Query registrator to get the details about a service and return the endpoint in for of a string
+        :param service_name: the name of the service, can be retrieved from get_service_list
+        :return: the endpoint of the service
+        """
+        service = Config.get_service_info(service_name)
+        return 'http://%s:%s' % (service['url'], service['port'])
+
+# NOTE is this needed if this package is not meant to be execute from the CLI?
+if __name__ == '__main__':
+    config = Config()
+    config.init()
\ No newline at end of file