Added rest API for FPGA programming
Here is an example on how to call this api curl -s -X PATCH -H "X-Auth-Token: $OS_TOKEN" -H "Content-Type: application/json"\ -d '[{ "path": "/program", "op": "replace", "value": [{ "image_uuid": "9a17439a-85d0-4c53-a3d3-0f68a2eac896" }] }]'\ http://{api_ip}:6666/v1/accelerators/deployables/{pf_uuid}/program Change-Id: Iab6150f39be9ccb34f1fc86d6942b1b7c48a4348
This commit is contained in:
parent
06970bcd0b
commit
185169e380
|
@ -15,8 +15,10 @@
|
|||
|
||||
import datetime
|
||||
|
||||
import pecan
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
from pecan import rest
|
||||
|
||||
|
||||
class APIBase(wtypes.Base):
|
||||
|
@ -31,3 +33,30 @@ class APIBase(wtypes.Base):
|
|||
return dict((k, getattr(self, k))
|
||||
for k in self.fields
|
||||
if hasattr(self, k) and getattr(self, k) != wsme.Unset)
|
||||
|
||||
|
||||
class CyborgController(rest.RestController):
|
||||
|
||||
def _handle_patch(self, method, remainder, request=None):
|
||||
"""Routes ``PATCH`` _custom_actions."""
|
||||
# route to a patch_all or get if no additional parts are available
|
||||
if not remainder or remainder == ['']:
|
||||
controller = self._find_controller('patch_all', 'patch')
|
||||
if controller:
|
||||
return controller, []
|
||||
pecan.abort(404)
|
||||
|
||||
controller = getattr(self, remainder[0], None)
|
||||
if controller and not inspect.ismethod(controller):
|
||||
return pecan.routing.lookup_controller(controller, remainder[1:])
|
||||
# route to custom_action
|
||||
match = self._handle_custom_action(method, remainder, request)
|
||||
if match:
|
||||
return match
|
||||
|
||||
# finally, check for the regular patch_one/patch requests
|
||||
controller = self._find_controller('patch_one', 'patch')
|
||||
if controller:
|
||||
return controller, remainder
|
||||
|
||||
pecan.abort(405)
|
||||
|
|
|
@ -23,6 +23,7 @@ from cyborg.api.controllers import base
|
|||
from cyborg.api.controllers import link
|
||||
from cyborg.api.controllers.v1 import types
|
||||
from cyborg.api.controllers.v1 import utils as api_utils
|
||||
from cyborg.api.controllers.v1 import deployables
|
||||
from cyborg.api import expose
|
||||
from cyborg.common import exception
|
||||
from cyborg.common import policy
|
||||
|
@ -118,7 +119,7 @@ class AcceleratorPatchType(types.JsonPatchType):
|
|||
'/product_id', '/remotable']
|
||||
|
||||
|
||||
class AcceleratorsControllerBase(rest.RestController):
|
||||
class AcceleratorsControllerBase(base.CyborgController):
|
||||
|
||||
_resource = None
|
||||
|
||||
|
@ -130,6 +131,8 @@ class AcceleratorsControllerBase(rest.RestController):
|
|||
class AcceleratorsController(AcceleratorsControllerBase):
|
||||
"""REST controller for Accelerators."""
|
||||
|
||||
deployables = deployables.DeployablesController()
|
||||
|
||||
@policy.authorize_wsgi("cyborg:accelerator", "create", False)
|
||||
@expose.expose(Accelerator, body=types.jsontype,
|
||||
status_code=http_client.CREATED)
|
||||
|
|
|
@ -18,6 +18,7 @@ from pecan import rest
|
|||
from six.moves import http_client
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
import json
|
||||
|
||||
from cyborg.api.controllers import base
|
||||
from cyborg.api.controllers import link
|
||||
|
@ -79,6 +80,9 @@ class Deployable(base.APIBase):
|
|||
availability = wtypes.text
|
||||
"""The availability of the deployable"""
|
||||
|
||||
attributes_list = wtypes.text
|
||||
"""The json list of attributes of the deployable"""
|
||||
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
"""A list containing a self link"""
|
||||
|
||||
|
@ -98,6 +102,13 @@ class Deployable(base.APIBase):
|
|||
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 = json.dumps(attributes_list)
|
||||
return api_dep
|
||||
|
||||
|
||||
|
@ -125,9 +136,27 @@ class DeployablePatchType(types.JsonPatchType):
|
|||
return defaults + ['/address', '/host', '/type']
|
||||
|
||||
|
||||
class DeployablesController(rest.RestController):
|
||||
class DeployablesController(base.CyborgController):
|
||||
"""REST controller for Deployables."""
|
||||
|
||||
_custom_actions = {'program': ['PATCH']}
|
||||
|
||||
@policy.authorize_wsgi("cyborg:deployable", "program", False)
|
||||
@expose.expose(Deployable, types.uuid, body=[DeployablePatchType])
|
||||
def program(self, uuid, program_info):
|
||||
"""Program a new deployable(FPGA).
|
||||
|
||||
:param uuid: The uuid of the target deployable.
|
||||
:param program_info: JSON string containing what to program.
|
||||
"""
|
||||
|
||||
image_uuid = program_info[0]['value'][0]['image_uuid']
|
||||
obj_dep = objects.Deployable.get(pecan.request.context, uuid)
|
||||
# Set attribute of the new bitstream/image information
|
||||
obj_dep.add_attribute(pecan.request.context, 'image_uuid', image_uuid)
|
||||
# TODO (Li Liu) Trigger the program api in Agnet.
|
||||
return Deployable.convert_with_links(obj_dep)
|
||||
|
||||
@policy.authorize_wsgi("cyborg:deployable", "create", False)
|
||||
@expose.expose(Deployable, body=types.jsontype,
|
||||
status_code=http_client.CREATED)
|
||||
|
|
|
@ -104,11 +104,29 @@ deployable_policies = [
|
|||
policy.RuleDefault('cyborg:deployable:update',
|
||||
'rule:admin_api',
|
||||
description='Update deployable records'),
|
||||
policy.RuleDefault('cyborg:deployable:program',
|
||||
'rule:allow',
|
||||
description='Program deployable(FPGA) records'),
|
||||
]
|
||||
|
||||
fpga_policies = [
|
||||
policy.RuleDefault('cyborg:fpga:get_one',
|
||||
'rule:allow',
|
||||
description='Show fpga detail'),
|
||||
policy.RuleDefault('cyborg:fpga:get_all',
|
||||
'rule:allow',
|
||||
description='Retrieve all fpga records'),
|
||||
policy.RuleDefault('cyborg:fpga:update',
|
||||
'rule:allow',
|
||||
description='Update fpga records'),
|
||||
]
|
||||
|
||||
|
||||
def list_policies():
|
||||
return default_policies + accelerator_policies + deployable_policies
|
||||
return default_policies \
|
||||
+ accelerator_policies \
|
||||
+ deployable_policies \
|
||||
+ fpga_policies
|
||||
|
||||
|
||||
@lockutils.synchronized('policy_enforcer', 'cyborg-')
|
||||
|
|
|
@ -73,7 +73,7 @@ class Deployable(base.CyborgObject, object_base.VersionedObjectDictCompat):
|
|||
raise exception.ObjectActionError(action='create',
|
||||
reason='uuid is required')
|
||||
|
||||
if self.parent_uuid is None:
|
||||
if not hasattr(self, 'parent_uuid') or self.parent_uuid is None:
|
||||
self.root_uuid = self.uuid
|
||||
else:
|
||||
self.root_uuid = self._get_parent_root_uuid()
|
||||
|
@ -126,6 +126,10 @@ class Deployable(base.CyborgObject, object_base.VersionedObjectDictCompat):
|
|||
updates = self.obj_get_changes()
|
||||
db_dep = self.dbapi.deployable_update(context, self.uuid, updates)
|
||||
self._from_db_object(self, db_dep)
|
||||
query = {"deployable_id": self.id}
|
||||
attr_get_list = Attribute.get_by_filter(context,
|
||||
query)
|
||||
self.attributes_list = attr_get_list
|
||||
|
||||
def destroy(self, context):
|
||||
"""Delete a Deployable from the DB."""
|
||||
|
@ -133,19 +137,28 @@ class Deployable(base.CyborgObject, object_base.VersionedObjectDictCompat):
|
|||
self.dbapi.deployable_delete(context, self.uuid)
|
||||
self.obj_reset_changes()
|
||||
|
||||
def add_attribute(self, attribute):
|
||||
def add_attribute(self, context, key, value):
|
||||
"""add a attribute object to the attribute_list.
|
||||
If the attribute already exists, it will update the value,
|
||||
otherwise, the vf will be appended to the list
|
||||
"""
|
||||
|
||||
for exist_attr in self.attributes_list:
|
||||
if base.obj_equal_prims(attribute, exist_attr):
|
||||
if key == exist_attr.key:
|
||||
LOG.warning("The attribute already exists")
|
||||
if value != exist_attr.value:
|
||||
exist_attr.value = attribute.value
|
||||
exist_attr.save(context)
|
||||
return None
|
||||
attribute.deployable_id = self.id
|
||||
attribute_copy = copy.deepcopy(attribute)
|
||||
self.attributes_list.append(attribute_copy)
|
||||
# The attribute does not exist yet. Create it.
|
||||
attr_vals = {
|
||||
'deployable_id': self.id,
|
||||
'key': key,
|
||||
'value': value
|
||||
}
|
||||
attr = Attribute(context, **attr_vals)
|
||||
attr.create(context)
|
||||
self.attributes_list.append(attr)
|
||||
|
||||
def delete_attribute(self, context, attribute):
|
||||
"""remove an attribute from the attributes_list
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
# Copyright 2017 Huawei Technologies Co.,LTD.
|
||||
# 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 six.moves import http_client
|
||||
|
||||
from cyborg.api.controllers.v1.deployables import Deployable
|
||||
from cyborg.tests.unit.api.controllers.v1 import base as v1_test
|
||||
from cyborg.tests.unit import fake_deployable
|
||||
|
||||
|
||||
class TestFPGAProgramController(v1_test.APITestV1):
|
||||
|
||||
def setUp(self):
|
||||
super(TestFPGAProgramController, self).setUp()
|
||||
self.headers = self.gen_headers(self.context)
|
||||
self.deployable_uuids = ['0acbf8d6-e02a-4394-aae3-57557d209498']
|
||||
|
||||
@mock.patch('cyborg.objects.Deployable.get')
|
||||
def test_program(self, mock_get_dep):
|
||||
self.headers['X-Roles'] = 'admin'
|
||||
self.headers['Content-Type'] = 'application/json'
|
||||
dep_uuid = self.deployable_uuids[0]
|
||||
fake_dep = fake_deployable.fake_deployable_obj(self.context,
|
||||
uuid=dep_uuid)
|
||||
mock_get_dep.return_value = fake_dep
|
||||
body = [{"image_uuid": "9a17439a-85d0-4c53-a3d3-0f68a2eac896"}]
|
||||
response = self.\
|
||||
patch_json('/accelerators/deployables/%s/program' % dep_uuid,
|
||||
[{'path': '/program', 'value': body,
|
||||
'op': 'replace'}],
|
||||
headers=self.headers)
|
||||
self.assertEqual(http_client.OK, response.status_code)
|
||||
data = response.json_body
|
||||
self.assertEqual(dep_uuid, data['uuid'])
|
|
@ -62,10 +62,7 @@ def fake_db_deployable(**updates):
|
|||
def fake_deployable_obj(context, obj_dpl_class=None, **updates):
|
||||
if obj_dpl_class is None:
|
||||
obj_dpl_class = objects.Deployable
|
||||
expected_attrs = updates.pop('expected_attrs', None)
|
||||
deploy = obj_dpl_class._from_db_object(context,
|
||||
obj_dpl_class(),
|
||||
fake_db_deployable(**updates),
|
||||
expected_attrs=expected_attrs)
|
||||
deploy = obj_dpl_class._from_db_object(obj_dpl_class(),
|
||||
fake_db_deployable(**updates))
|
||||
deploy.obj_reset_changes()
|
||||
return deploy
|
||||
|
|
|
@ -161,17 +161,12 @@ class _TestDeployableObject(DbTestCase):
|
|||
dpl_get = objects.Deployable.get(self.context, dpl.uuid)
|
||||
|
||||
db_attr = self.fake_attribute
|
||||
attr = objects.Attribute(context=self.context,
|
||||
**db_attr)
|
||||
attr.deployable_id = dpl_get.id
|
||||
attr.create(self.context)
|
||||
|
||||
dpl.add_attribute(attr)
|
||||
dpl.add_attribute(self.context, db_attr['key'], db_attr['value'])
|
||||
dpl.save(self.context)
|
||||
|
||||
dpl_get = objects.Deployable.get(self.context, dpl.uuid)
|
||||
self.assertEqual(len(dpl_get.attributes_list), 1)
|
||||
self.assertEqual(dpl_get.attributes_list[0].id, attr.id)
|
||||
|
||||
def test_delete_attribute(self):
|
||||
db_acc = self.fake_accelerator
|
||||
|
@ -187,21 +182,13 @@ class _TestDeployableObject(DbTestCase):
|
|||
dpl.create(self.context)
|
||||
dpl_get = objects.Deployable.get(self.context, dpl.uuid)
|
||||
db_attr = self.fake_attribute
|
||||
attr = objects.Attribute(context=self.context,
|
||||
**db_attr)
|
||||
attr.deployable_id = dpl_get.id
|
||||
attr.create(self.context)
|
||||
dpl_get.add_attribute(attr)
|
||||
dpl_get.add_attribute(self.context, db_attr['key'], db_attr['value'])
|
||||
dpl_get.save(self.context)
|
||||
dpl_get = objects.Deployable.get(self.context, dpl_get.uuid)
|
||||
self.assertEqual(len(dpl_get.attributes_list), 1)
|
||||
self.assertEqual(dpl_get.attributes_list[0].id, attr.id)
|
||||
|
||||
dpl_get.delete_attribute(self.context, dpl_get.attributes_list[0])
|
||||
self.assertEqual(len(dpl_get.attributes_list), 0)
|
||||
self.assertRaises(exception.AttributeNotFound,
|
||||
objects.Attribute.get, self.context,
|
||||
attr.uuid)
|
||||
|
||||
def test_get_by_filter_with_attributes(self):
|
||||
db_acc = self.fake_accelerator
|
||||
|
@ -225,44 +212,26 @@ class _TestDeployableObject(DbTestCase):
|
|||
dpl2_get = objects.Deployable.get(self.context, dpl2.uuid)
|
||||
|
||||
db_attr = self.fake_attribute
|
||||
attr = objects.Attribute(context=self.context,
|
||||
**db_attr)
|
||||
attr.deployable_id = dpl_get.id
|
||||
attr.create(self.context)
|
||||
|
||||
db_attr2 = self.fake_attribute2
|
||||
attr2 = objects.Attribute(context=self.context,
|
||||
**db_attr2)
|
||||
attr2.deployable_id = dpl2_get.id
|
||||
attr2.create(self.context)
|
||||
|
||||
db_attr3 = self.fake_attribute3
|
||||
attr3 = objects.Attribute(context=self.context,
|
||||
**db_attr3)
|
||||
attr3.deployable_id = dpl2_get.id
|
||||
attr3.create(self.context)
|
||||
|
||||
dpl.add_attribute(attr)
|
||||
dpl.add_attribute(self.context, 'attr_key', 'attr_val')
|
||||
dpl.save(self.context)
|
||||
|
||||
dpl2.add_attribute(attr2)
|
||||
dpl2.add_attribute(self.context, 'test_key', 'test_val')
|
||||
dpl2.save(self.context)
|
||||
|
||||
dpl2.add_attribute(attr3)
|
||||
dpl2.add_attribute(self.context, 'test_key3', 'test_val3')
|
||||
dpl2.save(self.context)
|
||||
|
||||
query = {"attr_key": "attr_val"}
|
||||
|
||||
dpl_get_list = objects.Deployable.get_by_filter(self.context, query)
|
||||
self.assertEqual(len(dpl_get_list), 2)
|
||||
self.assertEqual(len(dpl_get_list), 1)
|
||||
self.assertEqual(dpl_get_list[0].uuid, dpl.uuid)
|
||||
|
||||
attr2.set_key_value_pair("test_key", "test_val")
|
||||
attr2.save(self.context)
|
||||
|
||||
attr3.set_key_value_pair("test_key3", "test_val3")
|
||||
attr3.save(self.context)
|
||||
|
||||
query = {"test_key": "test_val"}
|
||||
dpl_get_list = objects.Deployable.get_by_filter(self.context, query)
|
||||
self.assertEqual(len(dpl_get_list), 1)
|
||||
|
|
Loading…
Reference in New Issue