
The appdirs is abandonned upstream. platformdirs is API compatible. Let's switch to it. Note this patch is already in the Debian package. Change-Id: I021b710072a3bc8eb505ca29f30d16b4da9aa472
230 lines
7.0 KiB
Python
230 lines
7.0 KiB
Python
# 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)
|