Adds labels endpoints

- labels get/put/delete endpoint

Fixes Bug: 1612141

Change-Id: I21a2421b2e992827a602f3ab04b5c14a44014f96
This commit is contained in:
sulochan acharya 2016-10-06 08:33:23 +00:00 committed by Sulochan Acharya
parent 014377ab63
commit a27ad0899f
10 changed files with 240 additions and 2 deletions

View File

@ -77,3 +77,32 @@ class HostsData(base.Resource):
context = request.environ.get('context')
dbapi.hosts_data_delete(context, id, request.json)
return None, 204, None
class HostsLabels(base.Resource):
@base.http_codes
def get(self, id):
"""Get labels for given host device."""
context = request.environ.get('context')
host_obj = dbapi.hosts_get_by_id(context, id)
response = {"labels": list(host_obj.labels)}
return response, 200, None
@base.http_codes
def put(self, id):
"""
Update existing device label entirely, or add if it does
not exist.
"""
context = request.environ.get('context')
resp = dbapi.hosts_labels_update(context, id, request.json)
response = {"labels": list(resp.labels)}
return response, 200, None
@base.http_codes
def delete(self, id):
"""Delete device label entirely."""
context = request.environ.get('context')
dbapi.hosts_labels_delete(context, id, request.json)
return None, 204, None

View File

@ -105,6 +105,33 @@ class NetDeviceById(base.Resource):
return None, 204, None
class NetDeviceLabels(base.Resource):
"""Controller for Netowrk Device Labels."""
@base.http_codes
def get(self, id):
"""Get labels for given network device."""
context = request.environ.get('context')
obj = dbapi.netdevices_get_by_id(context, id)
response = {"labels": list(obj.labels)}
return response, 200, None
@base.http_codes
def put(self, id):
"""Update existing device label. Adds if it does not exist."""
context = request.environ.get('context')
resp = dbapi.netdevices_labels_update(context, id, request.json)
response = {"labels": list(resp.labels)}
return response, 200, None
@base.http_codes
def delete(self, id):
"""Delete device label(s)."""
context = request.environ.get('context')
dbapi.netdevices_labels_delete(context, id)
return None, 204, None
class NetInterfaces(base.Resource):
"""Controller for Netowrk Interfaces."""

View File

