Commit cef089f1 by Zuul Committed by Gerrit Code Review

Merge "Add Device and DeviceProfile objects"

parents 64aef5dc 354818ef
......@@ -102,6 +102,10 @@ class DeviceAlreadyExists(CyborgException):
_msg_fmt = _("Device with uuid %(uuid)s already exists.")
class DeviceProfileAlreadyExists(CyborgException):
_msg_fmt = _("DeviceProfile with uuid %(uuid)s already exists.")
class DeployableAlreadyExists(CyborgException):
_msg_fmt = _("Deployable with uuid %(uuid)s already exists.")
......@@ -172,6 +176,10 @@ class DeviceNotFound(NotFound):
_msg_fmt = _("Device %(uuid)s could not be found.")
class DeviceProfileNotFound(NotFound):
_msg_fmt = _("DeviceProfile %(uuid)s could not be found.")
class DeployableNotFound(NotFound):
_msg_fmt = _("Deployable %(uuid)s could not be found.")
......@@ -193,6 +201,10 @@ class DuplicateDeviceName(Conflict):
_msg_fmt = _("A device with name %(name)s already exists.")
class DuplicateDeviceProfileName(Conflict):
_msg_fmt = _("A device_profile with name %(name)s already exists.")
class DuplicateDeployableName(Conflict):
_msg_fmt = _("A deployable with name %(name)s already exists.")
......
......@@ -55,6 +55,13 @@ class Connection(object):
"""Get requested list of devices."""
@abc.abstractmethod
def device_list_by_filters(self, context,
filters, sort_key='created_at',
sort_dir='desc', limit=None,
marker=None, columns_to_join=None):
"""Get requested devices by filters."""
@abc.abstractmethod
def device_update(self, context, uuid, values):
"""Update a device."""
......@@ -62,6 +69,38 @@ class Connection(object):
def device_delete(self, context, uuid):
"""Delete a device when device is removed from the host."""
# device_profile
@abc.abstractmethod
def device_profile_create(self, context, values):
"""Create a new device_profile."""
@abc.abstractmethod
def device_profile_get_by_uuid(self, context, uuid):
"""Get requested device_profile by uuid."""
@abc.abstractmethod
def device_profile_get_by_id(self, context, id):
"""Get requested device_profile by id."""
@abc.abstractmethod
def device_profile_list(self, context):
"""Get requested list of device_profiles."""
@abc.abstractmethod
def device_profile_list_by_filters(self, context,
filters, sort_key='created_at',
sort_dir='desc', limit=None,
marker=None, columns_to_join=None):
"""Get requested list of device_profiles by filters."""
@abc.abstractmethod
def device_profile_update(self, context, uuid, values):
"""Update a device_profile."""
@abc.abstractmethod
def device_profile_delete(self, context, uuid):
"""Delete a device_profile."""
# deployable
@abc.abstractmethod
def deployable_create(self, context, values):
......
......@@ -18,6 +18,8 @@ state = sa.Enum('Initial', 'Bound', 'BindFailed', name='state')
substate = sa.Enum('Initial', name='substate')
attach_type = sa.Enum('PCI', 'MDEV', name='attach_type')
cpid_type = sa.Enum('PCI', name='cpid_type')
control_type = sa.Enum('PCI', name='control_type')
device_type = sa.Enum('GPU', 'FPGA', name='device_type')
def upgrade():
......@@ -32,7 +34,7 @@ def upgrade():
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', sa.String(length=36), nullable=False, unique=True),
sa.Column('type', sa.String(length=255), nullable=False),
sa.Column('type', device_type, nullable=False),
sa.Column('vendor', sa.String(length=255), nullable=False),
sa.Column('model', sa.String(length=255), nullable=False),
sa.Column('std_board_info', sa.Text(), nullable=True),
......
......@@ -358,12 +358,32 @@ class Connection(api.Connection):
except NoResultFound:
raise exception.DeviceNotFound(uuid=uuid)
def device_list(self, context, limit, marker, sort_key, sort_dir,
project_only):
query = model_query(context, models.Device,
project_only=project_only)
def device_list_by_filters(self, context,
filters, sort_key='created_at',
sort_dir='desc', limit=None,
marker=None, join_columns=None):
"""Return devices that match all filters sorted by the given keys."""
if limit == 0:
return []
query_prefix = model_query(context, models.Device)
filters = copy.deepcopy(filters)
exact_match_filter_names = ['uuid', 'id', 'type',
'vendor', 'model', 'hostname']
# Filter the query
query_prefix = self._exact_filter(models.Device, query_prefix,
filters, exact_match_filter_names)
if query_prefix is None:
return []
return _paginate_query(context, models.Device, limit, marker,
sort_key, sort_dir, query)
sort_key, sort_dir, query_prefix)
def device_list(self, context):
query = model_query(context, models.Device)
return _paginate_query(context, models.Device)
def device_update(self, context, uuid, values):
if 'uuid' in values:
......@@ -398,6 +418,95 @@ class Connection(api.Connection):
if count != 1:
raise exception.DeviceNotFound(uuid=uuid)
def device_profile_create(self, context, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
device_profile = models.DeviceProfile()
device_profile.update(values)
with _session_for_write() as session:
try:
session.add(device_profile)
session.flush()
except db_exc.DBDuplicateEntry:
raise exception.DeviceProfileAlreadyExists(uuid=values['uuid'])
return device_profile
def device_profile_get_by_uuid(self, context, uuid):
query = model_query(
context,
models.DeviceProfile).filter_by(uuid=uuid)
try:
return query.one()
except NoResultFound:
raise exception.DeviceProfileNotFound(uuid=uuid)
def device_profile_get_by_id(self, context, id):
query = model_query(
context,
models.DeviceProfile).filter_by(id=id)
try:
return query.one()
except NoResultFound:
raise exception.DeviceProfileNotFound(id=id)
def device_profile_list_by_filters(
self, context, filters, sort_key='created_at', sort_dir='desc',
limit=None, marker=None, join_columns=None):
if limit == 0:
return []
query_prefix = model_query(context, models.DeviceProfile)
filters = copy.deepcopy(filters)
exact_match_filter_names = ['uuid', 'id', 'name']
# Filter the query
query_prefix = self._exact_filter(models.DeviceProfile, query_prefix,
filters, exact_match_filter_names)
if query_prefix is None:
return []
return _paginate_query(context, models.DeviceProfile, limit, marker,
sort_key, sort_dir, query_prefix)
def device_profile_list(self, context):
query = model_query(context, models.DeviceProfile)
return _paginate_query(context, models.DeviceProfile)
def device_profile_update(self, context, uuid, values):
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing DeviceProfile.")
raise exception.InvalidParameterValue(err=msg)
try:
return self._do_update_device_profile(context, uuid, values)
except db_exc.DBDuplicateEntry as e:
if 'name' in e.columns:
raise exception.DuplicateDeviceProfileName(name=values['name'])
@oslo_db_api.retry_on_deadlock
def _do_update_device_profile(self, context, uuid, values):
with _session_for_write():
query = model_query(context, models.DeviceProfile)
query = add_identity_filter(query, uuid)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.DeviceProfileNotFound(uuid=uuid)
ref.update(values)
return ref
@oslo_db_api.retry_on_deadlock
def device_profile_delete(self, context, uuid):
with _session_for_write():
query = model_query(context, models.DeviceProfile)
query = add_identity_filter(query, uuid)
count = query.delete()
if count != 1:
raise exception.DeviceProfileNotFound(uuid=uuid)
def deployable_create(self, context, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
......
......@@ -75,7 +75,7 @@ class Device(Base):
id = Column(Integer, primary_key=True)
uuid = Column(String(36), nullable=False, unique=True)
type = Column(String(255), nullable=False)
type = Column(Enum('GPU', 'FPGA', name='device_type'), nullable=False)
vendor = Column(String(255), nullable=False)
model = Column(String(255), nullable=False)
std_board_info = Column(Text, nullable=True)
......
......@@ -25,10 +25,11 @@ def register_all():
# NOTE(danms): You must make sure your object gets imported in this
# function in order for it to be registered by services that may
# need to receive it via RPC.
__import__('cyborg.objects.accelerator')
__import__('cyborg.objects.deployable')
__import__('cyborg.objects.attribute')
__import__('cyborg.objects.arq')
__import__('cyborg.objects.ext_arq')
__import__('cyborg.objects.attach_handle')
__import__('cyborg.objects.control_path')
__import__('cyborg.objects.device')
__import__('cyborg.objects.device_profile')
# -*- encoding: utf-8 -*-
# Copyright (c) 2019 ZTE Corporation
#
# 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.
from oslo_log import log as logging
from oslo_versionedobjects import base as object_base
from cyborg.db import api as dbapi
from cyborg.objects import base
from cyborg.objects import fields as object_fields
LOG = logging.getLogger(__name__)
DEVICE_TYPE = ["GPU", "FPGA"]
@base.CyborgObjectRegistry.register
class Device(base.CyborgObject, object_base.VersionedObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = dbapi.get_instance()
fields = {
'id': object_fields.IntegerField(nullable=False),
'uuid': object_fields.UUIDField(nullable=False),
'type': object_fields.EnumField(valid_values=DEVICE_TYPE,
nullable=False),
'vendor': object_fields.StringField(nullable=False),
'model': object_fields.StringField(nullable=False),
'std_board_info': object_fields.StringField(nullable=True),
'vendor_board_info': object_fields.StringField(nullable=True),
'hostname': object_fields.StringField(nullable=False),
}
def create(self, context):
"""Create a device record in the DB."""
values = self.obj_get_changes()
db_device = self.dbapi.device_create(context, values)
self._from_db_object(self, db_device)
@classmethod
def get(cls, context, uuid):
"""Find a DB Device and return an Obj Device."""
db_device = cls.dbapi.device_get(context, uuid)
obj_device = cls._from_db_object(cls(context), db_device)
return obj_device
@classmethod
def list(cls, context, filters={}):
"""Return a list of Device objects."""
if filters:
sort_dir = filters.pop('sort_dir', 'desc')
sort_key = filters.pop('sort_key', 'create_at')
limit = filters.pop('limit', None)
marker = filters.pop('marker_obj', None)
db_devices = cls.dbapi.device_list_by_filters(context, filters,
sort_dir=sort_dir,
sort_key=sort_key,
limit=limit,
marker=marker)
else:
db_devices = cls.dbapi.device_list(context)
return cls._from_db_object_list(db_devices, context)
def save(self, context):
"""Update a Device record in the DB."""
updates = self.obj_get_changes()
db_device = self.dbapi.device_update(context, self.uuid, updates)
self._from_db_object(self, db_device)
def destroy(self, context):
"""Delete the Device from the DB."""
self.dbapi.device_delete(context, self.uuid)
self.obj_reset_changes()
# -*- encoding: utf-8 -*-
# Copyright (c) 2019 ZTE Corporation
#
# 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.
from oslo_log import log as logging
from oslo_versionedobjects import base as object_base
from cyborg.db import api as dbapi
from cyborg.objects import base
from cyborg.objects import fields as object_fields
LOG = logging.getLogger(__name__)
@base.CyborgObjectRegistry.register
class DeviceProfile(base.CyborgObject, object_base.VersionedObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = dbapi.get_instance()
fields = {
'id': object_fields.IntegerField(nullable=False),
'uuid': object_fields.UUIDField(nullable=False),
'name': object_fields.StringField(nullable=False),
'profile_json': object_fields.StringField(nullable=False),
}
def create(self, context):
"""Create a device_profile record in the DB."""
values = self.obj_get_changes()
db_device_profile = self.dbapi.device_profile_create(context, values)
self._from_db_object(self, db_device_profile)
@classmethod
def get(cls, context, uuid):
"""Find a DB Device_profile and return a Obj Device_profile."""
db_device_profile = cls.dbapi.device_profile_get_by_uuid(context, uuid)
obj_device_profile = cls._from_db_object(cls(context),
db_device_profile)
return obj_device_profile
@classmethod
def list(cls, context, filters={}):
"""Return a list of Device_profile objects."""
if filters:
sort_dir = filters.pop('sort_dir', 'desc')
sort_key = filters.pop('sort_key', 'create_at')
limit = filters.pop('limit', None)
marker = filters.pop('marker_obj', None)
db_device_profiles = cls.dbapi.device_profile_list_by_filters(
context, filters, sort_dir=sort_dir, sort_key=sort_key,
limit=limit, marker=marker)
else:
db_device_profiles = cls.dbapi.device_profile_list(context)
return cls._from_db_object_list(db_device_profiles, context)
def save(self, context):
"""Update a Device_profile record in the DB."""
updates = self.obj_get_changes()
db_device_profile = self.dbapi.device_profile_update(context,
self.uuid,
updates)
self._from_db_object(self, db_device_profile)
def destroy(self, context):
"""Delete the Device_profile from the DB."""
self.dbapi.device_profile_delete(context, self.uuid)
self.obj_reset_changes()
@classmethod
def get_by_id(cls, context, id):
"""Find a device_profile and return an Obj DeviceProfile."""
db_dp = cls.dbapi.device_profile_get_by_id(context, id)
obj_dp = cls._from_db_object(cls(context), db_dp)
return obj_dp
......@@ -110,3 +110,38 @@ def get_test_control_path(**kw):
'created_at': kw.get('create_at', None),
'updated_at': kw.get('updated_at', None),
}
def get_test_device(**kw):
return {
'id': kw.get('id', 1),
'uuid': kw.get('uuid', '83f92afa-1aa8-4548-a3a3-d218ff71d768'),
'type': kw.get('type', 'FPGA'),
'vendor': kw.get('vendor', 'Intel'),
# FIXME(Yumeng) should give details of std_board_info,
# vendor_board_info examples once they are determined.
'model': kw.get('model', 'PAC Arria 10'),
'std_board_info': kw.get('std_board_info',
'dictionary with standard fields'),
'vendor_board_info': kw.get('vendor_board_info',
'dictionary with vendor specific fields'),
'hostname': kw.get('hostname', 'hostname'),
'created_at': kw.get('create_at', None),
'updated_at': kw.get('updated_at', None),
}
def get_test_device_profile(**kw):
return {
'id': kw.get('id', 1),
'uuid': kw.get('uuid', 'c0f43d55-03bf-4831-8639-9bbdb6be2478'),
'name': kw.get('name', 'name'),
'profile_json': kw.get(
'profile_json',
'{"version": "1.0", \
"groups": [{"resources:CUSTOM_ACCELERATOR_FPGA": "1"}, \
{"trait:CUSTOM_FPGA_INTEL_PAC_ARRIA10": "required"}, \
{"trait:CUSTOM_FUNCTION_ID_3AFB": "required"}]}'),
'created_at': kw.get('create_at', None),
'updated_at': kw.get('updated_at', None),
}
# Copyright 2019 Intel, Inc.
#
# 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 cyborg import objects
from cyborg.tests.unit.db import base
from cyborg.tests.unit.db import utils
class TestDeviceObject(base.DbTestCase):
def setUp(self):
super(TestDeviceObject, self).setUp()
self.fake_device = utils.get_test_device()
def test_get(self):
uuid = self.fake_device['uuid']
with mock.patch.object(self.dbapi, 'device_get',
autospec=True) as mock_device_get:
mock_device_get.return_value = self.fake_device
device = objects.Device.get(self.context, uuid)
mock_device_get.assert_called_once_with(self.context, uuid)
self.assertEqual(self.context, device._context)
def test_list(self):
with mock.patch.object(self.dbapi, 'device_list',
autospec=True) as mock_device_list:
mock_device_list.return_value = [self.fake_device]
devices = objects.Device.list(self.context)
self.assertEqual(1, mock_device_list.call_count)
self.assertEqual(1, len(devices))
self.assertIsInstance(devices[0], objects.Device)
self.assertEqual(self.context, devices[0]._context)
def test_create(self):
with mock.patch.object(self.dbapi, 'device_create',
autospec=True) as mock_device_create:
mock_device_create.return_value = self.fake_device
device = objects.Device(self.context,
**self.fake_device)
device.create(self.context)
mock_device_create.assert_called_once_with(
self.context, self.fake_device)
self.assertEqual(self.context, device._context)
def test_destroy(self):
uuid = self.fake_device['uuid']
with mock.patch.object(self.dbapi, 'device_get',
autospec=True) as mock_device_get:
mock_device_get.return_value = self.fake_device
with mock.patch.object(self.dbapi, 'device_delete',
autospec=True) as mock_device_delete:
device = objects.Device.get(self.context, uuid)
device.destroy(self.context)
mock_device_delete.assert_called_once_with(self.context,
uuid)
self.assertEqual(self.context, device._context)
def test_update(self):
uuid = self.fake_device['uuid']
with mock.patch.object(self.dbapi, 'device_get',
autospec=True) as mock_device_get:
mock_device_get.return_value = self.fake_device
with mock.patch.object(self.dbapi, 'device_update',
autospec=True) as mock_device_update:
fake = self.fake_device
fake["vendor_board_info"] = "new_vendor_board_info"
mock_device_update.return_value = fake
device = objects.Device.get(self.context, uuid)
device.vendor_board_info = 'new_vendor_board_info'
device.save(self.context)
mock_device_get.assert_called_once_with(self.context,
uuid)
mock_device_update.assert_called_once_with(
self.context, uuid,
{'vendor_board_info': 'new_vendor_board_info'})
self.assertEqual(self.context, device._context)
# Copyright 2019 Intel, Inc.
#
# 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 cyborg import objects
from cyborg.tests.unit.db import base
from cyborg.tests.unit.db import utils
class TestDeviceProfileObject(base.DbTestCase):
def setUp(self):
super(TestDeviceProfileObject, self).setUp()
self.fake_device_profile = utils.get_test_device_profile()
def test_get_by_uuid(self):
uuid = self.fake_device_profile['uuid']
with mock.patch.object(self.dbapi, 'device_profile_get_by_uuid',
autospec=True) as mock_device_profile_get:
mock_device_profile_get.return_value = self.fake_device_profile
device_profile = objects.DeviceProfile.get(self.context, uuid)
mock_device_profile_get.assert_called_once_with(self.context, uuid)
self.assertEqual(self.context, device_profile._context)
def test_get_by_id(self):
id = self.fake_device_profile['id']
with mock.patch.object(self.dbapi, 'device_profile_get_by_id',
autospec=True) as mock_device_profile_get:
mock_device_profile_get.return_value = self.fake_device_profile
device_profile = objects.DeviceProfile.get_by_id(self.context, id)
mock_device_profile_get.assert_called_once_with(self.context, id)
self.assertEqual(self.context, device_profile._context)
def test_list(self):
with mock.patch.object(self.dbapi, 'device_profile_list',
autospec=True) as mock_device_profile_list:
mock_device_profile_list.return_value = [self.fake_device_profile]
device_profiles = objects.DeviceProfile.list(self.context)
self.assertEqual(1, mock_device_profile_list.call_count)
self.assertEqual(1, len(device_profiles))
self.assertIsInstance(device_profiles[0], objects.DeviceProfile)
self.assertEqual(self.context, device_profiles[0]._context)
def test_create(self):
with mock.patch.object(self.dbapi, 'device_profile_create',
autospec=True) as mock_device_profile_create:
mock_device_profile_create.return_value = self.fake_device_profile
device_profile = objects.DeviceProfile(self.context,
**self.fake_device_profile)
device_profile.create(self.context)
mock_device_profile_create.assert_called_once_with(
self.context, self.fake_device_profile)
self.assertEqual(self.context, device_profile._context)
def test_destroy(self):
uuid = self.fake_device_profile['uuid']
with mock.patch.object(self.dbapi, 'device_profile_get_by_uuid',
autospec=True) as mock_device_profile_get:
mock_device_profile_get.return_value = self.fake_device_profile
with mock.patch.object(self.dbapi, 'device_profile_delete',
autospec=True) as m_device_profile_delete:
device_profile = objects.DeviceProfile.get(self.context, uuid)
device_profile.destroy(self.context)
m_device_profile_delete.assert_called_once_with(
self.context, uuid)
self.assertEqual(self.context, device_profile._context)
def test_update(self):
uuid = self.fake_device_profile['uuid']
with mock.patch.object(self.dbapi, 'device_profile_get_by_uuid',
autospec=True) as mock_device_profile_get:
mock_device_profile_get.return_value = self.fake_device_profile
with mock.patch.object(self.dbapi, 'device_profile_update',
autospec=True) as m_device_profile_update:
fake = self.fake_device_profile
fake["profile_json"] = '{"version": 2.0,}'
m_device_profile_update.return_value = fake
dev_prof = objects.DeviceProfile.get(self.context, uuid)
dev_prof.profile_json = '{"version": 2.0,}'
dev_prof.save(self.context)
mock_device_profile_get.assert_called_once_with(self.context,
uuid)
m_device_profile_update.assert_called_once_with(
self.context, uuid,
{'profile_json': '{"version": 2.0,}'})
self.assertEqual(self.context, dev_prof._context)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment