Add nodegroup list/show commands

This adds the commands needed to list and show nodegroups in a
cluster. Specifically the commands added are:

* openstack coe nodegroup list <cluster>
* openstack coe nodegroup show <cluster> <nodegroup>

Depends-On: I5607c27eb0e84677acda29af006335374b60dd27
Change-Id: I910c7c7caff34aba2ce08ca5c9362403d11f324c
This commit is contained in:
Theodoros Tsioutsias 2019-03-26 15:48:27 +00:00
parent ba231d0046
commit ea7c571162
7 changed files with 630 additions and 0 deletions

View File

@ -0,0 +1,114 @@
# Copyright (c) 2018 European Organization for Nuclear Research.
# 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
NODEGROUP_ATTRIBUTES = [
'uuid',
'name',
'cluster_id',
'project_id',
'docker_volume_size',
'labels',
'flavor_id',
'image_id',
'node_addresses',
'node_count',
'role',
'max_node_count',
'min_node_count',
'is_default'
]
class ListNodeGroup(command.Lister):
_description = _("List nodegroups")
def get_parser(self, prog_name):
parser = super(ListNodeGroup, self).get_parser(prog_name)
parser.add_argument(
'cluster',
metavar='<cluster>',
help=_('ID or name of the cluster where the nodegroup belongs.'))
parser.add_argument(
'--limit',
metavar='<limit>',
type=int,
help=_('Maximum number of nodegroups to return'))
parser.add_argument(
'--sort-key',
metavar='<sort-key>',
help=_('Column to sort results by'))
parser.add_argument(
'--sort-dir',
metavar='<sort-dir>',
choices=['desc', 'asc'],
help=_('Direction to sort. "asc" or "desc".'))
parser.add_argument(
'--role',
metavar='<role>',
help=_('List the nodegroups in the cluster with this role'))
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', 'flavor_id', 'node_count', 'role']
cluster_id = parsed_args.cluster
nodegroups = mag_client.nodegroups.list(cluster_id,
limit=parsed_args.limit,
sort_key=parsed_args.sort_key,
sort_dir=parsed_args.sort_dir,
role=parsed_args.role)
return (
columns,
(utils.get_item_properties(n, columns) for n in nodegroups)
)
class ShowNodeGroup(command.ShowOne):
_description = _("Show a nodegroup")
def get_parser(self, prog_name):
parser = super(ShowNodeGroup, self).get_parser(prog_name)
parser.add_argument(
'cluster',
metavar='<cluster>',
help=_('ID or name of the cluster where the nodegroup belongs.'))
parser.add_argument(
'nodegroup',
metavar='<nodegroup>',
help=_('ID or name of the nodegroup to show.')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
columns = NODEGROUP_ATTRIBUTES
mag_client = self.app.client_manager.container_infra
cluster_id = parsed_args.cluster
nodegroup = mag_client.nodegroups.get(cluster_id,
parsed_args.nodegroup)
return (columns, utils.get_item_properties(nodegroup, columns))

View File

@ -62,6 +62,24 @@ class FakeQuotasModelManager(object):
pass
class FakeNodeGroupManager(object):
def list(self, cluster_id, limit=None, marker=None, sort_key=None,
sort_dir=None, detail=False):
pass
def get(self, cluster_id, id):
pass
def create(self, cluster_id, **kwargs):
pass
def delete(self, cluster_id, id):
pass
def update(self, cluster_id, id, patch):
pass
class FakeCertificatesModelManager(FakeBaseModelManager):
def get(self, cluster_uuid):
pass
@ -75,6 +93,7 @@ class MagnumFakeContainerInfra(object):
self.certificates = FakeCertificatesModelManager()
self.stats = FakeStatsModelManager()
self.quotas = FakeQuotasModelManager()
self.nodegroups = FakeNodeGroupManager()
class MagnumFakeClientManager(osc_fakes.FakeClientManager):
@ -279,3 +298,46 @@ class FakeQuota(object):
quota = osc_fakes.FakeResource(info=copy.deepcopy(quota_info),
loaded=True)
return quota
class FakeNodeGroup(object):
"""Fake one or more NodeGroup."""
@staticmethod
def create_one_nodegroup(attrs=None):
"""Create a fake NodeGroup.
: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.
nodegroup_info = {
'created_at': '2017-03-16T18:40:39+00:00',
'updated_at': '2017-03-16T18:40:45+00:00',
'uuid': '3a369884-b6ba-484f-a206-919b4b718aff',
'cluster_id': 'fake-cluster',
'docker_volume_size': None,
'node_addresses': [],
'labels': {},
'node_count': 1,
'name': 'fake-nodegroup',
'flavor_id': 'm1.medium',
'image_id': 'fedora-latest',
'project_id': None,
'role': 'worker',
'max_node_count': 10,
'min_node_count': 1,
'is_default': False
}
# Overwrite default attributes.
nodegroup_info.update(attrs)
nodegroup = osc_fakes.FakeResource(info=copy.deepcopy(nodegroup_info),
loaded=True)
return nodegroup

View File

@ -0,0 +1,158 @@
# Copyright (c) 2018 European Organization for Nuclear Research.
# 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 mock
from magnumclient.osc.v1 import nodegroups as osc_nodegroups
from magnumclient.tests.osc.unit.v1 import fakes as magnum_fakes
class TestNodeGroup(magnum_fakes.TestMagnumClientOSCV1):
def setUp(self):
super(TestNodeGroup, self).setUp()
self.ng_mock = self.app.client_manager.container_infra.nodegroups
class TestNodeGroupShow(TestNodeGroup):
def setUp(self):
super(TestNodeGroupShow, self).setUp()
self.nodegroup = magnum_fakes.FakeNodeGroup.create_one_nodegroup()
self.ng_mock.get = mock.Mock()
self.ng_mock.get.return_value = self.nodegroup
self.data = tuple(map(lambda x: getattr(self.nodegroup, x),
osc_nodegroups.NODEGROUP_ATTRIBUTES))
# Get the command object to test
self.cmd = osc_nodegroups.ShowNodeGroup(self.app, None)
def test_nodegroup_show_pass(self):
arglist = ['fake-cluster', 'fake-nodegroup']
verifylist = [
('cluster', 'fake-cluster'),
('nodegroup', 'fake-nodegroup')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.ng_mock.get.assert_called_with(
'fake-cluster', 'fake-nodegroup')
self.assertEqual(osc_nodegroups.NODEGROUP_ATTRIBUTES, columns)
self.assertEqual(self.data, data)
def test_nodegroup_show_no_nodegroup_fail(self):
arglist = ['fake-cluster']
verifylist = [
('cluster', 'fake-cluster'),
('nodegroup', '')
]
self.assertRaises(magnum_fakes.MagnumParseException,
self.check_parser, self.cmd, arglist, verifylist)
def test_nodegroup_show_no_args(self):
arglist = []
verifylist = [
('cluster', ''),
('nodegroup', '')
]
self.assertRaises(magnum_fakes.MagnumParseException,
self.check_parser, self.cmd, arglist, verifylist)
class TestNodeGroupList(TestNodeGroup):
nodegroup = magnum_fakes.FakeNodeGroup.create_one_nodegroup()
columns = ['uuid', 'name', 'flavor_id', 'node_count', 'role']
datalist = (
(
nodegroup.uuid,
nodegroup.name,
nodegroup.flavor_id,
nodegroup.node_count,
nodegroup.role,
),
)
def setUp(self):
super(TestNodeGroupList, self).setUp()
self.ng_mock.list = mock.Mock()
self.ng_mock.list.return_value = [self.nodegroup]
# Get the command object to test
self.cmd = osc_nodegroups.ListNodeGroup(self.app, None)
def test_nodegroup_list_no_options(self):
arglist = []
verifylist = [
('cluster', ''),
('limit', None),
('sort_key', None),
('sort_dir', None),
]
self.assertRaises(magnum_fakes.MagnumParseException,
self.check_parser, self.cmd, arglist, verifylist)
def test_nodegroup_list_ok(self):
arglist = ['fake-cluster']
verifylist = [
('cluster', 'fake-cluster'),
('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.ng_mock.list.assert_called_with(
'fake-cluster',
limit=None,
sort_dir=None,
sort_key=None,
role=None,
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist, tuple(data))
def test_nodegroup_list_options(self):
arglist = [
'fake-cluster',
'--limit', '1',
'--sort-key', 'key',
'--sort-dir', 'asc'
]
verifylist = [
('cluster', 'fake-cluster'),
('limit', 1),
('sort_key', 'key'),
('sort_dir', 'asc')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.ng_mock.list.assert_called_with(
'fake-cluster',
limit=1,
sort_dir='asc',
sort_key='key',
role=None
)

View File

@ -0,0 +1,224 @@
# Copyright (c) 2018 European Organization for Nuclear Research.
# 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 testtools
from testtools import matchers
from magnumclient.tests import utils
from magnumclient.v1 import nodegroups
NODEGROUP1 = {
'id': 123,
'uuid': '66666666-7777-8888-9999-000000000001',
'cluster_id': '66666666-7777-8888-9999-000000000000',
'name': 'test-worker',
'node_addresses': ['172.17.2.3'],
'node_count': 2,
'project_id': 'fake_project',
'labels': {},
'flavor_id': 'fake_flavor_1',
'image_id': 'fake_image',
'is_default': True,
'role': 'worker',
'max_node_count': 10,
'min_node_count': 1
}
NODEGROUP2 = {
'id': 124,
'uuid': '66666666-7777-8888-9999-000000000002',
'cluster_id': '66666666-7777-8888-9999-000000000000',
'name': 'test-master',
'node_addresses': ['172.17.2.4'],
'node_count': 2,
'project_id': 'fake_project',
'labels': {},
'flavor_id': 'fake_flavor_1',
'image_id': 'fake_image',
'is_default': True,
'role': 'master',
'max_node_count': 10,
'min_node_count': 1
}
fake_responses = {
'/v1/clusters/test/nodegroups/':
{
'GET': (
{},
{'nodegroups': [NODEGROUP1, NODEGROUP2]},
),
},
'/v1/clusters/test/nodegroups/%s' % NODEGROUP1['id']:
{
'GET': (
{},
NODEGROUP1
),
},
'/v1/clusters/test/nodegroups/%s' % NODEGROUP1['name']:
{
'GET': (
{},
NODEGROUP1
),
},
'/v1/clusters/test/nodegroups/?limit=2':
{
'GET': (
{},
{'nodegroups': [NODEGROUP1, NODEGROUP2]},
),
},
'/v1/clusters/test/nodegroups/?marker=%s' % NODEGROUP2['uuid']:
{
'GET': (
{},
{'nodegroups': [NODEGROUP1, NODEGROUP2]},
),
},
'/v1/clusters/test/nodegroups/?limit=2&marker=%s' % NODEGROUP2['uuid']:
{
'GET': (
{},
{'nodegroups': [NODEGROUP1, NODEGROUP2]},
),
},
'/v1/clusters/test/nodegroups/?sort_dir=asc':
{
'GET': (
{},
{'nodegroups': [NODEGROUP1, NODEGROUP2]},
),
},
'/v1/clusters/test/nodegroups/?sort_key=uuid':
{
'GET': (
{},
{'nodegroups': [NODEGROUP1, NODEGROUP2]},
),
},
'/v1/clusters/test/nodegroups/?sort_key=uuid&sort_dir=desc':
{
'GET': (
{},
{'nodegroups': [NODEGROUP2, NODEGROUP1]},
),
},
}
class NodeGroupManagerTest(testtools.TestCase):
def setUp(self):
super(NodeGroupManagerTest, self).setUp()
self.api = utils.FakeAPI(fake_responses)
self.mgr = nodegroups.NodeGroupManager(self.api)
self.cluster_id = 'test'
self.base_path = '/v1/clusters/test/nodegroups/'
def test_nodegroup_list(self):
clusters = self.mgr.list(self.cluster_id)
expect = [
('GET', self.base_path, {}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertThat(clusters, matchers.HasLength(2))
def _test_nodegroup_list_with_filters(self, cluster_id, limit=None,
marker=None, sort_key=None,
sort_dir=None, detail=False,
expect=[]):
nodegroup_filter = self.mgr.list(cluster_id,
limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir,
detail=detail)
self.assertEqual(expect, self.api.calls)
self.assertThat(nodegroup_filter, matchers.HasLength(2))
def test_nodegroup_list_with_limit(self):
expect = [
('GET', self.base_path + '?limit=2', {}, None),
]
self._test_nodegroup_list_with_filters(
self.cluster_id,
limit=2,
expect=expect)
def test_nodegroup_list_with_marker(self):
filter_ = '?marker=%s' % NODEGROUP2['uuid']
expect = [
('GET', self.base_path + filter_, {}, None),
]
self._test_nodegroup_list_with_filters(
self.cluster_id,
marker=NODEGROUP2['uuid'],
expect=expect)
def test_nodegroup_list_with_marker_limit(self):
filter_ = '?limit=2&marker=%s' % NODEGROUP2['uuid']
expect = [
('GET', self.base_path + filter_, {}, None),
]
self._test_nodegroup_list_with_filters(
self.cluster_id,
limit=2, marker=NODEGROUP2['uuid'],
expect=expect)
def test_nodegroup_list_with_sort_dir(self):
expect = [
('GET', '/v1/clusters/test/nodegroups/?sort_dir=asc', {}, None),
]
self._test_nodegroup_list_with_filters(
self.cluster_id,
sort_dir='asc',
expect=expect)
def test_nodegroup_list_with_sort_key(self):
expect = [
('GET', '/v1/clusters/test/nodegroups/?sort_key=uuid', {}, None),
]
self._test_nodegroup_list_with_filters(
self.cluster_id,
sort_key='uuid',
expect=expect)
def test_nodegroup_list_with_sort_key_dir(self):
expect = [
('GET', self.base_path + '?sort_key=uuid&sort_dir=desc', {}, None),
]
self._test_nodegroup_list_with_filters(
self.cluster_id,
sort_key='uuid', sort_dir='desc',
expect=expect)
def test_nodegroup_show_by_name(self):
nodegroup = self.mgr.get(self.cluster_id, NODEGROUP1['name'])
expect = [
('GET', self.base_path + '%s' % NODEGROUP1['name'], {}, None)
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(NODEGROUP1['name'], nodegroup.name)
def test_nodegroup_show_by_id(self):
nodegroup = self.mgr.get(self.cluster_id, NODEGROUP1['id'])
expect = [
('GET', self.base_path + '%s' % NODEGROUP1['id'], {}, None)
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(NODEGROUP1['name'], nodegroup.name)

View File

@ -25,6 +25,7 @@ from magnumclient.v1 import certificates
from magnumclient.v1 import cluster_templates
from magnumclient.v1 import clusters
from magnumclient.v1 import mservices
from magnumclient.v1 import nodegroups
from magnumclient.v1 import quotas
from magnumclient.v1 import stats
@ -215,3 +216,4 @@ class Client(object):
profiler.init(profile)
self.stats = stats.StatsManager(self.http_client)
self.quotas = quotas.QuotasManager(self.http_client)
self.nodegroups = nodegroups.NodeGroupManager(self.http_client)

View File

@ -0,0 +1,67 @@
# Copyright (c) 2018 European Organization for Nuclear Research.
# 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.common import utils
from magnumclient.v1 import baseunit
CREATION_ATTRIBUTES = ['docker_volume_size', 'labels', 'flavor_id', 'image_id',
'project_id', 'node_count', 'name', 'role',
'min_node_count', 'max_node_count']
class NodeGroup(baseunit.BaseTemplate):
template_name = "NodeGroups"
class NodeGroupManager(baseunit.BaseTemplateManager):
resource_class = NodeGroup
template_name = 'nodegroups'
api_name = 'nodegroups'
@classmethod
def _path(cls, cluster_id, id=None):
path = '/v1/clusters/%s/%s/' % (cluster_id, cls.template_name)
if id:
path += str(id)
return path
def list(self, cluster_id, limit=None, marker=None, sort_key=None,
sort_dir=None, role=None, detail=False):
if limit is not None:
limit = int(limit)
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
path = ''
if role:
filters.append('role=%s' % role)
if detail:
path += 'detail'
if filters:
path += '?' + '&'.join(filters)
if limit is None:
return self._list(self._path(cluster_id, id=path),
self.__class__.api_name)
else:
return self._list_pagination(self._path(cluster_id, id=path),
self.__class__.api_name,
limit=limit)
def get(self, cluster_id, id):
try:
return self._list(self._path(cluster_id, id=id))[0]
except IndexError:
return None

View File

@ -56,6 +56,9 @@ openstack.container_infra.v1 =
coe_service_list = magnumclient.osc.v1.mservices:ListService
coe_nodegroup_list = magnumclient.osc.v1.nodegroups:ListNodeGroup
coe_nodegroup_show = magnumclient.osc.v1.nodegroups:ShowNodeGroup
[compile_catalog]
directory = magnumclient/locale