Merge branch 'o3l_2.next' of git://ca-git.us.oracle.com/openstack-kollacli

Jira-Issue: OPENSTACK-592

Conflicts:
	kollacli/commands/host.py
	kollacli/common.py
	tests/deploy.py
	tests/password.py
	tests/property.py
	tools/log_collector.py
This commit is contained in:
Borne Mace 2016-01-19 14:05:04 -08:00
commit 654fd18433
37 changed files with 1203 additions and 652 deletions

View File

@ -43,6 +43,7 @@ Requires: python-cliff >= 1.13.0
Requires: python-cliff-tablib >= 1.1
Requires: python-jsonpickle >= 0.9.2
Requires: python-oslo-i18n >= 2.5.0
Requires: python-oslo-utils >= 2.4.0
Requires: python-paramiko >= 1.15.1
Requires: python-pbr >= 1.6.0
Requires: python-six >= 1.9.0
@ -50,6 +51,7 @@ Requires: PyYAML >= 3.10
Requires: /usr/bin/ssh-keygen
Conflicts: python-oslo-utils = 2.6.0
%description
The KollaCLI simplifies OpenStack Kolla deployments.
@ -154,6 +156,9 @@ esac
%changelog
* Fri Nov 06 2015 - Steve Noyes <steve.noyes@oracle.com>
- add python-oslo-utils requirement
* Mon Oct 26 2015 - Steve Noyes <steve.noyes@oracle.com>
- Remove obsolete json_generator

View File

@ -0,0 +1,307 @@
***********************
HOSTS API - Reference
***********************
.. warning::
This hosts documentation is work in progress and may change in near future.
Hosts API
===============
.. _get_hosts_all:
GET /v1/hosts
##########################
Retrieve all hosts in the inventory.
Request/Response:
************************************
.. code-block:: none
Request:
GET /v1/hosts
Headers:
X-Auth-Token: {token_id}
Response:
HTTP/1.1 200 OK
{
"hosts":[
{
"hostname":{host_name1},
"groups":[
{group_name1},
{group_name2},
.....
],
},
.....
]
}
HTTP Status Codes
*****************
+------+-----------------------------------------------------------------------------+
| Code | Description |
+======+=============================================================================+
| 200 | Successful request. |
+------+-----------------------------------------------------------------------------+
| 401 | Missing or Invalid X-Auth-Token. Authentication required. |
+------+-----------------------------------------------------------------------------+
.. _get_host:
GET /v1/hosts/{host_name}
##########################
Retrieve the host for the given host name.
Request/Response:
************************************
.. code-block:: none
Request:
GET /v1/hosts/{host_name}
Headers:
X-Auth-Token: {token_id}
Response:
HTTP/1.1 200 OK
{
"hostname":{host_name},
"groups":[
{group_name1},
{group_name2},
],
.....
}
HTTP Status Codes
*****************
+------+-----------------------------------------------------------------------------+
| Code | Description |
+======+=============================================================================+
| 200 | Successful request. |
+------+-----------------------------------------------------------------------------+
| 401 | Missing or Invalid X-Auth-Token. Authentication required. |
+------+-----------------------------------------------------------------------------+
| 404 | Host does not exist in inventory |
+------+-----------------------------------------------------------------------------+
.. _post_host:
POST /v1/hosts
##########################
Create new host.
This call is used to create a new host and add it to the inventory.
Request/Response (create or replace host):
**************************************
.. code-block:: none
Request:
POST /v1/hosts/{host_name}
Headers:
Content-Type: application/json
X-Auth-Token: {token_id}
{
"hostname":{host_name},
"groups": [
{group_name1},
{group_name2},
.....
],
}
Response:
HTTP/1.1 200 OK
HTTP Status Codes
*****************
+------+-----------------------------------------------------------------------------+
| Code | Description |
+======+=============================================================================+
| 200 | Successfully created host. |
+------+-----------------------------------------------------------------------------+
| 400 | Bad Request. |
+------+-----------------------------------------------------------------------------+
| 401 | Missing or Invalid X-Auth-Token. Authentication required. |
+------+-----------------------------------------------------------------------------+
| 404 | Group does not exist in inventory |
+------+-----------------------------------------------------------------------------+
| 409 | Host already exists in inventory |
+------+-----------------------------------------------------------------------------+
.. _delete_host:
DELETE /v1/hosts/{host_name}
##############################
Delete host from the inventory.
Request/Response:
*****************
.. code-block:: none
DELETE /v1/host/{host_name}
Headers:
X-Auth-Token: {token_id}
Response:
HTTP/1.1 200 OK
HTTP Status Codes
*****************
+------+-----------------------------------------------------------------------------+
| Code | Description |
+======+=============================================================================+
| 200 | Successfully deleted host. |
+------+-----------------------------------------------------------------------------+
| 401 | Missing or Invalid X-Auth-Token. Authentication required. |
+------+-----------------------------------------------------------------------------+
.. _check_host:
POST /v1/hosts/{host_name}/actions
##############################
Check verifies that the host has its ssh keys set up correctly (can be accessed without a
password from the deploy host). If the host check failed, the reason will be provided in
the response message.
Request/Response:
*****************
.. code-block:: none
POST /v1/hosts/{host_name}/actions
Headers:
Content-Type: application/json
X-Auth-Token: {token_id}
{
"check": {
"host-name": {host_name},
}
}
Response:
*********
.. code-block:: none
200 OK
{
"message":{message_string}
}
HTTP Status Codes
*****************
+------+-----------------------------------------------------------------------------+
| Code | Description |
+======+=============================================================================+
| 200 | Host check was successful |
+------+-----------------------------------------------------------------------------+
| 400 | Bad Request |
+------+-----------------------------------------------------------------------------+
| 401 | Invalid X-Auth-Token or the token doesn't have permissions to this resource |
+------+-----------------------------------------------------------------------------+
| 404 | Host does not exist in inventory |
+------+-----------------------------------------------------------------------------+
| 405 | Host check failed |
+------+-----------------------------------------------------------------------------+
.. _setup_host:
POST /v1/hosts/actions
##############################
Host setup distributes the ssh keys into the appropriate directory/file on the host.
This assumes docker has been installed and is running on the host. Setup can be done
for a single host or multiple hosts.
If a single host is to be setup, the host-name and host-password attributes must be
supplied. If multiple hosts are to be setup, the hosts-file-path must be
supplied.
Either the host-name/password or hosts-file-path must be supplied. If both are supplied,
then all the hosts specified will be setup.
If the host setup failed, the reason will be provided in
the response message.
Request/Response:
*****************
.. code-block:: none
POST /v1/hosts/actions
Headers:
Content-Type: application/json
X-Auth-Token: {token_id}
{
"setup": {
"host-name": {host_name},
"host-password": {password},
"hosts-file-path": {hosts_file_path}
}
}
Response:
*********
.. code-block:: none
200 OK
{
"message":{message_string}
}
HTTP Status Codes
*****************
+------+-----------------------------------------------------------------------------+
| Code | Description |
+======+=============================================================================+
| 200 | Host setup was successful |
+------+-----------------------------------------------------------------------------+
| 400 | Bad Request |
+------+-----------------------------------------------------------------------------+
| 401 | Invalid X-Auth-Token or the token doesn't have permissions to this resource |
+------+-----------------------------------------------------------------------------+
| 404 | Host does not exist in inventory |
+------+-----------------------------------------------------------------------------+
| 405 | Host setup failed |
+------+-----------------------------------------------------------------------------+

101
kollacli/commands/deploy.py Normal file
View File

@ -0,0 +1,101 @@
# Copyright(c) 2015, 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 logging
import traceback
import kollacli.i18n as u
from kollacli.common.ansible.actions import deploy
from kollacli.common.inventory import Inventory
from kollacli.common.utils import convert_to_unicode
from kollacli.exceptions import CommandError
from cliff.command import Command
LOG = logging.getLogger(__name__)
class Deploy(Command):
"""Deploy"""
def get_parser(self, prog_name):
parser = super(Deploy, self).get_parser(prog_name)
parser.add_argument('--hosts', nargs='?',
metavar='<host_list>',
help=u._('Deployment host list'))
parser.add_argument('--groups', nargs='?',
metavar='<group_list>',
help=u._('Deployment group list'))
parser.add_argument('--services', nargs='?',
metavar='<service_list>',
help=u._('Deployment service list'))
parser.add_argument('--serial', action='store_true',
help=u._('Deploy serially'))
return parser
def take_action(self, parsed_args):
hosts = None
groups = None
services = None
serial_flag = False
verbose_level = self.app.options.verbose_level
try:
if parsed_args.hosts:
host_list = parsed_args.hosts.strip()
hosts = convert_to_unicode(host_list).split(',')
if parsed_args.groups:
group_list = parsed_args.groups.strip()
groups = convert_to_unicode(group_list).split(',')
if parsed_args.services:
service_list = parsed_args.services.strip()
services = convert_to_unicode(service_list).split(',')
if parsed_args.serial:
serial_flag = True
deploy(hosts, groups, services, serial_flag,
verbose_level)
except Exception:
raise Exception(traceback.format_exc())
class Setdeploy(Command):
"""Set deploy mode
Set deploy mode to either local or remote. Local indicates
that the openstack deployment will be to the local host.
Remote means that the deployment is on remote hosts.
"""
def get_parser(self, prog_name):
parser = super(Setdeploy, self).get_parser(prog_name)
parser.add_argument('mode', metavar='<mode>',
help=u._('mode=<local, remote>'))
return parser
def take_action(self, parsed_args):
try:
mode = parsed_args.mode.strip()
remote_flag = False
if mode == 'remote':
remote_flag = True
elif mode != 'local':
raise CommandError(
u._('Invalid deploy mode. Mode must be '
'either "local" or "remote".'))
inventory = Inventory.load()
inventory.set_deploy_mode(remote_flag)
Inventory.save(inventory)
except CommandError as e:
raise e
except Exception:
raise Exception(traceback.format_exc())

View File

