Get device profile by name

Nowadays showing device_profile is supported only by uuid,
this patch supports to getting device profile by name.

Implements: blueprint show-device-profile-with-name
Change-Id: Ib9aff3312676923fc7478f2bcef79333e10496bf
This commit is contained in:
ericxiett 2021-11-11 00:54:33 +00:00
parent 2490e5b78f
commit cde7b78e22
9 changed files with 276 additions and 177 deletions

View File

@ -1,156 +1,157 @@
.. -*- rst -*-
.. needs:body_verification
Device Profiles
+++++++++++++++
Lists, creates, shows details for, updates and deletes device profiles.
A `device_profile
<http://specs.openstack.org/openstack/cyborg-specs/specs/train/implemented/device-profiles.html>`_
is a named set of the user requirements for one or more
accelerators. It can be viewed as a flavor for devices. Broadly it includes
two things: the desired amounts of specific resource classes and the
requirements that the resource provider(s) must satisfy. While the resource
classes are the same as those known to Placement, some requirements would
correspond to Placement traits and others to properties that Cyborg alone
knows about.
List Device Profiles
--------------------
.. rest_method:: GET /v2/device_profiles
Lists UUIDs, names, groups for all device profiles.
Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403)
Response
========
.. rest_parameters:: parameters.yaml
- device_profiles: device_profiles
- name: device_prof_name_resp
- uuid: device_profile_uuid
- description: device_profile_description
- groups: device_prof_groups_resp
- created_at: created
- updated_at: updated
- links: links
**Example response: list all device profiles**
.. literalinclude:: ../../doc/api_samples/device_profiles/device_profiles-list-resp.json
:language: javascript
Get One Device Profile
----------------------
.. rest_method:: GET /v2/device_profiles/{device_profile_uuid}
Gets the UUID, name, groups for one device_profile with the specified UUID.
Normal response codes: 200
Error response codes: badRequest(400), unauthorized(401), forbidden(403)
Request
=======
.. rest_parameters:: parameters.yaml
- device_profile_uuid: device_profile_uuid
Response
========
.. rest_parameters:: parameters.yaml
- device_profile: device_profile
- name: device_prof_name_resp
- uuid: device_profile_uuid
- description: device_profile_description
- groups: device_prof_groups_resp
- created_at: created
- updated_at: updated
- links: links
**Example response: get details of a specific device profile**
.. literalinclude:: ../../doc/api_samples/device_profiles/device_profiles-getone-resp.json
:language: javascript
Create Device Profile
---------------------
.. rest_method:: POST /v2/device_profiles
Creates a device profile. The payload should have these fields:
Normal response codes: 201
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
conflict(409)
Request
=======
.. rest_parameters:: parameters.yaml
- name: device_prof_name_req
- groups: device_prof_groups_req
Response
========
.. rest_parameters:: parameters.yaml
- name: device_prof_name_resp
- uuid: device_profile_uuid
- description: device_profile_description
- groups: device_prof_groups_resp
- created_at: created
- updated_at: updated
- links: links
**Example post curl with resource/trait**
.. literalinclude:: ../../doc/api_samples/device_profiles/device_profiles-post-curl.json
:language: javascript
**Example post curl with a cyborg property when bitstream is required**
.. literalinclude:: ../../doc/api_samples/device_profiles/device_profiles-post-curl-with-bitstream.json
:language: javascript
**Example response: create a device profile**
.. literalinclude:: ../../doc/api_samples/device_profiles/device_profiles-create-resp.json
:language: javascript
Delete One Device Profile by uuid
---------------------------------
.. rest_method:: DELETE /v2/device_profiles/{device_profile_uuid}
Delete a device profile. No query parameters required.
Delete Multiple Device Profiles by names
----------------------------------------
.. rest_method:: DELETE /v2/device_profiles?value={device_profile_name1},{device_profile_name2}
In the URL, Device profiles to be deleted should be in comma-delimited list of
device profile names.
.. note::
Today we do not allow deletion of a device profile when it is in use by
VMs, because ARQs have a foreign key on device profile table. But we copy
the device profile groups into the ARQ, so this foreign key is not needed.
So we can improve in Ussuri.
Response
========
Normal response codes: 204
There is no body content for the response of a successful DELETE query
.. -*- rst -*-
.. needs:body_verification
Device Profiles
+++++++++++++++
Lists, creates, shows details for, updates and deletes device profiles.
A `device_profile
<http://specs.openstack.org/openstack/cyborg-specs/specs/train/implemented/device-profiles.html>`_
is a named set of the user requirements for one or more
accelerators. It can be viewed as a flavor for devices. Broadly it includes
two things: the desired amounts of specific resource classes and the
requirements that the resource provider(s) must satisfy. While the resource
classes are the same as those known to Placement, some requirements would
correspond to Placement traits and others to properties that Cyborg alone
knows about.
List Device Profiles
--------------------
.. rest_method:: GET /v2/device_profiles
Lists UUIDs, names, groups for all device profiles.
Normal response codes: 200
Error response codes: unauthorized(401), forbidden(403)
Response
========
.. rest_parameters:: parameters.yaml
- device_profiles: device_profiles
- name: device_prof_name_resp
- uuid: device_profile_uuid
- description: device_profile_description
- groups: device_prof_groups_resp
- created_at: created
- updated_at: updated
- links: links
**Example response: list all device profiles**
.. literalinclude:: ../../doc/api_samples/device_profiles/device_profiles-list-resp.json
:language: javascript
Get One Device Profile
----------------------
.. rest_method:: GET /v2/device_profiles/{device_profile_name_or_uuid}
Gets the UUID, name, groups for one device_profile with the specified name or UUID.
Normal response codes: 200
Error response codes: badRequest(400), unauthorized(401), forbidden(403)
Request
=======
.. rest_parameters:: parameters.yaml
- device_profile_uuid: device_profile_uuid_v_21
- device_profile_name_or_uuid: device_profile_name_or_uuid
Response
========
.. rest_parameters:: parameters.yaml
- device_profile: device_profile
- name: device_prof_name_resp
- uuid: device_profile_uuid
- description: device_profile_description
- groups: device_prof_groups_resp
- created_at: created
- updated_at: updated
- links: links
**Example response: get details of a specific device profile(v2.2)**
.. literalinclude:: ../../doc/api_samples/device_profiles/v22/device_profiles-getone-resp.json
:language: javascript
Create Device Profile
---------------------
.. rest_method:: POST /v2/device_profiles
Creates a device profile. The payload should have these fields:
Normal response codes: 201
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
conflict(409)
Request
=======
.. rest_parameters:: parameters.yaml
- name: device_prof_name_req
- groups: device_prof_groups_req
Response
========
.. rest_parameters:: parameters.yaml
- name: device_prof_name_resp
- uuid: device_profile_uuid
- description: device_profile_description
- groups: device_prof_groups_resp
- created_at: created
- updated_at: updated
- links: links
**Example post curl with resource/trait**
.. literalinclude:: ../../doc/api_samples/device_profiles/device_profiles-post-curl.json
:language: javascript
**Example post curl with a cyborg property when bitstream is required**
.. literalinclude:: ../../doc/api_samples/device_profiles/device_profiles-post-curl-with-bitstream.json
:language: javascript
**Example response: create a device profile**
.. literalinclude:: ../../doc/api_samples/device_profiles/device_profiles-create-resp.json
:language: javascript
Delete One Device Profile by uuid
---------------------------------
.. rest_method:: DELETE /v2/device_profiles/{device_profile_uuid}
Delete a device profile. No query parameters required.
Delete Multiple Device Profiles by names
----------------------------------------
.. rest_method:: DELETE /v2/device_profiles?value={device_profile_name1},{device_profile_name2}
In the URL, Device profiles to be deleted should be in comma-delimited list of
device profile names.
.. note::
Today we do not allow deletion of a device profile when it is in use by
VMs, because ARQs have a foreign key on device profile table. But we copy
the device profile groups into the ARQ, so this foreign key is not needed.
So we can improve in Ussuri.
Response
========
Normal response codes: 204
There is no body content for the response of a successful DELETE query