@ -15,6 +15,9 @@ routes = [
dict(resource=hosts.HostsData,
urls=['/hosts/<id>/data'],
endpoint='hosts_data'),
dict(resource=hosts.HostsLabels,
urls=['/hosts/<id>/labels'],
endpoint='hosts_labels'),
dict(resource=hosts.HostById,
urls=['/hosts/<id>'],
endpoint='hosts_id'),
@ -69,4 +72,7 @@ routes = [
dict(resource=networks.NetDeviceById,
urls=['/netdevices/<id>'],
endpoint='netdevices_id'),
dict(resource=networks.NetDeviceLabels,
urls=['/netdevices/<id>/labels'],
endpoint='netdevices_labels'),
]

View File

@ -20,7 +20,8 @@ DefinitionsHost = {'discriminator': 'name',
'description': 'Parent Id of this host'},
'device_type': {'type': 'string',
'description': 'Type of host'},
'labels': {'type': 'allOf',
'labels': {'type': 'array',
'items': 'string',
'description': 'User defined labels'},
'data': {'type': 'allOf',
'description': 'User defined information'},
@ -36,7 +37,8 @@ DefinitionsHostId = {'discriminator': 'name',
'id': {'type': 'integer'},
'cell_id': {'type': 'integer'},
'project_id': {'type': 'integer'},
'labels': {'type': 'allOf',
'labels': {'type': 'array',
'items': 'string',
'description': 'User defined labels'},
'data': {'type': 'allOf',
'description': 'User defined information'},
@ -76,6 +78,11 @@ DefinitionsData = {'type': 'object',
'properties': {'key': {'type': 'string'},
'value': {'type': 'object'}}}
DefinitionsLabel = {'type': 'object',
'properties': {'labels': {
'type': 'array',
'items': {'type': 'string'}}}}
DefinitionsError = {'type': 'object',
'properties': {'fields': {'type': 'string'},
'message': {'type': 'string'},
@ -284,6 +291,7 @@ validators = {
'description': 'Cell id to generate inventory for'}}}
},
('hosts_id_data', 'PUT'): {'json': DefinitionsData},
('hosts_labels', 'PUT'): {'json': DefinitionsLabel},
('hosts_id', 'GET'): {
'args': {'required': [],
'properties': {
@ -426,6 +434,7 @@ validators = {
'default': True,
'type': 'boolean'}}}},
('netdevices', 'POST'): {'json': DefinitionNetDevice},
('netdevices_labels', 'PUT'): {'json': DefinitionsLabel},
('net_interfaces', 'GET'): {
'args': {'required': ['device_id'],
'properties': {
@ -502,6 +511,16 @@ filters = {
400: {'headers': None, 'schema': None},
404: {'headers': None, 'schema': None},
405: {'headers': None, 'schema': None}},
('hosts_labels', 'GET'):
{200: {'headers': None, 'schema': DefinitionsLabel},
400: {'headers': None, 'schema': None},
404: {'headers': None, 'schema': None},
405: {'headers': None, 'schema': None}},
('hosts_labels', 'PUT'):
{200: {'headers': None, 'schema': DefinitionsLabel},
400: {'headers': None, 'schema': None},
404: {'headers': None, 'schema': None},
405: {'headers': None, 'schema': None}},
('hosts', 'POST'):
{200: {'headers': None, 'schema': DefinitionsHost},
400: {'headers': None, 'schema': None},
@ -625,6 +644,16 @@ filters = {
400: {'headers': None, 'schema': None},
404: {'headers': None, 'schema': None},
405: {'headers': None, 'schema': None}},
('netdevices_labels', 'GET'):
{200: {'headers': None, 'schema': DefinitionsLabel},
400: {'headers': None, 'schema': None},
404: {'headers': None, 'schema': None},
405: {'headers': None, 'schema': None}},
('netdevices_labels', 'PUT'):
{200: {'headers': None, 'schema': DefinitionsLabel},
400: {'headers': None, 'schema': None},
404: {'headers': None, 'schema': None},
405: {'headers': None, 'schema': None}},
('networks', 'GET'):
{200: {'headers': None,
'schema': {'items': DefinitionNetwork, 'type': 'array'}},

View File

@ -187,6 +187,16 @@ def hosts_data_delete(context, host_id, data_key):
return IMPL.hosts_data_delete(context, host_id, data_key)
def hosts_labels_delete(context, host_id, labels):
"""Delete existing device label(s)."""
return IMPL.hosts_labels_delete(context, host_id, labels)
def hosts_labels_update(context, host_id, labels):
"""Update existing device label entirely."""
return IMPL.hosts_labels_update(context, host_id, labels)
# Projects
def projects_get_all(context):
@ -283,6 +293,16 @@ def netdevices_delete(context, netdevice_id):
return IMPL.netdevices_delete(context, netdevice_id)
def netdevices_labels_delete(context, netdevice_id, labels):
"""Delete network device labels."""
return IMPL.netdevices_labels_delete(context, netdevice_id, labels)
def netdevices_labels_update(context, netdevice_id, labels):
"""Update network device labels."""
return IMPL.netdevices_labels_update(context, netdevice_id, labels)
def net_interfaces_get_by_device(context, device_id, filters):
"""Get all network interfaces for the given device."""
return IMPL.net_interfaces_get_by_device(context, device_id, filters)

View File

@ -115,6 +115,47 @@ def get_user_info(context, username):
raise exceptions.UnknownException(message=err)
def _device_labels_update(context, device_type, device_id, labels):
"""Update labels for the given device. Add the label if it is not present
in host labels list, otherwise do nothing."""
session = get_session()
with session.begin():
devices = with_polymorphic(models.Device, '*')
query = model_query(context, devices, session=session,
project_only=True)
query = query.filter_by(type=device_type)
query = query.filter_by(id=device_id)
try:
device = query.one()
except sa_exc.NoResultFound:
raise exceptions.NotFound()
device.labels.update(labels["labels"])
device.save(session)
return device
def _device_labels_delete(context, device_type, device_id, labels):
"""Delete labels from the device labels list if it matches
the given label in the query, otherwise do nothing."""
session = get_session()
with session.begin():
devices = with_polymorphic(models.Device, '*')
query = model_query(context, devices, session=session,
project_only=True)
query = query.filter_by(type=device_type)
query = query.filter_by(id=device_id)
try:
device = query.one()
except sa_exc.NoResultFound:
raise exceptions.NotFound()
for label in labels["labels"]:
device.labels.discard(label)
device.save(session)
return device
def cells_get_all(context, region):
"""Get all cells."""
query = model_query(context, models.Cell, project_only=True)
@ -454,6 +495,18 @@ def hosts_data_delete(context, host_id, data):
return host_ref
def hosts_labels_update(context, host_id, labels):
"""Update labels for host. Add the label if it is not present
in host labels list, otherwise do nothing."""
return _device_labels_update(context, 'hosts', host_id, labels)
def hosts_labels_delete(context, host_id, labels):
"""Delete labels from the host labels list if it matches
the given label in the query, otherwise do nothing."""
return _device_labels_delete(context, 'hosts', host_id, labels)
@require_admin_context
def projects_get_all(context):
"""Get all the projects."""
@ -674,6 +727,18 @@ def netdevices_delete(context, netdevice_id):
query.delete()
def netdevices_labels_update(context, device_id, labels):
"""Update labels for a network device. Add the label if it is not present
in host labels list, otherwise do nothing."""
return _device_labels_update(context, 'net_devices', device_id, labels)
def netdevices_labels_delete(context, device_id, labels):
"""Delete labels from the network device labels list if it matches
the given label in the query, otherwise do nothing."""
return _device_labels_delete(context, 'net_devices', device_id, labels)
def net_interfaces_get_by_device(context, device_id, filters):
"""Get all network interfaces for the given host."""
query = model_query(context, models.NetInterface, project_only=True)

View File

@ -115,3 +115,25 @@ class HostsDBTestCase(base.DBTestCase):
host = dbapi.hosts_get_by_id(self.context, host_id)
self.assertEqual(host.name, 'www.example.xyz')
self.assertEqual(host.resolved, {'bar': 'bar2', 'foo': 'R1'})
def test_host_labels_create(self):
region_id = self.make_region('region_1', foo='R1')
host_id = self.make_host(region_id, 'www.example.xyz',
IPAddress(u'10.1.2.101'),
'server', bar='bar2')
labels = {"labels": ["tom", "jerry"]}
dbapi.hosts_labels_update(self.context, host_id, labels)
def test_host_labels_delete(self):
region_id = self.make_region('region_1', foo='R1')
host_id = self.make_host(region_id, 'www.example.xyz',
IPAddress(u'10.1.2.101'),
'server', bar='bar2')
_labels = {"labels": ["tom", "jerry", "jones"]}
dbapi.hosts_labels_update(self.context, host_id, _labels)
host = dbapi.hosts_get_by_id(self.context, host_id)
self.assertEqual(sorted(host.labels), sorted(_labels["labels"]))
_dlabels = {"labels": ["tom"]}
dbapi.hosts_labels_delete(self.context, host_id, _dlabels)
host = dbapi.hosts_get_by_id(self.context, host_id)
self.assertEqual(host.labels, {"jerry", "jones"})

View File

@ -108,6 +108,22 @@ class NetworkDevicesDBTestCase(base.DBTestCase):
self.assertRaises(exceptions.NotFound, dbapi.netdevices_get_by_id,
self.context, res.id)
def test_netdevice_labels_create(self):
device = dbapi.netdevices_create(self.context, device1)
labels = {"labels": ["tom", "jerry"]}
dbapi.netdevices_labels_update(self.context, device.id, labels)
def test_netdevice_labels_delete(self):
device = dbapi.netdevices_create(self.context, device1)
_labels = {"labels": ["tom", "jerry"]}
dbapi.netdevices_labels_update(self.context, device.id, _labels)
ndevice = dbapi.netdevices_get_by_id(self.context, device.id)
self.assertEqual(sorted(ndevice.labels), sorted(_labels["labels"]))
_dlabels = {"labels": ["tom"]}
dbapi.netdevices_labels_delete(self.context, ndevice.id, _dlabels)
ndevice = dbapi.netdevices_get_by_id(self.context, ndevice.id)
self.assertEqual(ndevice.labels, {"jerry"})
class NetworkInterfacesDBTestCase(base.DBTestCase):

View File

@ -77,6 +77,8 @@ HOST2 = Host("www.example.com", "1", "1", "192.168.1.2", "server",
{"key1": "value1", "key2": "value2"})
HOST3 = Host("www.example.net", "1", "2", "10.10.0.1", "server",
{"key1": "value1", "key2": "value2"})
HOST4 = Host("www.example.net", "1", "2", "10.10.0.1", "server",
{"key1": "value1", "key2": "value2"}, labels=["a", "b"])
HOSTS_LIST_R1 = [HOST1, HOST2]
HOSTS_LIST_R2 = [HOST3]

View File

@ -35,6 +35,14 @@ class APIV1Test(TestCase):
resp.json = jsonutils.loads(resp.data.decode('utf-8'))
return resp
def put(self, path, data, **kw):
content = jsonutils.dumps(data)
content_type = 'application/json'
resp = self.client.put(path=path, content_type=content_type,
data=content)
resp.json = jsonutils.loads(resp.data.decode('utf-8'))
return resp
def delete(self, path):
resp = self.client.delete(path=path)
return resp
@ -216,6 +224,20 @@ class APIV1HostsIDTest(APIV1Test):
resp = self.get('v1/hosts/1?resolved-values=false')
self.assertEqual(resp.json["data"], expected)
@mock.patch.object(dbapi, 'hosts_get_by_id')
def test_get_hosts_labels(self, mock_host):
mock_host.return_value = fake_resources.HOST4
resp = self.get('v1/hosts/1/labels')
self.assertEqual(resp.json["labels"], ["a", "b"])
@mock.patch.object(dbapi, 'hosts_labels_update')
def test_put_hosts_labels(self, mock_host):
payload = {"labels": ["a", "b"]}
mock_host.return_value = fake_resources.HOST4
resp = self.put('v1/hosts/1/labels', data=payload)
self.assertEqual(200, resp.status_code)
self.assertEqual(resp.json, payload)
class APIV1HostsTest(APIV1Test):
@mock.patch.object(dbapi, 'hosts_get_by_region')