summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvladimir.p <vladimir@zadarastorage.com>2011-08-18 15:42:30 -0700
committervladimir.p <vladimir@zadarastorage.com>2011-08-18 15:42:30 -0700
commitef3f02fb37d49ccf6099e012bc27b87d7859a306 (patch)
tree6a0cf4dd0ddff50dfd7a1fd3f1e427650b1ceba7
parentb703b33cdd48c2409205504ef09cc91d287862bf (diff)
added volume metadata. Fixed test_volume_types_extra_specs
-rw-r--r--nova/db/api.py18
-rw-r--r--nova/db/sqlalchemy/api.py140
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/037_add_volume_types_and_extradata.py20
-rw-r--r--nova/db/sqlalchemy/models.py15
-rw-r--r--nova/exception.py5
-rw-r--r--nova/tests/test_volume_types_extra_specs.py6
6 files changed, 201 insertions, 3 deletions
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)