Add overwrite option to host-label-assign

Add an overwrite option to host-label-assign. The behavior will be
consistent with kubectl label --overwrite.

If a label key exists for the host and no overwrite option is given the
operation will be rejected. With the overwrite option provided any
existing keys will be updated to the new value.

Change-Id: If32dfe191f002f991d8357c05ca4ed0ca4308fb6
Closes-Bug: 1850168
Signed-off-by: David Sullivan <david.sullivan@windriver.com>
This commit is contained in:
David Sullivan 2019-11-12 18:57:53 -05:00
parent 455fc28f6d
commit b2e4d43f45
4 changed files with 121 additions and 32 deletions

View File

@ -8,6 +8,7 @@
#
from cgtsclient.common import base
from cgtsclient.v1 import options
class KubernetesLabel(base.Resource):
@ -34,8 +35,8 @@ class KubernetesLabelManager(base.Manager):
except IndexError:
return None
def assign(self, host_uuid, label):
return self._create(self._path(host_uuid), label)
def assign(self, host_uuid, label, parameters=None):
return self._create(options.build_url(self._path(host_uuid), q=None, params=parameters), label)
def remove(self, uuid):
return self._delete(self._path(uuid))

View File

@ -43,11 +43,15 @@ def do_host_label_list(cc, args):
action='append',
default=[],
help="List of Kubernetes labels")
@utils.arg('--overwrite',
action='store_true',
help="Allow labels to be overwritten")
def do_host_label_assign(cc, args):
"""Update the Kubernetes labels on a host."""
attributes = utils.extract_keypairs(args)
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
new_labels = cc.label.assign(ihost.uuid, attributes)
parameters = ["overwrite=" + str(args.overwrite)]
new_labels = cc.label.assign(ihost.uuid, attributes, parameters)
for p in new_labels.labels:
uuid = p['uuid']
if uuid is not None:

View File

