Added support for upgrade and deploy per service
This adds a --services flag to the kollacli upgrade command. A list of services can be provided, delimited by commas. This also adds support for deploy to an individual or set of services through the api. Since it may not always work correctly due to ansible playbooks it is meant to be used by advanced users that make sure it behaves the way they want it to. Also fixing up / commenting out some tests that don't function at this point. They will be re-written once the destroy changes are finished Also moved verification of host specific deploy only to compute nodes into the CLI so that advanced use of the host deploy can be done through the API to non-compute nodes. Change-Id: Ia214627e45664d0958a73cb8c940efacd690d5ea Jira-Issue:OSTACKDEV-148
This commit is contained in:
parent
d02b381b48
commit
ea4ee6db65
@ -24,8 +24,8 @@ from kollacli.common.utils import safe_decode
|
||||
class AsyncApi(object):
|
||||
|
||||
def async_deploy(self, hostnames=[],
|
||||
serial_flag=False, verbose_level=1):
|
||||
# type: (List[str], bool, int) -> Job
|
||||
serial_flag=False, verbose_level=1, servicenames=[]):
|
||||
# type: (List[str], bool, int, List[str]) -> Job
|
||||
"""Deploy.
|
||||
|
||||
Deploy containers to hosts.
|
||||
@ -36,6 +36,8 @@ class AsyncApi(object):
|
||||
:type serial_flag: boolean
|
||||
:param verbose_level: the higher the number, the more verbose
|
||||
:type verbose_level: integer
|
||||
:param servicenames: services to deploy. If empty, then deploy all.
|
||||
:type servicenames: list of strings
|
||||
:return: Job object
|
||||
:rtype: Job
|
||||
"""
|
||||
@ -43,18 +45,23 @@ class AsyncApi(object):
|
||||
empty_ok=True, none_ok=True)
|
||||
check_arg(serial_flag, u._('Serial flag'), bool)
|
||||
check_arg(verbose_level, u._('Verbose level'), int)
|
||||
check_arg(servicenames, u._('Service names'), list,
|
||||
empty_ok=True, none_ok=True)
|
||||
hostnames = safe_decode(hostnames)
|
||||
servicenames = safe_decode(servicenames)
|
||||
|
||||
ansible_job = actions.deploy(hostnames,
|
||||
serial_flag, verbose_level)
|
||||
serial_flag, verbose_level, servicenames)
|
||||
return Job(ansible_job)
|
||||
|
||||
def async_upgrade(self, verbose_level=1):
|
||||
# type: (int) -> Job
|
||||
def async_upgrade(self, verbose_level=1, servicenames=[]):
|
||||
# type: (int, List[str]) -> Job
|
||||
"""Upgrade.
|
||||
|
||||
:param verbose_level: the higher the number, the more verbose
|
||||
:type verbose_level: integer
|
||||
:param servicenames: services to upgrade. If empty, then upgrade all.
|
||||
:type servicenames: list of strings
|
||||
:return: Job object
|
||||
:rtype: Job
|
||||
|
||||
@ -62,7 +69,11 @@ class AsyncApi(object):
|
||||
"openstack_release."
|
||||
"""
|
||||
check_arg(verbose_level, u._('Verbose level'), int)
|
||||
ansible_job = actions.upgrade(verbose_level)
|
||||
check_arg(servicenames, u._('Service names'), list,
|
||||
empty_ok=True, none_ok=True)
|
||||
servicenames = safe_decode(servicenames)
|
||||
|
||||
ansible_job = actions.upgrade(verbose_level, servicenames)
|
||||
return Job(ansible_job)
|
||||
|
||||
def async_host_destroy(self, hostnames, destroy_type, verbose_level=1,
|
||||
|
@ -58,6 +58,22 @@ class Deploy(Command):
|
||||
raise CommandError(u._('Timeout value is not a number.'))
|
||||
timeout_target = time.time() + (60 * timeout)
|
||||
|
||||
# if we are doing a targeted host deploy make sure we are doing it
|
||||
# to only compute nodes
|
||||
if hosts:
|
||||
invalid_host_list = []
|
||||
compute_group = CLIENT.group_get(['compute'])[0]
|
||||
compute_hosts = compute_group.get_hosts()
|
||||
for host in hosts:
|
||||
if host not in compute_hosts:
|
||||
invalid_host_list.append(host)
|
||||
if len(invalid_host_list) > 0:
|
||||
raise CommandError(
|
||||
u._('Invalid hosts for host targeted deploy. '
|
||||
'Hosts must be in the compute group only.'
|
||||
'Invalid hosts: {hosts}')
|
||||
.format(hosts=invalid_host_list))
|
||||
|
||||
job = CLIENT.async_deploy(hosts, serial_flag,
|
||||
verbose_level)
|
||||
|
||||
|
@ -31,12 +31,19 @@ class Upgrade(Command):
|
||||
"""Upgrade."""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(Upgrade, self).get_parser(prog_name)
|
||||
parser.add_argument('--services', nargs='?',
|
||||
metavar='<service_list>',
|
||||
help=u._('Upgrade service list'))
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
services = None
|
||||
try:
|
||||
if parsed_args.services:
|
||||
service_list = parsed_args.services.strip()
|
||||
services = service_list.split(',')
|
||||
verbose_level = self.app.options.verbose_level
|
||||
job = CLIENT.async_upgrade(verbose_level)
|
||||
job = CLIENT.async_upgrade(verbose_level, services)
|
||||
status = job.wait()
|
||||
if verbose_level > 2:
|
||||
LOG.info('\n\n' + 80 * '=')
|
||||
|
@ -69,7 +69,7 @@ def destroy_hosts(hostnames, destroy_type,
|
||||
|
||||
|
||||
def deploy(hostnames=[],
|
||||
serial_flag=False, verbose_level=1):
|
||||
serial_flag=False, verbose_level=1, servicenames=[]):
|
||||
playbook = AnsiblePlaybook()
|
||||
kolla_home = get_kolla_home()
|
||||
playbook.playbook_path = os.path.join(kolla_home,
|
||||
@ -77,8 +77,8 @@ def deploy(hostnames=[],
|
||||
playbook.extra_vars = 'action=deploy'
|
||||
playbook.hosts = hostnames
|
||||
playbook.serial = serial_flag
|
||||
|
||||
playbook.verbose_level = verbose_level
|
||||
playbook.services = servicenames
|
||||
|
||||
_run_deploy_rules(playbook)
|
||||
|
||||
@ -100,11 +100,12 @@ def precheck(hostnames, verbose_level=1):
|
||||
playbook.hosts = hostnames
|
||||
playbook.print_output = True
|
||||
playbook.verbose_level = verbose_level
|
||||
|
||||
job = playbook.run()
|
||||
return job
|
||||
|
||||
|
||||
def upgrade(verbose_level=1):
|
||||
def upgrade(verbose_level=1, servicenames=[]):
|
||||
playbook = AnsiblePlaybook()
|
||||
kolla_home = get_kolla_home()
|
||||
playbook.playbook_path = os.path.join(kolla_home,
|
||||
@ -112,6 +113,8 @@ def upgrade(verbose_level=1):
|
||||
playbook.extra_vars = 'action=upgrade'
|
||||
playbook.print_output = True
|
||||
playbook.verbose_level = verbose_level
|
||||
playbook.services = servicenames
|
||||
|
||||
job = playbook.run()
|
||||
return job
|
||||
|
||||
@ -131,23 +134,6 @@ def _run_deploy_rules(playbook):
|
||||
'\nEmpty passwords: '
|
||||
'{keys}').format(etc=get_kolla_etc(), keys=empty_keys))
|
||||
|
||||
# if we are doing a targeted host deploy make sure we are doing it
|
||||
# to only compute nodes
|
||||
if playbook.hosts:
|
||||
inventory.validate_hostnames(playbook.hosts)
|
||||
host_groups = inventory.get_host_groups()
|
||||
invalid_host_list = []
|
||||
for host in playbook.hosts:
|
||||
groups = host_groups.get(host, None)
|
||||
if not groups or len(groups) != 1 or 'compute' not in groups:
|
||||
invalid_host_list.append(host)
|
||||
if len(invalid_host_list) > 0:
|
||||
raise InvalidArgument(
|
||||
u._('Invalid hosts for host targeted deploy. '
|
||||
'Hosts must be in the compute group only.'
|
||||
'Invalid hosts: {hosts}')
|
||||
.format(hosts=invalid_host_list))
|
||||
|
||||
# cannot have both groups and hosts
|
||||
if playbook.hosts and playbook.groups:
|
||||
raise InvalidArgument(
|
||||
|
@ -81,6 +81,7 @@ class AnsibleJob(object):
|
||||
'ansible job: {cmd} ')
|
||||
.format(lock=get_ansible_lock_path(), cmd=self._command))
|
||||
|
||||
LOG.debug('playbook command: %s' % self._command)
|
||||
# create and open named pipe, must be owned by kolla group
|
||||
os.mkfifo(self._fifo_path)
|
||||
_, grp_id = get_admin_uids()
|
||||
|
@ -13,7 +13,9 @@
|
||||
# under the License.
|
||||
#
|
||||
from common import KollaCliTest
|
||||
from common import TestConfig
|
||||
|
||||
from kollacli.api.client import ClientApi
|
||||
from kollacli.common.allinone import AllInOne
|
||||
from kollacli.common.ansible import job
|
||||
from kollacli.common.inventory import Inventory
|
||||
@ -21,6 +23,8 @@ from kollacli.common.inventory import Inventory
|
||||
import json
|
||||
import unittest
|
||||
|
||||
CLIENT = ClientApi()
|
||||
|
||||
|
||||
class TestFunctional(KollaCliTest):
|
||||
|
||||
@ -146,6 +150,26 @@ class TestFunctional(KollaCliTest):
|
||||
msg = self.run_cli_cmd('deploy --timeout .001', expect_error=True)
|
||||
self.assertIn('timed out', msg)
|
||||
|
||||
# full host deploy to non-compute host. this can only be done
|
||||
# through the api (cli test below makes sure it fails in cli)
|
||||
msg = ''
|
||||
try:
|
||||
test_config = TestConfig()
|
||||
test_config.load()
|
||||
hostnames = test_config.get_hostnames()
|
||||
CLIENT.host_add(hostnames)
|
||||
job = CLIENT.async_deploy(hostnames=[hostnames[0]])
|
||||
job.wait()
|
||||
msg = job.get_console_output()
|
||||
self.assertEqual(job.get_status(), 0,
|
||||
'error performing whole host deploy %s' % msg)
|
||||
except Exception as e:
|
||||
self.assertEqual(0, 1,
|
||||
'unexpected exception in host deploy %s, %s'
|
||||
% (e.message, msg))
|
||||
finally:
|
||||
CLIENT.host_remove(hostnames)
|
||||
|
||||
# run compute host deploy to invalid host
|
||||
err_msg = 'Status: unreachable'
|
||||
msg = ''
|
||||
@ -169,6 +193,27 @@ class TestFunctional(KollaCliTest):
|
||||
# but it will go through the client code paths.
|
||||
self.run_cli_cmd('upgrade -v')
|
||||
|
||||
msg = ''
|
||||
hostnames = []
|
||||
# run rabbitmq service deploy
|
||||
try:
|
||||
test_config = TestConfig()
|
||||
test_config.load()
|
||||
hostnames = test_config.get_hostnames()
|
||||
CLIENT.host_add(hostnames)
|
||||
job = CLIENT.async_upgrade(servicenames=['rabbitmq'])
|
||||
job.wait()
|
||||
msg = job.get_console_output()
|
||||
self.assertEqual(job.get_status(), 0,
|
||||
'error performing service specific deploy %s'
|
||||
% msg)
|
||||
except Exception as e:
|
||||
self.assertEqual(0, 1,
|
||||
'unexpected exception in service deploy: %s, %s'
|
||||
% (e.message, msg))
|
||||
finally:
|
||||
CLIENT.host_remove(hostnames)
|
||||
|
||||
def test_deserialize(self):
|
||||
# create a dummy ansible job
|
||||
j = job.AnsibleJob('', 123, True, '')
|
||||
|
@ -33,13 +33,14 @@ ENABLED_SERVICES = [
|
||||
|
||||
# after deploy
|
||||
EXPECTED_CONTAINERS_1 = [
|
||||
'rabbitmq', 'rabbitmq_data'
|
||||
'rabbitmq'
|
||||
]
|
||||
|
||||
# TODO(bmace) needs to be refactored after destroy change
|
||||
# after destroy --includedata
|
||||
EXPECTED_CONTAINERS_2 = [
|
||||
'rabbitmq_data'
|
||||
]
|
||||
# EXPECTED_CONTAINERS_2 = [
|
||||
# 'rabbitmq_data'
|
||||
# ]
|
||||
|
||||
|
||||
class TestFunctional(KollaCliTest):
|
||||
@ -140,27 +141,30 @@ class TestFunctional(KollaCliTest):
|
||||
'is not running on host: %s ' % hostname +
|
||||
'after deploy.')
|
||||
|
||||
# destroy non-data services (via --stop flag)
|
||||
# this should leave only data containers running
|
||||
self.log.info('Start destroy #2, do not include data')
|
||||
job = CLIENT.async_host_destroy(hostnames, destroy_type='stop',
|
||||
include_data=False)
|
||||
self._process_job(job, 'destroy #2', is_physical_host)
|
||||
|
||||
if is_physical_host:
|
||||
docker_ps = test_config.run_remote_cmd('docker ps', hostname)
|
||||
for service in CLIENT.service_get_all():
|
||||
if service.name not in ENABLED_SERVICES:
|
||||
self.assertNotIn(service.name, docker_ps,
|
||||
'disabled service: %s ' % service.name +
|
||||
'is running on host: %s ' % hostname +
|
||||
'after destroy (no data).')
|
||||
for servicename in EXPECTED_CONTAINERS_2:
|
||||
self.assertIn(servicename, docker_ps,
|
||||
'enabled service: %s ' % servicename +
|
||||
'is not running on host: %s ' % hostname +
|
||||
'after destroy (no data).')
|
||||
|
||||
# TODO(bmace) Invalid until new destroy changes are committed.
|
||||
# Data containers no longer exist.
|
||||
# The tests will need to check for data volumes.
|
||||
# # destroy non-data services (via --stop flag)
|
||||
# # this should leave only data containers running
|
||||
# self.log.info('Start destroy #2, do not include data')
|
||||
# job = CLIENT.async_host_destroy(hostnames, destroy_type='stop',
|
||||
# include_data=False)
|
||||
# self._process_job(job, 'destroy #2', is_physical_host)
|
||||
#
|
||||
# if is_physical_host:
|
||||
# docker_ps = test_config.run_remote_cmd('docker ps', hostname)
|
||||
# for service in CLIENT.service_get_all():
|
||||
# if service.name not in ENABLED_SERVICES:
|
||||
# self.assertNotIn(service.name, docker_ps,
|
||||
# 'disabled service: %s ' % service.name +
|
||||
# 'is running on host: %s ' % hostname +
|
||||
# 'after destroy (no data).')
|
||||
# for servicename in EXPECTED_CONTAINERS_2:
|
||||
# self.assertIn(servicename, docker_ps,
|
||||
# 'enabled service: %s ' % servicename +
|
||||
# 'is not running on host: %s ' % hostname +
|
||||
# 'after destroy (no data).')
|
||||
#
|
||||
self.log.info('Start destroy #3, include data')
|
||||
job = CLIENT.async_host_destroy(hostnames, destroy_type='stop',
|
||||
include_data=True)
|
||||
|
Loading…
Reference in New Issue
Block a user