From 7571e9877f22520516cc5cf0ec94036fd7bd72d4 Mon Sep 17 00:00:00 2001 From: dixiaoli Date: Mon, 22 Feb 2016 18:11:39 +0800 Subject: [PATCH] OSC plugin for openstack cluster list This change implements the "openstack cluster list" command Based on the existing senlin command: senlin cluster-list Change-Id: I2d6779ef55b619a02f96d11aa71aba00d40e5971 Blueprint: senlin-support-python-openstackclient --- senlinclient/osc/v1/cluster.py | 99 ++++++++++++ .../tests/unit/osc/v1/test_cluster.py | 144 ++++++++++++++++++ setup.cfg | 1 + 3 files changed, 244 insertions(+) create mode 100644 senlinclient/osc/v1/cluster.py create mode 100644 senlinclient/tests/unit/osc/v1/test_cluster.py diff --git a/senlinclient/osc/v1/cluster.py b/senlinclient/osc/v1/cluster.py new file mode 100644 index 00000000..f915d215 --- /dev/null +++ b/senlinclient/osc/v1/cluster.py @@ -0,0 +1,99 @@ +# 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. + +"""Clustering v1 cluster action implementations""" + +import logging + +from cliff import lister +from openstackclient.common import utils + +from senlinclient.common.i18n import _ +from senlinclient.common import utils as senlin_utils + + +class ListCluster(lister.Lister): + """List the user's clusters.""" + + log = logging.getLogger(__name__ + ".ListCluster") + + def get_parser(self, prog_name): + parser = super(ListCluster, self).get_parser(prog_name) + parser.add_argument( + '--filters', + metavar='', + help=_("Filter parameters to apply on returned clusters. " + "This can be specified multiple times, or once with " + "parameters separated by a semicolon. The valid filter" + " keys are: ['status', 'name']"), + action='append' + ) + parser.add_argument( + '--sort', + metavar='', + help=_("Sorting option which is a string containing a list of " + "keys separated by commas. Each key can be optionally " + "appended by a sort direction (:asc or :desc). The valid " + "sort keys are: ['name', 'status', 'init_at', " + "'created_at', 'updated_at']")) + parser.add_argument( + '--limit', + metavar='', + help=_('Limit the number of clusters returned') + ) + parser.add_argument( + '--marker', + metavar='', + help=_('Only return clusters that appear after the given cluster ' + 'ID') + ) + parser.add_argument( + '--global-project', + default=False, + action="store_true", + help=_('Indicate that the cluster list should include clusters ' + 'from all projects. This option is subject to access ' + 'policy checking. Default is False') + ) + parser.add_argument( + '--full-id', + default=False, + action="store_true", + help=_('Print full IDs in list') + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + senlin_client = self.app.client_manager.clustering + columns = ['id', 'name', 'status', 'created_at', 'updated_at'] + queries = { + 'limit': parsed_args.limit, + 'marker': parsed_args.marker, + 'sort': parsed_args.sort, + 'global_project': parsed_args.global_project, + } + if parsed_args.filters: + queries.update(senlin_utils.format_parameters(parsed_args.filters)) + + clusters = senlin_client.clusters(**queries) + formatters = {} + if not parsed_args.full_id: + formatters = { + 'id': lambda x: x[:8] + } + return ( + columns, + (utils.get_item_properties(c, columns, formatters=formatters) + for c in clusters) + ) diff --git a/senlinclient/tests/unit/osc/v1/test_cluster.py b/senlinclient/tests/unit/osc/v1/test_cluster.py new file mode 100644 index 00000000..518382f0 --- /dev/null +++ b/senlinclient/tests/unit/osc/v1/test_cluster.py @@ -0,0 +1,144 @@ +# 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 openstack.cluster.v1 import cluster as sdk_cluster +from openstack import exceptions as sdk_exc + +from senlinclient.osc.v1 import cluster as osc_cluster +from senlinclient.tests.unit.osc.v1 import fakes + + +class TestCluster(fakes.TestClusteringv1): + def setUp(self): + super(TestCluster, self).setUp() + self.mock_client = self.app.client_manager.clustering + + +class TestClusterList(TestCluster): + + columns = ['id', 'name', 'status', 'created_at', 'updated_at'] + response = {"clusters": [ + { + "created_at": "2015-02-10T14:26:14", + "data": {}, + "desired_capacity": 4, + "domain": 'null', + "id": "7d85f602-a948-4a30-afd4-e84f47471c15", + "init_time": "2015-02-10T14:26:11", + "max_size": -1, + "metadata": {}, + "min_size": 0, + "name": "cluster1", + "nodes": [ + "b07c57c8-7ab2-47bf-bdf8-e894c0c601b9", + "ecc23d3e-bb68-48f8-8260-c9cf6bcb6e61", + "da1e9c87-e584-4626-a120-022da5062dac" + ], + "policies": [], + "profile_id": "edc63d0a-2ca4-48fa-9854-27926da76a4a", + "profile_name": "mystack", + "project": "6e18cc2bdbeb48a5b3cad2dc499f6804", + "status": "ACTIVE", + "status_reason": "Cluster scale-in succeeded", + "timeout": 3600, + "updated_at": 'null', + "user": "5e5bf8027826429c96af157f68dc9072" + } + ]} + + defaults = { + 'global_project': False, + 'marker': None, + 'limit': None, + 'sort': None, + } + + def setUp(self): + super(TestClusterList, self).setUp() + self.cmd = osc_cluster.ListCluster(self.app, None) + self.mock_client.clusters = mock.Mock( + return_value=sdk_cluster.Cluster(None, {})) + + def test_cluster_list_defaults(self): + arglist = [] + parsed_args = self.check_parser(self.cmd, arglist, []) + columns, data = self.cmd.take_action(parsed_args) + self.mock_client.clusters.assert_called_with(**self.defaults) + self.assertEqual(self.columns, columns) + + def test_cluster_list_full_id(self): + arglist = ['--full-id'] + parsed_args = self.check_parser(self.cmd, arglist, []) + columns, data = self.cmd.take_action(parsed_args) + self.mock_client.clusters.assert_called_with(**self.defaults) + self.assertEqual(self.columns, columns) + + def test_cluster_list_limit(self): + kwargs = copy.deepcopy(self.defaults) + kwargs['limit'] = '3' + arglist = ['--limit', '3'] + parsed_args = self.check_parser(self.cmd, arglist, []) + columns, data = self.cmd.take_action(parsed_args) + self.mock_client.clusters.assert_called_with(**kwargs) + self.assertEqual(self.columns, columns) + + def test_cluster_list_sort(self): + kwargs = copy.deepcopy(self.defaults) + kwargs['sort'] = 'name:asc' + arglist = ['--sort', 'name:asc'] + parsed_args = self.check_parser(self.cmd, arglist, []) + columns, data = self.cmd.take_action(parsed_args) + self.mock_client.clusters.assert_called_with(**kwargs) + self.assertEqual(self.columns, columns) + + def test_cluster_list_sort_invalid_key(self): + self.mock_client.clusters = mock.Mock( + return_value=self.response) + kwargs = copy.deepcopy(self.defaults) + kwargs['sort'] = 'bad_key' + arglist = ['--sort', 'bad_key'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.mock_client.clusters.side_effect = sdk_exc.HttpException() + self.assertRaises(sdk_exc.HttpException, + self.cmd.take_action, parsed_args) + + def test_cluster_list_sort_invalid_direction(self): + self.mock_client.clusters = mock.Mock( + return_value=self.response) + kwargs = copy.deepcopy(self.defaults) + kwargs['sort'] = 'name:bad_direction' + arglist = ['--sort', 'name:bad_direction'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.mock_client.clusters.side_effect = sdk_exc.HttpException() + self.assertRaises(sdk_exc.HttpException, + self.cmd.take_action, parsed_args) + + def test_cluster_list_filter(self): + kwargs = copy.deepcopy(self.defaults) + kwargs['name'] = 'my_cluster' + arglist = ['--filter', 'name=my_cluster'] + parsed_args = self.check_parser(self.cmd, arglist, []) + columns, data = self.cmd.take_action(parsed_args) + self.mock_client.clusters.assert_called_with(**kwargs) + self.assertEqual(self.columns, columns) + + def test_cluster_list_marker(self): + kwargs = copy.deepcopy(self.defaults) + kwargs['marker'] = 'a9448bf6' + arglist = ['--marker', 'a9448bf6'] + parsed_args = self.check_parser(self.cmd, arglist, []) + columns, data = self.cmd.take_action(parsed_args) + self.mock_client.clusters.assert_called_with(**kwargs) + self.assertEqual(self.columns, columns) diff --git a/setup.cfg b/setup.cfg index 8b628e23..be0d5348 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ openstack.cli.extension = clustering = senlinclient.osc.plugin openstack.clustering.v1 = + cluster_list = senlinclient.osc.v1.cluster:ListCluster cluster_node_create = senlinclient.osc.v1.node:CreateNode cluster_node_delete = senlinclient.osc.v1.node:DeleteNode cluster_node_list = senlinclient.osc.v1.node:ListNode