@ -11,12 +11,13 @@
# 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 logging
import traceback
from kollacli.ansible.inventory import Inventory
import kollacli.i18n as u
from kollacli.common.inventory import Inventory
from kollacli.common.utils import convert_to_unicode
from kollacli.exceptions import CommandError
from kollacli import utils
from cliff.command import Command
from cliff.lister import Lister
@ -24,18 +25,16 @@ from cliff.lister import Lister
class GroupAdd(Command):
"""Add group to open-stack-kolla"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(GroupAdd, self).get_parser(prog_name)
parser.add_argument('groupname', metavar='<groupname>',
help='group')
help=u._('Group name'))
return parser
def take_action(self, parsed_args):
try:
groupname = parsed_args.groupname.strip()
groupname = utils.convert_to_unicode(groupname)
groupname = convert_to_unicode(groupname)
inventory = Inventory.load()
inventory.add_group(groupname)
@ -49,18 +48,16 @@ class GroupAdd(Command):
class GroupRemove(Command):
"""Remove group from openstack-kolla"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(GroupRemove, self).get_parser(prog_name)
parser.add_argument('groupname', metavar='<groupname>',
help='group name')
help=u._('Group name'))
return parser
def take_action(self, parsed_args):
try:
groupname = parsed_args.groupname.strip()
groupname = utils.convert_to_unicode(groupname)
groupname = convert_to_unicode(groupname)
inventory = Inventory.load()
inventory.remove_group(groupname)
Inventory.save(inventory)
@ -72,22 +69,20 @@ class GroupRemove(Command):
class GroupAddhost(Command):
"""Add host to group"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(GroupAddhost, self).get_parser(prog_name)
parser.add_argument('groupname', metavar='<groupname>',
help='group')
help=u._('Group name'))
parser.add_argument('hostname', metavar='<hostname>',
help='host')
help=u._('Host name'))
return parser
def take_action(self, parsed_args):
try:
groupname = parsed_args.groupname.strip()
groupname = utils.convert_to_unicode(groupname)
groupname = convert_to_unicode(groupname)
hostname = parsed_args.hostname.strip()
hostname = utils.convert_to_unicode(hostname)
hostname = convert_to_unicode(hostname)
inventory = Inventory.load()
inventory.add_host(hostname, groupname)
Inventory.save(inventory)
@ -100,22 +95,20 @@ class GroupAddhost(Command):
class GroupRemovehost(Command):
"""Remove host group from group"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(GroupRemovehost, self).get_parser(prog_name)
parser.add_argument('groupname', metavar='<groupname>',
help='group')
help=u._('Group name'))
parser.add_argument('hostname', metavar='<hostname>',
help='host')
help=u._('Host name'))
return parser
def take_action(self, parsed_args):
try:
groupname = parsed_args.groupname.strip()
groupname = utils.convert_to_unicode(groupname)
groupname = convert_to_unicode(groupname)
hostname = parsed_args.hostname.strip()
hostname = utils.convert_to_unicode(hostname)
hostname = convert_to_unicode(hostname)
inventory = Inventory.load()
inventory.remove_host(hostname, groupname)
@ -129,8 +122,6 @@ class GroupRemovehost(Command):
class GroupListhosts(Lister):
"""List all groups and their hosts"""
log = logging.getLogger(__name__)
def take_action(self, parsed_args):
try:
inventory = Inventory.load()
@ -142,7 +133,7 @@ class GroupListhosts(Lister):
data.append((groupname, hostnames))
else:
data.append(('', ''))
return (('Group', 'Hosts'), sorted(data))
return ((u._('Group'), u._('Hosts')), sorted(data))
except CommandError as e:
raise e
except Exception as e:
@ -151,22 +142,20 @@ class GroupListhosts(Lister):
class GroupAddservice(Command):
"""Add service to group"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(GroupAddservice, self).get_parser(prog_name)
parser.add_argument('groupname', metavar='<groupname>',
help='group')
help=u._('Group name'))
parser.add_argument('servicename', metavar='<servicename>',
help='service')
help=u._('Service name'))
return parser
def take_action(self, parsed_args):
try:
groupname = parsed_args.groupname.strip()
groupname = utils.convert_to_unicode(groupname)
groupname = convert_to_unicode(groupname)
servicename = parsed_args.servicename.strip()
servicename = utils.convert_to_unicode(servicename)
servicename = convert_to_unicode(servicename)
inventory = Inventory.load()
inventory.add_group_to_service(groupname, servicename)
@ -180,22 +169,20 @@ class GroupAddservice(Command):
class GroupRemoveservice(Command):
"""Remove service group from group"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(GroupRemoveservice, self).get_parser(prog_name)
parser.add_argument('groupname', metavar='<groupname>',
help='group')
help=u._('Group name'))
parser.add_argument('servicename', metavar='<servicename>',
help='service')
help=u._('Service name'))
return parser
def take_action(self, parsed_args):
try:
groupname = parsed_args.groupname.strip()
groupname = utils.convert_to_unicode(groupname)
groupname = convert_to_unicode(groupname)
servicename = parsed_args.servicename.strip()
servicename = utils.convert_to_unicode(servicename)
servicename = convert_to_unicode(servicename)
inventory = Inventory.load()
inventory.remove_group_from_service(groupname, servicename)
@ -209,8 +196,6 @@ class GroupRemoveservice(Command):
class GroupListservices(Lister):
"""List all groups and their services"""
log = logging.getLogger(__name__)
def take_action(self, parsed_args):
try:
inventory = Inventory.load()
@ -222,7 +207,7 @@ class GroupListservices(Lister):
data.append((groupname, sorted(servicenames)))
else:
data.append(('', ''))
return (('Group', 'Services'), sorted(data))
return ((u._('Group'), u._('Services')), sorted(data))
except CommandError as e:
raise e
except Exception as e:

View File

@ -16,41 +16,41 @@ import getpass
import logging
import os
import traceback
import utils
import yaml
from kollacli.ansible.inventory import Inventory
from kollacli.ansible.playbook import AnsiblePlaybook
from kollacli.ansible import properties
import kollacli.i18n as u
from kollacli.common.ansible.actions import destroy_hosts
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.utils import convert_to_unicode
from kollacli.utils import get_kollacli_home
from kollacli.utils import get_setup_user
from cliff.command import Command
from cliff.lister import Lister
LOG = logging.getLogger(__name__)
def _host_not_found(log, hostname):
def _host_not_found(hostname):
raise CommandError(
'Host (%s) not found. ' % hostname +
'Please add it with "host add"')
u._('Host ({host}) not found. Please add it with "host add".')
.format(host=hostname))
class HostAdd(Command):
"""Add host to open-stack-kolla"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(HostAdd, self).get_parser(prog_name)
parser.add_argument('hostname', metavar='<hostname>',
help='host name or ip address')
help=u._('Host name or ip address'))
return parser
def take_action(self, parsed_args):
try:
hostname = parsed_args.hostname.strip()
hostname = utils.convert_to_unicode(hostname)
hostname = convert_to_unicode(hostname)
inventory = Inventory.load()
inventory.add_host(hostname)
@ -67,16 +67,15 @@ class HostDestroy(Command):
Stops and removes all kolla related docker containers on either the
specified host or if no host is specified, on all hosts.
"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(HostDestroy, self).get_parser(prog_name)
parser.add_argument('hostname', metavar='<hostname | all>',
help='host name or ip address or "all"')
help=u._('Host name or ip address or "all"'))
parser.add_argument('--stop', action='store_true',
help='stop rather than kill')
help=u._('Stop rather than kill'))
parser.add_argument('--includedata', action='store_true',
help='destroy data containers')
help=u._('Destroy data containers'))
return parser
def take_action(self, parsed_args):
@ -85,36 +84,17 @@ class HostDestroy(Command):
hostname = parsed_args.hostname.strip()
hostname = convert_to_unicode(hostname)
if hostname != 'all':
inventory = Inventory.load()
host = inventory.get_host(hostname)
if not host:
_host_not_found(self.log, hostname)
destroy_type = 'kill'
if parsed_args.stop:
destroy_type = 'stop'
playbook_name = 'host_destroy_no_data.yml'
include_data = False
if parsed_args.includedata:
playbook_name = 'host_destroy.yml'
include_data = True
verbose_level = self.app.options.verbose_level
destroy_hosts(hostname, destroy_type, verbose_level, include_data)
self.log.info('please be patient as this may take a while.')
ansible_properties = properties.AnsibleProperties()
base_distro = \
ansible_properties.get_property('kolla_base_distro')
install_type = \
ansible_properties.get_property('kolla_install_type')
container_prefix = base_distro + '-' + install_type
kollacli_home = get_kollacli_home()
playbook = AnsiblePlaybook()
playbook.playbook_path = os.path.join(kollacli_home,
'ansible/' + playbook_name)
playbook.extra_vars = 'hosts=' + hostname + \
' prefix=' + container_prefix + \
' destroy_type=' + destroy_type
playbook.print_output = False
playbook.verbose_level = self.app.options.verbose_level
playbook.run()
except CommandError as e:
raise e
except Exception as e:
@ -124,17 +104,16 @@ class HostDestroy(Command):
class HostRemove(Command):
"""Remove host from openstack-kolla"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(HostRemove, self).get_parser(prog_name)
parser.add_argument('hostname', metavar='<hostname>', help='host name')
parser.add_argument('hostname', metavar='<hostname>',
help=u._('Host name'))
return parser
def take_action(self, parsed_args):
try:
hostname = parsed_args.hostname.strip()
hostname = utils.convert_to_unicode(hostname)
hostname = convert_to_unicode(hostname)
inventory = Inventory.load()
inventory.remove_host(hostname)
Inventory.save(inventory)
@ -150,12 +129,10 @@ class HostList(Lister):
If a hostname is provided, only list information about that host.
"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(HostList, self).get_parser(prog_name)
parser.add_argument('hostname', nargs='?', metavar='[hostname]',
help='hostname')
help=u._('Host name'))
return parser
def take_action(self, parsed_args):
@ -163,14 +140,14 @@ class HostList(Lister):
hostname = None
if parsed_args.hostname:
hostname = parsed_args.hostname.strip()
hostname = utils.convert_to_unicode(hostname)
hostname = convert_to_unicode(hostname)
inventory = Inventory.load()
if hostname:
host = inventory.get_host(hostname)
if not host:
_host_not_found(self.log, hostname)
_host_not_found(hostname)
data = []
host_groups = inventory.get_host_groups()
@ -182,7 +159,7 @@ class HostList(Lister):
data.append((hostname, groupnames))
else:
data.append(('', ''))
return (('Host', 'Groups'), sorted(data))
return ((u._('Host'), u._('Groups')), sorted(data))
except CommandError as e:
raise e
except Exception as e:
@ -192,20 +169,19 @@ class HostList(Lister):
class HostCheck(Command):
"""Check if openstack-kollacli is setup"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(HostCheck, self).get_parser(prog_name)
parser.add_argument('hostname', metavar='<hostname>', help='hostname')
parser.add_argument('hostname', metavar='<hostname>',
help=u._('Host name'))
return parser
def take_action(self, parsed_args):
try:
hostname = parsed_args.hostname.strip()
hostname = utils.convert_to_unicode(hostname)
hostname = convert_to_unicode(hostname)
inventory = Inventory.load()
if not inventory.get_host(hostname):
_host_not_found(self.log, hostname)
_host_not_found(hostname)
inventory.check_host(hostname)
except CommandError as e:
@ -217,26 +193,25 @@ class HostCheck(Command):
class HostSetup(Command):
"""Setup openstack-kollacli on host"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(HostSetup, self).get_parser(prog_name)
parser.add_argument('hostname', nargs='?',
metavar='<hostname>', help='hostname')
metavar='<hostname>', help=u._('Host name'))
parser.add_argument('--insecure', nargs='?', help=argparse.SUPPRESS)
parser.add_argument('--file', '-f', nargs='?',
metavar='<hosts_info_file>',
help='hosts info file absolute path')
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('Hostname or hosts info file path ' +
'is required')
raise CommandError(
u._('Host name or hosts info file path is required.'))
if parsed_args.hostname and parsed_args.file:
raise CommandError('Hostname and hosts info file path ' +
'cannot both be present')
raise CommandError(
u._('Host name and hosts info file path '
'cannot both be present.'))
inventory = Inventory.load()
if parsed_args.file:
@ -246,23 +221,25 @@ class HostSetup(Command):
else:
# single host setup
hostname = parsed_args.hostname.strip()
hostname = utils.convert_to_unicode(hostname)
hostname = convert_to_unicode(hostname)
if not inventory.get_host(hostname):
_host_not_found(self.log, hostname)
_host_not_found(hostname)
check_ok = inventory.check_host(hostname, True)
if check_ok:
self.log.info('Skipping setup of host (%s) as check is ok'
% hostname)
LOG.info(
u._LI('Skipping setup of host ({host}) as '
'check is ok.').format(host=hostname))
return True
if parsed_args.insecure:
password = parsed_args.insecure.strip()
else:
setup_user = get_setup_user()
password = getpass.getpass('%s password for %s: ' %
(setup_user, hostname))
password = utils.convert_to_unicode(password)
password = getpass.getpass(
u._('{user} password for {host}: ')
.format(user=setup_user, host=hostname))
password = convert_to_unicode(password)
inventory.setup_host(hostname, password)
except CommandError as e:
@ -272,13 +249,14 @@ class HostSetup(Command):
def get_yml_data(self, yml_path):
if not os.path.isfile(yml_path):
raise CommandError('No file exists at %s. ' % yml_path +
'An absolute file path is required.')
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.load(file_data)
hosts_info = yaml.safe_load(file_data)
if not hosts_info:
raise CommandError('%s is empty' % yml_path)
raise CommandError(u._('{path} is empty.').format(path=yml_path))
return hosts_info