View File

@ -12,13 +12,28 @@ deployable_uuid:
in: path
required: true
type: string
device_profile_uuid:
device_profile_name_or_uuid:
description: |
The UUID of the device_profile for your accelerator request.
This must be a valid UUID otherwise API will return 400.
The name or uuid of the device_profile for your accelerator request.
in: path
required: true
type: string
min_version: 2.2
device_profile_uuid:
description: |
The uuid of the device_profile for your accelerator request.
This must be a valid uuid otherwise api will return 400.
in: path
required: true
type: string
device_profile_uuid_v_21:
description: |
The uuid of the device_profile for your accelerator request.
This must be a valid uuid otherwise api will return 400.
in: path
required: true
type: string
max_version: 2.1
device_uuid:
description: |
The UUID of the device.

View File

@ -23,13 +23,16 @@ from wsme import types as wtypes
from oslo_log import log
from oslo_utils import uuidutils
from cyborg import api
from cyborg.api.controllers import base
from cyborg.api.controllers import link
from cyborg.api.controllers import types
from cyborg.api.controllers.v2 import versions
from cyborg.api import expose
from cyborg.common import authorize_wsgi
from cyborg.common import constants
from cyborg.common import exception
from cyborg.common.i18n import _
from cyborg import objects
LOG = log.getLogger(__name__)
@ -257,21 +260,33 @@ class DeviceProfilesController(base.CyborgController,
@authorize_wsgi.authorize_wsgi("cyborg:device_profile", "get_one")
@expose.expose('json', wtypes.text)
def get_one(self, uuid):
"""Retrieve a single device profile by uuid."""
LOG.info('[device_profiles] get_one. uuid=%s', uuid)
obj_devprofs = self._get_device_profile_list(uuid=uuid)
api_obj_devprofs = self.get_device_profiles(obj_devprofs)
if len(api_obj_devprofs) == 0:
def get_one(self, dp_uuid_or_name):
"""Retrieve a single device profile by uuid or name."""
context = pecan.request.context
if uuidutils.is_uuid_like(dp_uuid_or_name):
LOG.info('[device_profiles] get_one. uuid=%s', dp_uuid_or_name)
obj_devprof = objects.DeviceProfile.get_by_uuid(context,
dp_uuid_or_name)
else:
if api.request.version.minor >= versions.MINOR_2_DP_BY_NAME:
LOG.info('[device_profiles] get_one. name=%s', dp_uuid_or_name)
obj_devprof = \
objects.DeviceProfile.get_by_name(context,
dp_uuid_or_name)
else:
raise exception.NotAcceptable(_(
"Request not acceptable. The minimal required API "
"version should be %(base)s.%(opr)s") %
{'base': versions.BASE_VERSION,
'opr': versions.MINOR_2_DP_BY_NAME})
if not obj_devprof:
LOG.warning("Device profile with %s not found!", dp_uuid_or_name)
raise exception.ResourceNotFound(
resource='Device profile',
msg='with uuid %s' % uuid)
msg='with %s' % dp_uuid_or_name)
api_obj_devprof = self.get_device_profile(obj_devprof)
count = len(api_obj_devprofs)
if count != 1: # Should never happen because names are unique
raise exception.ExpectedOneObject(obj='device profile',
count=count)
ret = {"device_profile": api_obj_devprofs[0]}
ret = {"device_profile": api_obj_devprof}
LOG.info('[device_profiles] get_one returned: %s', ret)
# TODO(Sundar) Replace this with convert_with_links()
return wsme.api.Response(ret, status_code=HTTPStatus.OK,

View File

@ -24,8 +24,10 @@ BASE_VERSION = 2
#
# v2.0: Initial minor version.
# v2.1: Add project_id for arq patch
# v2.2: Support getting device profile by name (newly introduced) and uuid.
MINOR_0_INITIAL_VERSION = 0
MINOR_1_PROJECT_ID = 1
MINOR_2_DP_BY_NAME = 2
# When adding another version, update:
@ -34,7 +36,7 @@ MINOR_1_PROJECT_ID = 1
# explanation of what changed in the new version
MINOR_MAX_VERSION = MINOR_1_PROJECT_ID
MINOR_MAX_VERSION = MINOR_2_DP_BY_NAME
# String representations of the minor and maximum versions
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_0_INITIAL_VERSION)

