implementation of bp disable-enable-device

add disable and enable device api

Change-Id: I3d2f7ac3b8de29bc0c3125b7340d9a90cffa7127
This commit is contained in:
anguoming 2023-08-15 18:56:36 +08:00
parent 4d329240f9
commit ef7ce3f2c3
9 changed files with 116 additions and 5 deletions

View File

@ -13,19 +13,25 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from http import HTTPStatus
import pecan import pecan
import wsme import wsme
from wsme import types as wtypes from wsme import types as wtypes
from oslo_log import log from oslo_log import log
from cyborg.accelerator.common import exception
from cyborg import api
from cyborg.api.controllers import base from cyborg.api.controllers import base
from cyborg.api.controllers import link from cyborg.api.controllers import link
from cyborg.api.controllers import types from cyborg.api.controllers import types
from cyborg.api.controllers.v2 import versions
from cyborg.api import expose from cyborg.api import expose
from cyborg.common import authorize_wsgi from cyborg.common import authorize_wsgi
from cyborg.common import placement_client
from cyborg import objects from cyborg import objects
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -56,6 +62,9 @@ class Device(base.APIBase):
hostname = wtypes.text hostname = wtypes.text
"""The hostname of the device""" """The hostname of the device"""
status = wtypes.text
"""The status of the device"""
links = wsme.wsattr([link.Link], readonly=True) links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link""" """A list containing a self link"""
@ -69,6 +78,9 @@ class Device(base.APIBase):
@classmethod @classmethod
def convert_with_links(cls, obj_device): def convert_with_links(cls, obj_device):
api_device = cls(**obj_device.as_dict()) api_device = cls(**obj_device.as_dict())
# return status filed from microversion of 2.3
if not api.request.version.minor >= versions.MINOR_3_DEVICE_STATUS:
delattr(api_device, 'status')
api_device.links = [ api_device.links = [
link.Link.make_link('self', pecan.request.public_url, link.Link.make_link('self', pecan.request.public_url,
'devices', api_device.uuid) 'devices', api_device.uuid)
@ -93,6 +105,8 @@ class DeviceCollection(base.APIBase):
class DevicesController(base.CyborgController): class DevicesController(base.CyborgController):
"""REST controller for Devices.""" """REST controller for Devices."""
_custom_actions = {'disable': ['POST'], 'enable': ['POST']}
@authorize_wsgi.authorize_wsgi("cyborg:device", "get_one") @authorize_wsgi.authorize_wsgi("cyborg:device", "get_one")
@expose.expose(Device, wtypes.text) @expose.expose(Device, wtypes.text)
def get_one(self, uuid): def get_one(self, uuid):
@ -128,3 +142,51 @@ class DevicesController(base.CyborgController):
obj_devices = objects.Device.list(context, filters=filters_dict) obj_devices = objects.Device.list(context, filters=filters_dict)
LOG.info('[devices:get_all] Returned: %s', obj_devices) LOG.info('[devices:get_all] Returned: %s', obj_devices)
return DeviceCollection.convert_with_links(obj_devices) return DeviceCollection.convert_with_links(obj_devices)
@authorize_wsgi.authorize_wsgi("cyborg:device", "disable")
@expose.expose(None, wtypes.text, types.uuid,
status_code=HTTPStatus.OK)
def disable(self, uuid):
context = pecan.request.context
device = objects.Device.get(context, uuid)
device.status = 'maintaining'
device.save(context)
# update resource provider inventories
client = placement_client.PlacementClient()
deployable = objects.Deployable.get_by_id(context, device.id)
filters = {'deployable_id': deployable.id, 'key': 'rc'}
attributes = objects.Attribute.get_by_filter(context, filters)
if attributes:
att_type = attributes[0].value
else:
raise exception.ResourceNotFound(
resource='Attribute',
msg='with deployable_id=%s,key=%s' % (deployable.id, 'rc'))
client.update_rp_inventory_reserved(
deployable.rp_uuid, att_type,
deployable.num_accelerators,
deployable.num_accelerators)
@authorize_wsgi.authorize_wsgi("cyborg:device", "enable")
@expose.expose(None, wtypes.text, types.uuid,
status_code=HTTPStatus.OK)
def enable(self, uuid):
context = pecan.request.context
device = objects.Device.get(context, uuid)
device.status = 'enabled'
device.save(context)
# update resource provider inventories
client = placement_client.PlacementClient()
deployable = objects.Deployable.get_by_id(context, device.id)
filters = {'deployable_id': deployable.id, 'key': 'rc'}
attributes = objects.Attribute.get_by_filter(context, filters)
if attributes:
att_type = attributes[0].value
else:
raise exception.ResourceNotFound(
resource='Attribute',
msg='with deployable_id=%s,key=%s' % (deployable.id, 'rc'))
client.update_rp_inventory_reserved(
deployable.rp_uuid, att_type,
deployable.num_accelerators,
0)

View File

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

View File

@ -37,3 +37,11 @@ Changed ``device_profile_uuid`` to ``device_profile_name_or_uuid`` in
name (newly introduced) and uuid. name (newly introduced) and uuid.
- GET /v2/device_profiles/{device_profile_name_or_uuid} - GET /v2/device_profiles/{device_profile_name_or_uuid}
2.3
---
Add new status info for list Device and get device API.
- GET: /devices
- GET: /devices/{uuid}

View File

@ -66,6 +66,10 @@ DEVICE_TYPE = (DEVICE_GPU, DEVICE_FPGA, DEVICE_AICHIP, DEVICE_QAT, DEVICE_NIC,
DEVICE_SSD) DEVICE_SSD)
# Device type
DEVICE_STATUS = ("enabled", "maintaining")
# Attach handle type # Attach handle type
# 'TEST_PCI': used by fake driver, ignored by Nova virt driver. # 'TEST_PCI': used by fake driver, ignored by Nova virt driver.
ATTACH_HANDLE_TYPES = (AH_TYPE_PCI, AH_TYPE_MDEV, AH_TYPE_TEST_PCI) = ( ATTACH_HANDLE_TYPES = (AH_TYPE_PCI, AH_TYPE_MDEV, AH_TYPE_TEST_PCI) = (

View File

@ -145,7 +145,7 @@ class PlacementClient(object):
def update_inventory( def update_inventory(
self, resource_provider_uuid, inventories, self, resource_provider_uuid, inventories,
resource_provider_generation=None): resource_provider_generation=None, version=None):
if resource_provider_generation is None: if resource_provider_generation is None:
resource_provider_generation = self.get_resource_provider( resource_provider_generation = self.get_resource_provider(
resource_provider_uuid=resource_provider_uuid)['generation'] resource_provider_uuid=resource_provider_uuid)['generation']
@ -155,7 +155,7 @@ class PlacementClient(object):
'inventories': inventories 'inventories': inventories
} }
try: try:
return self.put(url, body).json() return self.put(url, body, version=version).json()
except ks_exc.NotFound: except ks_exc.NotFound:
raise exception.PlacementResourceProviderNotFound( raise exception.PlacementResourceProviderNotFound(
resource_provider=resource_provider_uuid) resource_provider=resource_provider_uuid)
@ -355,3 +355,7 @@ class PlacementClient(object):
elif resp.status_code == 204: elif resp.status_code == 204:
LOG.info("Successfully delete trait %(trait_name).", { LOG.info("Successfully delete trait %(trait_name).", {
"trait_name", name}) "trait_name", name})
def update_rp_inventory_reserved(self, rp_uuid, resource, total, reserved):
update_inventory = {resource: {"total": total, "reserved": reserved}}
self.update_inventory(rp_uuid, update_inventory, version='1.26')

View File

@ -48,6 +48,12 @@ device_policies = [
policy.RuleDefault('cyborg:device:get_all', policy.RuleDefault('cyborg:device:get_all',
'rule:allow', 'rule:allow',
description='Retrieve all device records'), description='Retrieve all device records'),
policy.RuleDefault('cyborg:device:disable',
'rule:admin_api',
description='Disable a device'),
policy.RuleDefault('cyborg:device:enable',
'rule:admin_api',
description='Enable a device'),
] ]
deployable_policies = [ deployable_policies = [

View File

@ -0,0 +1,21 @@
"""add-device-status
Revision ID: 6c77bd6afea5
Revises: 4cc1d79978fc
Create Date: 2023-08-15 23:05:31.918963
"""
# revision identifiers, used by Alembic.
revision = '6c77bd6afea5'
down_revision = '4cc1d79978fc'
from alembic import op
import sqlalchemy as sa
def upgrade():
new_column = sa.Column('status', sa.Enum('enabled', 'maintaining'),
nullable=False, default='enabled')
op.add_column('devices', new_column)

View File

@ -88,6 +88,8 @@ class Device(Base):
std_board_info = Column(Text, nullable=True) std_board_info = Column(Text, nullable=True)
vendor_board_info = Column(Text, nullable=True) vendor_board_info = Column(Text, nullable=True)
hostname = Column(String(255), nullable=False) hostname = Column(String(255), nullable=False)
status = Column(Enum("enabled", "maintaining", name='device_status'),
default='enabled', nullable=False)
class Deployable(Base): class Deployable(Base):

View File

@ -29,7 +29,8 @@ LOG = logging.getLogger(__name__)
class Device(base.CyborgObject, object_base.VersionedObjectDictCompat): class Device(base.CyborgObject, object_base.VersionedObjectDictCompat):
# Version 1.0: Initial version # Version 1.0: Initial version
# Version 1.1: Add AICHIP, GENERIC type # Version 1.1: Add AICHIP, GENERIC type
VERSION = '1.1' # Version 1.2: Add status field
VERSION = '1.2'
dbapi = dbapi.get_instance() dbapi = dbapi.get_instance()
@ -43,6 +44,8 @@ class Device(base.CyborgObject, object_base.VersionedObjectDictCompat):
'std_board_info': object_fields.StringField(nullable=True), 'std_board_info': object_fields.StringField(nullable=True),
'vendor_board_info': object_fields.StringField(nullable=True), 'vendor_board_info': object_fields.StringField(nullable=True),
'hostname': object_fields.StringField(nullable=False), 'hostname': object_fields.StringField(nullable=False),
'status': object_fields.EnumField(valid_values=constants.DEVICE_STATUS,
nullable=False, default="enabled"),
} }
def create(self, context): def create(self, context):