From e12872552b11464d155630b328b7464ac7727173 Mon Sep 17 00:00:00 2001 From: Vladimir Kuklin Date: Thu, 14 Apr 2016 18:31:09 +0300 Subject: [PATCH] Add support for dry-run deployment for deploy changes commands This commit adds an additional option for fuel client to support dry-run mode of deployment which is really useful for debugging and day-to day operations and life cycle management of clusters Partial-bug: #1569839 (squash of cherry-pick of 75971870953bf01a84d62170f4924f9880f05598 and 19007f1d7848529bf70fe22c49e8db785959988e) Change-Id: I77d0a8257a97a9f40e3cfd1a630c95b7800fe1e6 --- fuelclient/cli/actions/deploy.py | 6 +- fuelclient/cli/arguments.py | 8 +++ fuelclient/commands/environment.py | 28 +++++---- fuelclient/objects/environment.py | 8 +-- fuelclient/tests/unit/v1/test_environment.py | 54 ++++++++++++++++- fuelclient/tests/unit/v2/cli/test_env.py | 58 +++++++++++++++++-- .../tests/unit/v2/lib/test_environment.py | 41 ++++++++++++- fuelclient/v1/environment.py | 8 +-- 8 files changed, 182 insertions(+), 29 deletions(-) diff --git a/fuelclient/cli/actions/deploy.py b/fuelclient/cli/actions/deploy.py index 866a86e9..b39d9f32 100644 --- a/fuelclient/cli/actions/deploy.py +++ b/fuelclient/cli/actions/deploy.py @@ -28,6 +28,7 @@ class ChangesAction(Action): super(ChangesAction, self).__init__() self.args = ( Args.get_env_arg(required=True), + Args.get_dry_run_deployment_arg(), ) self.flag_func_map = ( (None, self.deploy_changes), @@ -38,7 +39,10 @@ class ChangesAction(Action): fuel --env 1 {action_name} """ env = Environment(params.env) - deploy_task = getattr(env, self.actions_func_map[self.action_name])() + + deploy_task = getattr( + env, self.actions_func_map[self.action_name])( + dry_run=params.dry_run) self.serializer.print_to_output( deploy_task.data, deploy_task, diff --git a/fuelclient/cli/arguments.py b/fuelclient/cli/arguments.py index 337d9c9c..7c315327 100644 --- a/fuelclient/cli/arguments.py +++ b/fuelclient/cli/arguments.py @@ -258,6 +258,14 @@ def get_sync_deployment_tasks_arg(): help="Update tasks for each release.") +def get_dry_run_deployment_arg(): + return get_boolean_arg( + "dry-run", + dest='dry_run', + help="Specifies to dry-run a deployment by configuring task executor" + "to dump the deployment graph to a dot file.") + + def get_file_pattern_arg(): return get_str_arg( "filepattern", diff --git a/fuelclient/commands/environment.py b/fuelclient/commands/environment.py index 98d14c8e..531686a0 100644 --- a/fuelclient/commands/environment.py +++ b/fuelclient/commands/environment.py @@ -203,10 +203,22 @@ class EnvDeploy(EnvMixIn, base.BaseCommand): type=int, help='Id of the environment to be deployed.') + dry_run_help_string = 'Specifies to dry-run a deployment by' \ + 'configuring task executor to dump the' \ + 'deployment graph to a dot file.' \ + 'Store cluster settings and serialized ' \ + 'data in the db and ask the task executor ' \ + 'to dump the resulting graph into a dot file' + + parser.add_argument( + '-d', '--dry-run', dest="dry_run", + action='store_true', help=dry_run_help_string) + return parser def take_action(self, parsed_args): - task_id = self.client.deploy_changes(parsed_args.id) + task_id = self.client.deploy_changes(parsed_args.id, + dry_run=parsed_args.dry_run) msg = 'Deployment task with id {t} for the environment {e} '\ 'has been started.\n'.format(t=task_id, e=parsed_args.id) @@ -214,20 +226,12 @@ class EnvDeploy(EnvMixIn, base.BaseCommand): self.app.stdout.write(msg) -class EnvRedeploy(EnvMixIn, base.BaseCommand): +class EnvRedeploy(EnvDeploy): """Redeploys changes on the specified environment.""" - def get_parser(self, prog_name): - parser = super(EnvRedeploy, self).get_parser(prog_name) - - parser.add_argument('id', - type=int, - help='Id of the environment to be redeployed.') - - return parser - def take_action(self, parsed_args): - task_id = self.client.redeploy_changes(parsed_args.id) + task_id = self.client.redeploy_changes(parsed_args.id, + dry_run=parsed_args.dry_run) msg = 'Deployment task with id {t} for the environment {e} '\ 'has been started.\n'.format(t=task_id, e=parsed_args.id) diff --git a/fuelclient/objects/environment.py b/fuelclient/objects/environment.py index 4bf91b66..77f9b55f 100644 --- a/fuelclient/objects/environment.py +++ b/fuelclient/objects/environment.py @@ -93,17 +93,17 @@ class Environment(BaseObject): [{"id": n.id} for n in nodes] ) - def deploy_changes(self): + def deploy_changes(self, dry_run=False): deploy_data = self.connection.put_request( "clusters/{0}/changes".format(self.id), - {} + {}, dry_run=int(dry_run) ) return DeployTask.init_with_data(deploy_data) - def redeploy_changes(self): + def redeploy_changes(self, dry_run=False): deploy_data = self.connection.put_request( "clusters/{0}/changes/redeploy".format(self.id), - {} + {}, dry_run=int(dry_run) ) return DeployTask.init_with_data(deploy_data) diff --git a/fuelclient/tests/unit/v1/test_environment.py b/fuelclient/tests/unit/v1/test_environment.py index 6e86bf4b..6ebe98bd 100644 --- a/fuelclient/tests/unit/v1/test_environment.py +++ b/fuelclient/tests/unit/v1/test_environment.py @@ -14,7 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. - import mock import requests_mock as rm from six import moves @@ -60,6 +59,59 @@ class TestEnvironment(base.UnitTestCase): "deprecated since 7.0 release.", m_stderr.getvalue()) + @mock.patch('fuelclient.objects.task.DeployTask.init_with_data') + def test_deploy_changes(self, task_data): + dry_run = False + mdeploy = self.m_request.put('/api/v1/clusters/1/changes' + '?dry_run={0}'.format( + int(dry_run)), json={}) + + cmd = ['fuel', 'deploy-changes', '--env', '1'] + self.execute(cmd) + self.check_deploy_redeploy_changes(dry_run, mdeploy) + + @mock.patch('fuelclient.objects.task.DeployTask.init_with_data') + def test_deploy_changes_dry_run(self, task_data): + dry_run = True + mdeploy = self.m_request.put('/api/v1/clusters/1/changes' + '?dry_run={0}'.format( + int(dry_run)), json={}) + + cmd = ['fuel', 'deploy-changes', '--env', '1'] + + cmd.append('--dry-run') + self.execute(cmd) + self.check_deploy_redeploy_changes(dry_run, mdeploy) + + @mock.patch('fuelclient.objects.task.DeployTask.init_with_data') + def test_redeploy_changes(self, task_data): + dry_run = False + mdeploy = self.m_request.put('/api/v1/clusters/1/changes/redeploy' + '?dry_run={0}'.format( + int(dry_run)), json={}) + + cmd = ['fuel', 'redeploy-changes', '--env', '1'] + + self.execute(cmd) + self.check_deploy_redeploy_changes(dry_run, mdeploy) + + @mock.patch('fuelclient.objects.task.DeployTask.init_with_data') + def test_redeploy_changes_dry_run(self, task_data): + dry_run = True + mdeploy = self.m_request.put('/api/v1/clusters/1/changes/redeploy' + '?dry_run={0}'.format( + int(dry_run)), json={}) + + cmd = ['fuel', 'redeploy-changes', '--env', '1'] + + cmd.append('--dry-run') + self.execute(cmd) + self.check_deploy_redeploy_changes(dry_run, mdeploy) + + def check_deploy_redeploy_changes(self, dry_run, mdeploy): + self.assertEqual(mdeploy.last_request.qs['dry_run'][0], + str(int(dry_run))) + class TestEnvironmentOstf(base.UnitTestCase): diff --git a/fuelclient/tests/unit/v2/cli/test_env.py b/fuelclient/tests/unit/v2/cli/test_env.py index 83298e58..ec5d321c 100644 --- a/fuelclient/tests/unit/v2/cli/test_env.py +++ b/fuelclient/tests/unit/v2/cli/test_env.py @@ -89,18 +89,64 @@ class TestEnvCommand(test_engine.BaseCLITest): self.assertIn('--force', m_stdout.getvalue()) def test_env_deploy(self): - args = 'env deploy 42' + dry_run = False + args = 'env deploy' + + args += ' 42' + self.exec_command(args) - self.m_get_client.assert_called_once_with('environment', mock.ANY) - self.m_client.deploy_changes.assert_called_once_with(42) + calls = list() + calls.append(mock.call.deploy_changes(42, + dry_run=dry_run)) + + self.m_get_client.assert_called_with('environment', mock.ANY) + self.m_client.assert_has_calls(calls) + + def test_env_deploy_dry_run(self): + dry_run = True + + args = 'env deploy -d' + args += ' 42' + + self.exec_command(args) + + calls = list() + calls.append(mock.call.deploy_changes(42, + dry_run=dry_run)) + + self.m_get_client.assert_called_with('environment', mock.ANY) + self.m_client.assert_has_calls(calls) def test_env_redeploy(self): - args = 'env redeploy 42' + dry_run = False + args = 'env redeploy' + + args += ' 42' + self.exec_command(args) - self.m_get_client.assert_called_once_with('environment', mock.ANY) - self.m_client.redeploy_changes.assert_called_once_with(42) + calls = list() + calls.append(mock.call.redeploy_changes(42, + dry_run=dry_run)) + + self.m_get_client.assert_called_with('environment', mock.ANY) + self.m_client.assert_has_calls(calls) + + def test_env_redeploy_dry_run(self): + dry_run = True + args = 'env redeploy -d' + + args += ' 42' + + self.exec_command(args) + + calls = list() + calls.append(mock.call.redeploy_changes(42, + dry_run=dry_run)) + + self.m_get_client.assert_called_with('environment', mock.ANY) + self.m_client.assert_has_calls(calls) def test_env_add_nodes(self): args = 'env add nodes -e 42 -n 24 25 -r compute cinder' diff --git a/fuelclient/tests/unit/v2/lib/test_environment.py b/fuelclient/tests/unit/v2/lib/test_environment.py index a9f7ea4b..2d7b44b7 100644 --- a/fuelclient/tests/unit/v2/lib/test_environment.py +++ b/fuelclient/tests/unit/v2/lib/test_environment.py @@ -116,10 +116,49 @@ class TestEnvFacade(test_api.BaseLibTest): env_id = 42 expected_uri = self.get_object_uri(self.res_uri, env_id, '/changes') matcher = self.m_request.put(expected_uri, json={}) + dry_run = False - self.client.deploy_changes(env_id) + self.client.deploy_changes(env_id, dry_run=dry_run) + self.check_deploy_redeploy_changes(dry_run, matcher) + def check_deploy_redeploy_changes(self, dry_run, matcher): self.assertTrue(matcher.called) + self.assertEqual(matcher.last_request.qs['dry_run'][0], + str(int(dry_run))) + + @mock.patch.object(task_object.DeployTask, 'init_with_data') + def test_env_deploy_dry_run(self, m_init): + env_id = 42 + expected_uri = self.get_object_uri(self.res_uri, env_id, '/changes') + matcher = self.m_request.put(expected_uri, json={}) + + dry_run = True + + self.client.deploy_changes(env_id, dry_run=dry_run) + self.check_deploy_redeploy_changes(dry_run, matcher) + + @mock.patch.object(task_object.DeployTask, 'init_with_data') + def test_env_redeploy(self, m_init): + env_id = 42 + expected_uri = self.get_object_uri(self.res_uri, env_id, + '/changes/redeploy') + matcher = self.m_request.put(expected_uri, json={}) + dry_run = False + + self.client.redeploy_changes(env_id, dry_run=dry_run) + self.check_deploy_redeploy_changes(dry_run, matcher) + + @mock.patch.object(task_object.DeployTask, 'init_with_data') + def test_env_redeploy_dry_run(self, m_init): + env_id = 42 + expected_uri = self.get_object_uri(self.res_uri, env_id, + '/changes/redeploy') + matcher = self.m_request.put(expected_uri, json={}) + + dry_run = True + + self.client.redeploy_changes(env_id, dry_run=dry_run) + self.check_deploy_redeploy_changes(dry_run, matcher) @mock.patch.object(base_object.BaseObject, 'init_with_data') def test_env_update(self, m_init): diff --git a/fuelclient/v1/environment.py b/fuelclient/v1/environment.py index 5f7ebea5..fbf800c6 100644 --- a/fuelclient/v1/environment.py +++ b/fuelclient/v1/environment.py @@ -65,16 +65,16 @@ class EnvironmentClient(base_v1.BaseV1Client): env.assign(nodes, roles) - def deploy_changes(self, environment_id): + def deploy_changes(self, environment_id, dry_run=False): env = self._entity_wrapper(obj_id=environment_id) - deploy_task = env.deploy_changes() + deploy_task = env.deploy_changes(dry_run=dry_run) return deploy_task.id - def redeploy_changes(self, environment_id): + def redeploy_changes(self, environment_id, dry_run=False): env = self._entity_wrapper(obj_id=environment_id) - redeploy_task = env.redeploy_changes() + redeploy_task = env.redeploy_changes(dry_run=dry_run) return redeploy_task.id def spawn_vms(self, environment_id):