From b883423d677df3537b57cde0ced3f87643dae76d Mon Sep 17 00:00:00 2001 From: Steve Noyes Date: Mon, 21 Mar 2016 13:41:17 -0400 Subject: [PATCH] update host api & cli - update host add, remove api - new apis for host setup, ssh_check, get_hosts, get_groups - new exception classes - update host cli commands to use new apis Jira-Issue: OSTACKDEV-16 --- kollacli/api/async.py | 18 ++++ kollacli/api/exceptions.py | 79 ++++++++++++++++ kollacli/api/host.py | 82 +++++++++++++++-- kollacli/commands/deploy.py | 2 +- kollacli/{ => commands}/exceptions.py | 5 +- kollacli/commands/group.py | 2 +- kollacli/commands/host.py | 125 +++++++++----------------- kollacli/commands/password.py | 2 +- kollacli/commands/property.py | 2 +- kollacli/commands/service.py | 2 +- kollacli/commands/upgrade.py | 2 +- kollacli/common/ansible/actions.py | 18 ++-- kollacli/common/ansible/playbook.py | 8 +- kollacli/common/inventory.py | 61 +++++++------ kollacli/common/passwords.py | 8 +- kollacli/common/properties.py | 26 ++---- kollacli/common/utils.py | 37 ++++++-- kollacli/shell.py | 2 +- tests/host.py | 3 +- 19 files changed, 307 insertions(+), 177 deletions(-) create mode 100644 kollacli/api/exceptions.py rename kollacli/{ => commands}/exceptions.py (85%) diff --git a/kollacli/api/async.py b/kollacli/api/async.py index 80ddf3f..89984da 100644 --- a/kollacli/api/async.py +++ b/kollacli/api/async.py @@ -13,8 +13,13 @@ # under the License. import logging +import kollacli.i18n as u + +from kollacli.api.exceptions import InvalidArgument from kollacli.api.job import Job from kollacli.common.ansible import actions +from kollacli.common.inventory import Inventory +from kollacli.common.utils import safe_decode LOG = logging.getLogger(__name__) @@ -49,6 +54,15 @@ class AsyncApi(object): Stops and removes all kolla related docker containers on the specified hosts. """ + if destroy_type not in ['stop', 'kill']: + raise InvalidArgument( + u._('Invalid destroy type ({type}). Must be either ' + '"stop" or "kill".').format(type=destroy_type)) + + hostnames = safe_decode(hostnames) + inventory = Inventory.load() + inventory.validate_hostnames(hostnames) + ansible_job = actions.destroy_hosts(hostnames, destroy_type, verbose_level, include_data) return Job(ansible_job) @@ -60,5 +74,9 @@ class AsyncApi(object): any of the hosts are not configured correctly or if they have already been deployed to. """ + hostnames = safe_decode(hostnames) + inventory = Inventory.load() + inventory.validate_hostnames(hostnames) + ansible_job = actions.precheck(hostnames, verbose_level) return Job(ansible_job) diff --git a/kollacli/api/exceptions.py b/kollacli/api/exceptions.py new file mode 100644 index 0000000..1051523 --- /dev/null +++ b/kollacli/api/exceptions.py @@ -0,0 +1,79 @@ +# 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. +"""Exception definitions.""" +import kollacli.i18n as u + + +class ClientException(Exception): + """KollaClient Base Class Exception""" + def __init__(self, message, *args): + if not message: + message = u._('An unknown exception occurred.') + super(ClientException, self).__init__(message, *args) + + +class NotInInventory(ClientException): + """Not in inventory exception""" + def __init__(self, obj_type, obj_names, *args): + if isinstance(obj_names, list): + # list of names + invalid_objs = '' + comma = '' + for obj_name in obj_names: + invalid_objs = ''.join([invalid_objs, comma, obj_name]) + comma = ',' + else: + # single object name + invalid_objs = obj_names + message = (u._('{type} ({objs}) does not exist.') + .format(type=obj_type, objs=invalid_objs)) + super(NotInInventory, self).__init__(message, *args) + + +class HostError(ClientException): + pass + + +class HostsSshCheckError(ClientException): + def __init__(self, hostnames, *args): + failed_hosts = '' + comma = '' + for hostname in hostnames: + failed_hosts = ''.join([failed_hosts, comma, hostname]) + comma = ',' + message = (u._('host(s) ssh check failed: {hosts}') + .format(hosts=failed_hosts)) + super(HostsSshCheckError, self).__init__(message, *args) + + +class InvalidArgument(ClientException): + """Invalid argument""" + pass + + +class InvalidConfiguration(ClientException): + """Invalid configuration""" + pass + + +class FailedOperation(ClientException): + pass + + +class MissingArgument(ClientException): + """Missing argument""" + def __init__(self, argname, *args): + message = (u._('argument is missing: {name}') + .format(name=argname)) + super(InvalidArgument, self).__init__(message, *args) diff --git a/kollacli/api/host.py b/kollacli/api/host.py index 11ef247..29df060 100644 --- a/kollacli/api/host.py +++ b/kollacli/api/host.py @@ -13,26 +13,92 @@ # under the License. import logging +from kollacli.api.exceptions import MissingArgument from kollacli.common.inventory import Inventory +from kollacli.common.utils import safe_decode LOG = logging.getLogger(__name__) class HostApi(object): - def host_add(self, hostname): + def host_add(self, hostnames): + """add hosts to the inventory""" + if not hostnames: + raise MissingArgument('host names') + hostnames = safe_decode(hostnames) + inventory = Inventory.load() - inventory.add_host(hostname) + for hostname in hostnames: + inventory.add_host(hostname) Inventory.save(inventory) - def host_remove(self, hostname): - # TODO(bmace) - need to do a lot of validity - # / null checking in these api calls + def host_remove(self, hostnames): + """remove hosts from the inventory""" inventory = Inventory.load() - if hostname.lower() == 'all': - inventory.remove_all_hosts() - else: + if not hostnames: + raise MissingArgument('host name') + + hostnames = safe_decode(hostnames) + for hostname in hostnames: inventory.remove_host(hostname) Inventory.save(inventory) + + def host_get_all(self): + """get all hosts in the inventory""" + # TODO(snoyes) - need to make a host object + inventory = Inventory.load() + hostnames = inventory.get_hostnames() + return hostnames + + def host_get_groups(self, hostname=None): + """get groups for hosts + + Return: + - if hostname, {hostname: [groups]} + - else, {hostname: [groups], hostname: [groups]...} + """ + inventory = Inventory.load() + host_groups = inventory.get_host_groups() + if hostname: + hostname = safe_decode(hostname) + inventory.validate_hostnames([hostname]) + groupnames = host_groups[hostname] + host_groups = {hostname: groupnames} + return host_groups + + def host_check_ssh(self, hostnames): + """ssh check for hosts + + return {hostname: {'success': True|False, + 'msg': message}} + """ + inventory = Inventory.load() + hostnames = safe_decode(hostnames) + inventory.validate_hostnames(hostnames) + summary = inventory.ssh_check_hosts(hostnames) + return summary + + def host_setup_hosts(self, hosts_info): + """setup multiple hosts + + hosts_info is a dict of format: + {'hostname1': { + 'password': password + 'uname': user_name + } + } + The uname entry is optional. + """ + inventory = Inventory.load() + inventory.validate_hostnames(hosts_info.keys()) + inventory.setup_hosts(hosts_info) + + def host_setup(self, hostname, password): + # TODO(snoyes) move to host object + inventory = Inventory.load() + hostname = safe_decode(hostname) + inventory.validate_hostnames([hostname]) + inventory.setup_host(hostname, password) diff --git a/kollacli/commands/deploy.py b/kollacli/commands/deploy.py index 2b639cf..52a4348 100644 --- a/kollacli/commands/deploy.py +++ b/kollacli/commands/deploy.py @@ -17,8 +17,8 @@ import traceback import kollacli.i18n as u from kollacli.api.client import ClientApi +from kollacli.commands.exceptions import CommandError from kollacli.common.utils import convert_to_unicode -from kollacli.exceptions import CommandError from cliff.command import Command diff --git a/kollacli/exceptions.py b/kollacli/commands/exceptions.py similarity index 85% rename from kollacli/exceptions.py rename to kollacli/commands/exceptions.py index efaa742..d5903bc 100644 --- a/kollacli/exceptions.py +++ b/kollacli/commands/exceptions.py @@ -16,6 +16,9 @@ import kollacli.i18n as u class CommandError(Exception): + """CLI command error""" def __init__(self, message, *args): - message = u._('ERROR: {message}').format(message=message) + prefix = u._('ERROR: ') + if not message.startswith(prefix): + message = prefix + message super(CommandError, self).__init__(message, *args) diff --git a/kollacli/commands/group.py b/kollacli/commands/group.py index 7a79df7..b607aaa 100644 --- a/kollacli/commands/group.py +++ b/kollacli/commands/group.py @@ -15,9 +15,9 @@ import traceback import kollacli.i18n as u +from kollacli.commands.exceptions import CommandError from kollacli.common.inventory import Inventory from kollacli.common.utils import convert_to_unicode -from kollacli.exceptions import CommandError from cliff.command import Command from cliff.lister import Lister diff --git a/kollacli/commands/host.py b/kollacli/commands/host.py index 003e56a..9b4bf28 100644 --- a/kollacli/commands/host.py +++ b/kollacli/commands/host.py @@ -21,11 +21,8 @@ import yaml import kollacli.i18n as u from kollacli.api.client import ClientApi -# TODO(snoyes): remove inventory reference from CLI -from kollacli.common.inventory import Inventory -from kollacli.common.utils import convert_to_unicode -from kollacli.common.utils import get_setup_user -from kollacli.exceptions import CommandError +from kollacli.api.exceptions import ClientException +from kollacli.commands.exceptions import CommandError from cliff.command import Command from cliff.lister import Lister @@ -34,12 +31,6 @@ LOG = logging.getLogger(__name__) CLIENT = ClientApi() -def _host_not_found(hostname): - raise CommandError( - u._('Host ({host}) not found. Please add it with "host add".') - .format(host=hostname)) - - class HostAdd(Command): """Add host to openstack-kolla.""" @@ -52,17 +43,10 @@ class HostAdd(Command): def take_action(self, parsed_args): try: hostname = parsed_args.hostname.strip() - hostname = convert_to_unicode(hostname) + CLIENT.host_add([hostname]) - if hostname.lower() == 'all': - raise CommandError( - u._('Special host name "all" cannot be added as an ' - 'individual host.')) - - CLIENT.host_add(hostname) - - except CommandError as e: - raise e + except ClientException as e: + raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) @@ -86,17 +70,11 @@ class HostDestroy(Command): def take_action(self, parsed_args): try: - hostname = '' hostname = parsed_args.hostname.strip() - hostname = convert_to_unicode(hostname) - inventory = Inventory.load() hostnames = [hostname] if hostname == 'all': - hostnames = inventory.get_hostnames() - else: - if not inventory.get_host(hostname): - _host_not_found(hostname) + hostnames = CLIENT.host_get_all() destroy_type = 'kill' if parsed_args.stop: @@ -118,8 +96,8 @@ class HostDestroy(Command): raise CommandError(u._('Job failed:\n{msg}') .format(msg=job.get_error_message())) - except CommandError as e: - raise e + except ClientException as e: + raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) @@ -129,19 +107,20 @@ class HostRemove(Command): def get_parser(self, prog_name): parser = super(HostRemove, self).get_parser(prog_name) - parser.add_argument('hostname', metavar='', - help=u._('Host 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() - hostname = convert_to_unicode(hostname) + hostnames = [hostname] + if hostname == 'all': + hostnames = CLIENT.host_get_all() + CLIENT.host_remove(hostnames) - CLIENT.host_remove(hostname) - - except CommandError as e: - raise e + except ClientException as e: + raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) @@ -163,28 +142,19 @@ class HostList(Lister): hostname = None if parsed_args.hostname: hostname = parsed_args.hostname.strip() - hostname = convert_to_unicode(hostname) - - inventory = Inventory.load() - - if hostname: - host = inventory.get_host(hostname) - if not host: - _host_not_found(hostname) + host_groups = CLIENT.host_get_groups(hostname) data = [] - host_groups = inventory.get_host_groups() if host_groups: - if hostname: - data.append((hostname, host_groups[hostname])) - else: - for (hostname, groupnames) in host_groups.items(): - data.append((hostname, groupnames)) + for (hostname, groupnames) in host_groups.items(): + data.append((hostname, groupnames)) else: data.append(('', '')) + return ((u._('Host'), u._('Groups')), sorted(data)) - except CommandError as e: - raise e + + except ClientException as e: + raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) @@ -194,7 +164,7 @@ class HostCheck(Command): def get_parser(self, prog_name): parser = super(HostCheck, self).get_parser(prog_name) - parser.add_argument('hostname', metavar='', + 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.')) @@ -203,14 +173,9 @@ class HostCheck(Command): def take_action(self, parsed_args): try: hostname = parsed_args.hostname.strip() - hostname = convert_to_unicode(hostname) - inventory = Inventory.load() hostnames = [hostname] if hostname == 'all': - hostnames = inventory.get_hostnames() - else: - if not inventory.get_host(hostname): - _host_not_found(hostname) + hostnames = CLIENT.host_get_all() if parsed_args.predeploy: # run pre-deploy checks @@ -224,24 +189,23 @@ class HostCheck(Command): if status != 0: raise CommandError(u._('Job failed:\n{msg}') .format(msg=job.get_error_message())) - else: - # run ssh checks + summary = CLIENT.host_check_ssh(hostnames) all_ok = True - summary = inventory.ssh_check_hosts(hostnames) for hostname, info in summary.items(): - status = 'success' + status = u._('success') msg = '' if not info['success']: - status = 'failed- ' + status = u._('failed- ') msg = info['msg'] all_ok = False - LOG.info('Host (%s): %s %s' % (hostname, status, msg)) + LOG.info(u._('Host {host}: {sts} {msg}') + .format(host=hostname, sts=status, msg=msg)) if not all_ok: - raise CommandError('Host check failed.') - except CommandError as e: - raise e + raise CommandError(u._('Host check failed.')) + except ClientException as e: + raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) @@ -268,21 +232,16 @@ class HostSetup(Command): raise CommandError( u._('Host name and hosts info file path ' 'cannot both be present.')) - inventory = Inventory.load() if parsed_args.file: # multi-host setup via xml file hosts_data = self.get_yml_data(parsed_args.file.strip()) - inventory.setup_hosts(hosts_data) + CLIENT.host_setup_hosts(hosts_data) else: # single host setup hostname = parsed_args.hostname.strip() - hostname = convert_to_unicode(hostname) - if not inventory.get_host(hostname): - _host_not_found(hostname) - - check_ok, _ = inventory.ssh_check_host(hostname) - if check_ok: + summary = CLIENT.host_check_ssh([hostname]) + if summary[hostname]['success']: LOG.info( u._LI('Skipping setup of host ({host}) as ' 'ssh check is ok.').format(host=hostname)) @@ -291,15 +250,13 @@ class HostSetup(Command): if parsed_args.insecure: password = parsed_args.insecure.strip() else: - setup_user = get_setup_user() password = getpass.getpass( - u._('{user} password for {host}: ') - .format(user=setup_user, host=hostname)) - password = convert_to_unicode(password) - inventory.setup_host(hostname, password) + u._('kolla password for {host}: ') + .format(host=hostname)) + CLIENT.host_setup(hostname, password) - except CommandError as e: - raise e + except ClientException as e: + raise CommandError(str(e)) except Exception as e: raise Exception(traceback.format_exc()) diff --git a/kollacli/commands/password.py b/kollacli/commands/password.py index 93d7a17..1f1f372 100644 --- a/kollacli/commands/password.py +++ b/kollacli/commands/password.py @@ -20,10 +20,10 @@ import kollacli.i18n as u from cliff.command import Command from cliff.lister import Lister +from kollacli.commands.exceptions import CommandError from kollacli.common.passwords import clear_password from kollacli.common.passwords import get_password_names from kollacli.common.passwords import set_password -from kollacli.exceptions import CommandError class PasswordSet(Command): diff --git a/kollacli/commands/property.py b/kollacli/commands/property.py index f0dc425..8cdff6d 100644 --- a/kollacli/commands/property.py +++ b/kollacli/commands/property.py @@ -15,9 +15,9 @@ import traceback import kollacli.i18n as u +from kollacli.commands.exceptions import CommandError from kollacli.common import properties from kollacli.common import utils -from kollacli.exceptions import CommandError from cliff.command import Command from cliff.lister import Lister diff --git a/kollacli/commands/service.py b/kollacli/commands/service.py index ebc6a36..d630800 100644 --- a/kollacli/commands/service.py +++ b/kollacli/commands/service.py @@ -15,9 +15,9 @@ import traceback import kollacli.i18n as u +from kollacli.commands.exceptions import CommandError from kollacli.common.inventory import Inventory from kollacli.common.utils import convert_to_unicode -from kollacli.exceptions import CommandError from cliff.command import Command from cliff.lister import Lister diff --git a/kollacli/commands/upgrade.py b/kollacli/commands/upgrade.py index b35b96c..cd68ad6 100644 --- a/kollacli/commands/upgrade.py +++ b/kollacli/commands/upgrade.py @@ -19,7 +19,7 @@ from cliff.command import Command import kollacli.i18n as u from kollacli.api.client import ClientApi -from kollacli.exceptions import CommandError +from kollacli.commands.exceptions import CommandError CLIENT = ClientApi() diff --git a/kollacli/common/ansible/actions.py b/kollacli/common/ansible/actions.py index 925f8b2..fca25c9 100644 --- a/kollacli/common/ansible/actions.py +++ b/kollacli/common/ansible/actions.py @@ -16,6 +16,9 @@ import os import kollacli.i18n as u +from kollacli.api.exceptions import InvalidArgument +from kollacli.api.exceptions import InvalidConfiguration +from kollacli.api.exceptions import NotInInventory from kollacli.common.ansible.playbook import AnsiblePlaybook from kollacli.common.inventory import Inventory from kollacli.common import properties @@ -24,7 +27,6 @@ from kollacli.common.utils import get_kolla_etc from kollacli.common.utils import get_kolla_home from kollacli.common.utils import get_kollacli_home from kollacli.common.utils import is_string_true -from kollacli.exceptions import CommandError LOG = logging.getLogger(__name__) @@ -37,11 +39,6 @@ def destroy_hosts(hostnames, destroy_type, or killed. That will be determined by the destroy_type, which can either be 'stop' or 'kill'. ''' - if destroy_type not in ['stop', 'kill']: - raise CommandError( - u._('Invalid destroy type ({type}). Must be either ' - '"stop" or "kill".').format(type=destroy_type)) - playbook_name = 'host_destroy_no_data.yml' if include_data: playbook_name = 'host_destroy.yml' @@ -125,7 +122,7 @@ def _run_deploy_rules(playbook): # cannot have both groups and hosts if playbook.hosts and playbook.groups: - raise CommandError( + raise InvalidArgument( u._('Hosts and Groups arguments cannot ' 'both be present at the same time.')) @@ -134,8 +131,7 @@ def _run_deploy_rules(playbook): for service in playbook.services: valid_service = inventory.get_service(service) if not valid_service: - raise CommandError(u._('Service ({srvc}) not found.') - .format(srvc=service)) + raise NotInInventory(u._('Service'), service) # check that every group with enabled services # has hosts associated to it @@ -160,7 +156,7 @@ def _run_deploy_rules(playbook): failed_groups.append(groupname) if len(failed_groups) > 0: - raise CommandError( + raise InvalidConfiguration( u._('Deploy failed. ' 'Groups: {groups} with enabled ' 'services : {services} ' @@ -184,4 +180,4 @@ def _run_deploy_rules(playbook): 'not yet been set up. Please see the ' 'documentation for swift configuration ' 'instructions.') - raise CommandError(msg) + raise InvalidConfiguration(msg) diff --git a/kollacli/common/ansible/playbook.py b/kollacli/common/ansible/playbook.py index b6dea57..cbbb79f 100644 --- a/kollacli/common/ansible/playbook.py +++ b/kollacli/common/ansible/playbook.py @@ -18,11 +18,11 @@ import traceback import kollacli.i18n as u +from kollacli.api.exceptions import NotInInventory from kollacli.common.ansible.job import AnsibleJob from kollacli.common.utils import get_admin_user from kollacli.common.utils import get_ansible_command from kollacli.common.utils import get_kolla_etc -from kollacli.exceptions import CommandError from kollacli.common.inventory import Inventory @@ -114,15 +114,13 @@ class AnsiblePlaybook(object): for hostname in self.hosts: host = self.inventory.get_host(hostname) if not host: - raise CommandError(u._('Host ({host}) not found.') - .format(host=hostname)) + raise NotInInventory(u._('Host'), hostname) inventory_filter['deploy_hosts'] = self.hosts elif self.groups: for groupname in self.groups: group = self.inventory.get_group(groupname) if not group: - raise CommandError(u._('Group ({group}) not found.') - .format(group=groupname)) + raise NotInInventory(u._('Group'), groupname) inventory_filter['deploy_groups'] = self.groups inventory_path = \ self.inventory.create_json_gen_file(inventory_filter) diff --git a/kollacli/common/inventory.py b/kollacli/common/inventory.py index 94e22a4..8a876e3 100644 --- a/kollacli/common/inventory.py +++ b/kollacli/common/inventory.py @@ -21,6 +21,12 @@ import uuid import kollacli.i18n as u +from kollacli.api.exceptions import FailedOperation +from kollacli.api.exceptions import HostError +from kollacli.api.exceptions import InvalidArgument +from kollacli.api.exceptions import InvalidConfiguration +from kollacli.api.exceptions import MissingArgument +from kollacli.api.exceptions import NotInInventory from kollacli.common.sshutils import ssh_setup_host from kollacli.common.utils import get_admin_user from kollacli.common.utils import get_ansible_command @@ -31,8 +37,6 @@ from kollacli.common.utils import run_cmd from kollacli.common.utils import sync_read_file from kollacli.common.utils import sync_write_file -from kollacli.exceptions import CommandError - ANSIBLE_SSH_USER = 'ansible_ssh_user' ANSIBLE_CONNECTION = 'ansible_connection' ANSIBLE_BECOME = 'ansible_become' @@ -336,7 +340,7 @@ class Inventory(object): else: inventory = Inventory() except Exception: - raise CommandError( + raise FailedOperation( u._('Loading inventory failed. : {error}') .format(error=traceback.format_exc())) return inventory @@ -353,7 +357,7 @@ class Inventory(object): sync_write_file(inventory_path, pretty_data) except Exception as e: - raise CommandError( + raise FailedOperation( u._('Saving inventory failed. : {error}') .format(error=str(e))) @@ -397,17 +401,14 @@ class Inventory(object): if group name is not none, add host to group """ if groupname and groupname not in self._groups: - raise CommandError( - u._('Group name ({group}) does not exist.') - .format(group=groupname)) + raise NotInInventory(u._('Group'), groupname) if groupname and hostname not in self._hosts: - raise CommandError( - u._('Host name ({host}) does not exist.') - .format(host=hostname)) + # if a groupname is specified, the host must already exist + raise NotInInventory(u._('Host'), hostname) if not groupname and not self.remote_mode and len(self._hosts) >= 1: - raise CommandError( + raise InvalidConfiguration( u._('Cannot have more than one host when in local deploy ' 'mode.')) @@ -436,9 +437,7 @@ class Inventory(object): if group name is not none, remove host from group """ if groupname and groupname not in self._groups: - raise CommandError( - u._('Group name ({group}) does not exist.') - .format(group=groupname)) + raise NotInInventory(u._('Group'), groupname) if hostname not in self._hosts: return @@ -488,7 +487,7 @@ class Inventory(object): summary = '\n' for hostname, err in failed_hosts.items(): summary = summary + '- %s: %s\n' % (hostname, err) - raise CommandError( + raise HostError( u._('Not all hosts were set up. : {reasons}') .format(reasons=summary)) else: @@ -507,7 +506,7 @@ class Inventory(object): LOG.info(u._LI('Host ({host}) setup succeeded.') .format(host=hostname)) except Exception as e: - raise CommandError( + raise HostError( u._('Host ({host}) setup failed : {error}') .format(host=hostname, error=str(e))) return True @@ -559,7 +558,7 @@ class Inventory(object): # Group names cannot overlap with service names: if groupname in self._services or groupname in self._sub_services: - raise CommandError( + raise InvalidArgument( u._('Invalid group name. A service name ' 'cannot be used for a group name.')) @@ -574,7 +573,7 @@ class Inventory(object): def remove_group(self, groupname): if groupname in PROTECTED_GROUPS: - raise CommandError( + raise InvalidArgument( u._('Cannot remove {group} group. It is required by kolla.') .format(group=groupname)) @@ -617,7 +616,7 @@ class Inventory(object): return groups def get_host_groups(self): - """return { hostname : groupnames }""" + """return { hostname : [groupnames] }""" host_groups = {} for host in self._hosts.values(): @@ -676,8 +675,7 @@ class Inventory(object): def add_group_to_service(self, groupname, servicename): if groupname not in self._groups: - raise CommandError(u._('Group ({group}) not found.') - .format(group=groupname)) + raise NotInInventory(u._('Group'), groupname) if servicename in self._services: service = self.get_service(servicename) service.add_groupname(groupname) @@ -685,13 +683,11 @@ class Inventory(object): sub_service = self.get_sub_service(servicename) sub_service.add_groupname(groupname) else: - raise CommandError(u._('Service ({service})) not found.') - .format(service=servicename)) + raise NotInInventory(u._('Service'), servicename) def remove_group_from_service(self, groupname, servicename): if groupname not in self._groups: - raise CommandError(u._('Group ({group}) not found.') - .format(group=groupname)) + raise NotInInventory(u._('Group'), groupname) if servicename in self._services: service = self.get_service(servicename) service.remove_groupname(groupname) @@ -699,8 +695,7 @@ class Inventory(object): sub_service = self.get_sub_service(servicename) sub_service.remove_groupname(groupname) else: - raise CommandError(u._('Service ({service})) not found.') - .format(service=servicename)) + raise NotInInventory(u._('Service'), servicename) def create_sub_service(self, sub_servicename): if sub_servicename not in self._sub_services: @@ -750,7 +745,7 @@ class Inventory(object): def set_deploy_mode(self, remote_flag): if not remote_flag and len(self._hosts) > 1: - raise CommandError( + raise InvalidConfiguration( u._('Cannot set local deploy mode when multiple hosts exist.')) self.remote_mode = remote_flag @@ -887,3 +882,13 @@ class Inventory(object): def remove_json_gen_file(self, path): remove_temp_inventory(path) + + def validate_hostnames(self, hostnames): + if not hostnames: + raise MissingArgument(u._('host name(s)')) + invalid_hosts = [] + for hostname in hostnames: + if hostname not in self._hosts: + invalid_hosts.append(hostname) + if invalid_hosts: + raise NotInInventory(u._('Host'), invalid_hosts) diff --git a/kollacli/common/passwords.py b/kollacli/common/passwords.py index 34c62da..8ab5b46 100644 --- a/kollacli/common/passwords.py +++ b/kollacli/common/passwords.py @@ -15,8 +15,8 @@ import os import kollacli.i18n as u +from kollacli.api.exceptions import FailedOperation from kollacli.common import utils -from kollacli.exceptions import CommandError PWDS_FILENAME = 'passwords.yml' PWD_EDITOR_FILENAME = 'passwd_editor.py' @@ -31,7 +31,7 @@ def set_password(pwd_key, pwd_value): cmd = '%s -k %s -v %s' % (_get_cmd_prefix(), pwd_key, pwd_value) err_msg, output = utils.run_cmd(cmd, print_output=False) if err_msg: - raise CommandError( + raise FailedOperation( u._('Password set failed. {error} {message}') .format(error=err_msg, message=output)) @@ -44,7 +44,7 @@ def clear_password(pwd_key): cmd = '%s -k %s -c' % (_get_cmd_prefix(), pwd_key) err_msg, output = utils.run_cmd(cmd, print_output=False) if err_msg: - raise CommandError('%s %s' % (err_msg, output)) + raise FailedOperation('%s %s' % (err_msg, output)) def get_password_names(): @@ -52,7 +52,7 @@ def get_password_names(): cmd = '%s -l' % (_get_cmd_prefix()) err_msg, output = utils.run_cmd(cmd, print_output=False) if err_msg: - raise CommandError('%s %s' % (err_msg, output)) + raise FailedOperation('%s %s' % (err_msg, output)) pwd_names = [] if output and ',' in output: diff --git a/kollacli/common/properties.py b/kollacli/common/properties.py index 1143b7f..3c4dd36 100644 --- a/kollacli/common/properties.py +++ b/kollacli/common/properties.py @@ -18,13 +18,13 @@ import yaml import kollacli.i18n as u +from kollacli.api.exceptions import NotInInventory from kollacli.common.inventory import Inventory from kollacli.common.utils import change_property from kollacli.common.utils import get_group_vars_dir from kollacli.common.utils import get_host_vars_dir from kollacli.common.utils import get_kolla_home from kollacli.common.utils import sync_read_file -from kollacli.exceptions import CommandError LOG = logging.getLogger(__name__) @@ -185,9 +185,7 @@ class AnsibleProperties(object): for host_name in host_list: host = inventory.get_host(host_name) if host is None: - raise CommandError( - u._('Host {host} does not exist.') - .format(host=host_name)) + raise NotInInventory(u._('Host'), host_name) if host_name in self.host_props: prop_list += self.host_props[host_name] else: @@ -204,9 +202,7 @@ class AnsibleProperties(object): for group_name in group_list: group = inventory.get_group(group_name) if group is None: - raise CommandError( - u._('Group {group} does not exist.') - .format(group=group_name)) + raise NotInInventory(u._('Group'), group_name) if group_name in self.group_props: prop_list += self.group_props[group_name] else: @@ -264,9 +260,7 @@ class AnsibleProperties(object): for host_name in hosts: host = inventory.get_host(host_name) if host is None: - raise CommandError( - u._('Host {host} does not exist.') - .format(host=host_name)) + raise NotInInventory(u._('Host'), host_name) host_list.append(host) try: for host in host_list: @@ -286,9 +280,7 @@ class AnsibleProperties(object): for group_name in groups: group = inventory.get_group(group_name) if group is None: - raise CommandError( - u._('Group {group} does not exist.') - .format(group=group_name)) + raise NotInInventory(u._('Group'), group_name) group_list.append(group) try: for group in group_list: @@ -315,9 +307,7 @@ class AnsibleProperties(object): for host_name in hosts: host = inventory.get_host(host_name) if host is None: - raise CommandError( - u._('Host {host} does not exist.') - .format(host=host_name)) + raise NotInInventory(u._('Host'), host_name) host_list.append(host) try: for host in host_list: @@ -337,9 +327,7 @@ class AnsibleProperties(object): for group_name in groups: group = inventory.get_group(group_name) if group is None: - raise CommandError( - u._('Group {group} does not exist.') - .format(group=group_name)) + raise NotInInventory(u._('Group'), group_name) group_list.append(group) try: for group in group_list: diff --git a/kollacli/common/utils.py b/kollacli/common/utils.py index d02d831..d7f0a27 100644 --- a/kollacli/common/utils.py +++ b/kollacli/common/utils.py @@ -22,7 +22,7 @@ import sys import kollacli.i18n as u -from kollacli.exceptions import CommandError +from kollacli.api.exceptions import InvalidArgument LOG = logging.getLogger(__name__) @@ -69,7 +69,7 @@ def get_kolla_log_file_size(): try: size = int(size_str) except Exception: - raise CommandError( + raise InvalidArgument( u._('Environmental variable ({env_var}) is not an ' 'integer ({log_size}).') .format(env_var=envvar, log_size=size_str)) @@ -82,7 +82,7 @@ def get_property_list_length(): try: length = int(length_str) except Exception: - raise CommandError( + raise InvalidArgument( u._('Environmental variable ({env_var}) is not an ' 'integer ({prop_length}).') .format(env_var=envvar, prop_length=length_str)) @@ -133,6 +133,8 @@ def convert_to_unicode(the_string): This is used to fixup extended ascii chars in strings. these chars cause errors in json pickle/unpickle. """ + if the_string is None: + return the_string return six.u(the_string) @@ -265,15 +267,32 @@ def sync_write_file(path, data, mode='w'): raise e -def safe_decode(text): - """Convert bytes or string to unicode string""" - if text is not None: +def safe_decode(obj_to_decode): + """Convert bytes or string to unicode string + + Convert either a string or list of strings to + unicode. + """ + if obj_to_decode is None: + return None + + new_obj = None + if isinstance(obj_to_decode, list): + new_obj = [] + for text in obj_to_decode: + try: + text = text.decode('utf-8') + except AttributeError: # nosec + # py3 will raise if text is already a string + pass + new_obj.append(text) + else: try: - text = text.decode('utf-8') + new_obj = obj_to_decode.decode('utf-8') except AttributeError: # nosec # py3 will raise if text is already a string - pass - return text + new_obj = obj_to_decode + return new_obj def is_string_true(string): diff --git a/kollacli/shell.py b/kollacli/shell.py index b3af2df..e96427c 100755 --- a/kollacli/shell.py +++ b/kollacli/shell.py @@ -21,11 +21,11 @@ from cliff.commandmanager import CommandManager import kollacli.i18n as u +from kollacli.commands.exceptions import CommandError from kollacli.common.inventory import INVENTORY_PATH from kollacli.common.utils import get_kolla_log_dir from kollacli.common.utils import get_kolla_log_file_size from kollacli.common.utils import get_kollacli_etc -from kollacli.exceptions import CommandError LOG = logging.getLogger(__name__) diff --git a/tests/host.py b/tests/host.py index 8f77b79..7120892 100644 --- a/tests/host.py +++ b/tests/host.py @@ -159,7 +159,8 @@ class TestFunctional(KollaCliTest): # no hostname and no file spec msg = self.run_cli_cmd('host setup', True) - self.assertIn('ERROR', msg, 'No command params did not error') + self.assertIn('ERROR', msg, + 'Setup with no command params did not error') # hostname and file spec both present msg = self.run_cli_cmd('host setup -f zzzzz hostname', True)