From 019e5c7acd1690c42c3b09b69868dad46fc3b262 Mon Sep 17 00:00:00 2001 From: Dmitry Shulyak Date: Fri, 16 Jan 2015 13:49:26 +0200 Subject: [PATCH] Commands to execute only part of graph Added several flags to execute particular tasks Examples of commands below: Next command will execute only tasks specified in tasks parameter fuel node --node 2 --tasks hiera netconfig Command with skip, will requests all tasks and remove tasks in skip parameter fuel node --node 2 --skip hiera netconfig Command with end flag, will requests all tasks up to the end node in graph and execute them fuel node --node 2 --end hiera Also end can be combined with skip, and then part of graph will be fetched and part of graph removed fuel node --node 2 --end netconfig --skip hiera netconfig implements blueprint granular-deployment-based-on-tasks Change-Id: I48340bdabe8c1aabc2e47b1a2e153618742981d2 --- fuelclient/cli/actions/node.py | 51 +++++++++++++--- fuelclient/cli/arguments.py | 27 +++++++++ fuelclient/client.py | 5 +- fuelclient/objects/environment.py | 24 +++++++- fuelclient/tests/test_environment.py | 7 +++ fuelclient/tests/test_nodes_execute_tasks.py | 64 ++++++++++++++++++++ 6 files changed, 167 insertions(+), 11 deletions(-) create mode 100644 fuelclient/tests/test_nodes_execute_tasks.py diff --git a/fuelclient/cli/actions/node.py b/fuelclient/cli/actions/node.py index 84579e9..06e91b7 100644 --- a/fuelclient/cli/actions/node.py +++ b/fuelclient/cli/actions/node.py @@ -64,7 +64,12 @@ class NodeAction(Action): Args.get_node_arg("Node id."), Args.get_force_arg("Bypassing parameter validation."), Args.get_all_arg("Select all nodes."), - Args.get_role_arg("Role to assign for node.") + Args.get_role_arg("Role to assign for node."), + group( + Args.get_skip_tasks(), + Args.get_tasks() + ), + Args.get_graph_endpoint() ] self.flag_func_map = ( @@ -75,6 +80,9 @@ class NodeAction(Action): ("deploy", self.start), ("provision", self.start), ("delete-from-db", self.delete_from_db), + ("tasks", self.execute_tasks), + ("skip", self.execute_tasks), + ("end", self.execute_tasks), (None, self.list) ) @@ -205,6 +213,14 @@ class NodeAction(Action): " to:\n{1}".format(attribute_type, "\n".join(files)) print(message) + def get_env_id(self, node_collection): + env_ids = set(n.env_id for n in node_collection) + if len(env_ids) != 1: + raise ActionException( + "Inputed nodes assigned to multiple environments!") + else: + return env_ids.pop() + @check_all("env", "node") def start(self, params): """Deploy/Provision some node: @@ -213,19 +229,40 @@ class NodeAction(Action): """ node_collection = NodeCollection.init_with_ids(params.node) method_type = "deploy" if params.deploy else "provision" - env_ids = set(n.env_id for n in node_collection) - if len(env_ids) != 1: - raise ActionException( - "Inputed nodes assigned to multiple environments!") - else: - env_id_to_start = env_ids.pop() + env_id_to_start = self.get_env_id(node_collection) + task = Environment(env_id_to_start).install_selected_nodes( method_type, node_collection.collection) + self.serializer.print_to_output( task.data, "Started {0}ing {1}." .format(method_type, node_collection)) + @check_all("node") + def execute_tasks(self, params): + """Execute deployment tasks + fuel node --node 2 --tasks hiera netconfig + fuel node --node 2 --skip hiera netconfig + fuel node --node 2 --skip rsync --end pre_deployment + fuel node --node 2 --end netconfig + """ + node_collection = NodeCollection.init_with_ids(params.node) + env_id_to_start = self.get_env_id(node_collection) + + env = Environment(env_id_to_start) + + if params.tasks: + tasks = params.tasks + else: + tasks = env.get_tasks(skip=params.skip, end=params.end) + + task = env.execute_tasks(node_collection.collection, tasks=tasks) + + self.serializer.print_to_output( + task.data, + "Started tasks {0} for nodes {1}.".format(tasks, node_collection)) + def list(self, params): """To list all available nodes: fuel node diff --git a/fuelclient/cli/arguments.py b/fuelclient/cli/arguments.py index 872a32d..fc37197 100644 --- a/fuelclient/cli/arguments.py +++ b/fuelclient/cli/arguments.py @@ -292,6 +292,33 @@ def get_net_arg(help_msg): default="nova") +def get_graph_endpoint(): + return get_arg( + 'end', + flags=('--end',), + action="store", + default=None, + help="Specify endpoint for the graph of tasks.") + + +def get_skip_tasks(): + return get_arg( + 'skip', + flags=('--skip',), + nargs = '+', + default=[], + help="Get list of tasks to be skipped.") + + +def get_tasks(): + return get_arg( + 'tasks', + flags=('--tasks',), + nargs = '+', + default=[], + help="Get list of tasks to be executed.") + + def get_nst_arg(help_msg): return get_arg("nst", flags=("--net-segment-type",), diff --git a/fuelclient/client.py b/fuelclient/client.py index b32defb..7c14b5c 100644 --- a/fuelclient/client.py +++ b/fuelclient/client.py @@ -144,7 +144,7 @@ class Client(object): return resp.json() @exceptions_decorator - def get_request(self, api, ostf=False): + def get_request(self, api, ostf=False, params=None): """Make GET request to specific API """ url = (self.ostf_root if ostf else self.api_root) + api @@ -154,7 +154,8 @@ class Client(object): ) headers = {'x-auth-token': self.auth_token} - resp = requests.get(url, headers=headers) + params = params or {} + resp = requests.get(url, params=params, headers=headers) resp.raise_for_status() return resp.json() diff --git a/fuelclient/objects/environment.py b/fuelclient/objects/environment.py index 7f4e1a7..938af7b 100644 --- a/fuelclient/objects/environment.py +++ b/fuelclient/objects/environment.py @@ -370,9 +370,29 @@ class Environment(BaseObject): ) ) - def get_deployment_tasks(self): + def execute_tasks(self, nodes, tasks): + return Task.init_with_data( + self.connection.put_request( + self._get_method_url('deploy_tasks', nodes), + tasks + ) + ) + + def get_tasks(self, skip=None, end=None): + """Stores logic to filter tasks by known parameters. + + :param skip: list of tasks or None + :param end: string or None + """ + tasks = [t['id'] for t in self.get_deployment_tasks(end=end)] + if skip: + tasks_to_execute = set(tasks) - set(skip) + return list(tasks_to_execute) + return tasks + + def get_deployment_tasks(self, end=None): url = self.deployment_tasks_path.format(self.id) - return self.connection.get_request(url) + return self.connection.get_request(url, params={'end': end}) def update_deployment_tasks(self, data): url = self.deployment_tasks_path.format(self.id) diff --git a/fuelclient/tests/test_environment.py b/fuelclient/tests/test_environment.py index 865a811..d627dd5 100644 --- a/fuelclient/tests/test_environment.py +++ b/fuelclient/tests/test_environment.py @@ -54,3 +54,10 @@ class TestEnvironmentOstf(base.UnitTestCase): self.assertEqual(tests, [ {'id': 1, 'status': 'running'}, {'id': 2, 'status': 'finished'}]) + + @mock.patch('fuelclient.client.requests') + def test_get_deployment_tasks_with_end(self, mrequests): + end = 'task1' + self.env.get_deployment_tasks(end=end) + kwargs = mrequests.get.call_args[1] + self.assertEqual(kwargs['params'], {'end': end}) diff --git a/fuelclient/tests/test_nodes_execute_tasks.py b/fuelclient/tests/test_nodes_execute_tasks.py new file mode 100644 index 0000000..b07b218 --- /dev/null +++ b/fuelclient/tests/test_nodes_execute_tasks.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2015 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. + +import json + +from mock import patch + +from fuelclient.tests import base + + +@patch('fuelclient.client.requests') +class TestNodeExecuteTasksAction(base.UnitTestCase): + + def setUp(self): + super(TestNodeExecuteTasksAction, self).setUp() + self.tasks = ['netconfig', 'hiera', 'install'] + + def test_execute_provided_list_of_tasks(self, mrequests): + + self.execute(['fuel', 'node', '--node', '1,2', '--tasks'] + self.tasks) + kwargs = mrequests.put.call_args_list[0][1] + self.assertEqual(json.loads(kwargs['data']), self.tasks) + + @patch('fuelclient.objects.environment.Environment.get_deployment_tasks') + def test_skipped_tasks(self, get_tasks, mrequests): + get_tasks.return_value = [{'id': t} for t in self.tasks] + self.execute( + ['fuel', 'node', '--node', '1,2', '--skip'] + self.tasks[:2]) + + kwargs = mrequests.put.call_args_list[0][1] + self.assertEqual(json.loads(kwargs['data']), self.tasks[2:]) + + @patch('fuelclient.objects.environment.Environment.get_deployment_tasks') + def test_end_param(self, get_tasks, mrequests): + get_tasks.return_value = [{'id': t} for t in self.tasks[:2]] + self.execute( + ['fuel', 'node', '--node', '1,2', '--end', self.tasks[-2]]) + kwargs = mrequests.put.call_args_list[0][1] + self.assertEqual(json.loads(kwargs['data']), self.tasks[:2]) + get_tasks.assert_called_once_with(end=self.tasks[-2]) + + @patch('fuelclient.objects.environment.Environment.get_deployment_tasks') + def test_skip_with_end_param(self, get_tasks, mrequests): + get_tasks.return_value = [{'id': t} for t in self.tasks] + self.execute( + ['fuel', 'node', '--node', '1,2', + '--end', self.tasks[-1], '--skip'] + self.tasks[:2]) + + kwargs = mrequests.put.call_args_list[0][1] + self.assertEqual(json.loads(kwargs['data']), self.tasks[2:]) + get_tasks.assert_called_once_with(end=self.tasks[-1])