View File

@ -13,26 +13,25 @@
# under the License.
import argparse
import getpass
import logging
import traceback
import kollacli.i18n as u
from cliff.command import Command
from cliff.lister import Lister
from kollacli.ansible.passwords import clear_password
from kollacli.ansible.passwords import get_password_names
from kollacli.ansible.passwords import set_password
from kollacli.common.passwords import clear_password
from kollacli.common.passwords import get_password_names
from kollacli.common.passwords import set_password
class PasswordSet(Command):
"Password Set"
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(PasswordSet, self).get_parser(prog_name)
parser.add_argument('passwordname', metavar='<passwordname>',
help='passwordname')
help=u._('Password name'))
parser.add_argument('--insecure', nargs='?', help=argparse.SUPPRESS)
return parser
@ -42,7 +41,7 @@ class PasswordSet(Command):
if parsed_args.insecure:
password = parsed_args.insecure.strip()
else:
password = getpass.getpass('Password: ').strip()
password = getpass.getpass(u._('Password: ')).strip()
set_password(password_name, password)
@ -53,12 +52,10 @@ class PasswordSet(Command):
class PasswordClear(Command):
"Password Clear"
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(PasswordClear, self).get_parser(prog_name)
parser.add_argument('passwordname', metavar='<passwordname>',
help='passwordname')
help=u._('Password name'))
return parser
def take_action(self, parsed_args):
@ -72,8 +69,6 @@ class PasswordClear(Command):
class PasswordList(Lister):
"""List all password names"""
log = logging.getLogger(__name__)
def take_action(self, parsed_args):
password_names = get_password_names()
password_names = sorted(password_names)
@ -82,4 +77,4 @@ class PasswordList(Lister):
for password_name in password_names:
data.append((password_name, '-'))
return (('Password Name', 'Password'), data)
return ((u._('Password Name'), u._('Password')), data)

View File

@ -14,23 +14,26 @@
import logging
import traceback
from kollacli.ansible import properties
import kollacli.i18n as u
from kollacli.common import properties
from kollacli.common import utils
from cliff.command import Command
from cliff.lister import Lister
LOG = logging.getLogger(__name__)
class PropertySet(Command):
"Property Set"
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(PropertySet, self).get_parser(prog_name)
parser.add_argument('propertyname', metavar='<propertyname>',
help='propertyname')
help=u._('Property name'))
parser.add_argument('propertyvalue', metavar='<propertyvalue',
help='propertyvalue')
help=u._('Property value'))
return parser
def take_action(self, parsed_args):
@ -47,12 +50,10 @@ class PropertySet(Command):
class PropertyClear(Command):
"Property Clear"
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(PropertyClear, self).get_parser(prog_name)
parser.add_argument('propertyname', metavar='<propertyname>',
help='propertyname')
help=u._('Property name'))
return parser
def take_action(self, parsed_args):
@ -68,16 +69,52 @@ class PropertyClear(Command):
class PropertyList(Lister):
"""List all properties"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(PropertyList, self).get_parser(prog_name)
parser.add_argument('--all', action='store_true',
help=u._('List all properties'))
parser.add_argument('--long', action='store_true',
help=u._('Show all property attributes'))
return parser
def take_action(self, parsed_args):
ansible_properties = properties.AnsibleProperties()
property_list = ansible_properties.get_all_unique()
list_all = False
if parsed_args.all:
list_all = True
list_long = False
if parsed_args.long:
list_long = True
property_length = utils.get_property_list_length()
data = []
if property_list:
for value in property_list:
data.append((value.name, value.value))
else:
data.append(('', ''))
for prop in property_list:
include_prop = False
if (prop.value is not None and
len(str(prop.value)) > property_length):
if list_all:
include_prop = True
else:
include_prop = True
return (('Property Name', 'Property Value'), data)
if include_prop:
if list_long:
data.append((prop.name, prop.value, prop.overrides,
prop.orig_value))
else:
data.append((prop.name, prop.value))
else:
if list_long:
data.append(('', '', '', ''))
else:
data.append(('', ''))
if list_long:
return ((u._('Property Name'), u._('Property Value'),
u._('Overrides'), u._('Original Value')), data)
else:
return ((u._('Property Name'), u._('Property Value')), data)

View File

@ -11,12 +11,13 @@
# 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 logging
import traceback
from kollacli.ansible.inventory import Inventory
import kollacli.i18n as u
from kollacli.common.inventory import Inventory
from kollacli.common.utils import convert_to_unicode
from kollacli.exceptions import CommandError
from kollacli import utils
from cliff.command import Command
from cliff.lister import Lister
@ -28,22 +29,21 @@ class ServiceAddGroup(Command):
Associated the service to a group. If this is a sub-service,
the inherit flag will be cleared.
"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(ServiceAddGroup, self).get_parser(prog_name)
parser.add_argument('servicename', metavar='<servicename>',
help='service')
help=u._('Service name'))
parser.add_argument('groupname', metavar='<groupname>',
help='group')
help=u._('Group name'))
return parser
def take_action(self, parsed_args):
try:
groupname = parsed_args.groupname.strip()
groupname = utils.convert_to_unicode(groupname)
groupname = convert_to_unicode(groupname)
servicename = parsed_args.servicename.strip()
servicename = utils.convert_to_unicode(servicename)
servicename = convert_to_unicode(servicename)
inventory = Inventory.load()
@ -59,22 +59,20 @@ class ServiceAddGroup(Command):
class ServiceRemoveGroup(Command):
"""Remove group from service"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(ServiceRemoveGroup, self).get_parser(prog_name)
parser.add_argument('servicename', metavar='<servicename>',
help='service')
help=u._('Service name'))
parser.add_argument('groupname', metavar='<groupname>',
help='group')
help=u._('Group name'))
return parser
def take_action(self, parsed_args):
try:
groupname = parsed_args.groupname.strip()
groupname = utils.convert_to_unicode(groupname)
groupname = convert_to_unicode(groupname)
servicename = parsed_args.servicename.strip()
servicename = utils.convert_to_unicode(servicename)
servicename = convert_to_unicode(servicename)
inventory = Inventory.load()
@ -90,8 +88,6 @@ class ServiceRemoveGroup(Command):
class ServiceListGroups(Lister):
"""List services and their groups"""
log = logging.getLogger(__name__)
def take_action(self, parsed_args):
try:
inventory = Inventory.load()
@ -109,7 +105,8 @@ class ServiceListGroups(Lister):
data.append((servicename, groupnames, inh_str))
else:
data.append(('', ''))
return (('Service', 'Groups', 'Inherited'), sorted(data))
return ((u._('Service'), u._('Groups'), u._('Inherited')),
sorted(data))
except CommandError as e:
raise e
except Exception as e:
@ -119,8 +116,6 @@ class ServiceListGroups(Lister):
class ServiceList(Lister):
"""List services and their sub-services"""
log = logging.getLogger(__name__)
def take_action(self, parsed_args):
try:
inventory = Inventory.load()
@ -132,7 +127,7 @@ class ServiceList(Lister):
data.append((servicename, sub_svcname))
else:
data.append(('', ''))
return (('Service', 'Sub-Services'), sorted(data))
return ((u._('Service'), u._('Sub-Services')), sorted(data))
except CommandError as e:
raise e
except Exception as e:

View File

@ -0,0 +1,27 @@
# Copyright(c) 2015, 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.
from kollacli.common.support import dump
from cliff.command import Command
class Dump(Command):
"""Dumps configuration data for debugging
Dumps most files in /etc/kolla and /usr/share/kolla into a
tar file so be given to support / development to help with
debugging problems.
"""
def take_action(self, parsed_args):
dump()

View File

