Add ComputeDisk object

This is a general compute disk object, where we will save bare
metal disks for scheduling.

Change-Id: Ie2870cb3a3003073baed9c5713745f17918b5082
This commit is contained in:
Zhenguo Niu 2017-03-30 17:19:19 +08:00
parent edffb25664
commit 4b3a7dc31c
12 changed files with 459 additions and 0 deletions

View File

@ -180,6 +180,14 @@ class ComputePortNotFound(NotFound):
msg_fmt = _("ComputePort %(port)s could not be found.")
class ComputeDiskAlreadyExists(MoganException):
_msg_fmt = _("ComputeDisk with disk_uuid %(disk)s already exists.")
class ComputeDiskNotFound(NotFound):
msg_fmt = _("ComputeDisk %(disk)s could not be found.")
class NodeNotFound(NotFound):
msg_fmt = _("Node associated with instance %(instance)s "
"could not be found.")

View File

@ -132,6 +132,31 @@ class Connection(object):
def compute_port_update(self, context, port_uuid, values):
"""Update a compute port."""
# Compute disks
@abc.abstractmethod
def compute_disk_create(self, context, values):
"""Create a new compute disk."""
@abc.abstractmethod
def compute_disk_get(self, context, disk_uuid):
"""Get compute disk by disk uuid."""
@abc.abstractmethod
def compute_disk_get_all(self, context):
"""Get all compute disks."""
@abc.abstractmethod
def compute_disk_get_by_node_uuid(self, context, node_uuid):
"""Get compute disks by node_uuid."""
@abc.abstractmethod
def compute_disk_destroy(self, context, disk_uuid):
"""Delete a compute disk."""
@abc.abstractmethod
def compute_disk_update(self, context, disk_uuid, values):
"""Update a compute disk."""
# Instance Type extra specs
@abc.abstractmethod
def extra_specs_update_or_create(self, context,

View File

@ -126,6 +126,22 @@ def upgrade():
mysql_ENGINE='InnoDB',
mysql_DEFAULT_CHARSET='UTF8'
)
op.create_table(
'compute_disks',
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('disk_type', sa.String(length=255), nullable=False),
sa.Column('size_gb', sa.Integer(), nullable=False),
sa.Column('disk_uuid', sa.String(length=36), nullable=False),
sa.Column('node_uuid', sa.String(length=36), nullable=False),
sa.Column('extra_specs', sa.Text(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('disk_uuid', name='uniq_compute_disks0disk_uuid'),
sa.ForeignKeyConstraint(['node_uuid'], ['compute_nodes.node_uuid'], ),
mysql_ENGINE='InnoDB',
mysql_DEFAULT_CHARSET='UTF8'
)
op.create_table(
'instance_nics',
sa.Column('created_at', sa.DateTime(), nullable=True),

View File

@ -287,6 +287,10 @@ class Connection(api.Connection):
node_uuid=node_uuid)
port_query.delete()
disk_query = model_query(context, models.ComputeDisk).filter_by(
node_uuid=node_uuid)
disk_query.delete()
count = query.delete()
if count != 1:
raise exception.ComputeNodeNotFound(node=node_uuid)
@ -355,6 +359,57 @@ class Connection(api.Connection):
ref.update(values)
return ref
def compute_disk_create(self, context, values):
compute_disk = models.ComputeDisk()
compute_disk.update(values)
with _session_for_write() as session:
try:
session.add(compute_disk)
session.flush()
except db_exc.DBDuplicateEntry:
raise exception.ComputeDiskAlreadyExists(
disk=values['disk_uuid'])
return compute_disk
def compute_disk_get(self, context, disk_uuid):
query = model_query(
context,
models.ComputeDisk).filter_by(disk_uuid=disk_uuid)
try:
return query.one()
except NoResultFound:
raise exception.ComputeDiskNotFound(disk=disk_uuid)
def compute_disk_get_all(self, context):
return model_query(context, models.ComputeDisk)
def compute_disk_get_by_node_uuid(self, context, node_uuid):
return model_query(context, models.ComputeDisk).filter_by(
node_uuid=node_uuid).all()
def compute_disk_destroy(self, context, disk_uuid):
with _session_for_write():
query = model_query(
context,
models.ComputeDisk).filter_by(disk_uuid=disk_uuid)
count = query.delete()
if count != 1:
raise exception.ComputeDiskNotFound(disk=disk_uuid)
def compute_disk_update(self, context, disk_uuid, values):
with _session_for_write():
query = model_query(
context,
models.ComputeDisk).filter_by(disk_uuid=disk_uuid)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.ComputeDiskNotFound(disk=disk_uuid)
ref.update(values)
return ref
def extra_specs_update_or_create(self, context,
instance_type_uuid, specs,
max_retries=10):

View File

@ -131,6 +131,28 @@ class ComputePort(Base):
primaryjoin='ComputeNode.node_uuid == ComputePort.node_uuid')
class ComputeDisk(Base):
"""Represents the compute disks."""
__tablename__ = 'compute_disks'
__table_args__ = (
schema.UniqueConstraint('disk_uuid',
name='uniq_compute_disks0disk_uuid'),
table_args()
)
id = Column(Integer, primary_key=True)
disk_type = Column(String(255), nullable=False)
size_gb = Column(Integer, nullable=False)
disk_uuid = Column(String(36), nullable=False)
node_uuid = Column(String(36), nullable=False)
extra_specs = Column(db_types.JsonEncodedDict)
_node = orm.relationship(
"ComputeNode",
backref='disks',
foreign_keys=node_uuid,
primaryjoin='ComputeNode.node_uuid == ComputeDisk.node_uuid')
class InstanceNic(Base):
"""Represents the NIC info for instances."""

View File

@ -31,3 +31,4 @@ def register_all():
__import__('mogan.objects.instance_fault')
__import__('mogan.objects.compute_node')
__import__('mogan.objects.compute_port')
__import__('mogan.objects.compute_disk')

View File

@ -0,0 +1,95 @@
# 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.
from oslo_versionedobjects import base as object_base
from mogan.db import api as dbapi
from mogan.objects import base
from mogan.objects import fields as object_fields
@base.MoganObjectRegistry.register
class ComputeDisk(base.MoganObject, object_base.VersionedObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = dbapi.get_instance()
fields = {
'id': object_fields.IntegerField(read_only=True),
'disk_type': object_fields.StringField(),
'size_gb': object_fields.IntegerField(),
'disk_uuid': object_fields.UUIDField(read_only=True),
'node_uuid': object_fields.UUIDField(read_only=True),
'extra_specs': object_fields.FlexibleDictField(nullable=True),
}
@classmethod
def list(cls, context):
"""Return a list of ComputeDisk objects."""
db_compute_disks = cls.dbapi.compute_disk_get_all(context)
return cls._from_db_object_list(context, db_compute_disks)
@classmethod
def get(cls, context, disk_uuid):
"""Find a compute disk and return a ComputeDisk object."""
db_compute_disk = cls.dbapi.compute_disk_get(context, disk_uuid)
compute_disk = cls._from_db_object(context, cls(context),
db_compute_disk)
return compute_disk
def create(self, context=None):
"""Create a ComputeDisk record in the DB."""
values = self.obj_get_changes()
db_compute_disk = self.dbapi.compute_disk_create(context, values)
self._from_db_object(context, self, db_compute_disk)
def destroy(self, context=None):
"""Delete the ComputeDisk from the DB."""
self.dbapi.compute_disk_destroy(context, self.disk_uuid)
self.obj_reset_changes()
def save(self, context=None):
"""Save updates to this ComputeDisk."""
updates = self.obj_get_changes()
self.dbapi.compute_disk_update(context, self.disk_uuid, updates)
self.obj_reset_changes()
def refresh(self, context=None):
"""Refresh the object by re-fetching from the DB."""
current = self.__class__.get(context, self.disk_uuid)
self.obj_refresh(current)
@base.MoganObjectRegistry.register
class ComputeDiskList(object_base.ObjectListBase, base.MoganObject,
object_base.VersionedObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = dbapi.get_instance()
fields = {
'objects': object_fields.ListOfObjectsField('ComputeDisk')
}
@classmethod
def get_by_node_uuid(cls, context, node_uuid):
db_disks = cls.dbapi.compute_disk_get_by_node_uuid(
context, node_uuid)
return object_base.obj_make_list(context, cls(context),
ComputeDisk, db_disks)

View File

@ -0,0 +1,78 @@
# 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.
"""Tests for manipulating ComputeDisks via the DB API"""
from oslo_utils import uuidutils
import six
from mogan.common import exception
from mogan.tests.unit.db import base
from mogan.tests.unit.db import utils
class DbComputeDiskTestCase(base.DbTestCase):
def test_compute_disk_create(self):
utils.create_test_compute_disk()
def test_compute_disk_create_with_same_uuid(self):
utils.create_test_compute_disk(disk_uuid='uuid')
self.assertRaises(exception.ComputeDiskAlreadyExists,
utils.create_test_compute_disk,
disk_uuid='uuid')
def test_compute_disk_get_by_uuid(self):
disk = utils.create_test_compute_disk()
res = self.dbapi.compute_disk_get(self.context, disk.disk_uuid)
self.assertEqual(disk.disk_uuid, res.disk_uuid)
def test_compute_disk_get_not_exist(self):
self.assertRaises(exception.ComputeDiskNotFound,
self.dbapi.compute_disk_get,
self.context,
'12345678-9999-0000-aaaa-123456789012')
def test_compute_disk_get_all(self):
disk_uuids = []
for i in range(0, 3):
disk = utils.create_test_compute_disk(
disk_uuid=uuidutils.generate_uuid())
disk_uuids.append(six.text_type(disk['disk_uuid']))
res = self.dbapi.compute_disk_get_all(self.context)
res_uuids = [r.disk_uuid for r in res]
self.assertItemsEqual(disk_uuids, res_uuids)
def test_compute_disk_destroy(self):
disk = utils.create_test_compute_disk()
self.dbapi.compute_disk_destroy(self.context, disk.disk_uuid)
self.assertRaises(exception.ComputeDiskNotFound,
self.dbapi.compute_disk_get,
self.context,
disk.disk_uuid)
def test_compute_disk_destroy_not_exist(self):
self.assertRaises(exception.ComputeDiskNotFound,
self.dbapi.compute_disk_destroy,
self.context,
'12345678-9999-0000-aaaa-123456789012')
def test_compute_disk_update(self):
disk = utils.create_test_compute_disk()
res = self.dbapi.compute_disk_update(self.context,
disk.disk_uuid,
{'disk_type': 'foo'})
self.assertEqual('foo', res.disk_type)

View File

@ -160,6 +160,40 @@ def create_test_compute_port(context={}, **kw):
return dbapi.compute_port_create(context, port)
def get_test_compute_disk(**kw):
return {
'id': kw.get('id', 123),
'disk_type': kw.get('disk_type', 'SSD'),
'size_gb': kw.get('size_gb', 100),
'disk_uuid': kw.get('disk_uuid',
'f978ef48-d4af-4dad-beec-e6174309bc73'),
'node_uuid': kw.get('node_uuid',
'f978ef48-d4af-4dad-beec-e6174309bc71'),
'extra_specs': kw.get('extra_specs', {}),
'updated_at': kw.get('updated_at'),
'created_at': kw.get('created_at'),
}
def create_test_compute_disk(context={}, **kw):
"""Create test compute disk entry in DB and return ComputeDisk DB object.
Function to be used to create test ComputeDisk objects in the database.
:param context: The request context, for access checks.
:param kw: kwargs with overriding values for port's attributes.
:returns: Test ComputeDisk DB object.
"""
disk = get_test_compute_disk(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del disk['id']
dbapi = db_api.get_instance()
return dbapi.compute_disk_create(context, disk)
def get_test_instance_type(**kw):
return {
'uuid': kw.get('uuid', uuidutils.generate_uuid()),

View File

@ -0,0 +1,94 @@
# 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 copy
import mock
from oslo_context import context
from mogan import objects
from mogan.tests.unit.db import base
from mogan.tests.unit.db import utils
from mogan.tests.unit.objects import utils as obj_utils
class TestComputeDiskObject(base.DbTestCase):
def setUp(self):
super(TestComputeDiskObject, self).setUp()
self.ctxt = context.get_admin_context()
self.fake_disk = utils.get_test_compute_disk(context=self.ctxt)
self.disk = obj_utils.get_test_compute_disk(
self.ctxt, **self.fake_disk)
def test_get(self):
disk_uuid = self.fake_disk['disk_uuid']
with mock.patch.object(self.dbapi, 'compute_disk_get',
autospec=True) as mock_disk_get:
mock_disk_get.return_value = self.fake_disk
disk = objects.ComputeDisk.get(self.context, disk_uuid)
mock_disk_get.assert_called_once_with(self.context, disk_uuid)
self.assertEqual(self.context, disk._context)
def test_list(self):
with mock.patch.object(self.dbapi, 'compute_disk_get_all',
autospec=True) as mock_disk_get_all:
mock_disk_get_all.return_value = [self.fake_disk]
disks = objects.ComputeDisk.list(self.context)
mock_disk_get_all.assert_called_once_with(self.context)
self.assertIsInstance(disks[0], objects.ComputeDisk)
self.assertEqual(self.context, disks[0]._context)
def test_create(self):
with mock.patch.object(self.dbapi, 'compute_disk_create',
autospec=True) as mock_disk_create:
mock_disk_create.return_value = self.fake_disk
disk = objects.ComputeDisk(self.context, **self.fake_disk)
disk.obj_get_changes()
disk.create(self.context)
expected_called = copy.deepcopy(self.fake_disk)
mock_disk_create.assert_called_once_with(self.context,
expected_called)
self.assertEqual(self.fake_disk['disk_uuid'], disk['disk_uuid'])
def test_destroy(self):
uuid = self.fake_disk['disk_uuid']
with mock.patch.object(self.dbapi, 'compute_disk_destroy',
autospec=True) as mock_disk_destroy:
disk = objects.ComputeDisk(self.context, **self.fake_disk)
disk.destroy(self.context)
mock_disk_destroy.assert_called_once_with(self.context, uuid)
def test_save(self):
uuid = self.fake_disk['disk_uuid']
with mock.patch.object(self.dbapi, 'compute_disk_update',
autospec=True) as mock_disk_update:
mock_disk_update.return_value = self.fake_disk
disk = objects.ComputeDisk(self.context, **self.fake_disk)
updates = disk.obj_get_changes()
disk.save(self.context)
mock_disk_update.assert_called_once_with(
self.context, uuid, updates)
def test_save_after_refresh(self):
db_disk = utils.create_test_compute_disk(context=self.ctxt)
disk = objects.ComputeDisk.get(self.context, db_disk.disk_uuid)
disk.refresh(self.context)
disk.disk_type = 'refresh'
disk.save(self.context)

View File

@ -387,6 +387,8 @@ expected_object_fingerprints = {
'ComputeNodeList': '1.0-33a2e1bb91ad4082f9f63429b77c1244',
'ComputePort': '1.0-ca4c1817ad7324286813f2cfcdcf802e',
'ComputePortList': '1.0-33a2e1bb91ad4082f9f63429b77c1244',
'ComputeDisk': '1.0-4db463b2d9954dab8e4ac655f7be0f00',
'ComputeDiskList': '1.0-33a2e1bb91ad4082f9f63429b77c1244',
'InstanceFault': '1.0-6b5b01b2cc7b6b547837acb168ec6eb9',
'InstanceFaultList': '1.0-43e8aad0258652921f929934e9e048fd',
'InstanceType': '1.0-589b096651fcdb30898ff50f748dd948',

View File

@ -153,3 +153,32 @@ def create_test_compute_port(ctxt, **kw):
port = get_test_compute_port(ctxt, **kw)
port.create()
return port
def get_test_compute_disk(ctxt, **kw):
"""Return a ComputeDisk object with appropriate attributes.
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
kw['object_type'] = 'compute_disk'
get_db_compute_disk_checked = check_keyword_arguments(
db_utils.get_test_compute_disk)
db_disk = get_db_compute_disk_checked(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del db_disk['id']
disk = objects.ComputeDisk(ctxt, **db_disk)
return disk
def create_test_compute_disk(ctxt, **kw):
"""Create and return a test compute disk object.
Create a compute disk in the DB and return a ComputeDisk object with
appropriate attributes.
"""
disk = get_test_compute_disk(ctxt, **kw)
disk.create()
return disk