# 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. import copy import os import jsonschema import logging import pbr.version import platformdirs import yaml from os_faults.ansible import executor from os_faults.api import error from os_faults.api import human from os_faults import registry LOG = logging.getLogger(__name__) # Set default logging handler to avoid "No handler found" warnings. LOG.addHandler(logging.NullHandler()) def get_version(): return pbr.version.VersionInfo('os_faults').version_string() def get_release(): return pbr.version.VersionInfo('os_faults').release_string() APPDIRS = platformdirs.AppDirs(appname='openstack', appauthor='OpenStack') UNIX_SITE_CONFIG_HOME = '/etc/openstack' CONFIG_SEARCH_PATH = [ os.getcwd(), APPDIRS.user_config_dir, UNIX_SITE_CONFIG_HOME, ] CONFIG_FILES = [ os.path.join(d, 'os-faults' + s) for d in CONFIG_SEARCH_PATH for s in ['.json', '.yaml', '.yml'] ] CONFIG_SCHEMA = { 'type': 'object', '$schema': 'http://json-schema.org/draft-04/schema#', 'properties': { 'node_discover': { 'type': 'object', 'properties': { 'driver': {'type': 'string'}, 'args': {}, }, 'required': ['driver', 'args'], 'additionalProperties': False, }, 'services': { 'type': 'object', 'patternProperties': { '.*': { 'type': 'object', 'properties': { 'driver': {'type': 'string'}, 'args': {'type': 'object'}, 'hosts': { 'type': 'array', 'minItems': 1, 'items': {'type': 'string'}, }, }, 'required': ['driver', 'args'], 'additionalProperties': False, } }, 'additionalProperties': False, }, 'cloud_management': { 'type': 'object', 'properties': { 'driver': {'type': 'string'}, 'args': {'type': 'object'}, }, 'required': ['driver'], 'additionalProperties': False, }, 'power_managements': { 'type': 'array', 'items': { 'type': 'object', 'properties': { 'driver': {'type': 'string'}, 'args': {'type': 'object'}, }, 'required': ['driver', 'args'], 'additionalProperties': False, }, 'minItems': 1, }, }, 'required': ['cloud_management'], } def get_default_config_file(): if 'OS_FAULTS_CONFIG' in os.environ: return os.environ['OS_FAULTS_CONFIG'] for config_file in CONFIG_FILES: if os.path.exists(config_file): return config_file msg = 'Config file is not found on any of paths: {}'.format(CONFIG_FILES) raise error.OSFError(msg) def _init_driver(params): driver_cls = registry.get_driver(params['driver']) args = params.get('args') or {} # driver may have no arguments if args: jsonschema.validate(args, driver_cls.CONFIG_SCHEMA) return driver_cls(args) def connect(cloud_config=None, config_filename=None): """Connects to the cloud :param cloud_config: dict with cloud and power management params :param config_filename: name of the file where to read config from :returns: CloudManagement object """ if cloud_config is None: config_filename = config_filename or get_default_config_file() with open(config_filename) as fd: cloud_config = yaml.safe_load(fd.read()) jsonschema.validate(cloud_config, CONFIG_SCHEMA) cloud_management_conf = cloud_config['cloud_management'] cloud_management = _init_driver(cloud_management_conf) services = cloud_config.get('services') if services: cloud_management.update_services(services) cloud_management.validate_services() containers = cloud_config.get('containers') if containers: cloud_management.update_containers(containers) cloud_management.validate_containers() node_discover_conf = cloud_config.get('node_discover') if node_discover_conf: node_discover = _init_driver(node_discover_conf) cloud_management.set_node_discover(node_discover) power_managements_conf = cloud_config.get('power_managements') if power_managements_conf: for pm_conf in power_managements_conf: pm = _init_driver(pm_conf) cloud_management.add_power_management(pm) return cloud_management def discover(cloud_config): """Connect to the cloud and discover nodes and services :param cloud_config: dict with cloud and power management params :returns: config dict with discovered nodes/services """ cloud_config = copy.deepcopy(cloud_config) cloud_management = connect(cloud_config) # discover nodes hosts = [] for host in cloud_management.get_nodes().hosts: hosts.append({'ip': host.ip, 'mac': host.mac, 'fqdn': host.fqdn}) LOG.info('Found node: %s' % str(host)) cloud_config['node_discover'] = {'driver': 'node_list', 'args': hosts} # discover services cloud_config['services'] = {} for service_name in cloud_management.list_supported_services(): service = cloud_management.get_service(service_name) ips = service.get_nodes().get_ips() cloud_config['services'][service_name] = { 'driver': service.NAME, 'args': service.config } if ips: cloud_config['services'][service_name]['hosts'] = ips LOG.info('Found service "%s" on hosts: %s' % ( service_name, str(ips))) else: LOG.warning('Service "%s" is not found' % service_name) return cloud_config def human_api(cloud_management, command): """Executes a command written as English sentence :param cloud_management: library instance as returned by :connect: function :param command: text command """ human.execute(cloud_management, command) def register_ansible_modules(paths): """Registers ansible modules by provided paths Allows to use custom ansible modules in NodeCollection.run_task method :param paths: list of paths to folders with ansible modules """ executor.add_module_paths(paths)