Following up patch of 6673dbb88f

This removes flavor extra specs table and related methods,
moving extra specs to flavor table to make things simple,
as we have already changed to use flavor PATCH method to
manage such things.

Change-Id: I1fb5445b7d194b50b052b77624a70e59af547911
This commit is contained in:
Zhenguo Niu 2017-06-14 12:48:01 +08:00
parent ef730d75df
commit 81f9d78d05
13 changed files with 9 additions and 283 deletions

View File

@ -1,6 +0,0 @@
{
"extra_specs":{
"key_1": "value_1",
"key_2": "value_2"
}
}

View File

@ -1,3 +0,0 @@
{
"key_3": "value_3"
}

View File

@ -1,7 +0,0 @@
{
"extra_specs":{
"key_1": "value_1",
"key_2": "value_2",
"key_3": "value_3"
}
}

View File

@ -255,16 +255,6 @@ class NoValidNode(MoganException):
_msg_fmt = _("No valid node was found. %(reason)s")
class FlavorExtraSpecUpdateCreateFailed(MoganException):
_msg_fmt = _("Flavor %(id)s extra spec cannot be updated or"
"created after %(retries)d retries.")
class FlavorExtraSpecsNotFound(NotFound):
_msg_fmt = _("Flavor %(flavor_id)s has no extra specs with "
"key %(extra_specs_key)s.")
class InterfacePlugException(MoganException):
_msg_fmt = _("Interface plugin failed")

View File

