Adds Labels Support
Adds labels attribute and associated tools/tests to the magnum client. Implements: blueprint extend-client-network-attributes Change-Id: Ie8465275646b82aa9bd7a9de756c519bdd6ec0e2
This commit is contained in:
parent
dca6785af8
commit
ce8924bb43
@ -78,3 +78,32 @@ def args_array_to_patch(op, attributes):
|
|||||||
else:
|
else:
|
||||||
raise exc.CommandError(_('Unknown PATCH operation: %s') % op)
|
raise exc.CommandError(_('Unknown PATCH operation: %s') % op)
|
||||||
return patch
|
return patch
|
||||||
|
|
||||||
|
|
||||||
|
def format_labels(lbls, parse_comma=True):
|
||||||
|
'''Reformat labels into dict of format expected by the API.'''
|
||||||
|
|
||||||
|
if not lbls:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if parse_comma:
|
||||||
|
# expect multiple invocations of --labels but fall back
|
||||||
|
# to , delimited if only one --labels is specified
|
||||||
|
if len(lbls) == 1:
|
||||||
|
lbls = lbls[0].split(',')
|
||||||
|
|
||||||
|
labels = {}
|
||||||
|
for l in lbls:
|
||||||
|
try:
|
||||||
|
(k, v) = l.split(('='), 1)
|
||||||
|
except ValueError:
|
||||||
|
raise exc.CommandError(_('labels must be a list of KEY=VALUE '
|
||||||
|
'not %s') % l)
|
||||||
|
if k not in labels:
|
||||||
|
labels[k] = v
|
||||||
|
else:
|
||||||
|
if not isinstance(labels[k], list):
|
||||||
|
labels[k] = [labels[k]]
|
||||||
|
labels[k].append(v)
|
||||||
|
|
||||||
|
return labels
|
||||||
|
@ -215,6 +215,7 @@ class TestCommandLineArgument(utils.TestCase):
|
|||||||
'--flavor-id test_flavor '
|
'--flavor-id test_flavor '
|
||||||
'--fixed-network public '
|
'--fixed-network public '
|
||||||
'--network-driver test_driver '
|
'--network-driver test_driver '
|
||||||
|
'--labels key=val '
|
||||||
'--master-flavor-id test_flavor '
|
'--master-flavor-id test_flavor '
|
||||||
'--docker-volume-size 10')
|
'--docker-volume-size 10')
|
||||||
self.assertTrue(mock_create.called)
|
self.assertTrue(mock_create.called)
|
||||||
@ -314,6 +315,39 @@ class TestCommandLineArgument(utils.TestCase):
|
|||||||
'--image-id test_image '
|
'--image-id test_image '
|
||||||
'--coe swarm '
|
'--coe swarm '
|
||||||
'--no-proxy no_proxy ')
|
'--no-proxy no_proxy ')
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||||
|
def test_baymodel_create_labels_success(self, mock_create):
|
||||||
|
self._test_arg_success('baymodel-create '
|
||||||
|
'--name test '
|
||||||
|
'--labels key=val '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||||
|
def test_baymodel_create_separate_labels_success(self, mock_create):
|
||||||
|
self._test_arg_success('baymodel-create '
|
||||||
|
'--name test '
|
||||||
|
'--labels key1=val1 '
|
||||||
|
'--labels key2=val2 '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||||
|
def test_baymodel_create_combined_labels_success(self, mock_create):
|
||||||
|
self._test_arg_success('baymodel-create '
|
||||||
|
'--name test '
|
||||||
|
'--labels key1=val1,key2=val2 '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm')
|
||||||
self.assertTrue(mock_create.called)
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
@mock.patch('magnumclient.v1.baymodels.BayModelManager.create')
|
||||||
|
@ -95,3 +95,80 @@ class ArgsArrayToPatchTest(test_utils.BaseTestCase):
|
|||||||
my_args['attributes'])
|
my_args['attributes'])
|
||||||
self.assertEqual([{'op': 'remove', 'path': '/foo'},
|
self.assertEqual([{'op': 'remove', 'path': '/foo'},
|
||||||
{'op': 'remove', 'path': '/extra/bar'}], patch)
|
{'op': 'remove', 'path': '/extra/bar'}], patch)
|
||||||
|
|
||||||
|
|
||||||
|
class FormatLabelsTest(test_utils.BaseTestCase):
|
||||||
|
|
||||||
|
def test_format_label_none(self):
|
||||||
|
self.assertEqual({}, utils.format_labels(None))
|
||||||
|
|
||||||
|
def test_format_labels(self):
|
||||||
|
l = utils.format_labels([
|
||||||
|
'K1=V1,K2=V2,'
|
||||||
|
'K3=V3,K4=V4,'
|
||||||
|
'K5=V5'])
|
||||||
|
self.assertEqual({'K1': 'V1',
|
||||||
|
'K2': 'V2',
|
||||||
|
'K3': 'V3',
|
||||||
|
'K4': 'V4',
|
||||||
|
'K5': 'V5'
|
||||||
|
}, l)
|
||||||
|
|
||||||
|
def test_format_labels_split(self):
|
||||||
|
l = utils.format_labels([
|
||||||
|
'K1=V1,'
|
||||||
|
'K2=V22222222222222222222222222222'
|
||||||
|
'222222222222222222222222222,'
|
||||||
|
'K3=3.3.3.3'])
|
||||||
|
self.assertEqual({'K1': 'V1',
|
||||||
|
'K2': 'V22222222222222222222222222222'
|
||||||
|
'222222222222222222222222222',
|
||||||
|
'K3': '3.3.3.3'}, l)
|
||||||
|
|
||||||
|
def test_format_labels_multiple(self):
|
||||||
|
l = utils.format_labels([
|
||||||
|
'K1=V1',
|
||||||
|
'K2=V22222222222222222222222222222'
|
||||||
|
'222222222222222222222222222',
|
||||||
|
'K3=3.3.3.3'])
|
||||||
|
self.assertEqual({'K1': 'V1',
|
||||||
|
'K2': 'V22222222222222222222222222222'
|
||||||
|
'222222222222222222222222222',
|
||||||
|
'K3': '3.3.3.3'}, l)
|
||||||
|
|
||||||
|
def test_format_labels_multiple_colon_values(self):
|
||||||
|
l = utils.format_labels([
|
||||||
|
'K1=V1',
|
||||||
|
'K2=V2,V22,V222,V2222',
|
||||||
|
'K3=3.3.3.3'])
|
||||||
|
self.assertEqual({'K1': 'V1',
|
||||||
|
'K2': 'V2,V22,V222,V2222',
|
||||||
|
'K3': '3.3.3.3'}, l)
|
||||||
|
|
||||||
|
def test_format_labels_parse_comma_false(self):
|
||||||
|
l = utils.format_labels(
|
||||||
|
['K1=V1,K2=2.2.2.2,K=V'],
|
||||||
|
parse_comma=False)
|
||||||
|
self.assertEqual({'K1': 'V1,K2=2.2.2.2,K=V'}, l)
|
||||||
|
|
||||||
|
def test_format_labels_multiple_values_per_labels(self):
|
||||||
|
l = utils.format_labels([
|
||||||
|
'K1=V1',
|
||||||
|
'K1=V2'])
|
||||||
|
self.assertIn('K1', l)
|
||||||
|
self.assertIn('V1', l['K1'])
|
||||||
|
self.assertIn('V2', l['K1'])
|
||||||
|
|
||||||
|
def test_format_label_bad_label(self):
|
||||||
|
labels = ['K1=V1,K22.2.2.2']
|
||||||
|
ex = self.assertRaises(exc.CommandError,
|
||||||
|
utils.format_labels, labels)
|
||||||
|
self.assertEqual('labels must be a list of KEY=VALUE '
|
||||||
|
'not K22.2.2.2', str(ex))
|
||||||
|
|
||||||
|
def test_format_multiple_bad_label(self):
|
||||||
|
labels = ['K1=V1', 'K22.2.2.2']
|
||||||
|
ex = self.assertRaises(exc.CommandError,
|
||||||
|
utils.format_labels, labels)
|
||||||
|
self.assertEqual('labels must be a list of KEY=VALUE '
|
||||||
|
'not K22.2.2.2', str(ex))
|
||||||
|
@ -44,6 +44,7 @@ BAYMODEL1 = {'id': 123,
|
|||||||
'http_proxy': 'http_proxy',
|
'http_proxy': 'http_proxy',
|
||||||
'https_proxy': 'https_proxy',
|
'https_proxy': 'https_proxy',
|
||||||
'no_proxy': 'no_proxy',
|
'no_proxy': 'no_proxy',
|
||||||
|
'labels': 'key1=val1,key11=val11',
|
||||||
}
|
}
|
||||||
BAYMODEL2 = {'id': 124,
|
BAYMODEL2 = {'id': 124,
|
||||||
'uuid': '66666666-7777-8888-9999-000000000002',
|
'uuid': '66666666-7777-8888-9999-000000000002',
|
||||||
@ -65,6 +66,7 @@ BAYMODEL2 = {'id': 124,
|
|||||||
'X8vjlQUnTK0HijrbSTLxp/9kazWWraBS0AyXe6'
|
'X8vjlQUnTK0HijrbSTLxp/9kazWWraBS0AyXe6'
|
||||||
'Jv0Zio4VeFrfpytB8RtAAA test1234@magnum',
|
'Jv0Zio4VeFrfpytB8RtAAA test1234@magnum',
|
||||||
'coe': 'kubernetes',
|
'coe': 'kubernetes',
|
||||||
|
'labels': 'key2=val2,key22=val22',
|
||||||
}
|
}
|
||||||
|
|
||||||
CREATE_BAYMODEL = copy.deepcopy(BAYMODEL1)
|
CREATE_BAYMODEL = copy.deepcopy(BAYMODEL1)
|
||||||
@ -153,6 +155,7 @@ class BayModelManagerTest(testtools.TestCase):
|
|||||||
self.assertEqual(BAYMODEL1['https_proxy'], baymodel.https_proxy)
|
self.assertEqual(BAYMODEL1['https_proxy'], baymodel.https_proxy)
|
||||||
self.assertEqual(BAYMODEL1['no_proxy'], baymodel.no_proxy)
|
self.assertEqual(BAYMODEL1['no_proxy'], baymodel.no_proxy)
|
||||||
self.assertEqual(BAYMODEL1['network_driver'], baymodel.network_driver)
|
self.assertEqual(BAYMODEL1['network_driver'], baymodel.network_driver)
|
||||||
|
self.assertEqual(BAYMODEL1['labels'], baymodel.labels)
|
||||||
|
|
||||||
def test_baymodel_show_by_name(self):
|
def test_baymodel_show_by_name(self):
|
||||||
baymodel = self.mgr.get(BAYMODEL1['name'])
|
baymodel = self.mgr.get(BAYMODEL1['name'])
|
||||||
@ -172,6 +175,7 @@ class BayModelManagerTest(testtools.TestCase):
|
|||||||
self.assertEqual(BAYMODEL1['https_proxy'], baymodel.https_proxy)
|
self.assertEqual(BAYMODEL1['https_proxy'], baymodel.https_proxy)
|
||||||
self.assertEqual(BAYMODEL1['no_proxy'], baymodel.no_proxy)
|
self.assertEqual(BAYMODEL1['no_proxy'], baymodel.no_proxy)
|
||||||
self.assertEqual(BAYMODEL1['network_driver'], baymodel.network_driver)
|
self.assertEqual(BAYMODEL1['network_driver'], baymodel.network_driver)
|
||||||
|
self.assertEqual(BAYMODEL1['labels'], baymodel.labels)
|
||||||
|
|
||||||
def test_baymodel_create(self):
|
def test_baymodel_create(self):
|
||||||
baymodel = self.mgr.create(**CREATE_BAYMODEL)
|
baymodel = self.mgr.create(**CREATE_BAYMODEL)
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from magnumclient.common import utils as magnum_utils
|
||||||
from magnumclient.tests import base
|
from magnumclient.tests import base
|
||||||
from magnumclient.v1 import shell
|
from magnumclient.v1 import shell
|
||||||
|
|
||||||
@ -190,6 +191,8 @@ class ShellTest(base.TestCase):
|
|||||||
args.https_proxy = 'https_proxy'
|
args.https_proxy = 'https_proxy'
|
||||||
no_proxy = 'no_proxy'
|
no_proxy = 'no_proxy'
|
||||||
args.no_proxy = no_proxy
|
args.no_proxy = no_proxy
|
||||||
|
labels = ['key1=val1']
|
||||||
|
args.labels = labels
|
||||||
|
|
||||||
shell.do_baymodel_create(client_mock, args)
|
shell.do_baymodel_create(client_mock, args)
|
||||||
client_mock.baymodels.create.assert_called_once_with(
|
client_mock.baymodels.create.assert_called_once_with(
|
||||||
@ -200,7 +203,8 @@ class ShellTest(base.TestCase):
|
|||||||
fixed_network=fixed_network, dns_nameserver=dns_nameserver,
|
fixed_network=fixed_network, dns_nameserver=dns_nameserver,
|
||||||
ssh_authorized_key=ssh_authorized_key, coe=coe,
|
ssh_authorized_key=ssh_authorized_key, coe=coe,
|
||||||
http_proxy=http_proxy, https_proxy=https_proxy,
|
http_proxy=http_proxy, https_proxy=https_proxy,
|
||||||
no_proxy=no_proxy, network_driver=network_driver)
|
no_proxy=no_proxy, network_driver=network_driver,
|
||||||
|
labels=magnum_utils.format_labels(labels))
|
||||||
|
|
||||||
def test_do_baymodel_delete(self):
|
def test_do_baymodel_delete(self):
|
||||||
client_mock = mock.MagicMock()
|
client_mock = mock.MagicMock()
|
||||||
@ -297,10 +301,10 @@ class ShellTest(base.TestCase):
|
|||||||
args.pod = pod_id
|
args.pod = pod_id
|
||||||
op = 'add'
|
op = 'add'
|
||||||
args.op = op
|
args.op = op
|
||||||
attributes = "label={'name': 'value'}"
|
attributes = "labels={'name': 'value'}"
|
||||||
args.attributes = attributes
|
args.attributes = attributes
|
||||||
shell.magnum_utils.args_array_to_patch = mock.MagicMock()
|
shell.magnum_utils.args_array_to_patch = mock.MagicMock()
|
||||||
patch = [{'path': '/label', 'value': {'name': 'value'}, 'op': 'add'}]
|
patch = [{'path': '/labels', 'value': {'name': 'value'}, 'op': 'add'}]
|
||||||
shell.magnum_utils.args_array_to_patch.return_value = patch
|
shell.magnum_utils.args_array_to_patch.return_value = patch
|
||||||
|
|
||||||
shell.do_pod_update(client_mock, args)
|
shell.do_pod_update(client_mock, args)
|
||||||
|
@ -17,7 +17,7 @@ from magnumclient import exceptions
|
|||||||
|
|
||||||
CREATION_ATTRIBUTES = ['name', 'image_id', 'flavor_id', 'master_flavor_id',
|
CREATION_ATTRIBUTES = ['name', 'image_id', 'flavor_id', 'master_flavor_id',
|
||||||
'keypair_id', 'external_network_id', 'fixed_network',
|
'keypair_id', 'external_network_id', 'fixed_network',
|
||||||
'dns_nameserver', 'docker_volume_size',
|
'dns_nameserver', 'docker_volume_size', 'labels',
|
||||||
'ssh_authorized_key', 'coe', 'http_proxy',
|
'ssh_authorized_key', 'coe', 'http_proxy',
|
||||||
'https_proxy', 'no_proxy', 'network_driver']
|
'https_proxy', 'no_proxy', 'network_driver']
|
||||||
|
|
||||||
|
@ -200,6 +200,11 @@ def do_bay_update(cs, args):
|
|||||||
@utils.arg('--no-proxy',
|
@utils.arg('--no-proxy',
|
||||||
metavar='<no-proxy>',
|
metavar='<no-proxy>',
|
||||||
help='The no_proxy address to use for nodes in bay.')
|
help='The no_proxy address to use for nodes in bay.')
|
||||||
|
@utils.arg('--labels', metavar='<KEY1=VALUE1,KEY2=VALUE2...>',
|
||||||
|
action='append', default=[],
|
||||||
|
help='Arbitrary labels in the form of key=value pairs '
|
||||||
|
'to associate with a baymodel. '
|
||||||
|
'May be used multiple times.')
|
||||||
def do_baymodel_create(cs, args):
|
def do_baymodel_create(cs, args):
|
||||||
"""Create a baymodel."""
|
"""Create a baymodel."""
|
||||||
opts = {}
|
opts = {}
|
||||||
@ -218,6 +223,7 @@ def do_baymodel_create(cs, args):
|
|||||||
opts['http_proxy'] = args.http_proxy
|
opts['http_proxy'] = args.http_proxy
|
||||||
opts['https_proxy'] = args.https_proxy
|
opts['https_proxy'] = args.https_proxy
|
||||||
opts['no_proxy'] = args.no_proxy
|
opts['no_proxy'] = args.no_proxy
|
||||||
|
opts['labels'] = magnum_utils.format_labels(args.labels)
|
||||||
|
|
||||||
baymodel = cs.baymodels.create(**opts)
|
baymodel = cs.baymodels.create(**opts)
|
||||||
_show_baymodel(baymodel)
|
_show_baymodel(baymodel)
|
||||||
|
Loading…
Reference in New Issue
Block a user