import configparser import logging import sys import os from datetime import datetime from uncloud.common.etcd_wrapper import Etcd3Wrapper from os.path import join as join_path logger = logging.getLogger(__name__) class CustomConfigParser(configparser.RawConfigParser): def __getitem__(self, key): try: result = super().__getitem__(key) except KeyError as err: raise KeyError( 'Key \'{}\' not found in configuration. Make sure you configure uncloud.'.format( key ) ) from err else: return result class Settings(object): def __init__(self): conf_name = 'uncloud.conf' conf_dir = os.environ.get('UCLOUD_CONF_DIR', os.path.expanduser('~/uncloud/')) self.config_file = join_path(conf_dir, conf_name) # this is used to cache config from etcd for 1 minutes. Without this we # would make a lot of requests to etcd which slows down everything. self.last_config_update = datetime.fromtimestamp(0) self.config_parser = CustomConfigParser(allow_no_value=True) self.config_parser.add_section('etcd') self.config_parser.set('etcd', 'base_prefix', '/') try: self.config_parser.read(self.config_file) except Exception as err: logger.error('%s', err) self.config_key = join_path(self['etcd']['base_prefix'] + 'uncloud/config/') self.read_internal_values() def get_etcd_client(self): args = tuple() try: kwargs = { 'host': self.config_parser.get('etcd', 'url'), 'port': self.config_parser.get('etcd', 'port'), 'ca_cert': self.config_parser.get('etcd', 'ca_cert'), 'cert_cert': self.config_parser.get('etcd', 'cert_cert'), 'cert_key': self.config_parser.get('etcd', 'cert_key'), } except configparser.Error as err: raise configparser.Error( '{} in config file {}'.format( err.message, self.config_file ) ) from err else: try: wrapper = Etcd3Wrapper(*args, **kwargs) except Exception as err: logger.error( 'etcd connection not successfull. Please check your config file.' '\nDetails: %s\netcd connection parameters: %s', err, kwargs, ) sys.exit(1) else: return wrapper def read_internal_values(self): base_prefix = self['etcd']['base_prefix'] self.config_parser.read_dict( { 'etcd': { 'file_prefix': join_path(base_prefix, 'files/'), 'host_prefix': join_path(base_prefix, 'hosts/'), 'image_prefix': join_path(base_prefix, 'images/'), 'image_store_prefix': join_path(base_prefix, 'imagestore/'), 'network_prefix': join_path(base_prefix, 'networks/'), 'request_prefix': join_path(base_prefix, 'requests/'), 'user_prefix': join_path(base_prefix, 'users/'), 'vm_prefix': join_path(base_prefix, 'vms/'), 'counter': { 'vxlan': join_path(base_prefix, 'counters/vxlan'), 'tap': join_path(base_prefix, 'counters/tap') } } } ) def read_config_file_values(self, config_file): try: # Trying to read configuration file with open(config_file, 'r') as config_file_handle: self.config_parser.read_file(config_file_handle) except FileNotFoundError: sys.exit( 'Configuration file {} not found!'.format(config_file) ) except Exception as err: logger.exception(err) sys.exit('Error occurred while reading configuration file') def read_values_from_etcd(self): etcd_client = self.get_etcd_client() if (datetime.utcnow() - self.last_config_update).total_seconds() > 60: config_from_etcd = etcd_client.get(self.config_key, value_in_json=True) if config_from_etcd: self.config_parser.read_dict(config_from_etcd.value) self.last_config_update = datetime.utcnow() else: raise KeyError('Key \'{}\' not found in etcd. Please configure uncloud.'.format(self.config_key)) def __getitem__(self, key): # Allow failing to read from etcd if we have # it locally if key not in self.config_parser.sections(): try: self.read_values_from_etcd() except KeyError: pass return self.config_parser[key] settings = Settings()