@ -1,228 +0,0 @@
# Copyright(c) 2015, 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 logging
import os
import tarfile
import tempfile
import traceback
from kollacli.ansible.inventory import Inventory
from kollacli.ansible.playbook import AnsiblePlaybook
from kollacli.ansible.properties import AnsibleProperties
from kollacli.exceptions import CommandError
from kollacli.utils import convert_to_unicode
from kollacli.utils import get_kolla_etc
from kollacli.utils import get_kolla_home
from kollacli.utils import get_kolla_log_dir
from kollacli.utils import get_kollacli_etc
from kollacli.utils import run_cmd
from cliff.command import Command
class Deploy(Command):
"""Deploy"""
log = logging.getLogger(__name__)
def get_parser(self, prog_name):
parser = super(Deploy, self).get_parser(prog_name)
parser.add_argument('--hosts', nargs='?',
metavar='<host_list>',
help='deployment host list')
parser.add_argument('--groups', nargs='?',
metavar='<group_list>',
help='deployment group list')
parser.add_argument('--services', nargs='?',
metavar='<service_list>',
help='deployment service list')
parser.add_argument('--serial', action='store_true',
help='deploy serially')
return parser
def take_action(self, parsed_args):
try:
if parsed_args.hosts and parsed_args.groups:
raise CommandError('Hosts and Groups arguments cannot both ' +
'be present at the same time.')
self._run_rules()
playbook = AnsiblePlaybook()
kolla_home = get_kolla_home()
playbook.playbook_path = os.path.join(kolla_home,
'ansible/site.yml')
if parsed_args.hosts:
host_list = parsed_args.hosts.strip()
host_list = convert_to_unicode(host_list)
playbook.hosts = host_list.split(',')
if parsed_args.groups:
group_list = parsed_args.groups.strip()
group_list = convert_to_unicode(group_list)
playbook.groups = group_list.split(',')
if parsed_args.services:
tag_list = parsed_args.services.strip()
tag_list = convert_to_unicode(tag_list)
playbook.services = tag_list.split(',')
if parsed_args.serial:
playbook.serial = True
playbook.verbose_level = self.app.options.verbose_level
playbook.run()
except CommandError as e:
raise e
except Exception:
raise Exception(traceback.format_exc())
def _run_rules(self):
# check that ring files are in /etc/kolla/config/swift if
# swift is enabled
expected_files = ['account.ring.gz',
'container.ring.gz',
'object.ring.gz']
properties = AnsibleProperties()
is_enabled = properties.get_property('enable_swift')
if is_enabled == 'yes':
path_pre = os.path.join(get_kolla_etc(), 'config', 'swift')
for expected_file in expected_files:
path = os.path.join(path_pre, expected_file)
if not os.path.isfile(path):
msg = ('Deploy failed. ' +
'Swift is enabled but ring buffers have ' +
'not yet been set up. Please see the ' +
'documentation for swift configuration ' +
'instructions.')
raise CommandError(msg)
class Dump(Command):
"""Dumps configuration data for debugging
Dumps most files in /etc/kolla and /usr/share/kolla into a
tar file so be given to support / development to help with
debugging problems.
"""
log = logging.getLogger(__name__)
def take_action(self, parsed_args):
try:
kolla_home = get_kolla_home()
kolla_logs = get_kolla_log_dir()
kolla_ansible = os.path.join(kolla_home, 'ansible')
kolla_docs = os.path.join(kolla_home, 'docs')
kolla_etc = get_kolla_etc()
kolla_config = os.path.join(kolla_etc, 'config')
kolla_globals = os.path.join(kolla_etc, 'globals.yml')
kollacli_etc = get_kollacli_etc().rstrip('/')
ketc = 'kolla/etc/'
kshare = 'kolla/share/'
fd, dump_path = tempfile.mkstemp(prefix='kollacli_dump_',
suffix='.tgz')
os.close(fd) # avoid fd leak
with tarfile.open(dump_path, 'w:gz') as tar:
# Can't blanket add kolla_home because the .ssh dir is
# accessible by the kolla user only (not kolla group)
tar.add(kolla_ansible,
arcname=kshare + os.path.basename(kolla_ansible))
tar.add(kolla_docs,
arcname=kshare + os.path.basename(kolla_docs))
# Can't blanket add kolla_etc because the passwords.yml
# file is accessible by the kolla user only (not kolla group)
tar.add(kolla_config,
arcname=ketc + os.path.basename(kolla_config))
tar.add(kolla_globals,
arcname=ketc + os.path.basename(kolla_globals))
tar.add(kollacli_etc,
arcname=ketc + os.path.basename(kollacli_etc))
# add kolla log files
if os.path.isdir(kolla_logs):
tar.add(kolla_logs)
# add output of various commands
self._add_cmd_info(tar)
self.log.info('dump successful to %s' % dump_path)
except Exception:
raise Exception(traceback.format_exc())
def _add_cmd_info(self, tar):
# run all the kollacli list commands
cmds = ['kollacli service listgroups',
'kollacli service list',
'kollacli group listservices',
'kollacli group listhosts',
'kollacli host list',
'kollacli property list',
'kollacli password list']
# collect the json inventory output
inventory = Inventory.load()
inv_path = inventory.create_json_gen_file()
cmds.append(inv_path)
try:
fd, path = tempfile.mkstemp(suffix='.tmp')
os.close(fd)
with open(path, 'w') as tmp_file:
for cmd in cmds:
err_msg, output = run_cmd(cmd, False)
tmp_file.write('\n\n$ %s\n' % cmd)
if err_msg:
tmp_file.write('Error message: %s\n' % err_msg)
lines = output.split('\n')
for line in lines:
tmp_file.write(line + '\n')
tar.add(path, arcname=os.path.join('kolla', 'cmds_output'))
except Exception as e:
raise e
finally:
if path:
os.remove(path)
if inv_path:
os.remove(inv_path)
return
class Setdeploy(Command):
"""Set deploy mode
Set deploy mode to either local or remote. Local indicates
that the openstack deployment will be to the local host.
Remote means that the deployment is on remote hosts.
"""
def get_parser(self, prog_name):
parser = super(Setdeploy, self).get_parser(prog_name)
parser.add_argument('mode', metavar='<mode>',
help='mode=<local, remote>')
return parser
def take_action(self, parsed_args):
try:
mode = parsed_args.mode.strip()
remote_flag = False
if mode == 'remote':
remote_flag = True
elif mode != 'local':
raise CommandError('Invalid deploy mode. Mode must be ' +
'either "local" or "remote"')
inventory = Inventory.load()
inventory.set_deploy_mode(remote_flag)
Inventory.save(inventory)
except CommandError as e:
raise e
except Exception:
raise Exception(traceback.format_exc())

View File

View File

View File

