From ef3f02fb37d49ccf6099e012bc27b87d7859a306 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Thu, 18 Aug 2011 15:42:30 -0700 Subject: added volume metadata. Fixed test_volume_types_extra_specs --- nova/db/api.py | 18 +++ nova/db/sqlalchemy/api.py | 140 +++++++++++++++++++++ .../versions/037_add_volume_types_and_extradata.py | 20 ++- nova/db/sqlalchemy/models.py | 15 +++ nova/exception.py | 5 + nova/tests/test_volume_types_extra_specs.py | 6 +- 6 files changed, 201 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index 47e73226a..494d27708 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1429,6 +1429,24 @@ def instance_type_extra_specs_update_or_create(context, instance_type_id, ################## +def volume_metadata_get(context, volume_id): + """Get all metadata for a volume.""" + return IMPL.volume_metadata_get(context, volume_id) + + +def volume_metadata_delete(context, volume_id, key): + """Delete the given metadata item.""" + IMPL.volume_metadata_delete(context, volume_id, key) + + +def volume_metadata_update(context, volume_id, metadata, delete): + """Update metadata if it exists, otherwise create it.""" + IMPL.volume_metadata_update(context, volume_id, metadata, delete) + + +################## + + def volume_type_create(context, values): """Create a new volume type.""" return IMPL.volume_type_create(context, values) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index a57133b72..143162fc6 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -132,6 +132,20 @@ def require_instance_exists(f): return wrapper +def require_volume_exists(f): + """Decorator to require the specified volume to exist. + + Requres the wrapped function to use context and volume_id as + their first two arguments. + """ + + def wrapper(context, volume_id, *args, **kwargs): + db.api.volume_get(context, volume_id) + return f(context, volume_id, *args, **kwargs) + wrapper.__name__ = f.__name__ + return wrapper + + ################### @@ -2083,6 +2097,8 @@ def volume_attached(context, volume_id, instance_id, mountpoint): @require_context def volume_create(context, values): + values['metadata'] = _metadata_refs(values.get('metadata')) + volume_ref = models.Volume() volume_ref.update(values) @@ -2119,6 +2135,11 @@ def volume_destroy(context, volume_id): session.query(models.IscsiTarget).\ filter_by(volume_id=volume_id).\ update({'volume_id': None}) + session.query(models.VolumeMetadata).\ + filter_by(volume_id=volume_id).\ + update({'deleted': True, + 'deleted_at': utils.utcnow(), + 'updated_at': literal_column('updated_at')}) @require_admin_context @@ -2142,12 +2163,16 @@ def volume_get(context, volume_id, session=None): if is_admin_context(context): result = session.query(models.Volume).\ options(joinedload('instance')).\ + options(joinedload('metadata')).\ + options(joinedload('volume_type')).\ filter_by(id=volume_id).\ filter_by(deleted=can_read_deleted(context)).\ first() elif is_user_context(context): result = session.query(models.Volume).\ options(joinedload('instance')).\ + options(joinedload('metadata')).\ + options(joinedload('volume_type')).\ filter_by(project_id=context.project_id).\ filter_by(id=volume_id).\ filter_by(deleted=False).\ @@ -2163,6 +2188,8 @@ def volume_get_all(context): session = get_session() return session.query(models.Volume).\ options(joinedload('instance')).\ + options(joinedload('metadata')).\ + options(joinedload('volume_type')).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -2172,6 +2199,8 @@ def volume_get_all_by_host(context, host): session = get_session() return session.query(models.Volume).\ options(joinedload('instance')).\ + options(joinedload('metadata')).\ + options(joinedload('volume_type')).\ filter_by(host=host).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -2181,6 +2210,8 @@ def volume_get_all_by_host(context, host): def volume_get_all_by_instance(context, instance_id): session = get_session() result = session.query(models.Volume).\ + options(joinedload('metadata')).\ + options(joinedload('volume_type')).\ filter_by(instance_id=instance_id).\ filter_by(deleted=False).\ all() @@ -2196,6 +2227,8 @@ def volume_get_all_by_project(context, project_id): session = get_session() return session.query(models.Volume).\ options(joinedload('instance')).\ + options(joinedload('metadata')).\ + options(joinedload('volume_type')).\ filter_by(project_id=project_id).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -2208,6 +2241,8 @@ def volume_get_instance(context, volume_id): filter_by(id=volume_id).\ filter_by(deleted=can_read_deleted(context)).\ options(joinedload('instance')).\ + options(joinedload('metadata')).\ + options(joinedload('volume_type')).\ first() if not result: raise exception.VolumeNotFound(volume_id=volume_id) @@ -2242,12 +2277,117 @@ def volume_get_iscsi_target_num(context, volume_id): @require_context def volume_update(context, volume_id, values): session = get_session() + metadata = values.get('metadata') + if metadata is not None: + volume_metadata_update(context, + volume_id, + values.pop('metadata'), + delete=True) with session.begin(): volume_ref = volume_get(context, volume_id, session=session) volume_ref.update(values) volume_ref.save(session=session) + +#################### + + +@require_context +@require_volume_exists +def volume_metadata_get(context, volume_id): + session = get_session() + + meta_results = session.query(models.VolumeMetadata).\ + filter_by(volume_id=volume_id).\ + filter_by(deleted=False).\ + all() + + meta_dict = {} + for i in meta_results: + meta_dict[i['key']] = i['value'] + return meta_dict + + +@require_context +@require_volume_exists +def volume_metadata_delete(context, volume_id, key): + session = get_session() + session.query(models.VolumeMetadata).\ + filter_by(volume_id=volume_id).\ + filter_by(key=key).\ + filter_by(deleted=False).\ + update({'deleted': True, + 'deleted_at': utils.utcnow(), + 'updated_at': literal_column('updated_at')}) + + +@require_context +@require_volume_exists +def volume_metadata_delete_all(context, volume_id): + session = get_session() + session.query(models.VolumeMetadata).\ + filter_by(volume_id=volume_id).\ + filter_by(deleted=False).\ + update({'deleted': True, + 'deleted_at': utils.utcnow(), + 'updated_at': literal_column('updated_at')}) + + +@require_context +@require_volume_exists +def volume_metadata_get_item(context, volume_id, key, session=None): + if not session: + session = get_session() + + meta_result = session.query(models.VolumeMetadata).\ + filter_by(volume_id=volume_id).\ + filter_by(key=key).\ + filter_by(deleted=False).\ + first() + + if not meta_result: + raise exception.VolumeMetadataNotFound(metadata_key=key, + volume_id=volume_id) + return meta_result + + +@require_context +@require_volume_exists +def volume_metadata_update(context, volume_id, metadata, delete): + session = get_session() + + # Set existing metadata to deleted if delete argument is True + if delete: + original_metadata = volume_metadata_get(context, volume_id) + for meta_key, meta_value in original_metadata.iteritems(): + if meta_key not in metadata: + meta_ref = volume_metadata_get_item(context, volume_id, + meta_key, session) + meta_ref.update({'deleted': True}) + meta_ref.save(session=session) + + meta_ref = None + + # Now update all existing items with new values, or create new meta objects + for meta_key, meta_value in metadata.iteritems(): + + # update the value whether it exists or not + item = {"value": meta_value} + + try: + meta_ref = volume_metadata_get_item(context, volume_id, + meta_key, session) + except exception.VolumeMetadataNotFound, e: + meta_ref = models.VolumeMetadata() + item.update({"key": meta_key, "volume_id": volume_id}) + + meta_ref.update(item) + meta_ref.save(session=session) + + return metadata + + ################### diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_add_volume_types_and_extradata.py b/nova/db/sqlalchemy/migrate_repo/versions/037_add_volume_types_and_extradata.py index ed8eeb172..fc365d2b2 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/037_add_volume_types_and_extradata.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/037_add_volume_types_and_extradata.py @@ -65,7 +65,25 @@ volume_type_extra_specs_table = Table('volume_type_extra_specs', meta, unicode_error=None, _warn_on_bytestring=False))) -new_tables = (volume_types, volume_type_extra_specs_table) +volume_metadata_table = Table('volume_metadata', meta, + Column('created_at', DateTime(timezone=False)), + Column('updated_at', DateTime(timezone=False)), + Column('deleted_at', DateTime(timezone=False)), + Column('deleted', Boolean(create_constraint=True, name=None)), + Column('id', Integer(), primary_key=True, nullable=False), + Column('volume_id', + Integer(), + ForeignKey('volumes.id'), + nullable=False), + Column('key', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False)), + Column('value', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False))) + + +new_tables = (volume_types, volume_type_extra_specs_table, volume_metadata_table) # # Tables to alter diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 08ce34647..99e6f412e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -315,6 +315,20 @@ class Volume(BASE, NovaBase): volume_type_id = Column(Integer) +class VolumeMetadata(BASE, NovaBase): + """Represents a metadata key/value pair for a volume""" + __tablename__ = 'volume_metadata' + id = Column(Integer, primary_key=True) + key = Column(String(255)) + value = Column(String(255)) + volume_id = Column(Integer, ForeignKey('volumes.id'), nullable=False) + volume = relationship(Volume, backref="metadata", + foreign_keys=volume_id, + primaryjoin='and_(' + 'VolumeMetadata.volume_id == Volume.id,' + 'VolumeMetadata.deleted == False)') + + class VolumeTypes(BASE, NovaBase): """Represent possible volume_types of volumes offered""" __tablename__ = "volume_types" @@ -824,6 +838,7 @@ def register_models(): Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, Project, Certificate, ConsolePool, Console, Zone, + VolumeMetadata, VolumeTypes, VolumeTypeExtraSpecs, AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration) engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: diff --git a/nova/exception.py b/nova/exception.py index ff4b7c80e..1b118f6f9 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -338,6 +338,11 @@ class VolumeNotFoundForInstance(VolumeNotFound): message = _("Volume not found for instance %(instance_id)s.") +class VolumeMetadataNotFound(NotFound): + message = _("Volume %(volume_id)s has no metadata with " + "key %(metadata_key)s.") + + class NoVolumeTypesFound(NotFound): message = _("Zero volume types found.") diff --git a/nova/tests/test_volume_types_extra_specs.py b/nova/tests/test_volume_types_extra_specs.py index c48ed789e..8d2aa2df3 100644 --- a/nova/tests/test_volume_types_extra_specs.py +++ b/nova/tests/test_volume_types_extra_specs.py @@ -37,6 +37,8 @@ class VolumeTypeExtraSpecsTestCase(test.TestCase): self.vol_type1['extra_specs'] = self.vol_type1_specs ref = db.api.volume_type_create(self.context, self.vol_type1) self.volume_type1_id = ref.id + for k, v in self.vol_type1_specs.iteritems(): + self.vol_type1_specs[k] = str(v) self.vol_type2_noextra = dict(name="TEST: Volume type without extra") ref = db.api.volume_type_create(self.context, self.vol_type2_noextra) @@ -71,7 +73,7 @@ class VolumeTypeExtraSpecsTestCase(test.TestCase): def test_volume_type_extra_specs_update(self): expected_specs = self.vol_type1_specs.copy() - expected_specs['vol_extra3'] = 4 + expected_specs['vol_extra3'] = "4" db.api.volume_type_extra_specs_update_or_create( context.get_admin_context(), self.volume_type1_id, @@ -89,7 +91,7 @@ class VolumeTypeExtraSpecsTestCase(test.TestCase): context.get_admin_context(), self.volume_type1_id, dict(vol_extra4="value4", - vol_extra5=5)) + vol_extra5="value5")) actual_specs = db.api.volume_type_extra_specs_get( context.get_admin_context(), self.volume_type1_id) -- cgit