View File

@ -27,3 +27,11 @@ microversions.
Add ``project_id`` for Accelerator Requests PATCH API. ``project_id`` is
used to control the operation of arq with different roles.
2.2
---
Changed ``device_profile_uuid`` to ``device_profile_name_or_uuid`` in
`Get One Device Profile` API path, so support getting device profile by
name (newly introduced) and uuid.
- GET /v2/device_profiles/{device_profile_name_or_uuid}

View File

@ -19,6 +19,7 @@ import webtest
from oslo_serialization import jsonutils
from cyborg.api.controllers import base
from cyborg.tests.unit.api.controllers.v2 import base as v2_test
from cyborg.tests.unit import fake_device_profile
@ -51,13 +52,40 @@ class TestDeviceProfileController(v2_test.APITestV2):
# Check that the link is properly set up
self._validate_links(out_dp['links'], in_dp['uuid'])
@mock.patch('cyborg.objects.DeviceProfile.list')
def test_get_one_by_uuid(self, mock_dp):
@mock.patch('cyborg.objects.DeviceProfile.get_by_uuid')
def test_get_one_by_uuid(self, mock_dp_uuid):
dp = self.fake_dp_objs[0]
mock_dp.return_value = [dp]
mock_dp_uuid.return_value = dp
url = self.DP_URL + '/%s'
data = self.get_json(url % dp['uuid'], headers=self.headers)
mock_dp.assert_called_once()
mock_dp_uuid.assert_called_once()
out_dp = data['device_profile']
self._validate_dp(dp, out_dp)
@mock.patch('cyborg.objects.DeviceProfile.get_by_name')
def test_get_one_by_name_before_v22(self, mock_dp_name):
dp = self.fake_dp_objs[0]
mock_dp_name.return_value = dp
url = self.DP_URL + '/%s'
headers = self.headers
headers[base.Version.current_api_version] = 'accelerator 2.1'
self.assertRaisesRegex(
webtest.app.AppError,
"Request not acceptable.*",
self.get_json,
url % dp['name'],
headers=headers)
@mock.patch('cyborg.objects.DeviceProfile.get_by_name')
def test_get_one_by_name(self, mock_dp_name):
dp = self.fake_dp_objs[0]
mock_dp_name.return_value = dp
url = self.DP_URL + '/%s'
headers = self.headers
headers[base.Version.current_api_version] = 'accelerator 2.2'
data = self.get_json(url % dp['name'],
headers=headers)
mock_dp_name.assert_called_once()
out_dp = data['device_profile']
self._validate_dp(dp, out_dp)

