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:
Li Liu 2018-06-29 22:41:46 -04:00
parent 06970bcd0b
commit 185169e380
8 changed files with 156 additions and 51 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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-')

View File

@ -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

View File

@ -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'])

View File

@ -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

View File

@ -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)