# Copyright(c) 2016, Oracle and/or its affiliates. All Rights Reserved. # # 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 argparse import getpass import logging import os import traceback import yaml import kolla_cli.i18n as u from kolla_cli.api.client import ClientApi from kolla_cli.api.exceptions import ClientException from kolla_cli.commands.exceptions import CommandError from kolla_cli.common.utils import convert_lists_to_string from kolla_cli.common.utils import get_setup_user from cliff.command import Command from cliff.lister import Lister LOG = logging.getLogger(__name__) CLIENT = ClientApi() class HostAdd(Command): """Add host to openstack-kolla.""" def get_parser(self, prog_name): parser = super(HostAdd, self).get_parser(prog_name) parser.add_argument('hostname', metavar='', help=u._('Host name or ip address')) return parser def take_action(self, parsed_args): try: hostname = parsed_args.hostname.strip() CLIENT.host_add([hostname]) except ClientException as e: raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) class HostDestroy(Command): """Destroy all kolla containers on host(s). Stops and removes all kolla related docker containers on either the specified host or all hosts if the hostname all is used. """ def get_parser(self, prog_name): parser = super(HostDestroy, self).get_parser(prog_name) parser.add_argument('hostname', metavar='', help=u._('Host name or ip address or "all"')) parser.add_argument('--stop', action='store_true', help=u._('Stop rather than kill')) parser.add_argument('--includedata', action='store_true', help=u._('Destroy data containers')) parser.add_argument('--removeimages', action='store_true', help=u._('Remove docker images')) return parser def take_action(self, parsed_args): try: hostname = parsed_args.hostname.strip() hostnames = [hostname] if hostname == 'all': hostnames = _get_all_hostnames() # if there are no hosts, don't bother doing anything if not hostnames: return destroy_type = 'kill' if parsed_args.stop: destroy_type = 'stop' include_data = False if parsed_args.includedata: include_data = True remove_images = False if parsed_args.removeimages: remove_images = True if not include_data: question = ('This will delete all containers and data' ', are you sure? (y/n)') answer = raw_input(question) while answer != 'y' and answer != 'n': answer = raw_input(question) if answer is 'n': LOG.info('Aborting destroy') return verbose_level = self.app.options.verbose_level job = CLIENT.host_destroy(hostnames, destroy_type, verbose_level, include_data, remove_images) status = job.wait() if verbose_level > 2: LOG.info('\n\n' + 80 * '=') LOG.info(u._('DEBUG command output:\n{out}') .format(out=job.get_console_output())) if status != 0: raise CommandError(u._('Job failed:\n{msg}') .format(msg=job.get_error_message())) elif verbose_level > 1: # log any ansible warnings msg = job.get_error_message() if msg: LOG.warn(msg) except ClientException as e: raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) class HostRemove(Command): """Remove host from openstack-kolla.""" def get_parser(self, prog_name): parser = super(HostRemove, self).get_parser(prog_name) parser.add_argument('hostname', metavar='', help=u._('Host name or "all"')) return parser def take_action(self, parsed_args): try: hostname = parsed_args.hostname.strip() hostnames = [hostname] if hostname == 'all': hostnames = _get_all_hostnames() if len(hostnames) > 0: CLIENT.host_remove(hostnames) except ClientException as e: raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) class HostList(Lister): """List hosts and their groups. If a hostname is provided, only list information about that host. """ def get_parser(self, prog_name): parser = super(HostList, self).get_parser(prog_name) parser.add_argument('hostname', nargs='?', metavar='[hostname]', help=u._('Host name')) return parser def take_action(self, parsed_args): try: hostname = None if parsed_args.hostname: hostname = parsed_args.hostname.strip() hosts = [] if hostname: hosts = CLIENT.host_get([hostname]) else: hosts = CLIENT.host_get_all() data = [] if hosts: for host in hosts: data.append((host.name, host.get_groups())) else: data.append(('', '')) data = convert_lists_to_string(data, parsed_args) return ((u._('Host'), u._('Groups')), sorted(data)) except ClientException as e: raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) class HostCheck(Command): """Check configuration of host(s).""" def get_parser(self, prog_name): parser = super(HostCheck, self).get_parser(prog_name) parser.add_argument('hostname', metavar='', help=u._('Host name or "all"')) parser.add_argument('--predeploy', action='store_true', help=u._('Run pre-deploy host checks.')) return parser def take_action(self, parsed_args): try: hostname = parsed_args.hostname.strip() hostnames = [hostname] if hostname == 'all': hostnames = _get_all_hostnames() if parsed_args.predeploy: # run pre-deploy checks verbose_level = self.app.options.verbose_level job = CLIENT.host_precheck(hostnames, verbose_level) status = job.wait() if verbose_level > 2: LOG.info('\n\n' + 80 * '=') LOG.info(u._('DEBUG command output:\n{out}') .format(out=job.get_console_output())) if status != 0: raise CommandError(u._('Job failed:\n{msg}') .format(msg=job.get_error_message())) elif verbose_level > 1: # log any ansible warnings msg = job.get_error_message() if msg: LOG.warn(msg) else: # just do an ssh check summary = CLIENT.host_ssh_check(hostnames) all_ok = True for hostname, info in summary.items(): status = u._('success') msg = '' if not info['success']: status = u._('failed- ') msg = info['msg'] all_ok = False LOG.info(u._('Host {host}: {sts} {msg}') .format(host=hostname, sts=status, msg=msg)) if not all_ok: raise CommandError(u._('Host check failed.')) except ClientException as e: raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) class HostSetup(Command): """Setup kolla-cli on host.""" def get_parser(self, prog_name): parser = super(HostSetup, self).get_parser(prog_name) parser.add_argument('hostname', nargs='?', metavar='', help=u._('Host name')) parser.add_argument('--insecure', nargs='?', help=argparse.SUPPRESS) parser.add_argument('--file', '-f', nargs='?', metavar='', help=u._('Absolute path to hosts info file ')) return parser def take_action(self, parsed_args): try: if not parsed_args.hostname and not parsed_args.file: raise CommandError( u._('Host name or hosts info file path is required.')) if parsed_args.hostname and parsed_args.file: raise CommandError( u._('Host name and hosts info file path ' 'cannot both be present.')) if parsed_args.file: # multi-host setup via xml file hosts_data = self._get_yml_data(parsed_args.file.strip()) CLIENT.host_setup(hosts_data) else: # single host setup hostname = parsed_args.hostname.strip() summary = CLIENT.host_ssh_check([hostname]) if summary[hostname]['success']: LOG.info( u._LI('Skipping setup of host ({host}) as ' 'ssh check is ok.').format(host=hostname)) return True if parsed_args.insecure: password = parsed_args.insecure.strip() else: password = getpass.getpass( u._('{name} password for {host}: ') .format(name=get_setup_user(), host=hostname)) CLIENT.host_setup({hostname: {'password': password}}) except ClientException as e: raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) def _get_yml_data(self, yml_path): if not os.path.isfile(yml_path): raise CommandError( u._('No file exists at {path}. An absolute file path is ' 'required.').format(path=yml_path)) with open(yml_path, 'r') as hosts_file: file_data = hosts_file.read() hosts_info = yaml.safe_load(file_data) if not hosts_info: raise CommandError(u._('{path} is empty.').format(path=yml_path)) return hosts_info class HostStop(Command): """Stop all kolla containers on host(s). Stops all kolla related docker containers on either the specified host or all hosts if the hostname all is used. """ def get_parser(self, prog_name): parser = super(HostStop, self).get_parser(prog_name) parser.add_argument('hostname', metavar='', help=u._('Host name or ip address or "all"')) return parser def take_action(self, parsed_args): try: hostname = parsed_args.hostname.strip() hostnames = [hostname] if hostname == 'all': hostnames = _get_all_hostnames() verbose_level = self.app.options.verbose_level job = CLIENT.host_stop(hostnames, verbose_level) status = job.wait() if verbose_level > 2: LOG.info('\n\n' + 80 * '=') LOG.info(u._('DEBUG command output:\n{out}') .format(out=job.get_console_output())) if status != 0: raise CommandError(u._('Job failed:\n{msg}') .format(msg=job.get_error_message())) elif verbose_level > 1: # log any ansible warnings msg = job.get_error_message() if msg: LOG.warn(msg) except ClientException as e: raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) def _get_all_hostnames(): hostnames = [] hosts = CLIENT.host_get_all() for host in hosts: hostnames.append(host.name) return hostnames