From 3dc2837d2aba91af11d1ef9c65851e08742a97f6 Mon Sep 17 00:00:00 2001 From: "Vladimir Sharshov (warpc)" Date: Fri, 25 Mar 2016 20:10:50 +0300 Subject: [PATCH] Support deployment history for fuel client v1 Add new commands for Fuel v1: fuel deployment-tasks --task-id 5 --statuses error --nodes 1,2 Add tests Change-Id: I5a9633058925491b939c3dfe8979dc43e4c7763c Implements: blueprint store-deployment-tasks-history --- fuelclient/cli/actions/__init__.py | 2 + fuelclient/cli/actions/deployment_tasks.py | 71 ++++++++++++++ fuelclient/cli/arguments.py | 32 +++++++ fuelclient/objects/__init__.py | 1 + fuelclient/objects/deployment_history.py | 34 +++++++ .../unit/v1/test_deployment_history_action.py | 95 +++++++++++++++++++ 6 files changed, 235 insertions(+) create mode 100644 fuelclient/cli/actions/deployment_tasks.py create mode 100644 fuelclient/objects/deployment_history.py create mode 100644 fuelclient/tests/unit/v1/test_deployment_history_action.py diff --git a/fuelclient/cli/actions/__init__.py b/fuelclient/cli/actions/__init__.py index 016b054d..12b4c04f 100644 --- a/fuelclient/cli/actions/__init__.py +++ b/fuelclient/cli/actions/__init__.py @@ -18,6 +18,7 @@ All action classes must be added to action_tuple to be used by parser """ from fuelclient.cli.actions.deploy import DeployChangesAction from fuelclient.cli.actions.deploy import RedeployChangesAction +from fuelclient.cli.actions.deployment_tasks import DeploymentTasksAction from fuelclient.cli.actions.environment import EnvironmentAction from fuelclient.cli.actions.fact import DeploymentAction from fuelclient.cli.actions.fact import ProvisioningAction @@ -48,6 +49,7 @@ from fuelclient.cli.actions.vip import VIPAction actions_tuple = ( DeployChangesAction, DeploymentAction, + DeploymentTasksAction, EnvironmentAction, FuelVersionAction, GraphAction, diff --git a/fuelclient/cli/actions/deployment_tasks.py b/fuelclient/cli/actions/deployment_tasks.py new file mode 100644 index 00000000..3c19b75a --- /dev/null +++ b/fuelclient/cli/actions/deployment_tasks.py @@ -0,0 +1,71 @@ +# Copyright 2016 Mirantis, 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. + +from fuelclient.cli.actions.base import Action +import fuelclient.cli.arguments as Args +from fuelclient.cli.arguments import group +from fuelclient.cli.formatting import format_table +from fuelclient.objects.deployment_history import DeploymentHistory + + +class DeploymentTasksAction(Action): + """Show deployment tasks + """ + action_name = "deployment-tasks" + acceptable_keys = ("deployment_graph_task_name", "node_id", "status", + "time_start", "time_end") + + def __init__(self): + super(DeploymentTasksAction, self).__init__() + self.args = [ + group( + Args.get_list_arg("List all deployment tasks"), + ), + Args.get_single_task_arg(required=True), + Args.get_deployment_node_arg( + "Node ids." + ), + Args.get_status_arg( + "Statuses: pending, error, ready, running, skipped" + ) + ] + self.flag_func_map = ( + (None, self.list), + ) + + def list(self, params): + """To display all deployment tasks: + fuel deployment-tasks --task-id 5 + + To display deployment tasks with for some nodes: + fuel deployment-tasks --task-id 5 --nodes 1,2 + + To display deployment tasks with for some statuses(pending, error, + ready, running): + fuel deployment-tasks --task-id 5 --statuses pending,running + + To display deployment tasks with for some statuses(pending, error, + ready, running) on some nodes: + fuel deployment-tasks --task-id 5 --statuses error --nodes 1,2 + """ + + d_tasks_data = DeploymentHistory.get_all_with_nodes_and_statuses( + params.task, + params.node, + params.status + ) + self.serializer.print_to_output( + d_tasks_data, + format_table(d_tasks_data, acceptable_keys=self.acceptable_keys) + ) diff --git a/fuelclient/cli/arguments.py b/fuelclient/cli/arguments.py index df957557..3d840490 100644 --- a/fuelclient/cli/arguments.py +++ b/fuelclient/cli/arguments.py @@ -25,6 +25,7 @@ substitutions = { # replace from: to "env": "environment", "nodes": "node", + "statuses": "status", "net": "network", "rel": "release", "list": "--list", @@ -179,6 +180,15 @@ def get_env_arg(required=False): ) +def get_single_task_arg(required=False): + return get_int_arg( + "task", + flags=("--task-id", "--tid"), + help="task id", + required=required + ) + + def get_new_password_arg(help_msg): return get_str_arg( "newpass", @@ -666,3 +676,25 @@ def get_upload_file_arg(help_msg): flags=("-u", "--upload",), help=help_msg ) + + +def get_status_arg(help_msg): + default_kwargs = { + "action": SetAction, + "flags": ("--status",), + "nargs": '+', + "default": None, + "help": help_msg + } + return get_arg("status", **default_kwargs) + + +def get_deployment_node_arg(help_msg): + default_kwargs = { + "action": SetAction, + "flags": ("--node-id",), + "nargs": '+', + "default": None, + "help": help_msg + } + return get_arg("node", **default_kwargs) diff --git a/fuelclient/objects/__init__.py b/fuelclient/objects/__init__.py index 41696164..2ca6032a 100644 --- a/fuelclient/objects/__init__.py +++ b/fuelclient/objects/__init__.py @@ -17,6 +17,7 @@ functionality from nailgun objects. """ from fuelclient.objects.base import BaseObject +from fuelclient.objects.deployment_history import DeploymentHistory from fuelclient.objects.environment import Environment from fuelclient.objects.node import Node from fuelclient.objects.node import NodeCollection diff --git a/fuelclient/objects/deployment_history.py b/fuelclient/objects/deployment_history.py new file mode 100644 index 00000000..ae45bbb9 --- /dev/null +++ b/fuelclient/objects/deployment_history.py @@ -0,0 +1,34 @@ +# Copyright 2016 Mirantis, 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. + +from fuelclient.objects.base import BaseObject + + +class DeploymentHistory(BaseObject): + + class_api_path = "transactions/{transaction_id}/deployment_history/"\ + "?nodes={nodes}&statuses={statuses}" + + @classmethod + def get_all_with_nodes_and_statuses(cls, transaction_id, + nodes=None, statuses=None): + statuses = ",".join(str(s) for s in statuses) if statuses else "" + nodes = ",".join(str(n) for n in nodes) if nodes else "" + history = cls.connection.get_request( + cls.class_api_path.format( + transaction_id=transaction_id, + nodes=nodes, + statuses=statuses)) + + return history diff --git a/fuelclient/tests/unit/v1/test_deployment_history_action.py b/fuelclient/tests/unit/v1/test_deployment_history_action.py new file mode 100644 index 00000000..671e8f95 --- /dev/null +++ b/fuelclient/tests/unit/v1/test_deployment_history_action.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Copyright 2016 Mirantis, 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. + +from mock import patch + +from fuelclient.cli.actions import DeploymentTasksAction +from fuelclient.cli.formatting import format_table +from fuelclient.cli.serializers import Serializer +from fuelclient.tests.unit.v1 import base + + +HISTORY_API_OUTPUT = [ + { + "status": "ready", + "time_start": "2016-03-25T17:22:10.687135", + "time_end": "2016-03-25T17:22:30.830701", + "node_id": "1", + "deployment_graph_task_name": "controller_remaining_tasks" + }, + { + "status": "skipped", + "time_start": "2016-03-25T17:23:37.313212", + "time_end": "2016-03-25T17:23:37.313234", + "node_id": "2", + "deployment_graph_task_name": "ironic-compute" + } +] + + +class TestDeploymentTasksAction(base.UnitTestCase): + + def assert_print_table(self, print_mock, tasks): + print_mock.assert_called_once_with( + tasks, format_table( + tasks, + acceptable_keys=DeploymentTasksAction.acceptable_keys)) + + @patch.object(Serializer, 'print_to_output') + def test_show_full_history(self, print_mock): + self.m_history_api = self.m_request.get( + '/api/v1/transactions/1/deployment_history/?nodes=&statuses=', + json=HISTORY_API_OUTPUT) + + self.execute( + ['fuel', 'deployment-tasks', '--tid', '1'] + ) + + self.assert_print_table(print_mock, HISTORY_API_OUTPUT) + + def test_show_history_for_special_nodes(self): + self.m_history_api = self.m_request.get( + '/api/v1/transactions/1/deployment_history/?nodes=1,2&statuses=', + json={}) + + self.execute( + ['fuel', 'deployment-tasks', '--tid', '1', + '--node-id', '1,2'] + ) + + self.assertEqual(self.m_history_api.call_count, 1) + + def test_show_history_with_special_statuses(self): + self.m_history_api = self.m_request.get( + '/api/v1/transactions/1/deployment_history/' + '?nodes=&statuses=ready,skipped', + json={}) + self.execute( + ['fuel', 'deployment-tasks', '--tid', '1', + '--status', 'ready,skipped'] + ) + self.assertEqual(self.m_history_api.call_count, 1) + + def test_show_history_with_special_statuses_for_special_nodes(self): + self.m_history_api = self.m_request.get( + '/api/v1/transactions/1/deployment_history/' + '?nodes=1,2&statuses=ready,skipped', + json={}) + self.execute( + ['fuel', 'deployment-tasks', '--tid', '1', + '--status', 'ready,skipped', '--node', '1,2'] + ) + self.assertEqual(self.m_history_api.call_count, 1)