From bb5434f0cfefd41ee53518f936332ed2d74b1abe Mon Sep 17 00:00:00 2001 From: Dmitry Shulyak Date: Fri, 20 Feb 2015 14:08:51 +0200 Subject: [PATCH] Add commands to operate snapshot config Added two commands to dump snapshot config and provide it for snapshot task Will download default config and dump it to stdout fuel snapshot --conf > conf.yaml Next command will accept config as input and bypass it to snapshot generation request fuel snapshot < conf.yaml This is required to overwrite certain parameters if needed, some of those are: - target directory of snapshot generation - timeout of snapshot generation procedure - exclusion/inclusion of certain directories Depends on I5e01a7459cefe49a128192d82dd827f02866f909 Related-Bug: 1382511 Related-Bug: 1420054 DocImpact Change-Id: I1ddab26fba1346dad30955289e7d28f4d3aa1562 --- fuelclient/cli/actions/environment.py | 2 +- fuelclient/cli/actions/release.py | 4 +- fuelclient/cli/actions/snapshot.py | 29 +++++++++++++- fuelclient/cli/serializers.py | 12 +++++- fuelclient/objects/environment.py | 8 ++-- fuelclient/objects/node.py | 2 +- fuelclient/objects/task.py | 8 +++- fuelclient/tests/test_snapshot.py | 58 +++++++++++++++++++++++++++ 8 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 fuelclient/tests/test_snapshot.py diff --git a/fuelclient/cli/actions/environment.py b/fuelclient/cli/actions/environment.py index a87867d..95ffbb7 100644 --- a/fuelclient/cli/actions/environment.py +++ b/fuelclient/cli/actions/environment.py @@ -207,7 +207,7 @@ class EnvironmentAction(Action): full_path = '{0}/deployment_tasks'.format(dir_path) if params.download: tasks = cluster.get_deployment_tasks() - self.serializer.write_to_file(full_path, tasks) + self.serializer.write_to_path(full_path, tasks) print("Deployment tasks for cluster {0} " "downloaded into {1}.yaml.".format(cluster.id, full_path)) elif params.upload: diff --git a/fuelclient/cli/actions/release.py b/fuelclient/cli/actions/release.py index af94bda..d8bada2 100644 --- a/fuelclient/cli/actions/release.py +++ b/fuelclient/cli/actions/release.py @@ -95,7 +95,7 @@ class ReleaseAction(Action): full_path = '{0}/networks'.format(dir_path) if params.download: networks = release.get_networks() - self.serializer.write_to_file(full_path, networks) + self.serializer.write_to_path(full_path, networks) print("Networks for release {0} " "downloaded into {1}.yaml".format(release.id, full_path)) elif params.upload: @@ -117,7 +117,7 @@ class ReleaseAction(Action): full_path = '{0}/deployment_tasks'.format(dir_path) if params.download: tasks = release.get_deployment_tasks() - self.serializer.write_to_file(full_path, tasks) + self.serializer.write_to_path(full_path, tasks) print("Deployment tasks for release {0} " "downloaded into {1}.yaml.".format(release.id, full_path)) elif params.upload: diff --git a/fuelclient/cli/actions/snapshot.py b/fuelclient/cli/actions/snapshot.py index 8177dbb..f2e77e1 100644 --- a/fuelclient/cli/actions/snapshot.py +++ b/fuelclient/cli/actions/snapshot.py @@ -12,6 +12,10 @@ # License for the specific language governing permissions and limitations # under the License. +import sys + +import yaml + from fuelclient.cli.actions.base import Action import fuelclient.cli.arguments as Args from fuelclient.cli.formatting import download_snapshot_with_progress_bar @@ -27,8 +31,11 @@ class SnapshotAction(Action): super(SnapshotAction, self).__init__() self.args = ( Args.get_dir_arg("Directory to which download snapshot."), + Args.get_boolean_arg("conf", + help_="Provide this flag to generate conf") ) self.flag_func_map = ( + ('conf', self.get_snapshot_config), (None, self.get_snapshot), ) @@ -38,8 +45,17 @@ class SnapshotAction(Action): To download diagnostic snapshot to specific directory: fuel snapshot --dir path/to/directory + + To specify config for snapshoting + fuel snapshot < conf.yaml + """ - snapshot_task = SnapshotTask.start_snapshot_task() + if sys.stdin.isatty(): + conf = {} + else: + conf = yaml.load(sys.stdin.read()) + + snapshot_task = SnapshotTask.start_snapshot_task(conf) self.serializer.print_to_output( snapshot_task.data, "Generating dump..." @@ -49,3 +65,14 @@ class SnapshotAction(Action): snapshot_task.connection.root + snapshot_task.data["message"], directory=params.dir ) + + def get_snapshot_config(self, params): + """Download default config for snapshot + + fuel snapshot --conf > dump_conf.yaml + + To use json formatter + fuel snapshot --conf --json + """ + conf = SnapshotTask.get_default_config() + self.serializer.write_to_file(sys.stdout, conf) diff --git a/fuelclient/cli/serializers.py b/fuelclient/cli/serializers.py index ce06b75..a132511 100644 --- a/fuelclient/cli/serializers.py +++ b/fuelclient/cli/serializers.py @@ -74,10 +74,10 @@ class Serializer(object): path, self.format ) - def write_to_file(self, path, data): + def write_to_path(self, path, data): full_path = self.prepare_path(path) with open(full_path, "w+") as file_to_write: - file_to_write.write(self.serializer["w"](data)) + self.write_to_file(file_to_write, data) return full_path def read_from_file(self, path): @@ -87,6 +87,14 @@ class Serializer(object): with open(full_path, "r") as file_to_read: return self.serializer["r"](file_to_read.read()) + def write_to_file(self, file_obj, data): + """Writes to opened file or file like object + :param file_obj: opened file + :param data: any serializable object + """ + serialized = self.serializer["w"](data) + file_obj.write(serialized) + class FileFormatBasedSerializer(Serializer): diff --git a/fuelclient/objects/environment.py b/fuelclient/objects/environment.py index dd6c4fe..533de6f 100644 --- a/fuelclient/objects/environment.py +++ b/fuelclient/objects/environment.py @@ -134,14 +134,14 @@ class Environment(BaseObject): def write_network_data(self, network_data, directory=os.curdir, serializer=None): - return (serializer or self.serializer).write_to_file( + return (serializer or self.serializer).write_to_path( self.get_network_data_path(directory), network_data ) def write_settings_data(self, settings_data, directory=os.curdir, serializer=None): - return (serializer or self.serializer).write_to_file( + return (serializer or self.serializer).write_to_path( self.get_settings_data_path(directory), settings_data ) @@ -262,7 +262,7 @@ class Environment(BaseObject): os.makedirs(dir_name) if isinstance(facts, dict): engine_file_path = os.path.join(dir_name, "engine") - (serializer or self.serializer).write_to_file( + (serializer or self.serializer).write_to_path( engine_file_path, facts["engine"]) facts = facts["nodes"] name_template = u"{name}" @@ -273,7 +273,7 @@ class Environment(BaseObject): dir_name, name_template.format(**_fact) ) - (serializer or self.serializer).write_to_file(fact_path, _fact) + (serializer or self.serializer).write_to_path(fact_path, _fact) return dir_name def read_deployment_info(self, fact_type, diff --git a/fuelclient/objects/node.py b/fuelclient/objects/node.py index 9782664..3ae5caf 100644 --- a/fuelclient/objects/node.py +++ b/fuelclient/objects/node.py @@ -93,7 +93,7 @@ class Node(BaseObject): ) if os.path.exists(attribute_path): os.remove(attribute_path) - return (serializer or self.serializer).write_to_file( + return (serializer or self.serializer).write_to_path( attribute_path, attributes ) diff --git a/fuelclient/objects/task.py b/fuelclient/objects/task.py index 98fdb10..388a5d8 100644 --- a/fuelclient/objects/task.py +++ b/fuelclient/objects/task.py @@ -95,6 +95,10 @@ class DeployTask(Task): class SnapshotTask(Task): @classmethod - def start_snapshot_task(cls): - dump_task = cls.connection.put_request("logs/package", {}) + def start_snapshot_task(cls, conf): + dump_task = cls.connection.put_request("logs/package", conf) return cls(dump_task["id"]) + + @classmethod + def get_default_config(cls): + return cls.connection.get_request("logs/package/config/default/") diff --git a/fuelclient/tests/test_snapshot.py b/fuelclient/tests/test_snapshot.py new file mode 100644 index 0000000..432f530 --- /dev/null +++ b/fuelclient/tests/test_snapshot.py @@ -0,0 +1,58 @@ +# 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. + + +from mock import call +from mock import patch + +from fuelclient.tests import base + + +class TestSnapshot(base.UnitTestCase): + + @patch('fuelclient.cli.actions.snapshot.SnapshotTask.get_default_config') + @patch('sys.stdout.write') + def test_get_default_config(self, mwrite, mconf): + + mconf.return_value = {'key': 'value'} + + self.execute(['fuel', 'snapshot', '--conf']) + self.assertEqual(mwrite.call_args_list, [call('key: value\n')]) + + @patch('fuelclient.cli.actions.snapshot.SnapshotTask.start_snapshot_task') + @patch('fuelclient.cli.actions.snapshot.' + 'download_snapshot_with_progress_bar') + @patch('sys.stdin') + def test_snapshot_with_provided_conf(self, mstdin, mbar, mstart): + conf = 'key: value\n' + + mstdin.isatty.return_value = False + mstdin.read.return_value = conf + + self.execute(['fuel', 'snapshot']) + + mstart.assert_called_once_with({'key': 'value'}) + self.assertEqual(mstdin.read.call_count, 1) + + @patch('fuelclient.cli.actions.snapshot.SnapshotTask.start_snapshot_task') + @patch('fuelclient.cli.actions.snapshot.' + 'download_snapshot_with_progress_bar') + @patch('sys.stdin') + def test_snapshot_without_conf(self, mstdin, mbar, mstart): + + mstdin.isatty.return_value = True + + self.execute(['fuel', 'snapshot']) + + mstart.assert_called_once_with({})