@ -0,0 +1,108 @@
# Copyright(c) 2015, 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 logging
import os
import kollacli.i18n as u
from kollacli.common.ansible.playbook import AnsiblePlaybook
from kollacli.common import properties
from kollacli.common.properties import AnsibleProperties
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.exceptions import CommandError
LOG = logging.getLogger(__name__)
def destroy_hosts(hostname, destroy_type, verbose_level=1, include_data=False):
'''destroy containers on a host (or all hosts).
If hostname == 'all', then containers on all hosts will be
stopped. Otherwise, the containers on the specified host
will be stopped.
The destroy type 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'
LOG.info(u._LI('Please be patient as this may take a while.'))
ansible_properties = properties.AnsibleProperties()
base_distro = \
ansible_properties.get_property('kolla_base_distro')
install_type = \
ansible_properties.get_property('kolla_install_type')
container_prefix = base_distro + '-' + install_type
kollacli_home = get_kollacli_home()
playbook = AnsiblePlaybook()
playbook.playbook_path = os.path.join(kollacli_home,
'ansible/' + playbook_name)
playbook.extra_vars = 'hosts=' + hostname + \
' prefix=' + container_prefix + \
' destroy_type=' + destroy_type
playbook.print_output = False
playbook.verbose_level = verbose_level
playbook.run()
def deploy(hostnames=[], groupnames=[], servicenames=[],
serial_flag=False, verbose_level=1):
if hostnames and groupnames:
raise CommandError(
u._('Hosts and Groups arguments cannot '
'both be present at the same time.'))
_run_deploy_rules()
playbook = AnsiblePlaybook()
kolla_home = get_kolla_home()
playbook.playbook_path = os.path.join(kolla_home,
'ansible/site.yml')
playbook.hosts = hostnames
playbook.groups = groupnames
playbook.services = servicenames
playbook.serial = serial_flag
playbook.verbose_level = verbose_level
playbook.run()
def _run_deploy_rules():
# check that ring files are in /etc/kolla/config/swift if
# swift is enabled
expected_files = ['account.ring.gz',
'container.ring.gz',
'object.ring.gz']
properties = AnsibleProperties()
is_enabled = properties.get_property('enable_swift')
if is_enabled == 'yes':
path_pre = os.path.join(get_kolla_etc(), 'config', 'swift')
for expected_file in expected_files:
path = os.path.join(path_pre, expected_file)
if not os.path.isfile(path):
msg = u._(
'Deploy failed. '
'Swift is enabled but ring buffers have '
'not yet been set up. Please see the '
'documentation for swift configuration '
'instructions.')
raise CommandError(msg)

View File

@ -13,14 +13,18 @@
# under the License.
import logging
import os
import subprocess
import subprocess # nosec
import traceback
from kollacli.ansible.inventory import Inventory
import kollacli.i18n as u
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.common.utils import run_cmd
from kollacli.exceptions import CommandError
from kollacli.utils import get_admin_user
from kollacli.utils import get_kolla_etc
from kollacli.utils import run_cmd
from kollacli.common.inventory import Inventory
class AnsiblePlaybook(object):
@ -49,27 +53,29 @@ class AnsiblePlaybook(object):
if self.verbose_level > 1:
flag = '-vvv'
ansible_cmd = get_ansible_command(playbook=True)
admin_user = get_admin_user()
command_string = ('/usr/bin/sudo -u %s ansible-playbook %s'
% (admin_user, flag))
inventory = Inventory.load()
command_string = ('/usr/bin/sudo -u %s %s %s'
% (admin_user, ansible_cmd, flag))
inventory_filter = {}
inventory = Inventory.load()
if self.hosts:
for hostname in self.hosts:
host = inventory.get_host(hostname)
if not host:
raise CommandError(
'Host (%s) not found. ' % hostname)
raise CommandError(u._('Host ({host}) not found.')
.format(host=hostname))
inventory_filter['deploy_hosts'] = self.hosts
elif self.groups:
for groupname in self.groups:
group = inventory.get_group(groupname)
if not group:
raise CommandError(
'Group (%s) not found. ' % groupname)
raise CommandError(u._('Group ({group}) not found.')
.format(group=groupname))
inventory_filter['deploy_groups'] = self.groups
inventory_path = inventory.create_json_gen_file(inventory_filter)
inventory_path = \
inventory.create_json_gen_file(inventory_filter)
inventory_string = '-i ' + inventory_path
cmd = (command_string + ' ' + inventory_string)
@ -101,8 +107,8 @@ class AnsiblePlaybook(object):
for service in self.services:
valid_service = inventory.get_service(service)
if not valid_service:
raise CommandError(
'Service (%s) not found. ' % service)
raise CommandError(u._('Service ({srvc}) not found.')
.format(srvc=service))
if not first:
service_string = service_string + ','
else:
@ -121,7 +127,7 @@ class AnsiblePlaybook(object):
# log the inventory
dbg_gen = inventory_path
(inv, _) = \
subprocess.Popen(dbg_gen.split(' '),
subprocess.Popen(dbg_gen.split(' '), # nosec
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
self.log.debug(inv)
@ -134,7 +140,7 @@ class AnsiblePlaybook(object):
err_msg = '%s %s' % (err_msg, output)
raise CommandError(err_msg)
self.log.info('Success')
self.log.info(u._('Success'))
except CommandError as e:
raise e
except Exception:

View File

@ -18,12 +18,17 @@ import os
import tempfile
import traceback
from kollacli import exceptions
from kollacli import utils
import kollacli.i18n as u
from kollacli.common.sshutils import ssh_setup_host
from kollacli.common.utils import get_admin_user
from kollacli.common.utils import get_ansible_command
from kollacli.common.utils import get_kollacli_etc
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
from kollacli.sshutils import ssh_setup_host
from kollacli.utils import get_admin_user
ANSIBLE_SSH_USER = 'ansible_ssh_user'
ANSIBLE_CONNECTION = 'ansible_connection'
@ -155,7 +160,7 @@ class HostGroup(object):
self.set_var(ANSIBLE_BECOME, 'yes')
if remote_flag:
# set the ssh info for all the servers in the group
self.set_var(ANSIBLE_SSH_USER, utils.get_admin_user())
self.set_var(ANSIBLE_SSH_USER, get_admin_user())
self.clear_var(ANSIBLE_CONNECTION)
else:
# remove ssh info, add local connection type
@ -276,11 +281,11 @@ class Inventory(object):
@staticmethod
def load():
"""load the inventory from a pickle file"""
inventory_path = os.path.join(utils.get_kollacli_etc(), INVENTORY_PATH)
inventory_path = os.path.join(get_kollacli_etc(), INVENTORY_PATH)
data = ''
try:
if os.path.exists(inventory_path):
data = utils.sync_read_file(inventory_path)
data = sync_read_file(inventory_path)
if data.strip():
inventory = jsonpickle.decode(data)
@ -291,23 +296,26 @@ class Inventory(object):
else:
inventory = Inventory()
except Exception:
raise CommandError('loading inventory failed: %s'
% traceback.format_exc())
raise CommandError(
u._('Loading inventory failed. : {error}')
.format(error=traceback.format_exc()))
return inventory
@staticmethod
def save(inventory):
"""Save the inventory in a pickle file"""
inventory_path = os.path.join(utils.get_kollacli_etc(), INVENTORY_PATH)
inventory_path = os.path.join(get_kollacli_etc(), INVENTORY_PATH)
try:
# multiple trips thru json to render a readable inventory file
data = jsonpickle.encode(inventory)
data_str = json.loads(data)
pretty_data = json.dumps(data_str, indent=4)
utils.sync_write_file(inventory_path, pretty_data)
sync_write_file(inventory_path, pretty_data)
except Exception as e:
raise CommandError('saving inventory failed: %s' % e)
raise CommandError(
u._('Saving inventory failed. : {error}')
.format(error=str(e)))
def _create_default_inventory(self):
@ -334,7 +342,7 @@ class Inventory(object):
return self._hosts.values()
def get_hostnames(self):
return self._hosts.keys()
return list(self._hosts.keys())
def get_host(self, hostname):
host = None
@ -349,16 +357,19 @@ class Inventory(object):
if group name is not none, add host to group
"""
if groupname and groupname not in self._groups:
raise CommandError('Group name (%s) does not exist'
% groupname)
raise CommandError(
u._('Group name ({group}) does not exist.')
.format(group=groupname))
if groupname and hostname not in self._hosts:
raise CommandError('Host name (%s) does not exist'
% hostname)
raise CommandError(
u._('Host name ({host}) does not exist.')
.format(host=hostname))
if not groupname and not self.remote_mode and len(self._hosts) >= 1:
raise CommandError('Cannot have more than one host when in ' +
'local deploy mode')
raise CommandError(
u._('Cannot have more than one host when in local deploy '
'mode.'))
# create new host if it doesn't exist
host = Host(hostname)
@ -379,8 +390,9 @@ class Inventory(object):
if group name is not none, remove host from group
"""
if groupname and groupname not in self._groups:
raise CommandError('Group name (%s) does not exist'
% groupname)
raise CommandError(
u._('Group name ({group}) does not exist.')
.format(group=groupname))
if hostname not in self._hosts:
return
@ -409,10 +421,10 @@ class Inventory(object):
for hostname, host_info in hosts_info.items():
host = self.get_host(hostname)
if not host:
failed_hosts[hostname] = "Host doesn't exist"
failed_hosts[hostname] = u._("Host doesn't exist.")
continue
if not host_info or 'password' not in host_info:
failed_hosts[hostname] = 'No password in yml file'
failed_hosts[hostname] = u._('No password in yml file.')
continue
passwd = host_info['password']
uname = None
@ -426,27 +438,32 @@ class Inventory(object):
summary = '\n'
for hostname, err in failed_hosts.items():
summary = summary + '- %s: %s\n' % (hostname, err)
raise CommandError('Not all hosts were set up: %s' % summary)
raise CommandError(
u._('Not all hosts were set up. : {reasons}')
.format(reasons=summary))
else:
self.log.info('All hosts were successfully set up')
self.log.info(u._LI('All hosts were successfully set up.'))
def setup_host(self, hostname, password, uname=None):
try:
self.log.info('Starting setup of host (%s)'
% hostname)
self.log.info(
u._LI('Starting setup of host ({host}).')
.format(host=hostname))
ssh_setup_host(hostname, password, uname)
check_ok = self.check_host(hostname, True)
if not check_ok:
raise Exception('Post setup check failed')
self.log.info('Host (%s) setup succeeded' % hostname)
raise Exception(u._('Post setup check failed.'))
self.log.info(u._LI('Host ({host}) setup succeeded.')
.format(host=hostname))
except Exception as e:
raise exceptions.CommandError(
'Host (%s) setup failed : %s'
% (hostname, e))
raise CommandError(
u._('Host ({host}) setup failed : {error}')
.format(host=hostname, error=str(e)))
return True
def check_host(self, hostname, result_only=False):
command_string = '/usr/bin/sudo -u %s ansible ' % get_admin_user()
command_string = '/usr/bin/sudo -u %s %s ' % \
(get_admin_user(), get_ansible_command())
gen_file_path = self.create_json_gen_file()
err_msg = None
output = None
@ -454,7 +471,7 @@ class Inventory(object):
inventory_string = '-i ' + gen_file_path
ping_string = ' %s %s' % (hostname, '-m ping')
cmd = (command_string + inventory_string + ping_string)
err_msg, output = utils.run_cmd(cmd, False)
err_msg, output = run_cmd(cmd, False)
except Exception as e:
raise e
finally:
@ -464,20 +481,23 @@ class Inventory(object):
if result_only:
return False
else:
raise exceptions.CommandError(
'Host (%s) check failed : %s %s'
% (hostname, err_msg, output))
raise CommandError(
u._('Host ({host}) check failed. : {error} {message}')
.format(host=hostname, error=err_msg, message=output))
else:
if not result_only:
self.log.info('Host (%s) check succeeded' % hostname)
self.log.info(
u._LI('Host ({host}) check succeeded.')
.format(host=hostname))
return True
def add_group(self, groupname):
# Group names cannot overlap with service names:
if groupname in self._services or groupname in self._sub_services:
raise CommandError('Invalid group name. A service name '
'cannot be used for a group name.')
raise CommandError(
u._('Invalid group name. A service name '
'cannot be used for a group name.'))
if groupname not in self._groups:
self._groups[groupname] = HostGroup(groupname)
@ -490,8 +510,9 @@ class Inventory(object):
def remove_group(self, groupname):
if groupname in PROTECTED_GROUPS:
raise CommandError('Cannot remove %s group. ' % groupname +
'It is required by kolla.')
raise CommandError(
u._('Cannot remove {group} group. It is required by kolla.')
.format(group=groupname))
# remove group from services & subservices
for service in self._services.values():
@ -510,7 +531,7 @@ class Inventory(object):
return group
def get_groupnames(self):
return self._groups.keys()
return list(self._groups.keys())
def get_groups(self, host=None):
"""return all groups containing host
@ -587,7 +608,8 @@ class Inventory(object):
def add_group_to_service(self, groupname, servicename):
if groupname not in self._groups:
raise CommandError('Group (%s) not found.' % groupname)
raise CommandError(u._('Group ({group}) not found.')
.format(group=groupname))
if servicename in self._services:
service = self.get_service(servicename)
service.add_groupname(groupname)
@ -595,11 +617,13 @@ class Inventory(object):
sub_service = self.get_sub_service(servicename)
sub_service.add_groupname(groupname)
else:
raise CommandError('Service (%s) not found.' % servicename)
raise CommandError(u._('Service ({service})) not found.')
.format(service=servicename))
def remove_group_from_service(self, groupname, servicename):
if groupname not in self._groups:
raise CommandError('Group (%s) not found.' % groupname)
raise CommandError(u._('Group ({group}) not found.')
.format(group=groupname))
if servicename in self._services:
service = self.get_service(servicename)
service.remove_groupname(groupname)
@ -607,7 +631,8 @@ class Inventory(object):
sub_service = self.get_sub_service(servicename)
sub_service.remove_groupname(groupname)
else:
raise CommandError('Service (%s) not found.' % servicename)
raise CommandError(u._('Service ({service})) not found.')
.format(service=servicename))
def create_sub_service(self, sub_servicename):
if sub_servicename not in self._sub_services:
@ -657,8 +682,8 @@ class Inventory(object):
def set_deploy_mode(self, remote_flag):
if not remote_flag and len(self._hosts) > 1:
raise CommandError('Cannot set local deploy mode when multiple ' +
'hosts exist')
raise CommandError(
u._('Cannot set local deploy mode when multiple hosts exist.'))
self.remote_mode = remote_flag
for group in self.get_groups():
@ -755,7 +780,10 @@ class Inventory(object):
return json.dumps(jdict)
def _filter_hosts(self, initial_hostnames, deploy_hostnames):
"""filter out hosts not in deploy hosts"""
"""filter out hosts not in deploy hosts
Must preserve the ordering of hosts in the group.
"""
filtered_hostnames = []
for hostname in initial_hostnames:
if hostname in deploy_hostnames:
@ -780,5 +808,5 @@ class Inventory(object):
json_gen_file.write("print('%s')" % json_out)
# set executable by group
os.chmod(json_gen_path, 0o555)
os.chmod(json_gen_path, 0o555) # nosec
return json_gen_path

View File

@ -13,8 +13,10 @@
# under the License.
import os
import kollacli.i18n as u
from kollacli.common import utils
from kollacli.exceptions import CommandError
from kollacli import utils
PWDS_FILENAME = 'passwords.yml'
PWD_EDITOR_FILENAME = 'passwd_editor.py'
@ -29,7 +31,9 @@ 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('%s %s' % (err_msg, output))
raise CommandError(
u._('Password set failed. {error} {message}')
.format(error=err_msg, message=output))
def clear_password(pwd_key):

View File

@ -13,12 +13,13 @@
# under the License.
import logging
import os
import six
import yaml
from kollacli.utils import change_property
from kollacli.utils import get_kolla_etc
from kollacli.utils import get_kolla_home
from kollacli.utils import sync_read_file
from kollacli.common.utils import change_property
from kollacli.common.utils import get_kolla_etc
from kollacli.common.utils import get_kolla_home
from kollacli.common.utils import sync_read_file
ALLVARS_PATH = 'ansible/group_vars/all.yml'
GLOBALS_FILENAME = 'globals.yml'
@ -58,9 +59,8 @@ class AnsibleProperties(object):
ANSIBLE_DEFAULTS_PATH)
if os.path.isfile(file_name):
with open(file_name) as service_file:
service_contents = yaml.load(service_file)
service_contents = yaml.safe_load(service_file)
self.file_contents[file_name] = service_contents
service_contents = self.filter_jinja2(service_contents)
prop_file_name = service_name + ':main.yml'
for key, value in service_contents.items():
ansible_property = AnsibleProperty(key, value,
@ -73,12 +73,17 @@ class AnsibleProperties(object):
try:
self.allvars_path = os.path.join(kolla_home, ALLVARS_PATH)
with open(self.allvars_path) as allvars_file:
allvars_contents = yaml.load(allvars_file)
allvars_contents = yaml.safe_load(allvars_file)
self.file_contents[self.allvars_path] = allvars_contents
allvars_contents = self.filter_jinja2(allvars_contents)
for key, value in allvars_contents.items():
overrides = False
orig_value = None
if key in self.unique_properties:
overrides = True
orig_value = self.unique_properties[key].value
ansible_property = AnsibleProperty(key, value,
'group_vars/all.yml')
'group_vars/all.yml',
overrides, orig_value)
self.properties.append(ansible_property)
self.unique_properties[key] = ansible_property
except Exception as e:
@ -87,12 +92,17 @@ class AnsibleProperties(object):
try:
self.globals_path = os.path.join(kolla_etc, GLOBALS_FILENAME)
globals_data = sync_read_file(self.globals_path)
globals_contents = yaml.load(globals_data)
globals_contents = yaml.safe_load(globals_data)
self.file_contents[self.globals_path] = globals_contents
globals_contents = self.filter_jinja2(globals_contents)
for key, value in globals_contents.items():
overrides = False
orig_value = None
if key in self.unique_properties:
overrides = True
orig_value = self.unique_properties[key].value
ansible_property = AnsibleProperty(key, value,
GLOBALS_FILENAME)
GLOBALS_FILENAME,
overrides, orig_value)
self.properties.append(ansible_property)
self.unique_properties[key] = ansible_property
except Exception as e:
@ -114,16 +124,20 @@ class AnsibleProperties(object):
unique_list.append(value)
return sorted(unique_list, key=lambda x: x.name)
# TODO(bmace) -- if this isn't used for 2.1.x it should be removed
# property listing is still being tweaked so leaving for
# the time being in case we want to use it
def filter_jinja2(self, contents):
new_contents = {}
for key, value in contents.items():
if isinstance(value, basestring) is False:
self.log.debug('removing non-string: %s' % str(value))
del contents[key]
if not isinstance(value, six.string_types):
self.log.debug('removing non-string: %s' % value)
continue
if '{{' in value and '}}' in value:
if value and '{{' in value and '}}' in value:
self.log.debug('removing jinja2 value: %s' % value)
del contents[key]
return contents
continue
new_contents[key] = value
return new_contents
def set_property(self, property_key, property_value):
# We only manipulate values in the globals.yml file so look up the key
@ -148,7 +162,10 @@ class AnsibleProperties(object):
class AnsibleProperty(object):
def __init__(self, name, value, file_name):
def __init__(self, name, value, file_name, overrides=False,
orig_value=None):
self.name = name
self.value = value
self.file_name = file_name
self.overrides = overrides
self.orig_value = orig_value

View File

@ -16,19 +16,20 @@ import os.path
import paramiko
import traceback
from distutils.version import StrictVersion
from kollacli.common.utils import get_admin_user
from kollacli.common.utils import get_kollacli_etc
from kollacli.common.utils import get_setup_user
import kollacli.i18n as u
from kollacli.exceptions import CommandError
from kollacli.utils import get_admin_user
from kollacli.utils import get_kollacli_etc
from kollacli.utils import get_setup_user
MIN_DOCKER_VERSION = '1.8.1'
LOG = logging.getLogger(__name__)
def ssh_connect(net_addr, username, password):
try:
logging.getLogger('paramiko').setLevel(logging.WARNING)
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname=net_addr, username=username,
@ -40,7 +41,6 @@ def ssh_connect(net_addr, username, password):
def ssh_setup_host(net_addr, password, setup_user=None):
log = logging.getLogger(__name__)
admin_user = get_admin_user()
if setup_user is None:
setup_user = get_setup_user()
@ -58,7 +58,7 @@ def ssh_setup_host(net_addr, password, setup_user=None):
'.ssh', 'authorized_keys')
cmd = ('/usr/bin/sudo su - %s -c "echo \'%s\' >> %s"'
% (admin_user, public_key, key_dir))
_exec_ssh_cmd(cmd, ssh_client, log)
_exec_ssh_cmd(cmd, ssh_client)
# TODO(bmace) verify ssh connection to the new account
except Exception as e:
@ -67,71 +67,24 @@ def ssh_setup_host(net_addr, password, setup_user=None):
_close_ssh_client(ssh_client)
def _pre_setup_checks(ssh_client, log):
cmd = 'docker --version'
msg, errmsg = _exec_ssh_cmd(cmd, ssh_client, log)
if errmsg:
raise CommandError("'%s' failed. Is docker installed? : %s"
% (cmd, errmsg))
if 'Docker version' not in msg:
raise CommandError("'%s' failed. Is docker installed? : %s"
% (cmd, msg))
version = msg.split('version ')[1].split(',')[0]
if StrictVersion(version) < StrictVersion(MIN_DOCKER_VERSION):
raise CommandError('docker version (%s) below minimum (%s)'
% (version, msg))
# docker is installed, now check if it is running
cmd = 'docker info'
_, errmsg = _exec_ssh_cmd(cmd, ssh_client, log)
# docker info can return warning messages in stderr, ignore them
if errmsg and 'WARNING' not in errmsg:
raise CommandError("'%s' failed. Is docker running? : %s"
% (cmd, errmsg))
# check for docker-py
cmd = 'python -c "import docker"'
msg, errmsg = _exec_ssh_cmd(cmd, ssh_client, log)
if errmsg:
raise CommandError('host check failed. ' +
'Is docker-py installed?')
def _post_setup_checks(net_addr, log):
try:
ssh_client = ssh_connect(net_addr, get_admin_user(), '')
except Exception as e:
raise CommandError("remote login failed : %s" % e)
try:
# a basic test
ssh_client.exec_command('ls')
except Exception as e:
raise CommandError("remote command 'ls' failed : %s" % e)
finally:
_close_ssh_client(ssh_client)
def _close_ssh_client(ssh_client):
if ssh_client:
try:
ssh_client.close()
except Exception:
except Exception: # nosec
pass
def _exec_ssh_cmd(cmd, ssh_client, log):
log.debug(cmd)
_, stdout, stderr = ssh_client.exec_command(cmd, get_pty=True)
def _exec_ssh_cmd(cmd, ssh_client):
LOG.debug(cmd)
_, stdout, stderr = ssh_client.exec_command(cmd, get_pty=True) # nosec
msg = stdout.read()
errmsg = stderr.read()
log.debug('%s : %s' % (msg, errmsg))
LOG.debug('%s : %s' % (msg, errmsg))
if errmsg:
log.warn('WARNING: command (%s) message : %s' % (cmd, errmsg.strip()))
LOG.warn(
u._LW('WARNING: command : {command})\nmessage : {message}')
.format(command=cmd, message=errmsg.strip()))
return msg, errmsg

129
kollacli/common/support.py Normal file
View File

@ -0,0 +1,129 @@
# Copyright(c) 2015, 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 logging
import os
import tarfile
import tempfile
import traceback
import kollacli.i18n as u
from kollacli.common.inventory import Inventory
from kollacli.common.utils import get_kolla_etc
from kollacli.common.utils import get_kolla_home
from kollacli.common.utils import get_kolla_log_dir
from kollacli.common.utils import get_kollacli_etc
from kollacli.common.utils import run_cmd
LOG = logging.getLogger(__name__)
def dump():
"""Dumps configuration data for debugging
Dumps most files in /etc/kolla and /usr/share/kolla into a
tar file so be given to support / development to help with
debugging problems.
"""
try:
msg = None
return_code = 0
kolla_home = get_kolla_home()
kolla_logs = get_kolla_log_dir()
kolla_ansible = os.path.join(kolla_home, 'ansible')
kolla_docs = os.path.join(kolla_home, 'docs')
kolla_etc = get_kolla_etc()
kolla_config = os.path.join(kolla_etc, 'config')
kolla_globals = os.path.join(kolla_etc, 'globals.yml')
kollacli_etc = get_kollacli_etc().rstrip('/')
ketc = 'kolla/etc/'
kshare = 'kolla/share/'
fd, dump_path = tempfile.mkstemp(prefix='kollacli_dump_',
suffix='.tgz')
os.close(fd) # avoid fd leak
with tarfile.open(dump_path, 'w:gz') as tar:
# Can't blanket add kolla_home because the .ssh dir is
# accessible by the kolla user only (not kolla group)
tar.add(kolla_ansible,
arcname=kshare + os.path.basename(kolla_ansible))
tar.add(kolla_docs,
arcname=kshare + os.path.basename(kolla_docs))
# Can't blanket add kolla_etc because the passwords.yml
# file is accessible by the kolla user only (not kolla group)
tar.add(kolla_config,
arcname=ketc + os.path.basename(kolla_config))
tar.add(kolla_globals,
arcname=ketc + os.path.basename(kolla_globals))
tar.add(kollacli_etc,
arcname=ketc + os.path.basename(kollacli_etc))
# add kolla log files
if os.path.isdir(kolla_logs):
tar.add(kolla_logs)
# add output of various commands
_add_cmd_info(tar)
msg = u._LI('dump successful to {path}').format(path=dump_path)
LOG.info(msg)
except Exception:
msg = (u._LI('dump failed: {reason}')
.format(reason=traceback.format_exc()))
LOG.error(msg)
return_code = -1
return return_code, msg
def _add_cmd_info(tar):
# run all the kollacli list commands
cmds = ['kollacli --version',
'kollacli service listgroups',
'kollacli service list',
'kollacli group listservices',
'kollacli group listhosts',
'kollacli host list',
'kollacli property list',
'kollacli password list']
# collect the json inventory output
inventory = Inventory.load()
inv_path = inventory.create_json_gen_file()
cmds.append(inv_path)
try:
fd, path = tempfile.mkstemp(suffix='.tmp')
os.close(fd)
with open(path, 'w') as tmp_file:
for cmd in cmds:
err_msg, output = run_cmd(cmd, False)
tmp_file.write('\n\n$ %s\n' % cmd)
if err_msg:
tmp_file.write('Error message: %s\n' % err_msg)
for line in output:
tmp_file.write(line + '\n')
tar.add(path, arcname=os.path.join('kolla', 'cmds_output'))
except Exception as e:
raise e
finally:
if path:
os.remove(path)
if inv_path:
os.remove(inv_path)
return

View File

@ -16,7 +16,13 @@ import logging
import os
import pexpect
import pwd
import yaml
import six
import sys
import kollacli.i18n as u
from kollacli.exceptions import CommandError
from oslo_utils.encodeutils import safe_decode
def get_kolla_home():
@ -48,7 +54,29 @@ def get_admin_uids():
def get_kolla_log_file_size():
return os.environ.get('KOLLA_LOG_FILE_SIZE', 500000)
envvar = 'KOLLA_LOG_FILE_SIZE'
size_str = os.environ.get(envvar, '500000')
try:
size = int(size_str)
except Exception:
raise CommandError(
u._('Environmental variable ({env_var}) is not an '
'integer ({log_size}).')
.format(env_var=envvar, log_size=size_str))
return size
def get_property_list_length():
envvar = 'KOLLA_PROP_LIST_LENGTH'
length_str = os.environ.get(envvar, '50')
try:
length = int(length_str)
except Exception:
raise CommandError(
u._('Environmental variable ({env_var}) is not an '
'integer ({prop_length}).')
.format(env_var=envvar, prop_length=length_str))
return length
def get_admin_user():
@ -68,21 +96,34 @@ def get_pk_bits():
return 1024
def load_etc_yaml(fileName):
contents = {}
try:
with open(get_kollacli_etc() + fileName, 'r') as f:
contents = yaml.load(f)
except Exception:
# TODO(bmace) if file doesn't exist on a load we don't
# want to blow up, some better behavior here?
pass
return contents or {}
def get_ansible_command(playbook=False):
"""get a python2 ansible command
def save_etc_yaml(fileName, contents):
with open(get_kollacli_etc() + fileName, 'w') as f:
f.write(yaml.dump(contents))
Ansible cannot run yet with python3. If the current default
python is py3, prefix the ansible command with a py2
interpreter.
"""
cmd = 'ansible'
if playbook:
cmd = 'ansible-playbook'
if sys.version_info[0] >= 3:
# running with py3, find a py2 interpreter for ansible
py2_path = None
usr_bin = os.path.join('/', 'usr', 'bin')
for fname in os.listdir(usr_bin):
if (fname.startswith('python2.') and
os.path.isfile(os.path.join(usr_bin, fname))):
suffix = fname.split('.')[1]
if suffix.isdigit():
py2_path = os.path.join(usr_bin, fname)
break
if py2_path is None:
raise Exception(
u._('ansible-playbook requires python2 and no '
'python2 interpreter found in {path}.')
.format(path=usr_bin))
cmd = '%s %s' % (py2_path, os.path.join(usr_bin, cmd))
return cmd
def convert_to_unicode(the_string):
@ -91,12 +132,7 @@ def convert_to_unicode(the_string):
This is used to fixup extended ascii chars in strings. these chars cause
errors in json pickle/unpickle.
"""
uni_string = ''
try:
uni_string = unicode(the_string)
except UnicodeDecodeError:
uni_string = the_string.decode('utf-8')
return uni_string
return six.u(the_string)
def run_cmd(cmd, print_output=True):
@ -118,13 +154,16 @@ def run_cmd(cmd, print_output=True):
try:
child = pexpect.spawn(cmd)
sniff = child.read(len(pwd_prompt))
sniff = safe_decode(sniff)
if sniff == pwd_prompt:
output = sniff + '\n'
raise Exception(
'Insufficient permissions to run command "%s"' % cmd)
u._('Insufficient permissions to run command "{command}".')
.format(command=cmd))
child.maxsize = 1
child.timeout = 86400
for line in child:
line = safe_decode(line)
outline = sniff + line.rstrip()
sniff = ''
output = ''.join([output, outline, '\n'])
@ -137,7 +176,8 @@ def run_cmd(cmd, print_output=True):
if child:
child.close()
if child.exitstatus != 0:
err_msg = 'Command Failed %s' % err_msg
err_msg = (u._('Command failed. : {error}')
.format(error=err_msg))
return err_msg, output

View File

@ -12,9 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
"""Exception definitions."""
import kollacli.i18n as u
class CommandError(Exception):
def __init__(self, message, *args):
message = 'ERROR: %s' % message
message = u._('ERROR: {message}').format(message=message)
super(CommandError, self).__init__(message, *args)

View File

@ -19,42 +19,51 @@ import sys
from cliff.app import App
from cliff.commandmanager import CommandManager
from kollacli.ansible.inventory import INVENTORY_PATH
import kollacli.i18n as u
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
from kollacli.utils import get_kolla_log_dir
from kollacli.utils import get_kolla_log_file_size
from kollacli.utils import get_kollacli_etc
LOG = logging.getLogger(__name__)
class KollaCli(App):
log = logging.getLogger(__name__)
def __init__(self):
super(KollaCli, self).__init__(
description='Command-Line Client for OpenStack Kolla',
description=u._('Command-Line Client for OpenStack Kolla'),
version='0.1',
command_manager=CommandManager('kolla.cli'),
)
# check that current user is in the kolla group
inventory_path = os.path.join(get_kollacli_etc(),
INVENTORY_PATH)
errString = 'Required file %s does not exist.\n' + \
'Please re-install the kollacli to recreate the file.'
if os.path.isfile(inventory_path) is False:
raise CommandError(errString % inventory_path)
err_string = u._(
'Required file ({inventory}) does not exist.\n'
'Please re-install the kollacli to '
'recreate the file.').format(inventory=inventory_path)
raise CommandError(err_string)
# check that current user can access the inventory file
inventory_file = None
try:
inventory_file = open(inventory_path, 'r+')
except Exception:
raise CommandError('Permission denied to run the kollacli.' +
'\nPlease add user to the kolla group and ' +
'then log out and back in.')
raise CommandError(
u._('Permission denied to run the kollacli.\n'
'Please add user to the kolla group and '
'then log out and back in.'))
finally:
if inventory_file and inventory_file.close is False:
inventory_file.close()
# paramiko log is very chatty, tune it down
logging.getLogger('paramiko').setLevel(logging.WARNING)
# set up logging
self.rotating_log_dir = get_kolla_log_dir()
self.max_bytes = get_kolla_log_file_size()
self.backup_count = 4
@ -64,9 +73,9 @@ class KollaCli(App):
self.add_rotational_log()
def clean_up(self, cmd, result, err):
self.log.debug('clean_up %s', cmd.__class__.__name__)
LOG.debug('clean_up %s', cmd.__class__.__name__)
if err:
self.log.debug('error: %s', err)
LOG.debug('ERROR: %s', err)
def add_rotational_log(self):
root_logger = logging.getLogger('')

View File

@ -2,9 +2,10 @@ ansible>=1.9.2
Babel>=0.9.6
cliff>=1.13.0 # Apache-2.0
cliff-tablib>=1.1
docker-py>=1.3.1
docker-py==1.3.1
jsonpickle>=0.9
oslo.i18n>=1.3.0 # Apache-2.0
oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0
paramiko>=1.15
pbr>=0.10
pexpect>=2.3,<3.3

View File

@ -31,31 +31,31 @@ console_scripts =
kollacli = kollacli.shell:main
kolla.cli =
deploy = kollacli.common:Deploy
dump = kollacli.common:Dump
group_add = kollacli.group:GroupAdd
group_addhost = kollacli.group:GroupAddhost
group_listhosts = kollacli.group:GroupListhosts
group_listservices = kollacli.group:GroupListservices
group_remove = kollacli.group:GroupRemove
group_removehost = kollacli.group:GroupRemovehost
host_add = kollacli.host:HostAdd
host_check = kollacli.host:HostCheck
host_destroy = kollacli.host:HostDestroy
host_list = kollacli.host:HostList
host_remove = kollacli.host:HostRemove
host_setup = kollacli.host:HostSetup
password_clear = kollacli.password:PasswordClear
password_list = kollacli.password:PasswordList
password_set = kollacli.password:PasswordSet
property_clear = kollacli.property:PropertyClear
property_list = kollacli.property:PropertyList
property_set = kollacli.property:PropertySet
service_addgroup = kollacli.service:ServiceAddGroup
service_list = kollacli.service:ServiceList
service_listgroups = kollacli.service:ServiceListGroups
service_removegroup = kollacli.service:ServiceRemoveGroup
setdeploy = kollacli.common:Setdeploy
deploy = kollacli.commands.deploy:Deploy
dump = kollacli.commands.support:Dump
group_add = kollacli.commands.group:GroupAdd
group_addhost = kollacli.commands.group:GroupAddhost
group_listhosts = kollacli.commands.group:GroupListhosts
group_listservices = kollacli.commands.group:GroupListservices
group_remove = kollacli.commands.group:GroupRemove
group_removehost = kollacli.commands.group:GroupRemovehost
host_add = kollacli.commands.host:HostAdd
host_check = kollacli.commands.host:HostCheck
host_destroy = kollacli.commands.host:HostDestroy
host_list = kollacli.commands.host:HostList
host_remove = kollacli.commands.host:HostRemove
host_setup = kollacli.commands.host:HostSetup
password_clear = kollacli.commands.password:PasswordClear
password_list = kollacli.commands.password:PasswordList
password_set = kollacli.commands.password:PasswordSet
property_clear = kollacli.commands.property:PropertyClear
property_list = kollacli.commands.property:PropertyList
property_set = kollacli.commands.property:PropertySet
service_addgroup = kollacli.commands.service:ServiceAddGroup
service_list = kollacli.commands.service:ServiceList
service_listgroups = kollacli.commands.service:ServiceListGroups
service_removegroup = kollacli.commands.service:ServiceRemoveGroup
setdeploy = kollacli.commands.deploy:Setdeploy
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext

View File

@ -4,6 +4,7 @@
# Hacking already pins down pep8, pyflakes and flake8
hacking>=0.10.2,<0.11
bandit>=0.13.2
coverage>=3.6
discover
fixtures>=0.3.14

View File

@ -17,12 +17,13 @@ import os
import pxssh
import subprocess
import sys
import testtools
import traceback
import yaml
import testtools
from oslo_utils.encodeutils import safe_decode
import kollacli.utils as utils
import kollacli.common.utils as utils
TEST_SUFFIX = 'test/'
VENV_PY_PATH = '.venv/bin/python'
@ -47,6 +48,7 @@ class KollaCliTest(testtools.TestCase):
% self._testMethodName)
# switch to test path
self.log.info('running python: %s/%s' % (sys.executable, sys.version))
etc_path = utils.get_kollacli_etc()
self.log.debug('etc for tests: %s' % etc_path)
@ -86,11 +88,12 @@ class KollaCliTest(testtools.TestCase):
msg = e.output
except Exception as e:
retval = e.errno
retval = -1
msg = ('Unexpected exception: %s, cmd: %s'
% (traceback.format_exc(), cmd))
# the py dev debugger adds a string at the line start, remove it
msg = safe_decode(msg)
if msg.startswith('pydev debugger'):
msg = msg.split('\n', 1)[1]
return (retval, msg)
@ -233,7 +236,7 @@ class TestConfig(object):
self.hosts_info[name]['groups'].remove(group)
def get_hostnames(self):
return self.hosts_info.keys()
return list(self.hosts_info.keys())
def set_username(self, name, username):
self.hosts_info[name]['username'] = username
@ -258,7 +261,7 @@ class TestConfig(object):
with open(path, 'r+') as cfg_file:
yml_data = cfg_file.read()
test_cfg = yaml.load(yml_data)
test_cfg = yaml.safe_load(yml_data)
hosts_info = test_cfg['hosts']
if hosts_info:
@ -298,6 +301,7 @@ class TestConfig(object):
session.sendline(cmd)
session.prompt()
out = session.before
out = safe_decode(out)
self.log.info(out)
session.logout()
return out

View File

@ -14,8 +14,8 @@
#
from common import KollaCliTest
from kollacli.ansible.inventory import Inventory
from kollacli.ansible.inventory import SERVICES
from kollacli.common.inventory import Inventory
from kollacli.common.inventory import SERVICES
import json
import os
@ -156,7 +156,6 @@ class TestFunctional(KollaCliTest):
is_file = os.path.isfile(dump_path)
self.assertTrue(is_file,
'dump file not found at %s' % dump_path)
file_paths = []
with tarfile.open(dump_path, 'r') as tar:
file_paths = tar.getnames()

View File

@ -15,16 +15,17 @@
from common import KollaCliTest
from common import TestConfig
from kollacli.ansible import inventory
import unittest
DISABLED_SERVICES = [
'cinder', 'glance', 'haproxy', 'heat', 'rabbitmq'
'cinder', 'glance', 'haproxy', 'heat', 'msqlcluster',
'horizon', 'keystone', 'murano', 'neutron', 'nova',
]
ENABLED_SERVICES = [
'mysqlcluster'
'rabbitmq'
]
ENABLED_DATA_SERVICES = [
'rabbitmq_data'
]
UNKNOWN_HOST = 'Name or service not known'
@ -60,9 +61,12 @@ class TestFunctional(KollaCliTest):
self.assertIn(UNKNOWN_HOST, '%s' % e,
'Unexpected exception in host setup: %s' % e)
# add host to all deploy groups
for group in inventory.DEPLOY_GROUPS:
self.run_cli_cmd('group addhost %s %s' % (group, hostname))
# add host to a new deploy group
group_name = 'test_group'
self.run_cli_cmd('group add %s' % group_name)
self.run_cli_cmd('group addhost %s %s' % (group_name, hostname))
for service in ENABLED_SERVICES:
self.run_cli_cmd('service addgroup %s %s' % (service, group_name))
# destroy services, initialize server
try:
@ -107,9 +111,10 @@ class TestFunctional(KollaCliTest):
'is not running on host: %s ' % hostname +
'after deploy.')
# destroy services (via --stop flag)
# destroy non-data services (via --stop flag)
# this should leave only data containers running
try:
self.run_cli_cmd('host destroy %s --stop --includedata' % hostname)
self.run_cli_cmd('host destroy %s --stop' % hostname)
except Exception as e:
self.assertFalse(is_physical_host, '2nd destroy exception: %s' % e)
self.assertIn(UNKNOWN_HOST, '%s' % e,
@ -123,10 +128,37 @@ class TestFunctional(KollaCliTest):
'is running on host: %s ' % hostname +
'after destroy.')
for enabled_service in ENABLED_DATA_SERVICES:
self.assertIn(enabled_service, docker_ps,
'enabled service: %s ' % enabled_service +
'is not running on host: %s ' % hostname +
'after no-data destroy.')
try:
self.run_cli_cmd('host destroy %s --includedata --stop' % hostname)
except Exception as e:
self.assertFalse(is_physical_host, '3rd destroy exception: %s' % e)
self.assertIn(UNKNOWN_HOST, '%s' % e,
'Unexpected exception in 3rd destroy: %s' % e)
if is_physical_host:
docker_ps = test_config.run_remote_cmd('docker ps', hostname)
for disabled_service in DISABLED_SERVICES:
self.assertNotIn(disabled_service, docker_ps,
'disabled service: %s ' % disabled_service +
'is running on host: %s ' % hostname +
'after destroy.')
for enabled_service in ENABLED_DATA_SERVICES:
self.assertNotIn(enabled_service, docker_ps,
'enabled service: %s ' % enabled_service +
'is running on host: %s ' % hostname +
'after destroy.')
for enabled_service in ENABLED_SERVICES:
self.assertNotIn(enabled_service, docker_ps,
'enabled service: %s ' % enabled_service +
'is still running on host: %s ' % hostname +
'is running on host: %s ' % hostname +
'after destroy.')
def tearDown(self):

View File

@ -91,7 +91,7 @@ class TestFunctional(KollaCliTest):
# check if host is not set-up
timeout = time.time() + 75
while time.time <= timeout:
while time.time() <= timeout:
msg = self.run_cli_cmd('host check %s' % hostname, True)
if 'ERROR:' not in msg:
self.log.info('waiting for ansible ssh session to timeout')

View File

@ -16,7 +16,7 @@ from common import KollaCliTest
import os
import unittest
from kollacli.utils import get_kolla_etc
from kollacli.common.utils import get_kolla_etc
class TestFunctional(KollaCliTest):

View File

@ -17,7 +17,7 @@ from common import KollaCliTest
import os
import unittest
from kollacli.utils import get_kolla_etc
from kollacli.common.utils import get_kolla_etc
class TestFunctional(KollaCliTest):

View File

@ -17,9 +17,9 @@ from common import KollaCliTest
import json
import unittest
from kollacli.ansible.inventory import DEFAULT_GROUPS
from kollacli.ansible.inventory import DEFAULT_OVERRIDES
from kollacli.ansible.inventory import SERVICES
from kollacli.common.inventory import DEFAULT_GROUPS
from kollacli.common.inventory import DEFAULT_OVERRIDES
from kollacli.common.inventory import SERVICES
class TestFunctional(KollaCliTest):

View File

@ -19,7 +19,7 @@ predeploy_cmds:
- setdeploy remote
- property set kolla_external_address 192.168.9.89
- property set kolla_internal_address 192.168.9.89
- property set docker_registry ca-build44.us.oracle.com:5000
- property set docker_registry ca-qa-docker-reg.us.oracle.com:5000
- property set docker_insecure_registry True
- property set network_interface enp0s3
- property set openstack_release 2.0.1.89

View File

@ -22,6 +22,9 @@ import tempfile
from kollacli.ansible.inventory import Inventory
from kollacli.ansible import properties
from kollacli.utils import get_admin_user
from kollacli.utils import get_ansible_command
from oslo_utils.encodeutils import safe_decode
tar_file_descr = None
@ -30,14 +33,15 @@ def run_ansible_cmd(cmd, host):
# sudo -u kolla ansible ol7-c4 -i inv_path -a "cmd"
out = None
user = get_admin_user()
inventory = Inventory.load()
inv_path = inventory.create_json_gen_file()
inv = Inventory.load()
inv_path = inv.create_json_gen_file()
acmd = ('/usr/bin/sudo -u %s ansible %s -i %s -a "%s"'
% (user, host, inv_path, cmd))
ansible_verb = get_ansible_command()
ansible_cmd = ('/usr/bin/sudo -u %s %s %s -i %s -a "%s"'
% (user, ansible_verb, host, inv_path, cmd))
try:
(out, err) = subprocess.Popen(acmd, shell=True,
(out, err) = subprocess.Popen(ansible_cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
except Exception as e:
@ -47,11 +51,13 @@ def run_ansible_cmd(cmd, host):
if not out:
print('Host %s is not accessible: %s, skipping' % (host, err))
elif '>>' not in out:
print('Ansible command: %s' % acmd)
print('Host: %s. \nInvalid ansible return data: [%s]. skipping'
% (host, out))
out = None
else:
out = safe_decode(out)
if '>>' not in out:
print('Ansible command: %s' % ansible_cmd)
print('Host: %s. \nInvalid ansible return data: [%s]. skipping'
% (host, out))
out = None
return out
@ -165,6 +171,7 @@ def main():
(_, err) = subprocess.Popen('kollacli dump'.split(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
err = safe_decode(err)
if '/' in err:
dump_path = '/' + err.strip().split('/', 1)[1]
if os.path.isfile(dump_path):

View File

@ -15,13 +15,14 @@
import getopt
import sys
from kollacli import utils
from kollacli.common.utils import change_property
from kollacli.common.utils import sync_read_file
def _print_pwd_keys(path):
pwd_keys = ''
prefix = ''
pwd_data = utils.sync_read_file(path)
pwd_data = sync_read_file(path)
for line in pwd_data.split('\n'):
if line.startswith('#'):
# skip commented lines
@ -67,7 +68,7 @@ def main():
_print_pwd_keys(path)
else:
# edit a password
utils.change_property(path, pwd_key, pwd_value, clear_flag)
change_property(path, pwd_key, pwd_value, clear_flag)
if __name__ == '__main__':

13
tox.ini
View File

@ -1,7 +1,7 @@
[tox]
minversion = 1.6
skipsdist = True
envlist = py27,pep8
envlist = py27,py34,pep8,bandit
[testenv]
usedevelop = True
@ -14,7 +14,12 @@ deps = -r{toxinidir}/requirements.txt
[testenv:py27]
commands =
/usr/bin/find . -type f -name "*.pyc" -delete
{envpython} setup.py test -s tests
{envpython} -m unittest discover -s tests -p "*.*"
[testenv:py34]
commands =
/usr/bin/find . -type f -name "*.pyc" -delete
{envpython} -m unittest discover -s tests -p "*.*"
[testenv:pep8]
commands = flake8
@ -25,3 +30,7 @@ commands = {posargs}
[flake8]
show-source = True
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
[testenv:bandit]
deps = -r{toxinidir}/test-requirements.txt
commands = bandit -r kollacli