Fix api-ref inaccuracies and add missing program endpoint
The ARQ PATCH endpoint was documented with a UUID in the URL path
(PATCH /v2/accelerator_requests/{uuid}) but the code takes no path
UUID — ARQ UUIDs are keys in the JSON request body. The docs also
referenced a response sample, but the endpoint returns 202 with no
body. Fix the URL, remove the orphan response sample, and add the
standard "no body content" text. Add prose clarifying the JSON-Patch
request structure for the parameter table.
The deployables program endpoint (PATCH /v2/deployables/{uuid}/program)
existed in code but had no api-ref documentation. Add the endpoint
section, request/response parameter docs, and sample JSON files.
Document the JSON-Patch envelope in the parameter description and
set bitstream_id to null in the response sample to match what the
controller actually returns.
Add a functional test for the new program endpoint samples, extending
_check_sample() to support PATCH requests with a request body.
Closes-Bug: #2158759
Assisted-by: Claude claude-opus-4-6 claude-code-2.1.196
Change-Id: Iaf9e68cf7174bf9993cef034e07d78f532d1077a
Signed-off-by: melanie witt <melwittt@gmail.com>
This commit is contained in:
+15
-5
@@ -75,15 +75,22 @@ to bind and unbind the ARQ with the host name, device’s RP UUID and instance U
|
||||
is an asynchronous call which prepares or reconfigures the device in the
|
||||
background.
|
||||
|
||||
.. rest_method:: PATCH /v2/accelerator_requests/{accelerator_request_uuid}
|
||||
.. rest_method:: PATCH /v2/accelerator_requests
|
||||
|
||||
Updates:an accelerator request. The payload should have these fields:
|
||||
Bind or unbind one or more accelerator requests. The payload is a JSON
|
||||
object whose keys are ARQ UUIDs and whose values are lists of JSON-Patch
|
||||
operations. Only ``add`` (bind) and ``remove`` (unbind) operations are
|
||||
supported. All operations in a single request must use the same op.
|
||||
|
||||
Request
|
||||
=======
|
||||
|
||||
The request body is a JSON object keyed by ARQ UUID. Each value is a
|
||||
list of JSON-Patch operations whose ``path`` and ``value`` fields carry
|
||||
the bind/unbind parameters:
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- accelerator_request_uuid: accelerator_request_uuid
|
||||
- hostname: hostname_bind_unbind_req
|
||||
- device_rp_uuid: device_rp_uuid_bind_unbind_req
|
||||
- instance_uuid: instance_uuid_bind_unbind_req
|
||||
@@ -94,9 +101,12 @@ Request
|
||||
.. literalinclude:: ../../doc/api_samples/accelerator_requests/accelerator_requests-patch-req.json
|
||||
:language: javascript
|
||||
|
||||
**Example response: update an accelerator request**
|
||||
Response
|
||||
========
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/accelerator_requests/accelerator_requests-after-update-resp.json
|
||||
Normal response codes: 202
|
||||
|
||||
There is no body content for the response of a successful request.
|
||||
|
||||
Delete Accelerator Requests by ARQ uuid
|
||||
---------------------------------------
|
||||
|
||||
@@ -89,3 +89,54 @@ Response
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/deployables/deployables-getone-resp.json
|
||||
:language: javascript
|
||||
|
||||
Program a Deployable
|
||||
--------------------
|
||||
|
||||
.. rest_method:: PATCH /v2/deployables/{deployable_uuid}/program
|
||||
|
||||
Program an FPGA deployable with a new bitstream image. This is an
|
||||
asynchronous operation that reconfigures the FPGA device.
|
||||
|
||||
Normal response codes: 200
|
||||
|
||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403), notfound(404)
|
||||
|
||||
Request
|
||||
=======
|
||||
|
||||
The request body is a JSON-Patch array. The ``image_uuid`` is supplied
|
||||
inside a ``replace`` operation on ``/program``:
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- deployable_uuid: deployable_uuid
|
||||
- image_uuid: image_uuid
|
||||
|
||||
**Example request: program a deployable**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/deployables/deployables-program-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
========
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- uuid: deployable_uuid_resp
|
||||
- parent_id: deployable_parent_id
|
||||
- root_id: deployable_root_id
|
||||
- name: deployable_name
|
||||
- num_accelerators: deployable_num_accelerators
|
||||
- device_id: deployable_device_id
|
||||
- attributes_list: deployable_attributes_list
|
||||
- rp_uuid: deployable_rp_uuid
|
||||
- driver_name: deployable_driver_name
|
||||
- bitstream_id: deployable_bitstream_id
|
||||
- created_at: created
|
||||
- updated_at: updated
|
||||
- links: links
|
||||
|
||||
**Example response: program a deployable**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/deployables/deployables-program-resp.json
|
||||
:language: javascript
|
||||
|
||||
@@ -359,6 +359,14 @@ hostname_resp:
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
image_uuid:
|
||||
description: |
|
||||
The UUID of the bitstream image to program onto the FPGA.
|
||||
Supplied inside a JSON-Patch ``replace`` operation on ``/program``
|
||||
(see the program endpoint example).
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
instance_project_id_bind_unbind_req:
|
||||
description: |
|
||||
Project id of the target instance for bind or unbind of one accelerator request.
|
||||
|
||||
@@ -157,5 +157,24 @@ class TestAcceleratorRequestSamples(base.ApiSampleTestBase):
|
||||
)
|
||||
|
||||
|
||||
class TestDeployableProgramSample(base.ApiSampleTestBase):
|
||||
def test_deployable_program(self):
|
||||
uuids = self.seed_programable_deployable()
|
||||
self._check_sample(
|
||||
f'/v2/deployables/{uuids["deployable"]}/program',
|
||||
os.path.join(
|
||||
SAMPLES_DIR,
|
||||
'deployables',
|
||||
'deployables-program-resp.json',
|
||||
),
|
||||
method='PATCH',
|
||||
req_path=os.path.join(
|
||||
SAMPLES_DIR,
|
||||
'deployables',
|
||||
'deployables-program-req.json',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -77,6 +77,16 @@ class ApiSampleTestBase(base.TestCase):
|
||||
result = common.seed_device_profiles(self.context)
|
||||
return {k: v.uuid for k, v in result.items()}
|
||||
|
||||
def seed_programable_deployable(self):
|
||||
self.useFixture(
|
||||
fixtures.MockPatch(
|
||||
'cyborg.api.controllers.v2.deployables.AgentAPI',
|
||||
autospec=True,
|
||||
)
|
||||
)
|
||||
result = common.seed_programable_deployable(self.context)
|
||||
return {k: v.uuid for k, v in result.items()}
|
||||
|
||||
def seed_arqs(self):
|
||||
self.useFixture(
|
||||
fixtures.MockPatch(
|
||||
@@ -102,14 +112,36 @@ class ApiSampleTestBase(base.TestCase):
|
||||
headers.update(extra_headers)
|
||||
return headers
|
||||
|
||||
def _check_sample(self, url, sample_path, extra_headers=None):
|
||||
def _check_sample(
|
||||
self, url, sample_path, extra_headers=None, method='GET', req_path=None
|
||||
):
|
||||
"""Compare API response structure against a sample JSON file.
|
||||
|
||||
When GENERATE_SAMPLES=1, overwrites the sample file instead.
|
||||
|
||||
:param method: HTTP method (default GET). For PATCH, also pass
|
||||
*req_path* with the path to a JSON request body sample.
|
||||
:param req_path: Path to a JSON file used as the request body
|
||||
for non-GET methods.
|
||||
"""
|
||||
response = self.app.get(
|
||||
url, headers=self._get_headers(extra_headers), expect_errors=False
|
||||
)
|
||||
headers = self._get_headers(extra_headers)
|
||||
if method == 'PATCH':
|
||||
if not req_path:
|
||||
raise ValueError("req_path is required for PATCH requests")
|
||||
with open(req_path) as f:
|
||||
body = json.load(f)
|
||||
response = self.app.patch_json(
|
||||
url,
|
||||
body,
|
||||
headers=headers,
|
||||
expect_errors=False,
|
||||
)
|
||||
else:
|
||||
response = self.app.get(
|
||||
url,
|
||||
headers=headers,
|
||||
expect_errors=False,
|
||||
)
|
||||
actual = response.json
|
||||
|
||||
if GENERATE_SAMPLES:
|
||||
|
||||
@@ -91,6 +91,52 @@ def seed_devices(context):
|
||||
}
|
||||
|
||||
|
||||
def seed_programable_deployable(context):
|
||||
"""Create a device, deployable, and controlpath ID for programming.
|
||||
|
||||
The program endpoint requires a controlpath ID associated with the
|
||||
device so it can locate the FPGA control path.
|
||||
"""
|
||||
dev = objects.Device(
|
||||
context,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
type='FPGA',
|
||||
vendor='0xABCD',
|
||||
model='miss model info',
|
||||
std_board_info="{'device_id': '0xabcd', 'class': 'Fake class'}",
|
||||
vendor_board_info='fake_vendor_info',
|
||||
hostname='test-node-1',
|
||||
status='enabled',
|
||||
)
|
||||
dev.create(context)
|
||||
|
||||
dep = objects.Deployable(
|
||||
context,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
name='test-deployable-0',
|
||||
num_accelerators=1,
|
||||
device_id=dev.id,
|
||||
rp_uuid=uuidutils.generate_uuid(),
|
||||
driver_name='fake',
|
||||
)
|
||||
dep.create(context)
|
||||
|
||||
cpid = objects.ControlpathID(
|
||||
context,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
device_id=dev.id,
|
||||
cpid_type='PCI',
|
||||
cpid_info='{"domain":"0000","bus":"0c","device":"00","function":"0"}',
|
||||
)
|
||||
cpid.create(context)
|
||||
|
||||
return {
|
||||
'device': dev,
|
||||
'deployable': dep,
|
||||
'controlpath_id': cpid,
|
||||
}
|
||||
|
||||
|
||||
def seed_device_profiles(context):
|
||||
"""Create two test device profiles.
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"instance_uuid":"506220bc-18f2-4294-9b29-80267a021dc0",
|
||||
"hostname":"centos-80",
|
||||
"uuid":"89ec9c4d-73b2-4aaf-9225-485945aa7162",
|
||||
"links":[
|
||||
{
|
||||
"href":"http://192.168.23.138/accelerator/v2/accelerator_requests/89ec9c4d-73b2-4aaf-9225-485945aa7162",
|
||||
"rel":"self"
|
||||
}
|
||||
],
|
||||
"created_at":"2019-10-25T12:01:05+00:00",
|
||||
"device_profile_group_id":0,
|
||||
"attach_handle_type":"TEST_PCI",
|
||||
"updated_at":"2019-10-25T12:49:49+00:00",
|
||||
"state":"Bound",
|
||||
"device_rp_uuid":"03631f82-20a9-3f67-a29d-dc1abe4042bf",
|
||||
"device_profile_name":"device-profile-name1",
|
||||
"attach_handle_info":{
|
||||
"device":"00",
|
||||
"bus":"0c",
|
||||
"domain":"0000",
|
||||
"function":"0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"path": "/program",
|
||||
"value": [
|
||||
{
|
||||
"image_uuid": "76babc1b-3222-4992-a046-52441e403577"
|
||||
}
|
||||
],
|
||||
"op": "replace"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"uuid": "29e23349-12ee-4978-963c-11484a4ae601",
|
||||
"parent_id": null,
|
||||
"root_id": null,
|
||||
"name": "computenode_FakeDevice",
|
||||
"num_accelerators": 16,
|
||||
"device_id": 1,
|
||||
"attributes_list": "[{'traits1': 'CUSTOM_FAKE_DEVICE'}, {'rc': 'FPGA'}]",
|
||||
"rp_uuid": "853f07a6-19de-3dd6-b9f6-6c782daa3f7b",
|
||||
"driver_name": "fake",
|
||||
"bitstream_id": null,
|
||||
"created_at": "2020-03-13T02:27:35+00:00",
|
||||
"updated_at": "2020-03-13T02:27:36+00:00",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://localhost/accelerator/v2/deployables/29e23349-12ee-4978-963c-11484a4ae601",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://localhost/accelerator/deployables/29e23349-12ee-4978-963c-11484a4ae601",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user