@ -189,9 +189,9 @@ class LabelController(rest.RestController):
return Label.convert_with_links(sp_label)
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(LabelCollection, types.uuid,
@wsme_pecan.wsexpose(LabelCollection, types.uuid, types.boolean,
body=types.apidict)
def post(self, uuid, body):
def post(self, uuid, overwrite=False, body=None):
"""Assign label(s) to a host.
"""
if self._from_ihosts:
@ -204,6 +204,21 @@ class LabelController(rest.RestController):
_semantic_check_worker_labels(body)
existing_labels = {}
for label_key in body.keys():
label = None
try:
label = pecan.request.dbapi.label_query(host.id, label_key)
except exception.HostLabelNotFoundByKey:
pass
if label:
if overwrite:
existing_labels.update({label_key: label.uuid})
else:
raise wsme.exc.ClientSideError(
_("Label %s exists for host %s. Use overwrite option to assign a new value." % (
label_key, host.hostname)))
try:
pecan.request.rpcapi.update_kubernetes_label(
pecan.request.context,
@ -223,12 +238,17 @@ class LabelController(rest.RestController):
'label_key': key,
'label_value': value
}
try:
new_label = pecan.request.dbapi.label_create(uuid, values)
if existing_labels.get(key, None):
# Update the value
label_uuid = existing_labels.get(key)
new_label = pecan.request.dbapi.label_update(label_uuid, {'label_value': value})
else:
new_label = pecan.request.dbapi.label_create(uuid, values)
new_records.append(new_label)
except exception.HostLabelAlreadyExists:
pass
# We should not be here
raise wsme.exc.ClientSideError(_("Error creating label %s") % label_key)
try:
vim_api.vim_host_update(

View File

@ -3,6 +3,8 @@
# SPDX-License-Identifier: Apache-2.0
#
import platform
from six.moves import http_client
from sysinv.common import constants
@ -10,6 +12,11 @@ from sysinv.db import api as dbapi
from sysinv.tests.api import base
from sysinv.tests.db import utils as dbutils
if platform.python_version().startswith('2.7'):
from urllib import urlencode
else:
from urllib.parse import urlencode
HEADER = {'User-Agent': 'sysinv'}
@ -41,55 +48,112 @@ class LabelTestCase(base.FunctionalTest):
invprovision=constants.PROVISIONED,
)
def _get_path(self, path=None):
if path:
return '/labels/' + path
def _get_path(self, host=None, params=None):
if host:
path = '/labels/' + host
else:
return '/labels'
path = '/labels'
if params:
path += '?' + urlencode(params)
return path
class LabelAssignTestCase(LabelTestCase):
def setUp(self):
super(LabelAssignTestCase, self).setUp()
generic_labels = {
'apps': 'enabled',
'foo': 'bar'
}
def validate_labels(self, input_data, response_data):
self.assertEqual(len(input_data), len(response_data))
for label in response_data:
label_key = label["label_key"]
label_value = label["label_value"]
self.assertIn(label_key, input_data.keys())
self.assertEqual(label_value, input_data[label_key])
def assign_labels(self, host_uuid, input_data, parameters=None):
response = self.post_json('%s' % self._get_path(host_uuid, parameters), input_data)
self.assertEqual(http_client.OK, response.status_int)
return response
def assign_labels_failure(self, host_uuid, input_data, parameters=None):
response = self.post_json('%s' % self._get_path(host_uuid, parameters), input_data, expect_errors=True)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertTrue(response.json['error_message'])
def get_host_labels(self, host_uuid):
response = self.get_json("/ihosts/%s/labels" % host_uuid)
return response['labels']
def test_create_labels(self):
host_uuid = self.worker.uuid
input_data = self.generic_labels
self.assign_labels(host_uuid, input_data)
response_data = self.get_host_labels(host_uuid)
self.validate_labels(input_data, response_data)
def test_overwrite_labels_success(self):
host_uuid = self.worker.uuid
input_data = self.generic_labels
self.assign_labels(host_uuid, input_data)
new_input_values = {
'apps': 'disabled',
'foo': 'free'
}
self.assign_labels(host_uuid, new_input_values, parameters={'overwrite': True})
response_data = self.get_host_labels(host_uuid)
self.validate_labels(new_input_values, response_data)
def test_overwrite_labels_failure(self):
host_uuid = self.worker.uuid
input_data = self.generic_labels
self.assign_labels(host_uuid, input_data)
new_input_values = {
'apps': 'disabled',
'foo': 'free'
}
# Default value should be overwrite=False
self.assign_labels_failure(host_uuid, new_input_values)
# Test explicit overwrite=False
self.assign_labels_failure(host_uuid, new_input_values, parameters={'overwrite': False})
# Labels should be unchanged from initial values
response_data = self.get_host_labels(host_uuid)
self.validate_labels(input_data, response_data)
def test_create_validated_labels_success(self):
host_uuid = self.worker.uuid
cpu_mgr_label = {
'kube-cpu-mgr-policy': 'static',
}
response = self.post_json('%s' % self._get_path(host_uuid), cpu_mgr_label)
self.assertEqual(http_client.OK, response.status_int)
self.assign_labels(host_uuid, cpu_mgr_label)
topology_mgr_label = {
'kube-topology-mgr-policy': 'restricted',
}
response = self.post_json('%s' % self._get_path(host_uuid), topology_mgr_label)
self.assertEqual(http_client.OK, response.status_int)
self.assign_labels(host_uuid, topology_mgr_label)
response = self.get_json("/ihosts/%s/labels" % host_uuid)
labels = response['labels']
self.assertEqual(2, len(labels))
input_data = {}
for input_label in [cpu_mgr_label, topology_mgr_label]:
input_data.update(input_label)
for label in labels:
label_key = label["label_key"]
label_value = label["label_value"]
self.assertIn(label_key, input_data.keys())
self.assertEqual(label_value, input_data[label_key])
response_data = self.get_host_labels(host_uuid)
self.validate_labels(input_data, response_data)
def test_create_validated_labels_failure(self):
host_uuid = self.worker.uuid
cpu_mgr_label = {
'kube-cpu-mgr-policy': 'invalid',
}
response = self.post_json('%s' % self._get_path(host_uuid), cpu_mgr_label, expect_errors=True)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertTrue(response.json['error_message'])
self.assign_labels_failure(host_uuid, cpu_mgr_label)
topology_mgr_label = {
'kube-topology-mgr-policy': 'invalid',
}
response = self.post_json('%s' % self._get_path(host_uuid), topology_mgr_label, expect_errors=True)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertTrue(response.json['error_message'])
self.assign_labels_failure(host_uuid, topology_mgr_label)