diff --git a/fuelclient/__init__.py b/fuelclient/__init__.py index 2ffc7a7..9f90572 100644 --- a/fuelclient/__init__.py +++ b/fuelclient/__init__.py @@ -48,10 +48,13 @@ def get_client(resource, version='v1'): version_map = { 'v1': { + 'cluster-settings': v1.cluster_settings, 'deployment_history': v1.deployment_history, + 'deployment-info': v1.deployment_info, 'environment': v1.environment, 'fuel-version': v1.fuelversion, 'graph': v1.graph, + 'network-configuration': v1.network_configuration, 'network-group': v1.network_group, 'node': v1.node, 'openstack-config': v1.openstack_config, diff --git a/fuelclient/commands/task.py b/fuelclient/commands/task.py index 8988403..fa5405b 100644 --- a/fuelclient/commands/task.py +++ b/fuelclient/commands/task.py @@ -12,6 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. +import os + +from fuelclient.cli.serializers import Serializer from fuelclient.commands import base from fuelclient.common import data_utils @@ -19,6 +22,97 @@ from fuelclient.common import data_utils class TaskMixIn(object): entity_name = 'task' + @staticmethod + def add_file_arg(parser): + parser.add_argument( + '-f', + '--file', + required=False, + type=str, + help='YAML file that contains network configuration.' + ) + + @classmethod + def write_info_to_file(cls, info_type, data, transaction_id, + serializer=None, file_path=None): + """Write additional info to the given path. + + :param info_type: deployment_info | cluster_settings | + network_configuration + :type info_type: str + :param data: data + :type data: list of dict + :param serializer: serializer + :param transaction_id: Transaction ID + :type transaction_id: str or int + :param file_path: path + :type file_path: str + :return: path to resulting file + :rtype: str + """ + return (serializer or Serializer()).write_to_path( + (file_path or cls.get_default_info_path(info_type, + transaction_id)), + data) + + @staticmethod + def get_default_info_path(info_type, transaction_id): + """Generate default path for task additional info e.g. deployment info + + :param info_type: deployment_info | cluster_settings | + network_configuration + :type info_type: str + :param transaction_id: Transaction ID + :type transaction_id: str or int + :return: path + :rtype: str + """ + return os.path.join( + os.path.abspath(os.curdir), + "{info_type}_{transaction_id}".format( + info_type=info_type, + transaction_id=transaction_id) + ) + + def download_info_to_file(self, transaction_id, info_type, file_path): + """Get and save to path for task additional info e.g. deployment info + + :param transaction_id: Transaction ID + :type transaction_id: str or int + :param info_type: deployment_info | cluster_settings | + network_configuration + :type info_type: str + :param file_path: path + :type file_path: str + :return: path + :rtype: str + """ + data = self.client.download(transaction_id=transaction_id) + data_file_path = TaskMixIn.write_info_to_file( + info_type, + data, + transaction_id, + file_path) + return data_file_path + + +class TaskInfoFileMixIn(TaskMixIn): + + def get_parser(self, prog_name): + parser = super(TaskInfoFileMixIn, self).get_parser( + prog_name) + parser.add_argument('id', type=int, help='Id of the Task.') + self.add_file_arg(parser) + return parser + + def download_info(self, parsed_args): + data_file_path = self.download_info_to_file( + transaction_id=parsed_args.id, + info_type=self.info_type, + file_path=parsed_args.file) + + return data_file_path + class TaskList(TaskMixIn, base.BaseListCommand): """Show list of all available tasks.""" @@ -85,3 +179,42 @@ class TaskHistoryShow(TaskMixIn, base.BaseListCommand): data = data_utils.get_display_data_multi(self.columns, data) return (self.columns, data) + + +class TaskNetworkConfigurationDownload(TaskInfoFileMixIn, base.BaseCommand): + + entity_name = 'network-configuration' + info_type = 'network_configuration' + + def take_action(self, parsed_args): + self.app.stdout.write( + "Network configuration for task with id={0}" + " downloaded to {1}\n".format(parsed_args.id, + self.download_info(parsed_args)) + ) + + +class TaskDeploymentInfoDownload(TaskInfoFileMixIn, base.BaseCommand): + + entity_name = 'deployment-info' + info_type = 'deployment_info' + + def take_action(self, parsed_args): + self.app.stdout.write( + "Deployment info for task with id={0}" + " downloaded to {1}\n".format(parsed_args.id, + self.download_info(parsed_args)) + ) + + +class TaskClusterSettingsDownload(TaskInfoFileMixIn, base.BaseCommand): + + entity_name = 'cluster-settings' + info_type = 'cluster_settings' + + def take_action(self, parsed_args): + self.app.stdout.write( + "Cluster settings for task with id={0}" + " downloaded to {1}\n".format(parsed_args.id, + self.download_info(parsed_args)) + ) diff --git a/fuelclient/objects/task.py b/fuelclient/objects/task.py index c410c48..10f2226 100644 --- a/fuelclient/objects/task.py +++ b/fuelclient/objects/task.py @@ -23,6 +23,10 @@ class Task(BaseObject): class_api_path = "transactions/" instance_api_path = "transactions/{0}/" + info_types_url_map = { + 'deployment_info': 'deployment_info', + 'cluster_settings': 'settings', + 'network_configuration': 'network_configuration'} def delete(self, force=False): return self.connection.delete_request( @@ -47,6 +51,31 @@ class Task(BaseObject): while not self.is_finished: sleep(0.5) + def deployment_info(self): + return self.connection.get_request( + self._get_additional_info_url('deployment_info')) + + def network_configuration(self): + return self.connection.get_request( + self._get_additional_info_url('network_configuration')) + + def cluster_settings(self): + return self.connection.get_request( + self._get_additional_info_url('cluster_settings')) + + def _get_additional_info_url(self, info_type): + """Generate additional info url. + + :param info_type: one of deployment_info, cluster_settings, + network_configuration + :type info_type: str + :return: url + :rtype: str + """ + + return self.instance_api_path.format(self.id) +\ + self.info_types_url_map[info_type] + class DeployTask(Task): diff --git a/fuelclient/tests/unit/v2/cli/test_task.py b/fuelclient/tests/unit/v2/cli/test_task.py index a1bbd7a..c54d3cd 100644 --- a/fuelclient/tests/unit/v2/cli/test_task.py +++ b/fuelclient/tests/unit/v2/cli/test_task.py @@ -15,6 +15,7 @@ # under the License. import mock +import yaml from fuelclient.tests.unit.v2.cli import test_engine from fuelclient.tests import utils @@ -58,3 +59,41 @@ class TestTaskCommand(test_engine.BaseCLITest): self.m_client.get_all.assert_called_once_with(transaction_id=task_id, nodes=None, statuses=None) + + def _test_cmd(self, cmd, method, cmd_line, client, + return_data, expected_kwargs): + self.m_get_client.reset_mock() + self.m_client.get_filtered.reset_mock() + self.m_client.__getattr__(method).return_value =\ + yaml.safe_load(return_data) + m_open = mock.mock_open() + with mock.patch('fuelclient.cli.serializers.open', + m_open, create=True): + self.exec_command('task {0} {1} {2}'.format(cmd, method, + cmd_line)) + + written_yaml = yaml.safe_load(m_open().write.mock_calls[0][1][0]) + expected_yaml = yaml.safe_load(return_data) + self.assertEqual(written_yaml, expected_yaml) + + self.m_get_client.assert_called_once_with(client, mock.ANY) + self.m_client.__getattr__(method).assert_called_once_with( + **expected_kwargs) + + def test_task_deployment_info_download(self): + self._test_cmd('deployment-info', 'download', '1', + 'deployment-info', + utils.get_fake_yaml_deployment_info(), + dict(transaction_id=1)) + + def test_task_cluster_settings_download(self): + self._test_cmd('settings', 'download', '1', + 'cluster-settings', + utils.get_fake_yaml_cluster_settings(), + dict(transaction_id=1)) + + def test_task_network_configuration_download(self): + self._test_cmd('network-configuration', 'download', '1', + 'network-configuration', + utils.get_fake_yaml_network_conf(), + dict(transaction_id=1)) diff --git a/fuelclient/tests/unit/v2/lib/test_task_additional_info.py b/fuelclient/tests/unit/v2/lib/test_task_additional_info.py new file mode 100644 index 0000000..758e4a8 --- /dev/null +++ b/fuelclient/tests/unit/v2/lib/test_task_additional_info.py @@ -0,0 +1,58 @@ +# -*- 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. + +import yaml + +import fuelclient +from fuelclient.tests.unit.v2.lib import test_api +from fuelclient.tests import utils + + +class TestTaskAdditionalInfoFacade(test_api.BaseLibTest): + + def setUp(self): + super(TestTaskAdditionalInfoFacade, self).setUp() + + self.version = 'v1' + self.task_id = 42 + self.res_uri = ( + '/api/{version}/transactions/{task_id}/'.format( + version=self.version, task_id=self.task_id)) + + def _test_info_download(self, client_name, yaml_data, uri): + client = fuelclient.get_client(client_name, self.version) + expected_body = yaml.load(yaml_data) + matcher = self.m_request.get("{0}{1}".format(self.res_uri, uri), + json=expected_body) + result = client.download(self.task_id) + + self.assertTrue(matcher.called) + self.assertEqual(expected_body, result) + + def test_network_configuration_download(self): + self._test_info_download('network-configuration', + utils.get_fake_yaml_network_conf(), + 'network_configuration') + + def test_cluster_settings_download(self): + self._test_info_download('cluster-settings', + utils.get_fake_yaml_cluster_settings(), + 'settings') + + def test_deployment_info_download(self): + self._test_info_download('deployment-info', + utils.get_fake_yaml_deployment_info(), + 'deployment_info') diff --git a/fuelclient/tests/utils/__init__.py b/fuelclient/tests/utils/__init__.py index 8cb462e..ca79745 100644 --- a/fuelclient/tests/utils/__init__.py +++ b/fuelclient/tests/utils/__init__.py @@ -15,6 +15,12 @@ # under the License. from fuelclient.tests.utils.random_data import random_string +from fuelclient.tests.utils.fake_additional_info \ + import get_fake_yaml_cluster_settings +from fuelclient.tests.utils.fake_additional_info \ + import get_fake_yaml_deployment_info +from fuelclient.tests.utils.fake_additional_info \ + import get_fake_yaml_network_conf from fuelclient.tests.utils.fake_deployment_history \ import get_fake_deployment_history from fuelclient.tests.utils.fake_net_conf import get_fake_interface_config @@ -31,6 +37,9 @@ from fuelclient.tests.utils.fake_openstack_config \ __all__ = (get_fake_deployment_history, + get_fake_yaml_cluster_settings, + get_fake_yaml_deployment_info, + get_fake_yaml_network_conf, get_fake_env, get_fake_fuel_version, get_fake_interface_config, diff --git a/fuelclient/tests/utils/fake_additional_info.py b/fuelclient/tests/utils/fake_additional_info.py new file mode 100644 index 0000000..f2e8d8a --- /dev/null +++ b/fuelclient/tests/utils/fake_additional_info.py @@ -0,0 +1,90 @@ +# -*- 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. + +CLUSTER_SETTINGS = '''--- + editable: + service_user: + name: + type: "hidden" + value: "fuel" + sudo: + type: "hidden" + value: "ALL=(ALL) NOPASSWD: ALL" + homedir: + type: "hidden" + value: "/var/lib/fuel" +''' + +DEPLOYMENT_INFO = '''--- +glance_glare: + user_password: yBw0bY60owLC1C0AplHpEiEX +user_node_name: Untitled (5e:89) +uid: '5' +aodh: + db_password: JnEjYacrjxU2TLdTUQE9LdKq + user_password: 8MhyQgtWjWkl0Dv1r1worTjK +mysql: + root_password: bQhzpWjWIOTHOwEA4qNI8X4K + wsrep_password: 01QSoq3bYHgA7oS0OPYQurgX +murano-cfapi: + db_password: hGrAhxUjv3kAPEjiV7uYNwgZ + user_password: 43x0pvQMXugwd8JBaRSQXX4l + enabled: false + rabbit_password: ZqTnnw7lsGQNOFJRN6pTaI8t +''' + +NETWORK_CONF = '''--- + vips: + vrouter_pub: + network_role: "public/vip" + ipaddr: "10.109.3.2" + namespace: "vrouter" + is_user_defined: false + vendor_specific: + iptables_rules: + ns_start: + - "iptables -t nat -A POSTROUTING -o <%INT%> -j MASQUERADE" +''' + + +def get_fake_yaml_cluster_settings(): + """Create a fake cluster settings + + Returns the serialized and parametrized representation of a dumped Fuel + Cluster Settings. Represents the average amount of data. + + """ + return CLUSTER_SETTINGS + + +def get_fake_yaml_deployment_info(): + """Create a fake cluster settings + + Returns the serialized and parametrized representation of a dumped Fuel + Deployment Info. Represents the average amount of data. + + """ + return DEPLOYMENT_INFO + + +def get_fake_yaml_network_conf(): + """Create a fake cluster settings + + Returns the serialized and parametrized representation of a dumped Fuel + Network Conf. Represents the average amount of data. + + """ + return NETWORK_CONF diff --git a/fuelclient/v1/__init__.py b/fuelclient/v1/__init__.py index 6918556..36de39e 100644 --- a/fuelclient/v1/__init__.py +++ b/fuelclient/v1/__init__.py @@ -12,10 +12,13 @@ # License for the specific language governing permissions and limitations # under the License. +from fuelclient.v1 import cluster_settings from fuelclient.v1 import deployment_history +from fuelclient.v1 import deployment_info from fuelclient.v1 import environment from fuelclient.v1 import fuelversion from fuelclient.v1 import graph +from fuelclient.v1 import network_configuration from fuelclient.v1 import network_group from fuelclient.v1 import node from fuelclient.v1 import openstack_config @@ -24,10 +27,13 @@ from fuelclient.v1 import task from fuelclient.v1 import vip # Please keeps the list in alphabetical order -__all__ = ('deployment_history', +__all__ = ('cluster_settings', + 'deployment_history', + 'deployment_info', 'environment', 'fuelversion', 'graph', + 'network_configuration', 'network_group', 'node', 'openstack_config', diff --git a/fuelclient/v1/cluster_settings.py b/fuelclient/v1/cluster_settings.py new file mode 100644 index 0000000..ffd7093 --- /dev/null +++ b/fuelclient/v1/cluster_settings.py @@ -0,0 +1,31 @@ +# -*- 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 fuelclient import objects +from fuelclient.v1 import base_v1 + + +class ClusterSettingsClient(base_v1.BaseV1Client): + + _entity_wrapper = objects.Task + + def download(self, transaction_id): + task = self._entity_wrapper(transaction_id) + return task.cluster_settings() + + +def get_client(): + return ClusterSettingsClient() diff --git a/fuelclient/v1/deployment_info.py b/fuelclient/v1/deployment_info.py new file mode 100644 index 0000000..6580dbd --- /dev/null +++ b/fuelclient/v1/deployment_info.py @@ -0,0 +1,31 @@ +# -*- 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 fuelclient import objects +from fuelclient.v1 import base_v1 + + +class DeploymentInfoClient(base_v1.BaseV1Client): + + _entity_wrapper = objects.Task + + def download(self, transaction_id): + task = self._entity_wrapper(transaction_id) + return task.deployment_info() + + +def get_client(): + return DeploymentInfoClient() diff --git a/fuelclient/v1/network_configuration.py b/fuelclient/v1/network_configuration.py new file mode 100644 index 0000000..337bb71 --- /dev/null +++ b/fuelclient/v1/network_configuration.py @@ -0,0 +1,31 @@ +# -*- 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 fuelclient import objects +from fuelclient.v1 import base_v1 + + +class NetworkConfigurationClient(base_v1.BaseV1Client): + + _entity_wrapper = objects.Task + + def download(self, transaction_id): + task = self._entity_wrapper(transaction_id) + return task.network_configuration() + + +def get_client(): + return NetworkConfigurationClient() diff --git a/setup.cfg b/setup.cfg index 00788cd..eb59575 100644 --- a/setup.cfg +++ b/setup.cfg @@ -66,6 +66,9 @@ fuelclient = task_list=fuelclient.commands.task:TaskList task_show=fuelclient.commands.task:TaskShow task_history_show=fuelclient.commands.task:TaskHistoryShow + task_settings_download=fuelclient.commands.task:TaskClusterSettingsDownload + task_deployment-info_download=fuelclient.commands.task:TaskDeploymentInfoDownload + task_network-configuration_download=fuelclient.commands.task:TaskNetworkConfigurationDownload openstack-config_list=fuelclient.commands.openstack_config:OpenstackConfigList openstack-config_upload=fuelclient.commands.openstack_config:OpenstackConfigUpload openstack-config_download=fuelclient.commands.openstack_config:OpenstackConfigDownload