Browse Source
This further refactors the update/upgrades cli and separates the update and upgrade code. This adds the overcloud_upgrade.py which defines the UpgradePrepare and UpgradeRun. The entry points are now: openstack overcloud upgrade prepare --container-registry-file ... For the no-op heat stack update to refresh stack outputs and openstack overcloud upgrade run --nodes foo --playbooks all For running all the upgrade ansible playbooks. A corresponding converge sub-command will be introduced in a subsequent patch. Change-Id: I1880e8f546df8d509871ba3b4f02877e95c611c8changes/65/545365/9
8 changed files with 463 additions and 42 deletions
@ -0,0 +1,61 @@
|
||||
# Copyright 2015 Red Hat, Inc. |
||||
# |
||||
# 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 mock |
||||
from osc_lib.tests import utils |
||||
|
||||
from tripleoclient.tests import fakes |
||||
|
||||
|
||||
class FakeClientWrapper(object): |
||||
|
||||
def __init__(self): |
||||
self._instance = mock.Mock() |
||||
self.object_store = FakeObjectClient() |
||||
|
||||
def messaging_websocket(self): |
||||
return fakes.FakeWebSocket() |
||||
|
||||
|
||||
class FakeObjectClient(object): |
||||
|
||||
def __init__(self): |
||||
self._instance = mock.Mock() |
||||
self.put_object = mock.Mock() |
||||
|
||||
def get_object(self, *args): |
||||
return |
||||
|
||||
|
||||
class TestOvercloudUpgradePrepare(utils.TestCommand): |
||||
|
||||
def setUp(self): |
||||
super(TestOvercloudUpgradePrepare, self).setUp() |
||||
|
||||
self.app.client_manager.auth_ref = mock.Mock(auth_token="TOKEN") |
||||
self.app.client_manager.baremetal = mock.Mock() |
||||
self.app.client_manager.orchestration = mock.Mock() |
||||
self.app.client_manager.tripleoclient = FakeClientWrapper() |
||||
self.app.client_manager.workflow_engine = mock.Mock() |
||||
|
||||
|
||||
class TestOvercloudUpgradeRun(utils.TestCommand): |
||||
|
||||
def setUp(self): |
||||
super(TestOvercloudUpgradeRun, self).setUp() |
||||
|
||||
self.app.client_manager.auth_ref = mock.Mock(auth_token="TOKEN") |
||||
self.app.client_manager.tripleoclient = FakeClientWrapper() |
||||
self.app.client_manager.workflow_engine = mock.Mock() |
@ -0,0 +1,188 @@
|
||||
# Copyright 2018 Red Hat, Inc. |
||||
# |
||||
# 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 mock |
||||
|
||||
from osc_lib.tests.utils import ParserException |
||||
from tripleoclient import constants |
||||
from tripleoclient import exceptions |
||||
from tripleoclient.tests.v1.overcloud_upgrade import fakes |
||||
from tripleoclient.v1 import overcloud_upgrade |
||||
|
||||
|
||||
class TestOvercloudUpgradePrepare(fakes.TestOvercloudUpgradePrepare): |
||||
|
||||
def setUp(self): |
||||
super(TestOvercloudUpgradePrepare, self).setUp() |
||||
|
||||
# Get the command object to test |
||||
app_args = mock.Mock() |
||||
app_args.verbose_level = 1 |
||||
self.cmd = overcloud_upgrade.UpgradePrepare(self.app, app_args) |
||||
|
||||
uuid4_patcher = mock.patch('uuid.uuid4', return_value="UUID4") |
||||
self.mock_uuid4 = uuid4_patcher.start() |
||||
self.addCleanup(self.mock_uuid4.stop) |
||||
|
||||
@mock.patch('tripleoclient.utils.get_stack', |
||||
autospec=True) |
||||
@mock.patch('tripleoclient.v1.overcloud_upgrade.UpgradePrepare.log', |
||||
autospec=True) |
||||
@mock.patch('tripleoclient.workflows.package_update.update', |
||||
autospec=True) |
||||
@mock.patch('os.path.abspath') |
||||
@mock.patch('yaml.load') |
||||
@mock.patch('shutil.copytree', autospec=True) |
||||
@mock.patch('six.moves.builtins.open') |
||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' |
||||
'_deploy_tripleo_heat_templates', autospec=True) |
||||
def test_upgrade_out(self, mock_deploy, mock_open, mock_copy, mock_yaml, |
||||
mock_abspath, mock_upgrade, mock_logger, |
||||
mock_get_stack): |
||||
mock_stack = mock.Mock() |
||||
mock_stack.stack_name = 'mystack' |
||||
mock_get_stack.return_value = mock_stack |
||||
mock_abspath.return_value = '/home/fake/my-fake-registry.yaml' |
||||
mock_yaml.return_value = {'fake_container': 'fake_value'} |
||||
|
||||
argslist = ['--stack', 'overcloud', '--templates', |
||||
'--container-registry-file', 'my-fake-registry.yaml'] |
||||
verifylist = [ |
||||
('stack', 'overcloud'), |
||||
('templates', constants.TRIPLEO_HEAT_TEMPLATES), |
||||
('container_registry_file', 'my-fake-registry.yaml') |
||||
] |
||||
|
||||
parsed_args = self.check_parser(self.cmd, argslist, verifylist) |
||||
self.cmd.take_action(parsed_args) |
||||
mock_upgrade.assert_called_once_with( |
||||
self.app.client_manager, |
||||
container='mystack', |
||||
container_registry={'fake_container': 'fake_value'}, |
||||
ceph_ansible_playbook='/usr/share/ceph-ansible' |
||||
'/site-docker.yml.sample' |
||||
) |
||||
|
||||
@mock.patch('tripleoclient.workflows.package_update.update', |
||||
autospec=True) |
||||
@mock.patch('six.moves.builtins.open') |
||||
@mock.patch('os.path.abspath') |
||||
@mock.patch('yaml.load') |
||||
@mock.patch('shutil.copytree', autospec=True) |
||||
@mock.patch('tripleoclient.v1.overcloud_deploy.DeployOvercloud.' |
||||
'_deploy_tripleo_heat_templates', autospec=True) |
||||
def test_upgrade_failed(self, mock_deploy, mock_copy, mock_yaml, |
||||
mock_abspath, mock_open, mock_upgrade): |
||||
mock_upgrade.side_effect = exceptions.DeploymentError() |
||||
mock_abspath.return_value = '/home/fake/my-fake-registry.yaml' |
||||
mock_yaml.return_value = {'fake_container': 'fake_value'} |
||||
argslist = ['--stack', 'overcloud', '--templates', |
||||
'--container-registry-file', 'my-fake-registry.yaml'] |
||||
verifylist = [ |
||||
('stack', 'overcloud'), |
||||
('templates', constants.TRIPLEO_HEAT_TEMPLATES), |
||||
('container_registry_file', 'my-fake-registry.yaml') |
||||
] |
||||
parsed_args = self.check_parser(self.cmd, argslist, verifylist) |
||||
|
||||
self.assertRaises(exceptions.DeploymentError, |
||||
self.cmd.take_action, parsed_args) |
||||
|
||||
|
||||
class TestOvercloudUpgradeRun(fakes.TestOvercloudUpgradeRun): |
||||
|
||||
def setUp(self): |
||||
super(TestOvercloudUpgradeRun, self).setUp() |
||||
|
||||
# Get the command object to test |
||||
app_args = mock.Mock() |
||||
app_args.verbose_level = 1 |
||||
self.cmd = overcloud_upgrade.UpgradeRun(self.app, app_args) |
||||
|
||||
uuid4_patcher = mock.patch('uuid.uuid4', return_value="UUID4") |
||||
self.mock_uuid4 = uuid4_patcher.start() |
||||
self.addCleanup(self.mock_uuid4.stop) |
||||
|
||||
@mock.patch('tripleoclient.workflows.package_update.update_ansible', |
||||
autospec=True) |
||||
@mock.patch('os.path.expanduser') |
||||
@mock.patch('oslo_concurrency.processutils.execute') |
||||
@mock.patch('six.moves.builtins.open') |
||||
def test_upgrade_with_playbook(self, mock_open, mock_execute, |
||||
mock_expanduser, upgrade_ansible): |
||||
mock_expanduser.return_value = '/home/fake/' |
||||
argslist = ['--nodes', 'Compute', '--playbook', |
||||
'fake-playbook.yaml'] |
||||
verifylist = [ |
||||
('nodes', 'Compute'), |
||||
('static_inventory', None), |
||||
('playbook', 'fake-playbook.yaml') |
||||
] |
||||
|
||||
parsed_args = self.check_parser(self.cmd, argslist, verifylist) |
||||
with mock.patch('os.path.exists') as mock_exists: |
||||
mock_exists.return_value = True |
||||
self.cmd.take_action(parsed_args) |
||||
upgrade_ansible.assert_called_once_with( |
||||
self.app.client_manager, |
||||
nodes='Compute', |
||||
inventory_file=mock_open().read(), |
||||
playbook='fake-playbook.yaml', |
||||
ansible_queue_name=constants.UPGRADE_QUEUE |
||||
) |
||||
|
||||
@mock.patch('tripleoclient.workflows.package_update.update_ansible', |
||||
autospec=True) |
||||
@mock.patch('os.path.expanduser') |
||||
@mock.patch('oslo_concurrency.processutils.execute') |
||||
@mock.patch('six.moves.builtins.open') |
||||
def test_upgrade_with_all_playbooks(self, mock_open, mock_execute, |
||||
mock_expanduser, upgrade_ansible): |
||||
mock_expanduser.return_value = '/home/fake/' |
||||
argslist = ['--nodes', 'Compute', '--playbook', 'all'] |
||||
verifylist = [ |
||||
('nodes', 'Compute'), |
||||
('static_inventory', None), |
||||
('playbook', 'all') |
||||
] |
||||
|
||||
parsed_args = self.check_parser(self.cmd, argslist, verifylist) |
||||
with mock.patch('os.path.exists') as mock_exists: |
||||
mock_exists.return_value = True |
||||
self.cmd.take_action(parsed_args) |
||||
for book in constants.MAJOR_UPGRADE_PLAYBOOKS: |
||||
upgrade_ansible.assert_any_call( |
||||
self.app.client_manager, |
||||
nodes='Compute', |
||||
inventory_file=mock_open().read(), |
||||
playbook=book, |
||||
ansible_queue_name=constants.UPGRADE_QUEUE |
||||
) |
||||
|
||||
@mock.patch('tripleoclient.workflows.package_update.update_ansible', |
||||
autospec=True) |
||||
@mock.patch('os.path.expanduser') |
||||
@mock.patch('oslo_concurrency.processutils.execute') |
||||
@mock.patch('six.moves.builtins.open') |
||||
def test_upgrade_with_no_nodes(self, mock_open, mock_execute, |
||||
mock_expanduser, upgrade_ansible): |
||||
mock_expanduser.return_value = '/home/fake/' |
||||
argslist = [] |
||||
verifylist = [ |
||||
('static_inventory', None), |
||||
('playbook', 'all') |
||||
] |
||||
self.assertRaises(ParserException, lambda: self.check_parser( |
||||
self.cmd, argslist, verifylist)) |
@ -0,0 +1,184 @@
|
||||
# Copyright 2018 Red Hat, Inc. |
||||
# |
||||
# 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 yaml |
||||
|
||||
from osc_lib.i18n import _ |
||||
|
||||
from tripleoclient import command |
||||
from tripleoclient import constants |
||||
from tripleoclient import utils as oooutils |
||||
from tripleoclient.v1.overcloud_deploy import DeployOvercloud |
||||
from tripleoclient.workflows import package_update |
||||
|
||||
|
||||
class UpgradePrepare(DeployOvercloud): |
||||
"""Run heat stack update for overcloud nodes to refresh heat stack outputs. |
||||
|
||||
The heat stack outputs are what we use later on to generate ansible |
||||
playbooks which deliver the major upgrade workflow. This is used as the |
||||
first step for a major upgrade of your overcloud. |
||||
""" |
||||
|
||||
log = logging.getLogger(__name__ + ".MajorUpgradePrepare") |
||||
|
||||
# enable preservation of all important files (plan env, user env, |
||||
# roles/network data, user files) so that we don't have to pass |
||||
# all env files on update command |
||||
_keep_env_on_update = True |
||||
|
||||
def get_parser(self, prog_name): |
||||
parser = super(UpgradePrepare, self).get_parser(prog_name) |
||||
parser.add_argument('--container-registry-file', |
||||
dest='container_registry_file', |
||||
default=None, |
||||
help=_("File which contains the container " |
||||
"registry data for the upgrade"), |
||||
) |
||||
parser.add_argument('--ceph-ansible-playbook', |
||||
action="store", |
||||
default="/usr/share/ceph-ansible" |
||||
"/site-docker.yml.sample", |
||||
help=_('Path to switch the ceph-ansible playbook ' |
||||
'used for upgrade. '), |
||||
) |
||||
return parser |
||||
|
||||
def take_action(self, parsed_args): |
||||
self.log.debug("take_action(%s)" % parsed_args) |
||||
clients = self.app.client_manager |
||||
|
||||
stack = oooutils.get_stack(clients.orchestration, |
||||
parsed_args.stack) |
||||
|
||||
stack_name = stack.stack_name |
||||
container_registry = parsed_args.container_registry_file |
||||
|
||||
# Update the container registry: |
||||
if container_registry: |
||||
with open(os.path.abspath(container_registry)) as content: |
||||
registry = yaml.load(content.read()) |
||||
else: |
||||
self.log.warning( |
||||
"You have not provided a container registry file. Note " |
||||
"that none of the containers on your environement will be " |
||||
"updated. If you want to update your container you have " |
||||
"to re-run this command and provide the registry file " |
||||
"with: --container-registry-file option.") |
||||
registry = None |
||||
# Run update |
||||
ceph_ansible_playbook = parsed_args.ceph_ansible_playbook |
||||
# Run Overcloud deploy (stack update) |
||||
# In case of update and upgrade we need to force the |
||||
# update_plan_only. The heat stack update is done by the |
||||
# packag_update mistral action |
||||
parsed_args.update_plan_only = True |
||||
super(UpgradePrepare, self).take_action(parsed_args) |
||||
package_update.update(clients, container=stack_name, |
||||
container_registry=registry, |
||||
ceph_ansible_playbook=ceph_ansible_playbook) |
||||
package_update.get_config(clients, container=stack_name) |
||||
print("Update init on stack {0} complete.".format( |
||||
parsed_args.stack)) |
||||
|
||||
|
||||
class UpgradeRun(command.Command): |
||||
"""Run major upgrade ansible playbooks on Overcloud nodes""" |
||||
|
||||
log = logging.getLogger(__name__ + ".MajorUpgradeRun") |
||||
|
||||
def get_parser(self, prog_name): |
||||
parser = super(UpgradeRun, self).get_parser(prog_name) |
||||
parser.add_argument('--nodes', |
||||
action="store", |
||||
required=True, |
||||
help=_("Required parameter. This specifies the " |
||||
"overcloud nodes to run the major upgrade " |
||||
"playbooks on. You can use the name of " |
||||
"a specific node, or the name of the role " |
||||
"(e.g. Compute).") |
||||
) |
||||
parser.add_argument('--playbook', |
||||
action="store", |
||||
default="all", |
||||
help=_("Ansible playbook to use for the major " |
||||
"upgrade. Defaults to the special value " |
||||
"\'all\' which causes all the upgrade " |
||||
"playbooks to run. That is the " |
||||
"upgrade_steps_playbook.yaml " |
||||
"then deploy_steps_playbook.yaml and then " |
||||
"post_upgrade_steps_playbooks.yaml. Set " |
||||
"this to each of those playbooks in " |
||||
"consecutive invocations of this command " |
||||
"if you prefer to run them manually. Note: " |
||||
"you will have to run all of those " |
||||
"playbooks so that all services are " |
||||
"upgraded and running with the target " |
||||
"version configuration.") |
||||
) |
||||
parser.add_argument('--static-inventory', |
||||
dest='static_inventory', |
||||
action="store", |
||||
default=None, |
||||
help=_('Path to an existing ansible inventory to ' |
||||
'use. If not specified, one will be ' |
||||
'generated in ' |
||||
'~/tripleo-ansible-inventory.yaml') |
||||
) |
||||
return parser |
||||
|
||||
def take_action(self, parsed_args): |
||||
self.log.debug("take_action(%s)" % parsed_args) |
||||
clients = self.app.client_manager |
||||
|
||||
# Run ansible: |
||||
nodes = parsed_args.nodes |
||||
playbook = parsed_args.playbook |
||||
inventory = oooutils.get_tripleo_ansible_inventory( |
||||
parsed_args.static_inventory) |
||||
upgrade_playbooks = [playbook] |
||||
if playbook == "all": |
||||
upgrade_playbooks = constants.MAJOR_UPGRADE_PLAYBOOKS |
||||
for book in upgrade_playbooks: |
||||
self.log.debug("Running major upgrade ansible playbook %s " % book) |
||||
package_update.update_ansible( |
||||
clients, nodes=nodes, |
||||
inventory_file=inventory, |
||||
playbook=book, |
||||
ansible_queue_name=constants.UPGRADE_QUEUE) |
||||
|
||||
|
||||
class UpgradeConvergeOvercloud(DeployOvercloud): |
||||
"""Converge the upgrade on Overcloud Nodes""" |
||||
|
||||
log = logging.getLogger(__name__ + ".UpgradeConvergeOvercloud") |
||||
|
||||
def get_parser(self, prog_name): |
||||
parser = super(UpgradeConvergeOvercloud, self).get_parser(prog_name) |
||||
return parser |
||||
|
||||
def take_action(self, parsed_args): |
||||
self.log.debug("take_action(%s)" % parsed_args) |
||||
clients = self.app.client_manager |
||||
|
||||
stack = oooutils.get_stack(clients.orchestration, |
||||
parsed_args.stack) |
||||
stack_name = stack.stack_name |
||||
|
||||
parsed_args.update_plan_only = True |
||||
super(UpgradeConvergeOvercloud, self).take_action(parsed_args) |
||||
# Run converge steps |
||||
package_update.converge_nodes(clients, container=stack_name) |
Loading…
Reference in new issue