View File

@ -5,7 +5,7 @@
"description": "",
"groups":[
{
"trait:CUSTOM_CHENKE_TRAITS":"required",
"trait:CUSTOM_FPGA_INSPUR":"required",
"resources:FPGA":"1",
"accel:bitstream_id":"d5ca2f11-3108-4426-a11c-a959987565df"
}

View File

@ -0,0 +1,22 @@
{
"device_profile":{
"name":"fpga-dp1",
"uuid":"5518a925-1c2c-49a2-a8bf-0927d9456f3e",
"description": "",
"groups":[
{
"trait:CUSTOM_FPGA_INSPUR":"required",
"resources:FPGA":"1",
"accel:bitstream_id":"d5ca2f11-3108-4426-a11c-a959987565df"
}
],
"created_at": "2020-03-09 11:26:05+00:00",
"updated_at": null,
"links":[
{
"href":"http://192.168.32.217/accelerator/v2/device_profiles/5518a925-1c2c-49a2-a8bf-0927d9456f3e",
"rel":"self"
}
]
}
}

View File

@ -0,0 +1,8 @@
---
features:
- |
Changed ``device_profile_uuid`` to ``device_profile_name_or_uuid`` in
`Get One Device Profile` API path, so it can support getting one device
profile by its `name` since microversion 2.2, not just uuid.
- GET /v2/device_profiles/{device_profile_name_or_uuid}