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 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): class FakeCertificatesModelManager(FakeBaseModelManager):
def get(self, cluster_uuid): def get(self, cluster_uuid):
pass pass
@ -75,6 +93,7 @@ class MagnumFakeContainerInfra(object):
self.certificates = FakeCertificatesModelManager() self.certificates = FakeCertificatesModelManager()
self.stats = FakeStatsModelManager() self.stats = FakeStatsModelManager()
self.quotas = FakeQuotasModelManager() self.quotas = FakeQuotasModelManager()
self.nodegroups = FakeNodeGroupManager()
class MagnumFakeClientManager(osc_fakes.FakeClientManager): class MagnumFakeClientManager(osc_fakes.FakeClientManager):
@ -279,3 +298,46 @@ class FakeQuota(object):
quota = osc_fakes.FakeResource(info=copy.deepcopy(quota_info), quota = osc_fakes.FakeResource(info=copy.deepcopy(quota_info),
loaded=True) loaded=True)
return quota 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 cluster_templates
from magnumclient.v1 import clusters from magnumclient.v1 import clusters
from magnumclient.v1 import mservices from magnumclient.v1 import mservices
from magnumclient.v1 import nodegroups
from magnumclient.v1 import quotas from magnumclient.v1 import quotas
from magnumclient.v1 import stats from magnumclient.v1 import stats
@ -215,3 +216,4 @@ class Client(object):
profiler.init(profile) profiler.init(profile)
self.stats = stats.StatsManager(self.http_client) self.stats = stats.StatsManager(self.http_client)
self.quotas = quotas.QuotasManager(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_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] [compile_catalog]
directory = magnumclient/locale directory = magnumclient/locale