@ -141,15 +141,6 @@ server_policies = [
policy.RuleDefault('mogan:flavor_access:get_all',
'rule:allow',
description='Retrieve all flavor access'),
policy.RuleDefault('mogan:flavor_extra_specs:patch',
'rule:admin_api',
description='Create/Update flavor extra specs'),
policy.RuleDefault('mogan:flavor_extra_specs:delete',
'rule:admin_api',
description='Remove flavor extra specs'),
policy.RuleDefault('mogan:flavor_extra_specs:get_all',
'rule:allow',
description='Retrieve flavor extra specs'),
policy.RuleDefault('mogan:node:get_all',
'rule:admin_api',
description='Retrieve all compute nodes'),

View File

@ -157,28 +157,6 @@ class Connection(object):
def compute_disk_update(self, context, disk_uuid, values):
"""Update a compute disk."""
# Flavor extra specs
@abc.abstractmethod
def extra_specs_update_or_create(self, context,
flavor_uuid, extra_specs):
"""Create or update server type extra specs.
This adds or modifies the key/value pairs specified in the
extra specs dict argument
"""
@abc.abstractmethod
def flavor_extra_specs_get(self, context, type_id):
"""Get server type extra specs"""
@abc.abstractmethod
def type_extra_specs_delete(self, context, flavor_uuid, key):
"""Delete server type extra specs.
This deletes the key/value pairs specified in the
extra specs dict argument
"""
# Flavor access
@abc.abstractmethod
def flavor_access_add(self, context, flavor_id, project_id):

View File

@ -35,25 +35,12 @@ def upgrade():
sa.Column('uuid', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('extra_specs', sa.Text(), nullable=True),
sa.Column('is_public', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('uuid'),
mysql_ENGINE='InnoDB',
mysql_DEFAULT_CHARSET='UTF8'
)
op.create_table(
'flavor_extra_specs',
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('flavor_uuid', sa.String(length=36), nullable=False),
sa.Column('key', sa.String(length=255), nullable=False),
sa.Column('value', sa.String(length=255), nullable=False),
sa.ForeignKeyConstraint(['flavor_uuid'],
['flavors.uuid']),
sa.PrimaryKeyConstraint('id'),
mysql_ENGINE='InnoDB',
mysql_DEFAULT_CHARSET='UTF8'
)
op.create_table(
'flavor_projects',
sa.Column('created_at', sa.DateTime(), nullable=True),

View File

@ -100,11 +100,6 @@ def _dict_with_extra_fields(flavor_query):
"""Takes a server type query and returns it as a dictionary."""
flavor_dict = dict(flavor_query)
# extra specs
extra_specs = {x['key']: x['value']
for x in flavor_query['extra_specs']}
flavor_dict['extra_specs'] = extra_specs
# cpus
cpus = {}
for c in flavor_query['cpus']:
@ -190,7 +185,7 @@ class Connection(api.Connection):
def flavor_get(self, context, flavor_uuid):
query = model_query(context, models.Flavors).filter_by(
uuid=flavor_uuid).options(joinedload('extra_specs'))
uuid=flavor_uuid)
if not context.is_admin:
the_filter = [models.Flavors.is_public == true()]
@ -231,13 +226,7 @@ class Connection(api.Connection):
def flavor_destroy(self, context, flavor_uuid):
with _session_for_write():
# First clean up all extra specs related to this type
type_id = _type_get_id_from_type(context, flavor_uuid)
extra_query = model_query(
context,
models.FlavorExtraSpecs).filter_by(
flavor_uuid=type_id)
extra_query.delete()
# Clean up cpus related to this flavor
cpus_query = model_query(
@ -525,61 +514,6 @@ class Connection(api.Connection):
ref.update(values)
return ref
def extra_specs_update_or_create(self, context,
flavor_uuid, specs,
max_retries=10):
"""Create or update server type extra specs.
This adds or modifies the key/value pairs specified in the
extra specs dict argument
"""
for attempt in range(max_retries):
with _session_for_write() as session:
try:
spec_refs = model_query(
context, models.FlavorExtraSpecs). \
filter_by(flavor_uuid=flavor_uuid). \
filter(models.FlavorExtraSpecs.key.in_(
specs.keys())).with_lockmode('update').all()
existing_keys = set()
for spec_ref in spec_refs:
key = spec_ref["key"]
existing_keys.add(key)
spec_ref.update({"value": specs[key]})
for key, value in specs.items():
if key in existing_keys:
continue
spec_ref = models.FlavorExtraSpecs()
spec_ref.update(
{"key": key, "value": value,
"flavor_uuid": flavor_uuid})
session.add(spec_ref)
session.flush()
return specs
except db_exc.DBDuplicateEntry:
# a concurrent transaction has been committed,
# try again unless this was the last attempt
if attempt == max_retries - 1:
raise exception.FlavorExtraSpecUpdateCreateFailed(
id=flavor_uuid, retries=max_retries)
def flavor_extra_specs_get(self, context, type_id):
rows = _type_extra_specs_get_query(context, type_id).all()
return {row['key']: row['value'] for row in rows}
def type_extra_specs_delete(self, context, type_id, key):
result = _type_extra_specs_get_query(context, type_id). \
filter(models.FlavorExtraSpecs.key == key). \
delete(synchronize_session=False)
# did not find the extra spec
if result == 0:
raise exception.FlavorExtraSpecsNotFound(
extra_specs_key=key, flavor_id=type_id)
def flavor_access_get(self, context, flavor_id):
return _flavor_access_query(context, flavor_id)
@ -1023,11 +957,6 @@ def _type_get_id_from_type(context, type_id):
return result.uuid
def _type_extra_specs_get_query(context, type_id):
return model_query(context, models.FlavorExtraSpecs). \
filter_by(flavor_uuid=type_id)
def _flavor_access_query(context, flavor_id):
return model_query(context, models.FlavorProjects). \
filter_by(flavor_uuid=flavor_id)

View File

@ -178,6 +178,7 @@ class Flavors(Base):
uuid = Column(String(36), primary_key=True)
name = Column(String(255), nullable=False)
description = Column(String(255), nullable=True)
extra_specs = Column(db_types.JsonEncodedDict)
is_public = Column(Boolean, default=True)
servers = orm.relationship(
Server,
@ -208,29 +209,6 @@ class FlavorProjects(Base):
' == Flavors.uuid')
class FlavorExtraSpecs(Base):
"""Represents additional specs as key/value pairs for an flavor."""
__tablename__ = 'flavor_extra_specs'
__table_args__ = (
schema.UniqueConstraint(
"flavor_uuid", "key",
name=("uniq_flavor_extra_specs0"
"flavor_uuid")
),
{'mysql_collate': 'utf8_bin'},
)
id = Column(Integer, primary_key=True)
key = Column(String(255))
value = Column(String(255))
flavor_uuid = Column(String(36), ForeignKey('flavors.uuid'),
nullable=False)
flavor = orm.relationship(
Flavors, backref="extra_specs",
foreign_keys=flavor_uuid,
primaryjoin='FlavorExtraSpecs.flavor_uuid '
'== Flavors.uuid')
class FlavorCpus(Base):
"""Represents the flavor cpus."""

View File

@ -21,7 +21,7 @@ from mogan.objects import base
from mogan.objects import fields as object_fields
OPTIONAL_FIELDS = ['extra_specs', 'projects']
OPTIONAL_FIELDS = ['projects']
@base.MoganObjectRegistry.register
@ -40,13 +40,12 @@ class Flavor(base.MoganObject, object_base.VersionedObjectDictCompat):
'nics': object_fields.ListOfDictOfNullableStringsField(),
'disks': object_fields.ListOfDictOfNullableStringsField(),
'is_public': object_fields.BooleanField(),
'extra_specs': object_fields.FlexibleDictField(),
'extra_specs': object_fields.FlexibleDictField(nullable=True),
'projects': object_fields.ListOfStringsField(),
}
def __init__(self, *args, **kwargs):
super(Flavor, self).__init__(*args, **kwargs)
self._orig_extra_specs = {}
self._orig_projects = {}
@staticmethod
@ -62,9 +61,6 @@ class Flavor(base.MoganObject, object_base.VersionedObjectDictCompat):
value = value if value is not None else 0
flavor[name] = value
if 'extra_specs' in expected_attrs:
flavor.extra_specs = db_flavor['extra_specs']
if 'projects' in expected_attrs:
flavor._load_projects(context)
@ -79,10 +75,6 @@ class Flavor(base.MoganObject, object_base.VersionedObjectDictCompat):
def obj_reset_changes(self, fields=None, recursive=False):
super(Flavor, self).obj_reset_changes(fields=fields,
recursive=recursive)
if fields is None or 'extra_specs' in fields:
self._orig_extra_specs = (dict(self.extra_specs)
if self.obj_attr_is_set('extra_specs')
else {})
if fields is None or 'projects' in fields:
self._orig_projects = (list(self.projects)
if self.obj_attr_is_set('projects')
@ -90,9 +82,6 @@ class Flavor(base.MoganObject, object_base.VersionedObjectDictCompat):
def obj_what_changed(self):
changes = super(Flavor, self).obj_what_changed()
if ('extra_specs' in self and
self.extra_specs != self._orig_extra_specs):
changes.add('extra_specs')
if 'projects' in self and self.projects != self._orig_projects:
changes.add('projects')
return changes
@ -100,8 +89,7 @@ class Flavor(base.MoganObject, object_base.VersionedObjectDictCompat):
@staticmethod
def _from_db_object_list(db_objects, cls, context):
"""Converts a list of database entities to a list of formal objects."""
return [Flavor._from_db_object(context, cls(context), obj,
expected_attrs=['extra_specs'])
return [Flavor._from_db_object(context, cls(context), obj)
for obj in db_objects]
@classmethod
@ -116,15 +104,14 @@ class Flavor(base.MoganObject, object_base.VersionedObjectDictCompat):
db_flavor = cls.dbapi.flavor_get(context, flavor_uuid)
flavor = Flavor._from_db_object(
context, cls(context), db_flavor,
expected_attrs=['extra_specs', 'projects'])
expected_attrs=['projects'])
return flavor
def create(self, context=None):
"""Create a Flavor record in the DB."""
values = self.obj_get_changes()
db_flavor = self.dbapi.flavor_create(context, values)
self._from_db_object(context, self, db_flavor,
expected_attrs=['extra_specs'])
self._from_db_object(context, self, db_flavor)
def destroy(self, context=None):
"""Delete the Flavor from the DB."""
@ -134,23 +121,11 @@ class Flavor(base.MoganObject, object_base.VersionedObjectDictCompat):
def save(self, context=None):
updates = self.obj_get_changes()
projects = updates.pop('projects', None)
extra_specs = updates.pop('extra_specs', None)
updates.pop('cpus', None)
updates.pop('memory', None)
updates.pop('nics', None)
updates.pop('disks', None)
# extra specs
if extra_specs is not None:
deleted_keys = (set(self._orig_extra_specs.keys()) -
set(extra_specs.keys()))
added_keys = self.extra_specs
else:
added_keys = deleted_keys = None
if added_keys or deleted_keys:
self.save_extra_specs(context, self.extra_specs, deleted_keys)
# access projects
if projects is not None:
deleted_projects = set(self._orig_projects) - set(projects)
@ -163,24 +138,6 @@ class Flavor(base.MoganObject, object_base.VersionedObjectDictCompat):
self.dbapi.flavor_update(context, self.uuid, updates)
def save_extra_specs(self, context, to_add=None, to_delete=None):
"""Add or delete extra_specs.
:param:to_add: A dict of new keys to add/update
:param:to_delete: A list of keys to remove
"""
ident = self.uuid
to_add = to_add if to_add is not None else {}
to_delete = to_delete if to_delete is not None else []
if to_add:
self.dbapi.extra_specs_update_or_create(context, ident, to_add)
for key in to_delete:
self.dbapi.type_extra_specs_delete(context, ident, key)
self.obj_reset_changes(['extra_specs'])
def save_projects(self, context, to_add=None, to_delete=None):
"""Add or delete projects.

View File

@ -1,67 +0,0 @@
# Copyright 2016 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 Flavor Extra Specs via the DB API"""
from mogan.common import exception
from mogan.tests.unit.db import base
from mogan.tests.unit.db import utils
class DbFlavorExtraSpecsTestCase(base.DbTestCase):
def setUp(self):
super(DbFlavorExtraSpecsTestCase, self).setUp()
self.context = {}
self.flavor = utils.create_test_flavor()
self.specs = {'k1': 'v1', 'k2': 'v2'}
def test_create_extra_specs(self):
self.dbapi.extra_specs_update_or_create(
self.context, self.flavor['uuid'], self.specs)
def test_get_extra_specs(self):
self.dbapi.extra_specs_update_or_create(
self.context, self.flavor['uuid'], self.specs)
extra_specs = self.dbapi.flavor_extra_specs_get(
self.context, self.flavor['uuid'])
self.assertEqual(self.specs, extra_specs)
def test_get_extra_specs_empty(self):
extra_specs = self.dbapi.flavor_extra_specs_get(
self.context, self.flavor['uuid'])
self.assertEqual({}, extra_specs)
def test_destroy_extra_specs(self):
self.dbapi.extra_specs_update_or_create(
self.context, self.flavor['uuid'], self.specs)
self.dbapi.type_extra_specs_delete(
self.context, self.flavor['uuid'], 'k1')
extra_specs = self.dbapi.flavor_extra_specs_get(
self.context, self.flavor['uuid'])
self.assertEqual({'k2': 'v2'}, extra_specs)
def test_delete_extra_specs_does_not_exist(self):
self.dbapi.extra_specs_update_or_create(
self.context, self.flavor['uuid'], self.specs)
self.assertRaises(exception.FlavorExtraSpecsNotFound,
self.dbapi.type_extra_specs_delete,
self.context,
self.flavor['uuid'],
'k3')

View File

@ -76,7 +76,6 @@ class TestFlavorObject(base.DbTestCase):
flavor.name = 'changed_name'
updates = flavor.obj_get_changes()
flavor.save(self.context)
updates.pop('extra_specs', None)
updates.pop('cpus', None)
updates.pop('memory', None)
updates.pop('disks', None)

View File

@ -391,7 +391,7 @@ expected_object_fingerprints = {
'ComputeDiskList': '1.0-33a2e1bb91ad4082f9f63429b77c1244',
'ServerFault': '1.0-74349ff701259e4834b4e9dc2dac1b12',
'ServerFaultList': '1.0-43e8aad0258652921f929934e9e048fd',
'Flavor': '1.0-7cc125bfe2dda6e5ffa07651d58047cd',
'Flavor': '1.0-ec52868368ce4bbf77b0532897067c4d',
'MyObj': '1.1-aad62eedc5a5cc8bcaf2982c285e753f',
'ServerNic': '1.0-ebbd767c2f6a7f14bd524c6067f2b382',
'ServerNics': '1.0-33a2e1bb91ad4082f9f63429b77c1244',