commit
28ce651e85
@ -0,0 +1,8 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
``openstack overcloud plan *`` commands have been removed.
|
||||
These commands are irrelevant as overcloud deploy/update/upgrade
|
||||
does not create/update swift plan anymore. Also, some of the
|
||||
``openstack overcloud role`` commands that use swift plan have
|
||||
been removed.
|
@ -1,412 +0,0 @@
|
||||
# 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 import constants
|
||||
from tripleoclient import exceptions
|
||||
from tripleoclient import plugin
|
||||
from tripleoclient.tests import fakes
|
||||
from tripleoclient.v1 import overcloud_plan
|
||||
|
||||
|
||||
class TestStringCapture(object):
|
||||
def __init__(self):
|
||||
self.capture_string = ''
|
||||
|
||||
def write(self, msg):
|
||||
self.capture_string = self.capture_string + msg
|
||||
|
||||
def getvalue(self):
|
||||
return self.capture_string
|
||||
|
||||
def flush(self):
|
||||
return
|
||||
|
||||
|
||||
class TestOvercloudPlanList(utils.TestCommand):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvercloudPlanList, self).setUp()
|
||||
self.app.client_manager.tripleoclient = plugin.ClientWrapper(
|
||||
instance=fakes.FakeInstanceData
|
||||
)
|
||||
self.cmd = overcloud_plan.ListPlans(self.app, None)
|
||||
|
||||
@mock.patch("tripleoclient.workflows.plan_management."
|
||||
"list_deployment_plans",
|
||||
autospec=True)
|
||||
def test_list_empty(self, mock_list_plans):
|
||||
mock_list_plans.return_value = []
|
||||
|
||||
result = self.cmd.take_action(None)
|
||||
|
||||
self.assertEqual(0, len(result[1]))
|
||||
|
||||
@mock.patch("tripleoclient.workflows.plan_management."
|
||||
"list_deployment_plans",
|
||||
autospec=True)
|
||||
def test_list(self, mock_list_plans):
|
||||
mock_list_plans.return_value = (['test-plan-1', 'test-plan-2'])
|
||||
|
||||
result = self.cmd.take_action(None)
|
||||
|
||||
self.assertEqual(1, len(result[0]))
|
||||
self.assertEqual([('test-plan-1',), ('test-plan-2',)], result[1])
|
||||
|
||||
|
||||
class TestOvercloudDeletePlan(fakes.FakePlaybookExecution):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvercloudDeletePlan, self).setUp()
|
||||
|
||||
self.cmd = overcloud_plan.DeletePlan(self.app, None)
|
||||
|
||||
@mock.patch("tripleo_common.actions.plan.DeletePlanAction.run",
|
||||
return_value=None)
|
||||
def test_delete_plan(self, mock_run):
|
||||
parsed_args = self.check_parser(self.cmd, ['test-plan'],
|
||||
[('plans', ['test-plan'])])
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
@mock.patch("tripleo_common.actions.plan.DeletePlanAction.run",
|
||||
return_value=None)
|
||||
def test_delete_multiple_plans(self, mock_run):
|
||||
argslist = ['test-plan1', 'test-plan2']
|
||||
verifylist = [('plans', ['test-plan1', 'test-plan2'])]
|
||||
parsed_args = self.check_parser(self.cmd, argslist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
|
||||
class TestOvercloudCreatePlan(utils.TestCommand):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvercloudCreatePlan, self).setUp()
|
||||
|
||||
app_args = mock.Mock()
|
||||
app_args.verbose_level = 1
|
||||
self.app.options = fakes.FakeOptions()
|
||||
self.cmd = overcloud_plan.CreatePlan(self.app, app_args)
|
||||
self.app.client_manager.workflow_engine = mock.Mock()
|
||||
self.tripleoclient = mock.Mock()
|
||||
self.app.client_manager.tripleoclient = self.tripleoclient
|
||||
|
||||
self.swift = self.app.client_manager.tripleoclient.object_store
|
||||
self.swift.get_account = mock.MagicMock()
|
||||
self.mock_tar = mock.patch(
|
||||
'tripleo_common.utils.tarball.create_tarball',
|
||||
autospec=True
|
||||
)
|
||||
self.mock_tar.start()
|
||||
|
||||
def tearDown(self):
|
||||
super(TestOvercloudCreatePlan, self).tearDown()
|
||||
self.mock_tar.stop()
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_create_default_plan(self, mock_tmp, mock_cd, mock_run_playbook):
|
||||
|
||||
# Setup
|
||||
arglist = ['overcast']
|
||||
verifylist = [
|
||||
('name', 'overcast'),
|
||||
('templates', None)
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# Run
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Verify
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-create-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "overcast",
|
||||
"generate_passwords": True,
|
||||
"use_default_templates": True,
|
||||
"disable_image_params_prepare": False,
|
||||
},
|
||||
verbosity=3,
|
||||
)
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_create_custom_plan(self, mock_tmp, mock_cd,
|
||||
mock_run_playbook):
|
||||
|
||||
# Setup
|
||||
arglist = ['overcast', '--templates', '/fake/path']
|
||||
verifylist = [
|
||||
('name', 'overcast'),
|
||||
('templates', '/fake/path')
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# Run
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Verify
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-create-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "overcast",
|
||||
"generate_passwords": True,
|
||||
"use_default_templates": False,
|
||||
"disable_image_params_prepare": False,
|
||||
},
|
||||
verbosity=3,
|
||||
)
|
||||
self.swift.get_account.assert_called_once()
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_create_custom_plan_plan_environment_file(
|
||||
self, mock_tmp, mock_cd, mock_run_playbook):
|
||||
# Setup
|
||||
arglist = ['overcast', '--templates', '/fake/path',
|
||||
'-p', 'the_plan_environment.yaml']
|
||||
verifylist = [
|
||||
('name', 'overcast'),
|
||||
('templates', '/fake/path'),
|
||||
('plan_environment_file', 'the_plan_environment.yaml')
|
||||
]
|
||||
self.app.options = fakes.FakeOptions()
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
mock_open = mock.mock_open()
|
||||
# Run
|
||||
with mock.patch('six.moves.builtins.open', mock_open):
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
mock_open.assert_has_calls(
|
||||
[mock.call('the_plan_environment.yaml', 'rb')])
|
||||
|
||||
# Verify
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-create-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "overcast",
|
||||
"generate_passwords": True,
|
||||
"plan_environment": "the_plan_environment.yaml",
|
||||
"use_default_templates": False,
|
||||
"disable_image_params_prepare": False,
|
||||
},
|
||||
verbosity=3,
|
||||
)
|
||||
self.swift.get_account.assert_called_once()
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_create_default_plan_with_password_gen_disabled(
|
||||
self, mock_tmp, mock_cd, mock_run_playbook):
|
||||
|
||||
# Setup
|
||||
arglist = ['overcast', '--disable-password-generation',
|
||||
'--disable-container-prepare']
|
||||
verifylist = [
|
||||
('name', 'overcast'),
|
||||
('templates', None),
|
||||
('disable_password_generation', True),
|
||||
('disable_container_prepare', True)
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# Run
|
||||
self.app.options = fakes.FakeOptions()
|
||||
self.cmd.take_action(parsed_args)
|
||||
# Verify
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-create-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "overcast",
|
||||
"generate_passwords": False,
|
||||
"use_default_templates": True,
|
||||
"disable_image_params_prepare": True,
|
||||
},
|
||||
verbosity=3,
|
||||
)
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_create_custom_plan_from_source_url(
|
||||
self, mock_tmp, mock_cd, mock_run_playbook):
|
||||
|
||||
# Setup
|
||||
arglist = ['overcast', '--source-url', 'http://tripleo.org/templates']
|
||||
verifylist = [
|
||||
('name', 'overcast'),
|
||||
('templates', None),
|
||||
('source_url', 'http://tripleo.org/templates')
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
# Run
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Verify
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-create-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "overcast",
|
||||
"generate_passwords": True,
|
||||
"use_default_templates": False,
|
||||
"source_url": "http://tripleo.org/templates",
|
||||
"disable_image_params_prepare": False,
|
||||
},
|
||||
verbosity=3,
|
||||
)
|
||||
|
||||
|
||||
class TestOvercloudDeployPlan(utils.TestCommand):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvercloudDeployPlan, self).setUp()
|
||||
|
||||
app_args = mock.Mock()
|
||||
app_args.verbose_level = 1
|
||||
self.app.options = fakes.FakeOptions()
|
||||
self.cmd = overcloud_plan.DeployPlan(self.app, app_args)
|
||||
|
||||
sleep_patch = mock.patch('time.sleep')
|
||||
self.addCleanup(sleep_patch.stop)
|
||||
sleep_patch.start()
|
||||
|
||||
@mock.patch("tripleoclient.utils.update_deployment_status", autospec=True)
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('tripleoclient.utils.wait_for_stack_ready', autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_overcloud_deploy_plan(self, mock_tmp, mock_cd,
|
||||
mock_for_stack_ready,
|
||||
mock_run_playbook,
|
||||
mock_update_status):
|
||||
|
||||
# Setup
|
||||
arglist = ['--run-validations', 'overcast']
|
||||
verifylist = [
|
||||
('name', 'overcast'),
|
||||
('run_validations', True),
|
||||
('timeout', 240)
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.orch = self.app.client_manager.orchestration = mock.Mock()
|
||||
# No existing stack, this is a new deploy.
|
||||
self.orch.stacks.get.return_value = None
|
||||
|
||||
mock_for_stack_ready.return_value = True
|
||||
|
||||
# Run
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-deploy-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
timeout=240,
|
||||
extra_vars={
|
||||
"container": "overcast",
|
||||
"run_validations": True,
|
||||
"skip_deploy_identifier": False,
|
||||
},
|
||||
verbosity=3,
|
||||
)
|
||||
mock_update_status.assert_called()
|
||||
|
||||
|
||||
class TestOvercloudExportPlan(utils.TestCommand):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvercloudExportPlan, self).setUp()
|
||||
self.cmd = overcloud_plan.ExportPlan(self.app, None)
|
||||
self.app.client_manager = mock.Mock()
|
||||
self.clients = self.app.client_manager
|
||||
|
||||
# Mock urlopen
|
||||
f = mock.Mock()
|
||||
f.read.return_value = 'tarball contents'
|
||||
urlopen_patcher = mock.patch('six.moves.urllib.request.urlopen',
|
||||
return_value=f)
|
||||
self.mock_urlopen = urlopen_patcher.start()
|
||||
self.addCleanup(self.mock_urlopen.stop)
|
||||
|
||||
@mock.patch(
|
||||
'tripleoclient.workflows.plan_management.export_deployment_plan',
|
||||
autospec=True)
|
||||
def test_export_plan(self, export_deployment_plan_mock):
|
||||
parsed_args = self.check_parser(self.cmd, ['test-plan'],
|
||||
[('plan', 'test-plan')])
|
||||
|
||||
export_deployment_plan_mock.return_value = 'http://fake-url.com'
|
||||
|
||||
with mock.patch('six.moves.builtins.open', mock.mock_open()):
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
export_deployment_plan_mock.assert_called_once_with(
|
||||
self.clients, 'test-plan')
|
||||
|
||||
@mock.patch('os.path.exists')
|
||||
def test_export_plan_outfile_exists(self, exists_mock):
|
||||
parsed_args = self.check_parser(self.cmd, ['test-plan'],
|
||||
[('plan', 'test-plan')])
|
||||
|
||||
exists_mock.return_value = True
|
||||
|
||||
self.assertRaises(exceptions.PlanExportError,
|
||||
self.cmd.take_action, parsed_args)
|
||||
|
||||
@mock.patch(
|
||||
'tripleoclient.workflows.plan_management.export_deployment_plan',
|
||||
autospec=True)
|
||||
@mock.patch('os.path.exists')
|
||||
def test_export_plan_outfile_exists_with_overwrite(
|
||||
self, exists_mock, export_deployment_plan_mock):
|
||||
arglist = ['-f', 'test-plan']
|
||||
verifylist = [
|
||||
('plan', 'test-plan'),
|
||||
('force_overwrite', True)
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
exists_mock.return_value = True
|
||||
export_deployment_plan_mock.return_value = 'http://fake-url.com'
|
||||
|
||||
with mock.patch('six.moves.builtins.open', mock.mock_open()):
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
export_deployment_plan_mock.assert_called_once_with(
|
||||
self.clients, 'test-plan')
|
@ -1,169 +0,0 @@
|
||||
# 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 import exceptions
|
||||
from osc_lib.tests import utils
|
||||
|
||||
from tripleoclient.v1 import overcloud_plan_roles
|
||||
|
||||
|
||||
class BaseTestCommand(utils.TestCommand):
|
||||
def setUp(self):
|
||||
super(BaseTestCommand, self).setUp()
|
||||
|
||||
tc = self.app.client_manager.tripleoclient = mock.Mock()
|
||||
tc.object_store.get_object.return_value = (
|
||||
{},
|
||||
'{"result": [{"name":"Controller","description":"Test desc",'
|
||||
'"random": "abcd"},{"name":"Test"}]}'
|
||||
)
|
||||
tc.object_store.get_container.return_value = (
|
||||
'container',
|
||||
[
|
||||
{
|
||||
"name": "Controller",
|
||||
"description": "Test desc",
|
||||
"random": "abcd",
|
||||
"efg": "123",
|
||||
"ServicesDefault": [
|
||||
"b",
|
||||
"c",
|
||||
"a"
|
||||
]
|
||||
}
|
||||
]
|
||||
)
|
||||
self.tripleoclient = tc
|
||||
|
||||
|
||||
class TestOvercloudListCurrentRoles(BaseTestCommand):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvercloudListCurrentRoles, self).setUp()
|
||||
|
||||
self.cmd = overcloud_plan_roles.ListRoles(self.app, None)
|
||||
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.plan.ListRolesAction.run',
|
||||
autospec=True,
|
||||
return_value=[]
|
||||
)
|
||||
def test_list_empty_on_non_default_plan(self, mock_list):
|
||||
arglist = ['--name', 'overcast', '--current']
|
||||
verifylist = [('name', 'overcast'), ('current', True)]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.assertEqual(0, len(result[1]))
|
||||
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.plan.ListRolesAction.run',
|
||||
autospec=True,
|
||||
return_value=["ObjectStorage", "Controller"]
|
||||
)
|
||||
def test_list(self, mock_list):
|
||||
arglist = ['--current']
|
||||
verifylist = [('name', 'overcloud'), ('current', True)]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.assertEqual(2, len(result[1]))
|
||||
self.assertEqual([('Controller',), ('ObjectStorage',)], result[1])
|
||||
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.plan.ListRolesAction.run',
|
||||
autospec=True,
|
||||
return_value=[
|
||||
{
|
||||
"name": "Controller",
|
||||
"description": "Test desc",
|
||||
"random": "abcd"
|
||||
},
|
||||
{"name": "Test"}
|
||||
]
|
||||
)
|
||||
def test_list_with_details(self, mock_list):
|
||||
parsed_args = self.check_parser(self.cmd,
|
||||
['--current', '--detail'],
|
||||
[])
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
data = result[1]
|
||||
self.assertEqual(2, len(data))
|
||||
|
||||
self.assertEqual(data[0][0], "Controller")
|
||||
self.assertEqual(data[0][3], "random: abcd")
|
||||
self.assertEqual(data[1][0], "Test")
|
||||
self.assertEqual(data[1][3], "")
|
||||
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.plan.ListRolesAction.run',
|
||||
autospec=True,
|
||||
return_value=[]
|
||||
)
|
||||
def test_list_with_details_empty(self, mock_list):
|
||||
parsed_args = self.check_parser(self.cmd,
|
||||
['--current', '--detail'],
|
||||
[])
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.assertEqual(0, len(result[1]))
|
||||
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.plan.ListRolesAction.run',
|
||||
autospec=True,
|
||||
return_value=[
|
||||
{"name": "Compute"},
|
||||
{"name": "Random"},
|
||||
{"name": "BlockStorage", "ServicesDefault": ["c", "b", "a"]}
|
||||
]
|
||||
)
|
||||
def test_list_with_details_sorted(self, mock_list):
|
||||
|
||||
parsed_args = self.check_parser(self.cmd,
|
||||
['--current', '--detail'],
|
||||
[])
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.assertEqual(3, len(result[1]))
|
||||
|
||||
# Test main list sorted
|
||||
self.assertEqual(result[1][0][0], "BlockStorage")
|
||||
self.assertEqual(result[1][1][0], "Compute")
|
||||
self.assertEqual(result[1][2][0], "Random")
|
||||
|
||||
# Test service sublist sorted
|
||||
self.assertEqual(result[1][0][2], "a\nb\nc")
|
||||
|
||||
|
||||
class TestOvercloudShowRole(BaseTestCommand):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvercloudShowRole, self).setUp()
|
||||
|
||||
self.cmd = overcloud_plan_roles.ShowRole(self.app, None)
|
||||
|
||||
self.app.client_manager.tripleoclient = self.tripleoclient
|
||||
|
||||
@mock.patch(
|
||||
'tripleo_common.actions.plan.ListRolesAction.run',
|
||||
autospec=True,
|
||||
return_value=[]
|
||||
)
|
||||
def test_role_not_found(self, mock_list):
|
||||
arglist = ['--name', 'overcast', 'doesntexist']
|
||||
verifylist = [('name', 'overcast'), ('role', 'doesntexist')]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
self.cmd.take_action,
|
||||
parsed_args)
|
@ -1,397 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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 import constants
|
||||
from tripleoclient.tests import base
|
||||
from tripleoclient.workflows import plan_management
|
||||
|
||||
|
||||
class TestPlanCreationWorkflows(utils.TestCommand):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPlanCreationWorkflows, self).setUp()
|
||||
self.tripleoclient = mock.Mock()
|
||||
self.app.client_manager.tripleoclient = self.tripleoclient
|
||||
self.tripleoclient.object_store.get_account = mock.MagicMock()
|
||||
self.mock_tar = mock.patch(
|
||||
'tripleo_common.utils.tarball.create_tarball',
|
||||
autospec=True
|
||||
)
|
||||
self.mock_tar.start()
|
||||
|
||||
def tearDown(self):
|
||||
super(TestPlanCreationWorkflows, self).tearDown()
|
||||
self.mock_tar.stop()
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_create_plan_from_templates_success(self, mock_tmp, mock_cd,
|
||||
mock_run_playbook):
|
||||
plan_management.create_plan_from_templates(
|
||||
self.app.client_manager,
|
||||
'test-overcloud',
|
||||
'/tht-root/')
|
||||
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-create-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "test-overcloud",
|
||||
"generate_passwords": True,
|
||||
"use_default_templates": False,
|
||||
"disable_image_params_prepare": False,
|
||||
},
|
||||
verbosity=0,
|
||||
)
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('tripleoclient.utils.rel_or_abs_path')
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_create_plan_from_templates_roles_data(self, mock_tmp, mock_cd,
|
||||
mock_norm_path,
|
||||
mock_run_playbook):
|
||||
mock_open_context = mock.mock_open()
|
||||
with mock.patch('six.moves.builtins.open', mock_open_context):
|
||||
plan_management.create_plan_from_templates(
|
||||
self.app.client_manager,
|
||||
'test-overcloud',
|
||||
'/tht-root/',
|
||||
'the_roles_file.yaml')
|
||||
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-create-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "test-overcloud",
|
||||
"generate_passwords": True,
|
||||
"use_default_templates": False,
|
||||
"disable_image_params_prepare": False,
|
||||
},
|
||||
verbosity=0,
|
||||
)
|
||||
|
||||
self.assertIn(mock.call('the_roles_file.yaml', '/tht-root/'),
|
||||
mock_norm_path.call_args_list)
|
||||
|
||||
self.tripleoclient.object_store.put_object.assert_called_with(
|
||||
'test-overcloud', 'roles_data.yaml', mock_open_context())
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_create_plan_from_templates_plan_env_data(self, mock_tmp, mock_cd,
|
||||
mock_run_playbook):
|
||||
mock_open_context = mock.mock_open()
|
||||
with mock.patch('six.moves.builtins.open', mock_open_context):
|
||||
plan_management.create_plan_from_templates(
|
||||
self.app.client_manager,
|
||||
'test-overcloud',
|
||||
'/tht-root/',
|
||||
plan_env_file='the-plan-environment.yaml')
|
||||
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-create-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "test-overcloud",
|
||||
"generate_passwords": True,
|
||||
"plan_environment": "the-plan-environment.yaml",
|
||||
"use_default_templates": False,
|
||||
"disable_image_params_prepare": False,
|
||||
},
|
||||
verbosity=0,
|
||||
)
|
||||
mock_open_context.assert_has_calls(
|
||||
[mock.call('the-plan-environment.yaml', 'rb')])
|
||||
|
||||
self.tripleoclient.object_store.put_object.assert_called_with(
|
||||
'test-overcloud', 'plan-environment.yaml', mock_open_context())
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_create_plan_from_templates_networks_data(self, mock_tmp, mock_cd,
|
||||
mock_run_playbook):
|
||||
mock_open_context = mock.mock_open()
|
||||
with mock.patch('six.moves.builtins.open', mock_open_context):
|
||||
plan_management.create_plan_from_templates(
|
||||
self.app.client_manager,
|
||||
'test-overcloud',
|
||||
'/tht-root/',
|
||||
networks_file='the-network-data.yaml')
|
||||
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-create-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "test-overcloud",
|
||||
"generate_passwords": True,
|
||||
"use_default_templates": False,
|
||||
"disable_image_params_prepare": False,
|
||||
},
|
||||
verbosity=0,
|
||||
)
|
||||
mock_open_context.assert_has_calls(
|
||||
[mock.call('the-network-data.yaml', 'rb')])
|
||||
|
||||
self.tripleoclient.object_store.put_object.assert_called_with(
|
||||
'test-overcloud', 'network_data.yaml', mock_open_context())
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_create_plan_with_password_gen_disabled(self, mock_tmp, mock_cd,
|
||||
mock_run_playbook):
|
||||
plan_management.create_plan_from_templates(
|
||||
self.app.client_manager,
|
||||
'test-overcloud',
|
||||
'/tht-root/',
|
||||
generate_passwords=False,
|
||||
disable_image_params_prepare=True)
|
||||
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-create-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "test-overcloud",
|
||||
"generate_passwords": False,
|
||||
"use_default_templates": False,
|
||||
"disable_image_params_prepare": True,
|
||||
},
|
||||
verbosity=0,
|
||||
)
|
||||
|
||||
|
||||
class TestPlanUpdateWorkflows(base.TestCommand):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPlanUpdateWorkflows, self).setUp()
|
||||
self.app.client_manager.tripleoclient = self.tripleoclient = \
|
||||
mock.Mock()
|
||||
self.tripleoclient.object_store = self.object_store = mock.Mock()
|
||||
|
||||
self.object_store.get_container.return_value = (
|
||||
{},
|
||||
[
|
||||
{'name': 'plan-environment.yaml'},
|
||||
{'name': 'user-environment.yaml'},
|
||||
{'name': 'roles_data.yaml'},
|
||||
{'name': 'network_data.yaml'},
|
||||
{'name': 'user-files/somecustomfile.yaml'},
|
||||
{'name': 'user-files/othercustomfile.yaml'},
|
||||
{'name': 'this-should-not-be-persisted.yaml'},
|
||||
]
|
||||
)
|
||||
|
||||
def get_object(*args, **kwargs):
|
||||
if args[0] != 'test-overcloud':
|
||||
raise RuntimeError('Unexpected container')
|
||||
if args[1] == 'plan-environment.yaml':
|
||||
return {}, ('passwords: somepasswords\n'
|
||||
'plan-environment.yaml: mock content\n')
|
||||
# Generic return valuebased on param,
|
||||
# e.g. 'plan-environment.yaml: mock content'
|
||||
return {}, '{0}: mock content\n'.format(args[1])
|
||||
self.object_store.get_object.side_effect = get_object
|
||||
self.mock_tar = mock.patch(
|
||||
'tripleo_common.utils.tarball.create_tarball',
|
||||
autospec=True
|
||||
)
|
||||
self.mock_tar.start()
|
||||
|
||||
def tearDown(self):
|
||||
super(TestPlanUpdateWorkflows, self).tearDown()
|
||||
self.mock_tar.stop()
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('tripleo_common.utils.swift.empty_container',
|
||||
autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_update_plan_from_templates_keep_env(
|
||||
self, mock_tmp, mock_cd, mock_empty_container,
|
||||
mock_run_playbook):
|
||||
|
||||
plan_management.update_plan_from_templates(
|
||||
self.app.client_manager,
|
||||
'test-overcloud',
|
||||
'/tht-root/',
|
||||
keep_env=True)
|
||||
|
||||
mock_empty_container.assert_called_once_with(
|
||||
self.object_store, 'test-overcloud')
|
||||
|
||||
# make sure we're pushing the saved files back to plan
|
||||
self.object_store.put_object.assert_has_calls(
|
||||
[
|
||||
mock.call('test-overcloud', 'plan-environment.yaml',
|
||||
'passwords: somepasswords\n'
|
||||
'plan-environment.yaml: mock content\n'),
|
||||
mock.call('test-overcloud', 'user-environment.yaml',
|
||||
'user-environment.yaml: mock content\n'),
|
||||
mock.call('test-overcloud', 'roles_data.yaml',
|
||||
'roles_data.yaml: mock content\n'),
|
||||
mock.call('test-overcloud', 'network_data.yaml',
|
||||
'network_data.yaml: mock content\n'),
|
||||
mock.call('test-overcloud', 'user-files/somecustomfile.yaml',
|
||||
'user-files/somecustomfile.yaml: mock content\n'),
|
||||
mock.call('test-overcloud', 'user-files/othercustomfile.yaml',
|
||||
'user-files/othercustomfile.yaml: mock content\n'),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-update-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "test-overcloud",
|
||||
"generate_passwords": True,
|
||||
"disable_image_params_prepare": False,
|
||||
},
|
||||
verbosity=1,
|
||||
)
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('tripleo_common.utils.swift.empty_container',
|
||||
autospec=True)
|
||||
def test_update_plan_from_templates_recreate_env(
|
||||
self, mock_empty_container, mock_run_playbook):
|
||||
|
||||
plan_management.update_plan_from_templates(
|
||||
self.app.client_manager,
|
||||
'test-overcloud',
|
||||
'/tht-root/')
|
||||
|
||||
mock_empty_container.assert_called_once_with(
|
||||
self.object_store, 'test-overcloud')
|
||||
|
||||
# make sure passwords got persisted
|
||||
self.object_store.put_object.assert_called_with(
|
||||
'test-overcloud', 'plan-environment.yaml',
|
||||
'passwords: somepasswords\n'
|
||||
'plan-environment.yaml: mock content\n'
|
||||
)
|
||||
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-update-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "test-overcloud",
|
||||
"generate_passwords": True,
|
||||
"disable_image_params_prepare": False,
|
||||
},
|
||||
verbosity=1,
|
||||
)
|
||||
|
||||
@mock.patch("tripleoclient.utils.run_ansible_playbook", autospec=True)
|
||||
@mock.patch('tripleoclient.workflows.plan_management._update_passwords',
|
||||
autospec=True)
|
||||
@mock.patch('yaml.safe_load',
|
||||
autospec=True)
|
||||
@mock.patch('tripleo_common.utils.swift.empty_container',
|
||||
autospec=True)
|
||||
@mock.patch('os.chdir', autospec=True)
|
||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||
def test_update_plan_from_templates_recreate_env_missing_passwords(
|
||||
self, mock_tmp, mock_cd, mock_empty_container,
|
||||
mock_yaml_safe_load, mock_update_passwords, mock_run_playbook):
|
||||
plan_management.update_plan_from_templates(
|
||||
self.app.client_manager,
|
||||
'test-overcloud',
|
||||
'/tht-root/',
|
||||
disable_image_params_prepare=True)
|
||||
# A dictionary without the "passwords" key is provided in
|
||||
# the _load_passwords method.
|
||||
mock_yaml_safe_load.return_value = {}
|
||||
# Ensure that the passwords variable is passed with a value of None.
|
||||
mock_update_passwords.assert_called_with(
|
||||
mock.ANY, 'test-overcloud', None)
|
||||
mock_run_playbook.assert_called_with(
|
||||
'cli-update-deployment-plan.yaml',
|
||||
'undercloud,',
|
||||
mock.ANY,
|
||||
constants.ANSIBLE_TRIPLEO_PLAYBOOKS,
|
||||
extra_vars={
|
||||
"container": "test-overcloud",
|
||||
"generate_passwords": True,
|
||||
"disable_image_params_prepare": True,
|
||||
},
|
||||
verbosity=1,
|
||||
)
|
||||
|
||||
|
||||
class TestUpdatePasswords(base.TestCase):
|
||||
|
||||
YAML_CONTENTS = """version: 1.0
|
||||
name: overcloud
|
||||
template: overcloud.yaml
|
||||
parameter_defaults:
|
||||
ControllerCount: 7
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestUpdatePasswords, self).setUp()
|
||||
self.swift_client = mock.MagicMock()
|
||||
self.swift_client.get_object.return_value = ({}, self.YAML_CONTENTS)
|
||||
|
||||
self.plan_name = "overcast"
|
||||
|
||||
def test_update_passwords(self):
|
||||
plan_management._update_passwords(self.swift_client,
|
||||
self.plan_name,
|
||||
{'AdminPassword': "1234"})
|
||||
|
||||
result = self.swift_client.put_object.call_args_list[0][0][2]
|
||||
|
||||
# Check new data is in
|
||||
self.assertIn("passwords:\n", result)
|
||||
self.assertIn("\n AdminPassword: '1234'", result)
|
||||
# Check previous data still is too
|
||||
self.assertIn("name: overcloud", result)
|
||||
|
||||
def test_no_passwords(self):
|
||||
plan_management._update_passwords(self.swift_client,
|
||||
self.plan_name,
|
||||
[])
|
||||
|
||||
self.swift_client.put_object.assert_not_called()
|
||||
|
||||
def test_no_plan_environment(self):
|
||||
self.swift_client.get_object.side_effect = (
|
||||
Exception("404"))
|
||||
|
||||
plan_management._update_passwords(self.swift_client,
|
||||
self.plan_name,
|
||||
{'SecretPassword': 'abcd'})
|
||||
|
||||
self.swift_client.put_object.assert_not_called()
|
@ -1,227 +0,0 @@
|
||||
# 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.path
|
||||
|
||||
from osc_lib.i18n import _
|
||||
from six.moves.urllib import request
|
||||
|
||||
from tripleoclient import command
|
||||
from tripleoclient import constants
|
||||
from tripleoclient import exceptions
|
||||
from tripleoclient import utils
|
||||
from tripleoclient.workflows import deployment
|
||||
from tripleoclient.workflows import plan_management
|
||||
|
||||
|
||||
class ListPlans(command.Lister):
|
||||
"""List overcloud deployment plans."""
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListPlans")
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)" % parsed_args)
|
||||
|
||||
clients = self.app.client_manager
|
||||
|
||||
plans = plan_management.list_deployment_plans(clients)
|
||||
|
||||
result = []
|
||||
for r in plans:
|
||||
result.append((r,))
|
||||
|
||||
return (("Plan Name",), result)
|
||||
|
||||
|
||||
class DeletePlan(command.Command):
|
||||
"""Delete an overcloud deployment plan.
|
||||
|
||||
The plan will not be deleted if a stack exists with the same name.
|
||||
"""
|
||||
|
||||
log = logging.getLogger(__name__ + ".DeletePlan")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(DeletePlan, self).get_parser(prog_name)
|
||||
parser.add_argument('plans', metavar='<name>', nargs="+",
|
||||
help=_('Name of the plan(s) to delete'))
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)" % parsed_args)
|
||||
|
||||
clients = self.app.client_manager
|
||||
|
||||
for plan in parsed_args.plans:
|
||||
print("Deleting plan %s..." % plan)
|
||||
plan_management.delete_deployment_plan(clients,
|
||||
container=plan)
|
||||
|
||||
|
||||
class CreatePlan(command.Command):
|
||||
"""Create a deployment plan"""
|
||||
|
||||
log = logging.getLogger(__name__ + ".CreatePlan")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreatePlan, self).get_parser(prog_name)
|
||||
source_group = parser.add_mutually_exclusive_group()
|
||||
parser.add_argument(
|
||||
'name',
|
||||
help=_('The name of the plan, which is used for the object '
|
||||
'storage container, workflow environment and orchestration '
|
||||
'stack names.'))
|
||||
source_group.add_argument(
|
||||
'--templates',
|
||||
help=_('The directory containing the Heat templates to deploy. '
|
||||
'If this or --source_url isn\'t provided, the templates '
|
||||
'packaged on the Undercloud will be used.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--plan-environment-file', '-p',
|
||||
help=_('Plan Environment file, overrides the default %s in the '
|
||||
'--templates directory') % constants.PLAN_ENVIRONMENT
|
||||
)
|
||||
parser.add_argument(
|
||||
'--disable-password-generation',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Disable password generation.')
|
||||
)
|
||||
source_group.add_argument(
|
||||
'--source-url',
|
||||
help=_('The url of a git repository containing the Heat templates '
|
||||
'to deploy. If this or --templates isn\'t provided, the '
|
||||
'templates packaged on the Undercloud will be used.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--disable-container-prepare',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Disable the container preparation actions to prevent '
|
||||
'container tags from being updated and new containers '
|
||||
'from being fetched. If you skip this but do not have '
|
||||
'the container parameters configured, the deployment '
|
||||
'action may fail.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)" % parsed_args)
|
||||
clients = self.app.client_manager
|
||||
|
||||
name = parsed_args.name
|
||||
use_default_templates = False
|
||||
generate_passwords = not parsed_args.disable_password_generation
|
||||
source_url = parsed_args.source_url
|
||||
# if the templates and source_url params are not used, then
|
||||
# use the default templates
|
||||
if not parsed_args.templates and not parsed_args.source_url:
|
||||
use_default_templates = True
|
||||
|
||||
# Needs this to avoid too long lines
|
||||
disable_container_prepare = parsed_args.disable_container_prepare
|
||||
|
||||
if parsed_args.templates:
|
||||
plan_management.create_plan_from_templates(
|
||||
clients, name, parsed_args.templates,
|
||||
generate_passwords=generate_passwords,
|
||||
plan_env_file=parsed_args.plan_environment_file,
|
||||
verbosity_level=utils.playbook_verbosity(self=self),
|
||||
disable_image_params_prepare=disable_container_prepare
|
||||
)
|
||||
else:
|
||||
plan_management.create_deployment_plan(
|
||||
container=name,
|
||||
generate_passwords=generate_passwords,
|
||||
source_url=source_url,
|
||||
use_default_templates=use_default_templates,
|
||||
verbosity_level=utils.playbook_verbosity(self=self),
|
||||
disable_image_params_prepare=disable_container_prepare
|
||||
)
|
||||
|
||||
|
||||
class DeployPlan(command.Command):
|
||||
"""Deploy a deployment plan"""
|
||||
|
||||
log = logging.getLogger(__name__ + ".DeployPlan")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(DeployPlan, self).get_parser(prog_name)
|
||||
parser.add_argument('name', help=_('The name of the plan to deploy.'))
|
||||
parser.add_argument('--timeout', '-t', metavar='<TIMEOUT>',
|
||||
type=int, default=240,
|
||||
help=_('Deployment timeout in minutes.'))
|
||||
parser.add_argument('--run-validations', action='store_true',
|
||||
default=False,
|
||||
help=_('Run the pre-deployment validations. These '
|
||||
'external validations are from the TripleO '
|
||||
'Validations project.'))
|
||||
return parser
|
||||
|
||||
def |