Deployable V2 API implementation
This patch aims at implementing basic functions of v2/deployable API. It contains: 1. Implement deployable list/show API based on v1 deployable API. 2. Remove 'patch' method, because Cyborg V2 API provides other API resource(ARQ) to let caller to do binding, not based on deployable. 3. Remove 'delete' method, because deployable can not actually be deleted, it is reported by each driver on the node. 4. Add related UT. As for further implementation like program API, it will be done in another patch. Change-Id: I4fd12a7d34594a63bd80bdc188ddc977d0a094e7 Task: 2007427
This commit is contained in:
parent
5e69508e97
commit
a9ca2c5ece
|
@ -33,7 +33,7 @@ class FilterType(wtypes.UserType):
|
|||
_supported_fields = wtypes.Enum(wtypes.text, 'parent_uuid', 'root_uuid',
|
||||
'board', 'availability', 'interface_type',
|
||||
'instance_uuid', 'limit', 'marker',
|
||||
'sort_key', 'sort_dir')
|
||||
'sort_key', 'sort_dir', 'name')
|
||||
|
||||
field = wsme.wsattr(_supported_fields, mandatory=True)
|
||||
value = wsme.wsattr(wtypes.text, mandatory=True)
|
||||
|
|
|
@ -23,6 +23,7 @@ from cyborg.api.controllers import base
|
|||
from cyborg.api.controllers import link
|
||||
from cyborg.api.controllers.v2 import api_version_request
|
||||
from cyborg.api.controllers.v2 import arqs
|
||||
from cyborg.api.controllers.v2 import deployables
|
||||
from cyborg.api.controllers.v2 import device_profiles
|
||||
from cyborg.api.controllers.v2 import devices
|
||||
from cyborg.api import expose
|
||||
|
@ -66,6 +67,7 @@ class Controller(rest.RestController):
|
|||
device_profiles = device_profiles.DeviceProfilesController()
|
||||
accelerator_requests = arqs.ARQsController()
|
||||
devices = devices.DevicesController()
|
||||
deployables = deployables.DeployablesController()
|
||||
|
||||
@expose.expose(V2)
|
||||
def get(self):
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
# Copyright 2020 Intel Inc.
|
||||
# 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 pecan
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from cyborg.api.controllers import base
|
||||
from cyborg.api.controllers import link
|
||||
from cyborg.api.controllers import types
|
||||
from cyborg.api import expose
|
||||
from cyborg.common import policy
|
||||
from cyborg import objects
|
||||
|
||||
|
||||
class Deployable(base.APIBase):
|
||||
"""API representation of a deployable.
|
||||
|
||||
This class enforces type checking and value constraints, and converts
|
||||
between the internal object model and the API representation of
|
||||
a deployable.
|
||||
"""
|
||||
|
||||
uuid = types.uuid
|
||||
"""The UUID of the deployable"""
|
||||
|
||||
parent_id = types.integer
|
||||
"""The parent ID of the deployable"""
|
||||
|
||||
root_id = types.integer
|
||||
"""The root ID of the deployable"""
|
||||
|
||||
name = wtypes.text
|
||||
"""The name of the deployable"""
|
||||
|
||||
num_accelerators = types.integer
|
||||
"""The number of accelerators of the deployable"""
|
||||
|
||||
device_id = types.integer
|
||||
"""The device on which the deployable is located"""
|
||||
|
||||
attributes_list = wtypes.text
|
||||
"""The json list of attributes of the deployable"""
|
||||
|
||||
rp_uuid = types.uuid
|
||||
"""The uuid of resouce provider which represents this deployable"""
|
||||
|
||||
driver_name = wtypes.text
|
||||
"""The driver name of this deployables"""
|
||||
|
||||
bitstream_id = wtypes.text
|
||||
"""The id of bitstream which has been program in this deployable"""
|
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
"""A list containing a self link"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Deployable, self).__init__(**kwargs)
|
||||
self.fields = []
|
||||
for field in objects.Deployable.fields:
|
||||
self.fields.append(field)
|
||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||
|
||||
def convert_with_link(self, obj_dep):
|
||||
api_dep = Deployable(**obj_dep.as_dict())
|
||||
url = pecan.request.public_url
|
||||
api_dep.links = [
|
||||
link.Link.make_link('self', url, 'deployables', api_dep.uuid),
|
||||
link.Link.make_link('bookmark', url, 'deployables', api_dep.uuid,
|
||||
bookmark=True)
|
||||
]
|
||||
query = {"deployable_id": obj_dep.id}
|
||||
attr_get_list = objects.Attribute.get_by_filter(pecan.request.context,
|
||||
query)
|
||||
attributes_list = []
|
||||
for exist_attr in attr_get_list:
|
||||
attributes_list.append({exist_attr.key: exist_attr.value})
|
||||
api_dep.attributes_list = jsonutils.dumps(attributes_list)
|
||||
return api_dep
|
||||
|
||||
|
||||
class DeployableCollection(Deployable):
|
||||
"""API representation of a collection of deployables."""
|
||||
|
||||
deployables = [Deployable]
|
||||
"""A list containing deployable objects"""
|
||||
|
||||
def convert_with_links(self, obj_deps):
|
||||
collection = DeployableCollection()
|
||||
collection.deployables = [
|
||||
self.convert_with_link(obj_dep) for obj_dep in obj_deps]
|
||||
return collection
|
||||
|
||||
|
||||
class DeployablesController(base.CyborgController,
|
||||
DeployableCollection):
|
||||
"""REST controller for Deployables."""
|
||||
|
||||
@policy.authorize_wsgi("cyborg:deployable", "get_one")
|
||||
@expose.expose(Deployable, types.uuid)
|
||||
def get_one(self, uuid):
|
||||
"""Retrieve information about the given deployable.
|
||||
|
||||
:param uuid: UUID of a deployable.
|
||||
"""
|
||||
obj_dep = objects.Deployable.get(pecan.request.context, uuid)
|
||||
return self.convert_with_link(obj_dep)
|
||||
|
||||
@policy.authorize_wsgi("cyborg:deployable", "get_all")
|
||||
@expose.expose(DeployableCollection, wtypes.ArrayType(types.FilterType))
|
||||
def get_all(self, filters=None):
|
||||
"""Retrieve a list of deployables.
|
||||
:param filters: a filter of FilterType to get deployables list by
|
||||
filter.
|
||||
"""
|
||||
filters_dict = {}
|
||||
if filters:
|
||||
for filter in filters:
|
||||
filters_dict.update(filter.as_dict())
|
||||
context = pecan.request.context
|
||||
obj_deps = objects.Deployable.list(context, filters=filters_dict)
|
||||
return self.convert_with_links(obj_deps)
|
|
@ -115,6 +115,16 @@ device_policies = [
|
|||
description='Retrieve all device records'),
|
||||
]
|
||||
|
||||
deployable_policies = [
|
||||
policy.RuleDefault('cyborg:deployable:get_one',
|
||||
'rule:allow',
|
||||
description='Show deployable detail'),
|
||||
policy.RuleDefault('cyborg:deployable:get_all',
|
||||
'rule:allow',
|
||||
description='Retrieve all deployable records'),
|
||||
]
|
||||
|
||||
|
||||
fpga_policies = [
|
||||
policy.RuleDefault('cyborg:fpga:get_one',
|
||||
'rule:allow',
|
||||
|
@ -133,7 +143,8 @@ def list_policies():
|
|||
+ fpga_policies \
|
||||
+ accelerator_request_policies \
|
||||
+ device_profile_policies \
|
||||
+ device_policies
|
||||
+ device_policies \
|
||||
+ deployable_policies
|
||||
|
||||
|
||||
@lockutils.synchronized('policy_enforcer', 'cyborg-')
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
# Copyright 2020 Intel, Inc.
|
||||
# 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 cyborg.tests.unit.api.controllers.v2 import base as v2_test
|
||||
from cyborg.tests.unit import fake_deployable
|
||||
|
||||
|
||||
class TestDeployablesController(v2_test.APITestV2):
|
||||
|
||||
DEPLOYABLE_URL = '/deployables'
|
||||
|
||||
def setUp(self):
|
||||
super(TestDeployablesController, self).setUp()
|
||||
self.headers = self.gen_headers(self.context)
|
||||
self.fake_deployable = fake_deployable.fake_deployable_obj(
|
||||
self.context)
|
||||
|
||||
def _validate_links(self, links, deployable_uuid):
|
||||
has_self_link = False
|
||||
for link in links:
|
||||
if link['rel'] == 'self':
|
||||
has_self_link = True
|
||||
url = link['href']
|
||||
components = url.split('/')
|
||||
self.assertEqual(components[-1], deployable_uuid)
|
||||
self.assertTrue(has_self_link)
|
||||
|
||||
def _validate_deployable(self, in_deployable, out_deployable):
|
||||
for field in in_deployable.keys():
|
||||
if field != 'id':
|
||||
self.assertEqual(in_deployable[field], out_deployable[field])
|
||||
# Check that the link is properly set up
|
||||
self._validate_links(out_deployable['links'], in_deployable['uuid'])
|
||||
|
||||
@mock.patch('cyborg.objects.Deployable.get')
|
||||
def test_get_one_by_uuid(self, mock_deployable):
|
||||
in_deployable = self.fake_deployable
|
||||
mock_deployable.return_value = in_deployable
|
||||
uuid = in_deployable['uuid']
|
||||
|
||||
url = self.DEPLOYABLE_URL + '/%s'
|
||||
out_deployable = self.get_json(url % uuid, headers=self.headers)
|
||||
mock_deployable.assert_called_once()
|
||||
self._validate_deployable(in_deployable, out_deployable)
|
||||
|
||||
@mock.patch('cyborg.objects.Deployable.list')
|
||||
def test_get_all(self, mock_deployables):
|
||||
mock_deployables.return_value = [self.fake_deployable]
|
||||
data = self.get_json(self.DEPLOYABLE_URL, headers=self.headers)
|
||||
out_deployable = data['deployables']
|
||||
self.assertIsInstance(out_deployable, list)
|
||||
for out_dev in out_deployable:
|
||||
self.assertIsInstance(out_dev, dict)
|
||||
self.assertTrue(len(out_deployable), 1)
|
||||
self._validate_deployable(self.fake_deployable, out_deployable[0])
|
||||
|
||||
@mock.patch('cyborg.objects.Deployable.list')
|
||||
def test_get_with_filters(self, mock_deployables):
|
||||
mock_deployables.return_value = [self.fake_deployable]
|
||||
# TODO(Xinran) Add API doc to explain the usage of filter.
|
||||
# Add "?filters.field=limit&filters.value=1" in DEPLOYABLE_URL, in
|
||||
# order to list the deployables with limited number which is 1.
|
||||
data = self.get_json(
|
||||
self.DEPLOYABLE_URL + "?filters.field=limit&filters.value=1",
|
||||
headers=self.headers)
|
||||
out_deployable = data['deployables']
|
||||
mock_deployables.assert_called_once_with(mock.ANY,
|
||||
filters={"limit": "1"})
|
||||
self._validate_deployable(self.fake_deployable, out_deployable[0])
|
||||
|
||||
@mock.patch('cyborg.objects.Deployable.list')
|
||||
def test_get_with_filters_not_match(self, mock_deployables):
|
||||
# This will return null list because the fake deployable's name
|
||||
# is "dp_name".
|
||||
mock_deployables.return_value = []
|
||||
data = self.get_json(
|
||||
self.DEPLOYABLE_URL +
|
||||
"?filters.field=name&filters.value=wrongname",
|
||||
headers=self.headers)
|
||||
out_deployable = data['deployables']
|
||||
mock_deployables.assert_called_once_with(mock.ANY,
|
||||
filters={"name": "wrongname"})
|
||||
self.assertEqual(len(out_deployable), 0)
|
|
@ -16,12 +16,10 @@ from oslo_utils import uuidutils
|
|||
|
||||
from cyborg import objects
|
||||
from cyborg.objects import fields
|
||||
import json
|
||||
|
||||
|
||||
def fake_db_deployable(**updates):
|
||||
root_uuid = uuidutils.generate_uuid()
|
||||
bdf = {"domain": "0000", "bus": "00", "device": "01", "function": "1"}
|
||||
db_deployable = {
|
||||
'id': 1,
|
||||
'uuid': root_uuid,
|
||||
|
@ -33,7 +31,6 @@ def fake_db_deployable(**updates):
|
|||
'driver_name': "fake-driver-name",
|
||||
'rp_uuid': None,
|
||||
'bitstream_id': None,
|
||||
"cpid_info": json.dumps(bdf).encode('utf-8')
|
||||
}
|
||||
|
||||
for name, field in objects.Deployable.fields.items():
|
||||
|
|
Loading…
Reference in New Issue