From 38f75fc6e80a7d1aa60317de1eb1f037aba64390 Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 18 Nov 2015 16:58:37 +0100 Subject: [PATCH] Implement CLI v2 for openstack configuration * Add openstack-config commands for fuel2 * Add API facade for openstack configuration Examples for fuel2: fuel2 openstack-config list --env 1 fuel2 openstack-config upload --env 1 [--node 1 | --role controller] --file config.yaml fuel2 openstack-config download 1 --file config.yaml fuel2 openstack-config execute --env 1 [--node 1 | --role controller] Change-Id: I405d9e1f2d35406fe4c163a64b5cb7e2742b461b Implements: blueprint openstack-config-change --- fuelclient/__init__.py | 1 + fuelclient/commands/openstack_config.py | 154 ++++++++++++++++++ .../unit/v2/cli/test_openstack_config.py | 85 ++++++++++ fuelclient/v1/__init__.py | 2 + fuelclient/v1/openstack_config.py | 44 +++++ setup.cfg | 4 + 6 files changed, 290 insertions(+) create mode 100644 fuelclient/commands/openstack_config.py create mode 100644 fuelclient/tests/unit/v2/cli/test_openstack_config.py create mode 100644 fuelclient/v1/openstack_config.py diff --git a/fuelclient/__init__.py b/fuelclient/__init__.py index ba6dbfdb..ffa2894b 100644 --- a/fuelclient/__init__.py +++ b/fuelclient/__init__.py @@ -52,6 +52,7 @@ def get_client(resource, version='v1'): 'fuel-version': v1.fuelversion, 'network-group': v1.network_group, 'node': v1.node, + 'openstack-config': v1.openstack_config, 'plugins': v1.plugins, 'task': v1.task, } diff --git a/fuelclient/commands/openstack_config.py b/fuelclient/commands/openstack_config.py new file mode 100644 index 00000000..22cdad7c --- /dev/null +++ b/fuelclient/commands/openstack_config.py @@ -0,0 +1,154 @@ +# 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 fuelclient.commands import base +from fuelclient.common import data_utils + + +class OpenstackConfigMixin(object): + + entity_name = 'openstack-config' + + @staticmethod + def add_env_arg(parser, required=False): + parser.add_argument( + '-e', '--env', + type=int, required=required, + help='Environment ID.') + + @staticmethod + def add_file_arg(parser): + parser.add_argument( + '-f', '--file', + type=str, help='YAML file that contains openstack configuration.') + + @staticmethod + def add_config_id_arg(parser): + parser.add_argument( + 'config', + type=int, help='Openstack configuration ID.' + ) + + @staticmethod + def add_node_id_arg(parser): + parser.add_argument( + '-n', '--node', + type=int, default=None, help='Node ID.' + ) + + @staticmethod + def add_node_role_arg(parser): + parser.add_argument( + '-r', '--role', + type=str, default=None, help='Node role.' + ) + + @staticmethod + def add_deleted_arg(parser): + parser.add_argument( + '-D', '--deleted', + type=bool, default=False, help='Show deleted configurations.' + ) + + +class OpenstackConfigList(OpenstackConfigMixin, base.BaseCommand): + """List all openstack configurations. + """ + + columns = ( + 'id', 'is_active', 'config_type', + 'cluster_id', 'node_id', 'node_role') + + def get_parser(self, prog_name): + parser = super(OpenstackConfigList, self).get_parser(prog_name) + + self.add_env_arg(parser) + self.add_node_id_arg(parser) + self.add_node_role_arg(parser) + self.add_deleted_arg(parser) + + return parser + + def take_action(self, args): + data = self.client.get_filtered( + cluster_id=args.env, node_id=args.node, + node_role=args.role, is_active=(not args.deleted)) + data = data_utils.get_display_data_multi(self.columns, data) + + return self.columns, data + + +class OpenstackConfigDownload(OpenstackConfigMixin, base.BaseCommand): + """Download specified configuration file. + """ + + def get_parser(self, prog_name): + parser = super(OpenstackConfigDownload, self).get_parser(prog_name) + + self.add_config_id_arg(parser) + self.add_file_arg(parser) + + return parser + + def take_action(self, args): + file_path = self.client.download(args.config, args.file) + + msg = ("OpenStack configuration with id={0} " + "downloaded to {1}\n").format(args.config, file_path) + self.app.stdout.write(msg) + + +class OpenstackConfigUpload(OpenstackConfigMixin, base.BaseCommand): + """Upload new opesntack configuration from file. + """ + + def get_parser(self, prog_name): + parser = super(OpenstackConfigUpload, self).get_parser(prog_name) + + self.add_env_arg(parser, required=True) + self.add_node_id_arg(parser) + self.add_node_role_arg(parser) + self.add_file_arg(parser) + + return parser + + def take_action(self, args): + config = self.client.upload( + path=args.file, cluster_id=args.env, + node_id=args.node, node_role=args.role) + + msg = "OpenStack configuration with id {0} " \ + "uploaded from file '{0}'\n".format(config.id, args.file) + self.app.stdout.write(msg) + + +class OpenstackConfigExecute(OpenstackConfigMixin, base.BaseCommand): + """Execute openstack configuration deployment. + """ + + def get_parser(self, prog_name): + parser = super(OpenstackConfigExecute, self).get_parser(prog_name) + + self.add_env_arg(parser, required=True) + self.add_node_id_arg(parser) + self.add_node_role_arg(parser) + + return parser + + def take_action(self, args): + self.client.execute( + cluster_id=args.env, node_id=args.node, node_role=args.role) + + msg = "OpenStack configuration execution started.\n" + self.app.stdout.write(msg) diff --git a/fuelclient/tests/unit/v2/cli/test_openstack_config.py b/fuelclient/tests/unit/v2/cli/test_openstack_config.py new file mode 100644 index 00000000..f2b56078 --- /dev/null +++ b/fuelclient/tests/unit/v2/cli/test_openstack_config.py @@ -0,0 +1,85 @@ +# 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 mock + +from fuelclient.tests.unit.v2.cli import test_engine + + +class TestOpenstackConfig(test_engine.BaseCLITest): + + CLUSTER_ID = 42 + NODE_ID = 64 + + def _test_config_list(self, cmd_line, expected_kwargs): + self.m_get_client.reset_mock() + self.m_client.get_filtered.reset_mock() + self.exec_command('openstack-config list {0}'.format(cmd_line)) + self.m_get_client.assert_called_once_with('openstack-config', + mock.ANY) + self.m_client.get_filtered.assert_called_once_with( + **expected_kwargs) + + def test_config_list_for_node(self): + self._test_config_list( + cmd_line='--env {0} --node {1}'.format(self.CLUSTER_ID, + self.NODE_ID), + expected_kwargs={'cluster_id': self.CLUSTER_ID, + 'node_id': self.NODE_ID, 'node_role': None, + 'is_active': True} + ) + + def test_config_list_for_role(self): + self._test_config_list( + cmd_line='--env {0} --role compute'.format(self.CLUSTER_ID), + expected_kwargs={'cluster_id': self.CLUSTER_ID, 'node_id': None, + 'node_role': 'compute', 'is_active': True} + ) + + def test_config_list_for_cluster(self): + self._test_config_list( + cmd_line='--env {0}'.format(self.CLUSTER_ID), + expected_kwargs={'cluster_id': self.CLUSTER_ID, 'node_id': None, + 'node_role': None, 'is_active': True} + ) + + def test_config_upload(self): + self.m_client.upload.return_value = 'config.yaml' + + cmd = 'openstack-config upload --env {0} --node {1} --file ' \ + 'config.yaml'.format(self.CLUSTER_ID, self.NODE_ID) + self.exec_command(cmd) + + self.m_get_client.assert_called_once_with('openstack-config', mock.ANY) + self.m_client.upload.assert_called_once_with( + path='config.yaml', cluster_id=self.CLUSTER_ID, + node_id=self.NODE_ID, node_role=None) + + def test_config_download(self): + self.m_client.download.return_value = 'config.yaml' + + cmd = 'openstack-config download 1 --file config.yaml' + self.exec_command(cmd) + + self.m_get_client.assert_called_once_with('openstack-config', mock.ANY) + self.m_client.download.assert_called_once_with(1, 'config.yaml') + + def test_config_execute(self): + cmd = 'openstack-config execute --env {0} --node {1}' \ + ''.format(self.CLUSTER_ID, self.NODE_ID) + self.exec_command(cmd) + + self.m_get_client.assert_called_once_with('openstack-config', mock.ANY) + self.m_client.execute.assert_called_once_with( + cluster_id=self.CLUSTER_ID, node_id=self.NODE_ID, node_role=None) diff --git a/fuelclient/v1/__init__.py b/fuelclient/v1/__init__.py index 93e17361..531c8aee 100644 --- a/fuelclient/v1/__init__.py +++ b/fuelclient/v1/__init__.py @@ -16,6 +16,7 @@ from fuelclient.v1 import environment from fuelclient.v1 import fuelversion from fuelclient.v1 import network_group from fuelclient.v1 import node +from fuelclient.v1 import openstack_config from fuelclient.v1 import task from fuelclient.v1 import plugins @@ -24,5 +25,6 @@ __all__ = ('environment', 'fuelversion', 'network_group', 'node', + 'openstack_config', 'plugins', 'task',) diff --git a/fuelclient/v1/openstack_config.py b/fuelclient/v1/openstack_config.py new file mode 100644 index 00000000..994e3767 --- /dev/null +++ b/fuelclient/v1/openstack_config.py @@ -0,0 +1,44 @@ +# 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 fuelclient import objects +from fuelclient.v1 import base_v1 + + +class OpenstackConfigClient(base_v1.BaseV1Client): + + _entity_wrapper = objects.OpenstackConfig + + def upload(self, path, cluster_id, node_id=None, node_role=None): + data = self._entity_wrapper.read_file(path) + return self._entity_wrapper.create( + cluster_id=cluster_id, configuration=data['configuration'], + node_id=node_id, node_role=node_role) + + def download(self, config_id, path): + config = self._entity_wrapper(config_id) + config.write_file(path, { + 'configuration': config.data['configuration']}) + + return path + + def execute(self, **kwargs): + return self._entity_wrapper.execute(**kwargs) + + def get_filtered(self, **kwargs): + return self._entity_wrapper.get_filtered_data(**kwargs) + + +def get_client(): + return OpenstackConfigClient() diff --git a/setup.cfg b/setup.cfg index 60a5d52c..191122f7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,6 +59,10 @@ fuelclient = plugins_sync=fuelclient.commands.plugins:PluginsSync task_list=fuelclient.commands.task:TaskList task_show=fuelclient.commands.task:TaskShow + openstack-config_list=fuelclient.commands.openstack_config:OpenstackConfigList + openstack-config_upload=fuelclient.commands.openstack_config:OpenstackConfigUpload + openstack-config_download=fuelclient.commands.openstack_config:OpenstackConfigDownload + openstack-config_execute=fuelclient.commands.openstack_config:OpenstackConfigExecute [global] setup-hooks =