From 98c4ed730d6f1cbb1d84c533cce950e7c7ebbe91 Mon Sep 17 00:00:00 2001 From: Stephen Watson Date: Wed, 15 Mar 2017 14:25:28 -0700 Subject: [PATCH] OSC 2/4 Add Cluster Create and List Add cluster create and cluster list for magnum's OSC plugin. Add cluster create and list unit tests Implements: blueprint openstackclient-support Co-Authored-By: Spyros Trigazis Change-Id: I815633e45df681e6bf089ae82d7451c2c0df05ef --- magnumclient/osc/v1/clusters.py | 127 ++++++++++++ magnumclient/tests/osc/unit/v1/fakes.py | 46 +++++ .../tests/osc/unit/v1/test_clusters.py | 194 ++++++++++++++++++ setup.cfg | 3 + 4 files changed, 370 insertions(+) create mode 100644 magnumclient/osc/v1/clusters.py create mode 100644 magnumclient/tests/osc/unit/v1/test_clusters.py diff --git a/magnumclient/osc/v1/clusters.py b/magnumclient/osc/v1/clusters.py new file mode 100644 index 00000000..d60c608e --- /dev/null +++ b/magnumclient/osc/v1/clusters.py @@ -0,0 +1,127 @@ +# Copyright 2016 EasyStack. All rights reserved. +# +# 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 magnumclient.i18n import _ + +from osc_lib.command import command +from osc_lib import utils + + +class CreateCluster(command.Command): + _description = _("Create a cluster") + + def get_parser(self, prog_name): + parser = super(CreateCluster, self).get_parser(prog_name) + # NOTE: All arguments are positional and, if not provided + # with a default, required. + parser.add_argument('--cluster-template', + dest='cluster_template', + required=True, + metavar='', + help='ID or name of the cluster template.') + parser.add_argument('--discovery-url', + dest='discovery_url', + metavar='', + help=('Specifies custom delivery url for ' + 'node discovery.')) + parser.add_argument('--docker-volume-size', + dest='docker_volume_size', + type=int, + metavar='', + help=('The size in GB for the docker volume to ' + 'use.')) + parser.add_argument('--keypair', + default=None, + metavar='', + help='UUID or name of the keypair to use.') + parser.add_argument('--master-count', + dest='master_count', + type=int, + default=1, + metavar='', + help='The number of master nodes for the cluster.') + parser.add_argument('--name', + metavar='', + help='Name of the cluster to create.') + parser.add_argument('--node-count', + dest='node_count', + type=int, + default=1, + metavar='', + help='The cluster node count.') + parser.add_argument('--timeout', + type=int, + default=60, + metavar='', + help=('The timeout for cluster creation time. The ' + 'default is 60 minutes.')) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + mag_client = self.app.client_manager.container_infra + args = { + 'cluster_template_id': parsed_args.cluster_template, + 'create_timeout': parsed_args.timeout, + 'discovery_url': parsed_args.discovery_url, + 'docker_volume_size': parsed_args.docker_volume_size, + 'keypair': parsed_args.keypair, + 'master_count': parsed_args.master_count, + 'name': parsed_args.name, + 'node_count': parsed_args.node_count, + } + cluster = mag_client.clusters.create(**args) + print("Request to create cluster %s accepted" + % cluster.uuid) + + +class ListCluster(command.Lister): + _description = _("List clusters") + + def get_parser(self, prog_name): + parser = super(ListCluster, self).get_parser(prog_name) + + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_('Maximum number of clusters to return')) + parser.add_argument( + '--sort-key', + metavar='', + help=_('Column to sort results by')) + parser.add_argument( + '--sort-dir', + metavar='', + choices=['desc', 'asc'], + help=_('Direction to sort. "asc" or "desc".')) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + mag_client = self.app.client_manager.container_infra + columns = [ + 'uuid', 'name', 'keypair', 'node_count', 'master_count', 'status'] + clusters = mag_client.clusters.list(limit=parsed_args.limit, + sort_key=parsed_args.sort_key, + sort_dir=parsed_args.sort_dir) + return ( + columns, + (utils.get_item_properties(c, columns) for c in clusters) + ) diff --git a/magnumclient/tests/osc/unit/v1/fakes.py b/magnumclient/tests/osc/unit/v1/fakes.py index 4f2fa882..7ef977b7 100644 --- a/magnumclient/tests/osc/unit/v1/fakes.py +++ b/magnumclient/tests/osc/unit/v1/fakes.py @@ -46,6 +46,7 @@ class FakeBaseModelManager(object): class MagnumFakeContainerInfra(object): def __init__(self): self.cluster_templates = FakeBaseModelManager() + self.clusters = FakeBaseModelManager() class MagnumFakeClientManager(osc_fakes.FakeClientManager): @@ -161,3 +162,48 @@ class FakeClusterTemplate(object): cts.append(FakeClusterTemplate.create_one_cluster_template(attrs)) return cts + + +class FakeCluster(object): + """Fake one or more Cluster.""" + + @staticmethod + def create_one_cluster(attrs=None): + """Create a fake Cluster. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with flavor_id, image_id, and so on + """ + + attrs = attrs or {} + + # set default attributes. + cluster_info = { + 'status': 'CREATE_IN_PROGRESS', + 'cluster_template_id': 'fake-ct', + 'node_addresses': [], + 'uuid': '3a369884-b6ba-484f-a206-919b4b718aff', + 'stack_id': 'c4554582-77bd-4734-8f1a-72c3c40e5fb4', + 'status_reason': None, + 'created_at': '2017-03-16T18:40:39+00:00', + 'updated_at': '2017-03-16T18:40:45+00:00', + 'coe_version': None, + 'keypair': 'fakekey', + 'api_address': None, + 'master_addresses': [], + 'create_timeout': 60, + 'node_count': 1, + 'discovery_url': 'https://fake.cluster', + 'master_count': 1, + 'container_version': None, + 'name': 'fake-cluster' + } + + # Overwrite default attributes. + cluster_info.update(attrs) + + cluster = osc_fakes.FakeResource(info=copy.deepcopy(cluster_info), + loaded=True) + return cluster diff --git a/magnumclient/tests/osc/unit/v1/test_clusters.py b/magnumclient/tests/osc/unit/v1/test_clusters.py new file mode 100644 index 00000000..f7d8d64b --- /dev/null +++ b/magnumclient/tests/osc/unit/v1/test_clusters.py @@ -0,0 +1,194 @@ +# Copyright 2016 Easystack. All rights reserved. +# +# 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 copy +import mock + +from magnumclient.osc.v1 import clusters as osc_clusters +from magnumclient.tests.osc.unit.v1 import fakes as magnum_fakes + + +class TestCluster(magnum_fakes.TestMagnumClientOSCV1): + + def setUp(self): + super(TestCluster, self).setUp() + + self.clusters_mock = self.app.client_manager.container_infra.clusters + + +class TestClusterCreate(TestCluster): + + def setUp(self): + super(TestClusterCreate, self).setUp() + + attr = dict() + attr['name'] = 'fake-cluster-1' + self._cluster = magnum_fakes.FakeCluster.create_one_cluster(attr) + + self._default_args = { + 'cluster_template_id': 'fake-ct', + 'create_timeout': 60, + 'discovery_url': None, + 'docker_volume_size': None, + 'keypair': None, + 'master_count': 1, + 'name': None, + 'node_count': 1 + } + + self.clusters_mock.create = mock.Mock() + self.clusters_mock.create.return_value = self._cluster + + self.clusters_mock.get = mock.Mock() + self.clusters_mock.get.return_value = copy.deepcopy(self._cluster) + + self.clusters_mock.update = mock.Mock() + self.clusters_mock.update.return_value = self._cluster + + # Get the command object to test + self.cmd = osc_clusters.CreateCluster(self.app, None) + + self.data = ( + self._cluster.status, + self._cluster.cluster_template_id, + self._cluster.node_addresses, + self._cluster.uuid, + self._cluster.stack_id, + self._cluster.status_reason, + self._cluster.created_at, + self._cluster.updated_at, + self._cluster.coe_version, + self._cluster.keypair, + self._cluster.api_address, + self._cluster.master_addresses, + self._cluster.create_timeout, + self._cluster.node_count, + self._cluster.discovery_url, + self._cluster.master_count, + self._cluster.container_version, + self._cluster.name + ) + + def test_cluster_create_required_args_pass(self): + """Verifies required arguments.""" + + arglist = [ + '--cluster-template', self._cluster.cluster_template_id + ] + verifylist = [ + ('cluster_template', self._cluster.cluster_template_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.clusters_mock.create.assert_called_with(**self._default_args) + + def test_cluster_create_missing_required_arg(self): + """Verifies missing required arguments.""" + + arglist = [ + '--name', self._cluster.name + ] + verifylist = [ + ('name', self._cluster.name) + ] + self.assertRaises(magnum_fakes.MagnumParseException, + self.check_parser, self.cmd, arglist, verifylist) + + +class TestClusterList(TestCluster): + attr = dict() + attr['name'] = 'fake-cluster-1' + _cluster = magnum_fakes.FakeCluster.create_one_cluster(attr) + + columns = [ + 'uuid', + 'name', + 'keypair', + 'node_count', + 'master_count', + 'status' + ] + + datalist = ( + ( + _cluster.uuid, + _cluster.name, + _cluster.keypair, + _cluster.node_count, + _cluster.master_count, + _cluster.status + ), + ) + + def setUp(self): + super(TestClusterList, self).setUp() + + self.clusters_mock.list = mock.Mock() + self.clusters_mock.list.return_value = [self._cluster] + + # Get the command object to test + self.cmd = osc_clusters.ListCluster(self.app, None) + + def test_cluster_list_no_options(self): + arglist = [] + verifylist = [ + ('limit', None), + ('sort_key', None), + ('sort_dir', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.clusters_mock.list.assert_called_with( + limit=None, + sort_dir=None, + sort_key=None, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_cluster_list_options(self): + arglist = [ + '--limit', '1', + '--sort-key', 'key', + '--sort-dir', 'asc' + ] + verifylist = [ + ('limit', 1), + ('sort_key', 'key'), + ('sort_dir', 'asc') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.clusters_mock.list.assert_called_with( + limit=1, + sort_dir='asc', + sort_key='key', + ) + + def test_cluster_list_bad_sort_dir_fail(self): + arglist = [ + '--sort-dir', 'foo' + ] + verifylist = [ + ('limit', None), + ('sort_key', None), + ('sort_dir', 'foo'), + ('fields', None), + ] + + self.assertRaises(magnum_fakes.MagnumParseException, + self.check_parser, self.cmd, arglist, verifylist) diff --git a/setup.cfg b/setup.cfg index 8bff36e5..52089181 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,9 @@ openstack.container_infra.v1 = coe_cluster_template_create = magnumclient.osc.v1.cluster_templates:CreateClusterTemplate coe_cluster_template_list = magnumclient.osc.v1.cluster_templates:ListTemplateCluster + coe_cluster_create = magnumclient.osc.v1.clusters:CreateCluster + coe_cluster_list = magnumclient.osc.v1.clusters:ListCluster + [build_sphinx] source-dir = doc/source build-dir = doc/build