summaryrefslogtreecommitdiffstats
path: root/nova/db
diff options
context:
space:
mode:
authordanwent@gmail.com <>2011-08-28 20:06:55 -0700
committerdanwent@gmail.com <>2011-08-28 20:06:55 -0700
commita4bd14f86f31ea82daad8e194b26e86e6dd37398 (patch)
tree3807a1131dc6a7ba7119782f356eb365f28d8b6c /nova/db
parent605fe4f19af3af830a2a8c82809e9ce5909c602d (diff)
parent0ef2581749f39fa4fd41c2376186418e730f0afb (diff)
merge trunk
Diffstat (limited to 'nova/db')
-rw-r--r--nova/db/api.py112
-rw-r--r--nova/db/sqlalchemy/api.py424
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py18
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py12
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py38
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/042_add_volume_types_and_extradata.py115
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/043_add_vsa_data.py75
-rw-r--r--nova/db/sqlalchemy/migration.py4
-rw-r--r--nova/db/sqlalchemy/models.py82
9 files changed, 864 insertions, 16 deletions
diff --git a/nova/db/api.py b/nova/db/api.py
index 17ef0bd0b..07d6e1095 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -49,7 +49,8 @@ flags.DEFINE_string('volume_name_template', 'volume-%08x',
'Template string to be used to generate instance names')
flags.DEFINE_string('snapshot_name_template', 'snapshot-%08x',
'Template string to be used to generate snapshot names')
-
+flags.DEFINE_string('vsa_name_template', 'vsa-%08x',
+ 'Template string to be used to generate VSA names')
IMPL = utils.LazyPluggable(FLAGS['db_backend'],
sqlalchemy='nova.db.sqlalchemy.api')
@@ -1446,3 +1447,112 @@ def instance_type_extra_specs_update_or_create(context, instance_type_id,
key/value pairs specified in the extra specs dict argument"""
IMPL.instance_type_extra_specs_update_or_create(context, instance_type_id,
extra_specs)
+
+
+##################
+
+
+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)
+
+
+def volume_type_get_all(context, inactive=False):
+ """Get all volume types."""
+ return IMPL.volume_type_get_all(context, inactive)
+
+
+def volume_type_get(context, id):
+ """Get volume type by id."""
+ return IMPL.volume_type_get(context, id)
+
+
+def volume_type_get_by_name(context, name):
+ """Get volume type by name."""
+ return IMPL.volume_type_get_by_name(context, name)
+
+
+def volume_type_destroy(context, name):
+ """Delete a volume type."""
+ return IMPL.volume_type_destroy(context, name)
+
+
+def volume_type_purge(context, name):
+ """Purges (removes) a volume type from DB.
+
+ Use volume_type_destroy for most cases
+
+ """
+ return IMPL.volume_type_purge(context, name)
+
+
+####################
+
+
+def volume_type_extra_specs_get(context, volume_type_id):
+ """Get all extra specs for a volume type."""
+ return IMPL.volume_type_extra_specs_get(context, volume_type_id)
+
+
+def volume_type_extra_specs_delete(context, volume_type_id, key):
+ """Delete the given extra specs item."""
+ IMPL.volume_type_extra_specs_delete(context, volume_type_id, key)
+
+
+def volume_type_extra_specs_update_or_create(context, volume_type_id,
+ extra_specs):
+ """Create or update volume type extra specs. This adds or modifies the
+ key/value pairs specified in the extra specs dict argument"""
+ IMPL.volume_type_extra_specs_update_or_create(context, volume_type_id,
+ extra_specs)
+
+
+####################
+
+
+def vsa_create(context, values):
+ """Creates Virtual Storage Array record."""
+ return IMPL.vsa_create(context, values)
+
+
+def vsa_update(context, vsa_id, values):
+ """Updates Virtual Storage Array record."""
+ return IMPL.vsa_update(context, vsa_id, values)
+
+
+def vsa_destroy(context, vsa_id):
+ """Deletes Virtual Storage Array record."""
+ return IMPL.vsa_destroy(context, vsa_id)
+
+
+def vsa_get(context, vsa_id):
+ """Get Virtual Storage Array record by ID."""
+ return IMPL.vsa_get(context, vsa_id)
+
+
+def vsa_get_all(context):
+ """Get all Virtual Storage Array records."""
+ return IMPL.vsa_get_all(context)
+
+
+def vsa_get_all_by_project(context, project_id):
+ """Get all Virtual Storage Array records by project ID."""
+ return IMPL.vsa_get_all_by_project(context, project_id)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 00af62682..09356e966 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
+
+
###################
@@ -1035,11 +1049,11 @@ def virtual_interface_delete_by_instance(context, instance_id):
###################
-def _metadata_refs(metadata_dict):
+def _metadata_refs(metadata_dict, meta_class):
metadata_refs = []
if metadata_dict:
for k, v in metadata_dict.iteritems():
- metadata_ref = models.InstanceMetadata()
+ metadata_ref = meta_class()
metadata_ref['key'] = k
metadata_ref['value'] = v
metadata_refs.append(metadata_ref)
@@ -1053,8 +1067,8 @@ def instance_create(context, values):
context - request context object
values - dict containing column values.
"""
- values['metadata'] = _metadata_refs(values.get('metadata'))
-
+ values['metadata'] = _metadata_refs(values.get('metadata'),
+ models.InstanceMetadata)
instance_ref = models.Instance()
instance_ref['uuid'] = str(utils.gen_uuid())
@@ -2173,6 +2187,8 @@ def volume_attached(context, volume_id, instance_id, mountpoint):
@require_context
def volume_create(context, values):
+ values['volume_metadata'] = _metadata_refs(values.get('metadata'),
+ models.VolumeMetadata)
volume_ref = models.Volume()
volume_ref.update(values)
@@ -2209,6 +2225,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
@@ -2232,12 +2253,16 @@ def volume_get(context, volume_id, session=None):
if is_admin_context(context):
result = session.query(models.Volume).\
options(joinedload('instance')).\
+ options(joinedload('volume_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('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(project_id=context.project_id).\
filter_by(id=volume_id).\
filter_by(deleted=False).\
@@ -2253,6 +2278,8 @@ def volume_get_all(context):
session = get_session()
return session.query(models.Volume).\
options(joinedload('instance')).\
+ options(joinedload('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -2262,6 +2289,8 @@ def volume_get_all_by_host(context, host):
session = get_session()
return session.query(models.Volume).\
options(joinedload('instance')).\
+ options(joinedload('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(host=host).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -2271,6 +2300,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('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(instance_id=instance_id).\
filter_by(deleted=False).\
all()
@@ -2286,6 +2317,8 @@ def volume_get_all_by_project(context, project_id):
session = get_session()
return session.query(models.Volume).\
options(joinedload('instance')).\
+ options(joinedload('volume_metadata')).\
+ options(joinedload('volume_type')).\
filter_by(project_id=project_id).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -2298,6 +2331,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('volume_metadata')).\
+ options(joinedload('volume_type')).\
first()
if not result:
raise exception.VolumeNotFound(volume_id=volume_id)
@@ -2332,12 +2367,116 @@ 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
+
+
###################
@@ -3172,7 +3311,7 @@ def instance_type_create(_context, values):
def _dict_with_extra_specs(inst_type_query):
- """Takes an instance type query returned by sqlalchemy
+ """Takes an instance OR volume type query returned by sqlalchemy
and returns it as a dictionary, converting the extra_specs
entry from a list of dicts:
@@ -3554,3 +3693,278 @@ def instance_type_extra_specs_update_or_create(context, instance_type_id,
"deleted": 0})
spec_ref.save(session=session)
return specs
+
+
+##################
+
+
+@require_admin_context
+def volume_type_create(_context, values):
+ """Create a new instance type. In order to pass in extra specs,
+ the values dict should contain a 'extra_specs' key/value pair:
+
+ {'extra_specs' : {'k1': 'v1', 'k2': 'v2', ...}}
+
+ """
+ try:
+ specs = values.get('extra_specs')
+
+ values['extra_specs'] = _metadata_refs(values.get('extra_specs'),
+ models.VolumeTypeExtraSpecs)
+ volume_type_ref = models.VolumeTypes()
+ volume_type_ref.update(values)
+ volume_type_ref.save()
+ except Exception, e:
+ raise exception.DBError(e)
+ return volume_type_ref
+
+
+@require_context
+def volume_type_get_all(context, inactive=False, filters={}):
+ """
+ Returns a dict describing all volume_types with name as key.
+ """
+ session = get_session()
+ if inactive:
+ vol_types = session.query(models.VolumeTypes).\
+ options(joinedload('extra_specs')).\
+ order_by("name").\
+ all()
+ else:
+ vol_types = session.query(models.VolumeTypes).\
+ options(joinedload('extra_specs')).\
+ filter_by(deleted=False).\
+ order_by("name").\
+ all()
+ vol_dict = {}
+ if vol_types:
+ for i in vol_types:
+ vol_dict[i['name']] = _dict_with_extra_specs(i)
+ return vol_dict
+
+
+@require_context
+def volume_type_get(context, id):
+ """Returns a dict describing specific volume_type"""
+ session = get_session()
+ vol_type = session.query(models.VolumeTypes).\
+ options(joinedload('extra_specs')).\
+ filter_by(id=id).\
+ first()
+
+ if not vol_type:
+ raise exception.VolumeTypeNotFound(volume_type=id)
+ else:
+ return _dict_with_extra_specs(vol_type)
+
+
+@require_context
+def volume_type_get_by_name(context, name):
+ """Returns a dict describing specific volume_type"""
+ session = get_session()
+ vol_type = session.query(models.VolumeTypes).\
+ options(joinedload('extra_specs')).\
+ filter_by(name=name).\
+ first()
+ if not vol_type:
+ raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
+ else:
+ return _dict_with_extra_specs(vol_type)
+
+
+@require_admin_context
+def volume_type_destroy(context, name):
+ """ Marks specific volume_type as deleted"""
+ session = get_session()
+ volume_type_ref = session.query(models.VolumeTypes).\
+ filter_by(name=name)
+ records = volume_type_ref.update(dict(deleted=True))
+ if records == 0:
+ raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
+ else:
+ return volume_type_ref
+
+
+@require_admin_context
+def volume_type_purge(context, name):
+ """ Removes specific volume_type from DB
+ Usually volume_type_destroy should be used
+ """
+ session = get_session()
+ volume_type_ref = session.query(models.VolumeTypes).\
+ filter_by(name=name)
+ records = volume_type_ref.delete()
+ if records == 0:
+ raise exception.VolumeTypeNotFoundByName(volume_type_name=name)
+ else:
+ return volume_type_ref
+
+
+####################
+
+
+@require_context
+def volume_type_extra_specs_get(context, volume_type_id):
+ session = get_session()
+
+ spec_results = session.query(models.VolumeTypeExtraSpecs).\
+ filter_by(volume_type_id=volume_type_id).\
+ filter_by(deleted=False).\
+ all()
+
+ spec_dict = {}
+ for i in spec_results:
+ spec_dict[i['key']] = i['value']
+ return spec_dict
+
+
+@require_context
+def volume_type_extra_specs_delete(context, volume_type_id, key):
+ session = get_session()
+ session.query(models.VolumeTypeExtraSpecs).\
+ filter_by(volume_type_id=volume_type_id).\
+ filter_by(key=key).\
+ filter_by(deleted=False).\
+ update({'deleted': True,
+ 'deleted_at': utils.utcnow(),
+ 'updated_at': literal_column('updated_at')})
+
+
+@require_context
+def volume_type_extra_specs_get_item(context, volume_type_id, key,
+ session=None):
+
+ if not session:
+ session = get_session()
+
+ spec_result = session.query(models.VolumeTypeExtraSpecs).\
+ filter_by(volume_type_id=volume_type_id).\
+ filter_by(key=key).\
+ filter_by(deleted=False).\
+ first()
+
+ if not spec_result:
+ raise exception.\
+ VolumeTypeExtraSpecsNotFound(extra_specs_key=key,
+ volume_type_id=volume_type_id)
+ return spec_result
+
+
+@require_context
+def volume_type_extra_specs_update_or_create(context, volume_type_id,
+ specs):
+ session = get_session()
+ spec_ref = None
+ for key, value in specs.iteritems():
+ try:
+ spec_ref = volume_type_extra_specs_get_item(
+ context, volume_type_id, key, session)
+ except exception.VolumeTypeExtraSpecsNotFound, e:
+ spec_ref = models.VolumeTypeExtraSpecs()
+ spec_ref.update({"key": key, "value": value,
+ "volume_type_id": volume_type_id,
+ "deleted": 0})
+ spec_ref.save(session=session)
+ return specs
+
+
+ ####################
+
+
+@require_admin_context
+def vsa_create(context, values):
+ """
+ Creates Virtual Storage Array record.
+ """
+ try:
+ vsa_ref = models.VirtualStorageArray()
+ vsa_ref.update(values)
+ vsa_ref.save()
+ except Exception, e:
+ raise exception.DBError(e)
+ return vsa_ref
+
+
+@require_admin_context
+def vsa_update(context, vsa_id, values):
+ """
+ Updates Virtual Storage Array record.
+ """
+ session = get_session()
+ with session.begin():
+ vsa_ref = vsa_get(context, vsa_id, session=session)
+ vsa_ref.update(values)
+ vsa_ref.save(session=session)
+ return vsa_ref
+
+
+@require_admin_context
+def vsa_destroy(context, vsa_id):
+ """
+ Deletes Virtual Storage Array record.
+ """
+ session = get_session()
+ with session.begin():
+ session.query(models.VirtualStorageArray).\
+ filter_by(id=vsa_id).\
+ update({'deleted': True,
+ 'deleted_at': utils.utcnow(),
+ 'updated_at': literal_column('updated_at')})
+
+
+@require_context
+def vsa_get(context, vsa_id, session=None):
+ """
+ Get Virtual Storage Array record by ID.
+ """
+ if not session:
+ session = get_session()
+ result = None
+
+ if is_admin_context(context):
+ result = session.query(models.VirtualStorageArray).\
+ options(joinedload('vsa_instance_type')).\
+ filter_by(id=vsa_id).\
+ filter_by(deleted=can_read_deleted(context)).\
+ first()
+ elif is_user_context(context):
+ result = session.query(models.VirtualStorageArray).\
+ options(joinedload('vsa_instance_type')).\
+ filter_by(project_id=context.project_id).\
+ filter_by(id=vsa_id).\
+ filter_by(deleted=False).\
+ first()
+ if not result:
+ raise exception.VirtualStorageArrayNotFound(id=vsa_id)
+
+ return result
+
+
+@require_admin_context
+def vsa_get_all(context):
+ """
+ Get all Virtual Storage Array records.
+ """
+ session = get_session()
+ return session.query(models.VirtualStorageArray).\
+ options(joinedload('vsa_instance_type')).\
+ filter_by(deleted=can_read_deleted(context)).\
+ all()
+
+
+@require_context
+def vsa_get_all_by_project(context, project_id):
+ """
+ Get all Virtual Storage Array records by project ID.
+ """
+ authorize_project_context(context, project_id)
+
+ session = get_session()
+ return session.query(models.VirtualStorageArray).\
+ options(joinedload('vsa_instance_type')).\
+ filter_by(project_id=project_id).\
+ filter_by(deleted=can_read_deleted(context)).\
+ all()
+
+
+ ####################
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
index a4fe3e482..56b287171 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py
@@ -75,8 +75,8 @@ def new_style_quotas_table(name):
)
-def existing_quotas_table(migrate_engine):
- return Table('quotas', meta, autoload=True, autoload_with=migrate_engine)
+def quotas_table(migrate_engine, name='quotas'):
+ return Table(name, meta, autoload=True, autoload_with=migrate_engine)
def _assert_no_duplicate_project_ids(quotas):
@@ -179,13 +179,18 @@ def upgrade(migrate_engine):
# bind migrate_engine to your metadata
meta.bind = migrate_engine
- old_quotas = existing_quotas_table(migrate_engine)
+ old_quotas = quotas_table(migrate_engine)
assert_old_quotas_have_no_active_duplicates(migrate_engine, old_quotas)
new_quotas = new_style_quotas_table('quotas_new')
new_quotas.create()
convert_forward(migrate_engine, old_quotas, new_quotas)
old_quotas.drop()
+
+ # clear metadata to work around this:
+ # http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=128
+ meta.clear()
+ new_quotas = quotas_table(migrate_engine, 'quotas_new')
new_quotas.rename('quotas')
@@ -193,11 +198,16 @@ def downgrade(migrate_engine):
# Operations to reverse the above upgrade go here.
meta.bind = migrate_engine
- new_quotas = existing_quotas_table(migrate_engine)
+ new_quotas = quotas_table(migrate_engine)
assert_new_quotas_have_no_active_duplicates(migrate_engine, new_quotas)
old_quotas = old_style_quotas_table('quotas_old')
old_quotas.create()
convert_backward(migrate_engine, old_quotas, new_quotas)
new_quotas.drop()
+
+ # clear metadata to work around this:
+ # http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=128
+ meta.clear()
+ old_quotas = quotas_table(migrate_engine, 'quotas_old')
old_quotas.rename('quotas')
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py
index f3244033b..dfbd4ba32 100644
--- a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py
+++ b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py
@@ -40,13 +40,17 @@ def upgrade(migrate_engine):
migrations.create_column(new_instance_type_id)
# Convert flavor_id to instance_type_id
+ itypes = {}
for instance_type in migrate_engine.execute(instance_types.select()):
+ itypes[instance_type.id] = instance_type.flavorid
+
+ for instance_type_id in itypes.keys():
migrate_engine.execute(migrations.update()\
- .where(migrations.c.old_flavor_id == instance_type.flavorid)\
- .values(old_instance_type_id=instance_type.id))
+ .where(migrations.c.old_flavor_id == itypes[instance_type_id])\
+ .values(old_instance_type_id=instance_type_id))
migrate_engine.execute(migrations.update()\
- .where(migrations.c.new_flavor_id == instance_type.flavorid)\
- .values(new_instance_type_id=instance_type.id))
+ .where(migrations.c.new_flavor_id == itypes[instance_type_id])\
+ .values(new_instance_type_id=instance_type_id))
migrations.c.old_flavor_id.drop()
migrations.c.new_flavor_id.drop()
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py
new file mode 100644
index 000000000..d3058f00d
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py
@@ -0,0 +1,38 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 Piston Cloud Computing, 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.
+
+from sqlalchemy import Column, Integer, MetaData, String, Table
+
+from nova import utils
+
+
+meta = MetaData()
+
+instances = Table("instances", meta,
+ Column("id", Integer(), primary_key=True, nullable=False))
+
+# matches the size of an image_ref
+config_drive_column = Column("config_drive", String(255), nullable=True)
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+ instances.create_column(config_drive_column)
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+ instances.drop_column(config_drive_column)
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/042_add_volume_types_and_extradata.py b/nova/db/sqlalchemy/migrate_repo/versions/042_add_volume_types_and_extradata.py
new file mode 100644
index 000000000..dd4cccb9e
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/042_add_volume_types_and_extradata.py
@@ -0,0 +1,115 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Zadara Storage Inc.
+# Copyright (c) 2011 OpenStack LLC.
+#
+# 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 sqlalchemy import Column, DateTime, Integer, MetaData, String, Table
+from sqlalchemy import Text, Boolean, ForeignKey
+
+from nova import log as logging
+
+meta = MetaData()
+
+# Just for the ForeignKey and column creation to succeed, these are not the
+# actual definitions of tables .
+#
+
+volumes = Table('volumes', meta,
+ Column('id', Integer(), primary_key=True, nullable=False),
+ )
+
+volume_type_id = Column('volume_type_id', Integer(), nullable=True)
+
+
+# New Tables
+#
+
+volume_types = Table('volume_types', 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('name',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False),
+ unique=True))
+
+volume_type_extra_specs_table = Table('volume_type_extra_specs', 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_type_id',
+ Integer(),
+ ForeignKey('volume_types.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)))
+
+
+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
+#
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+
+ for table in new_tables:
+ try:
+ table.create()
+ except Exception:
+ logging.info(repr(table))
+ logging.exception('Exception while creating table')
+ raise
+
+ volumes.create_column(volume_type_id)
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+
+ volumes.drop_column(volume_type_id)
+
+ for table in new_tables:
+ table.drop()
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/043_add_vsa_data.py b/nova/db/sqlalchemy/migrate_repo/versions/043_add_vsa_data.py
new file mode 100644
index 000000000..844643704
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/043_add_vsa_data.py
@@ -0,0 +1,75 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Zadara Storage Inc.
+# Copyright (c) 2011 OpenStack LLC.
+#
+# 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 sqlalchemy import Column, DateTime, Integer, MetaData, String, Table
+from sqlalchemy import Text, Boolean, ForeignKey
+
+from nova import log as logging
+
+meta = MetaData()
+
+#
+# New Tables
+#
+
+virtual_storage_arrays = Table('virtual_storage_arrays', 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('display_name',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ Column('display_description',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ Column('project_id',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ Column('availability_zone',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ Column('instance_type_id', Integer(), nullable=False),
+ Column('image_ref',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ Column('vc_count', Integer(), nullable=False),
+ Column('vol_count', Integer(), nullable=False),
+ Column('status',
+ String(length=255, convert_unicode=False, assert_unicode=None,
+ unicode_error=None, _warn_on_bytestring=False)),
+ )
+
+
+def upgrade(migrate_engine):
+ # Upgrade operations go here. Don't create your own engine;
+ # bind migrate_engine to your metadata
+ meta.bind = migrate_engine
+
+ try:
+ virtual_storage_arrays.create()
+ except Exception:
+ logging.info(repr(table))
+ logging.exception('Exception while creating table')
+ raise
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+
+ virtual_storage_arrays.drop()
diff --git a/nova/db/sqlalchemy/migration.py b/nova/db/sqlalchemy/migration.py
index d9e303599..bb05986c9 100644
--- a/nova/db/sqlalchemy/migration.py
+++ b/nova/db/sqlalchemy/migration.py
@@ -64,7 +64,9 @@ def db_version():
'users', 'user_project_association',
'user_project_role_association',
'user_role_association',
- 'volumes'):
+ 'virtual_storage_arrays',
+ 'volumes', 'volume_metadata',
+ 'volume_types', 'volume_type_extra_specs'):
assert table in meta.tables
return db_version_control(1)
except AssertionError:
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 11b147802..dc6f85aad 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -2,6 +2,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
+# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -230,6 +231,7 @@ class Instance(BASE, NovaBase):
uuid = Column(String(36))
root_device_name = Column(String(255))
+ config_drive = Column(String(255))
# User editable field meant to represent what ip should be used
# to connect to the instance
@@ -248,6 +250,32 @@ class Instance(BASE, NovaBase):
# 'shutdown', 'shutoff', 'crashed'])
+class VirtualStorageArray(BASE, NovaBase):
+ """
+ Represents a virtual storage array supplying block storage to instances.
+ """
+ __tablename__ = 'virtual_storage_arrays'
+
+ id = Column(Integer, primary_key=True, autoincrement=True)
+
+ @property
+ def name(self):
+ return FLAGS.vsa_name_template % self.id
+
+ # User editable field for display in user-facing UIs
+ display_name = Column(String(255))
+ display_description = Column(String(255))
+
+ project_id = Column(String(255))
+ availability_zone = Column(String(255))
+
+ instance_type_id = Column(Integer, ForeignKey('instance_types.id'))
+ image_ref = Column(String(255))
+ vc_count = Column(Integer, default=0) # number of requested VC instances
+ vol_count = Column(Integer, default=0) # total number of BE volumes
+ status = Column(String(255))
+
+
class InstanceActions(BASE, NovaBase):
"""Represents a guest VM's actions and results"""
__tablename__ = "instance_actions"
@@ -277,6 +305,12 @@ class InstanceTypes(BASE, NovaBase):
primaryjoin='and_(Instance.instance_type_id == '
'InstanceTypes.id)')
+ vsas = relationship(VirtualStorageArray,
+ backref=backref('vsa_instance_type', uselist=False),
+ foreign_keys=id,
+ primaryjoin='and_(VirtualStorageArray.instance_type_id'
+ ' == InstanceTypes.id)')
+
class Volume(BASE, NovaBase):
"""Represents a block storage device that can be attached to a vm."""
@@ -316,6 +350,50 @@ class Volume(BASE, NovaBase):
provider_location = Column(String(255))
provider_auth = Column(String(255))
+ 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="volume_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"
+ id = Column(Integer, primary_key=True)
+ name = Column(String(255), unique=True)
+
+ volumes = relationship(Volume,
+ backref=backref('volume_type', uselist=False),
+ foreign_keys=id,
+ primaryjoin='and_(Volume.volume_type_id == '
+ 'VolumeTypes.id)')
+
+
+class VolumeTypeExtraSpecs(BASE, NovaBase):
+ """Represents additional specs as key/value pairs for a volume_type"""
+ __tablename__ = 'volume_type_extra_specs'
+ id = Column(Integer, primary_key=True)
+ key = Column(String(255))
+ value = Column(String(255))
+ volume_type_id = Column(Integer, ForeignKey('volume_types.id'),
+ nullable=False)
+ volume_type = relationship(VolumeTypes, backref="extra_specs",
+ foreign_keys=volume_type_id,
+ primaryjoin='and_('
+ 'VolumeTypeExtraSpecs.volume_type_id == VolumeTypes.id,'
+ 'VolumeTypeExtraSpecs.deleted == False)')
+
class Quota(BASE, NovaBase):
"""Represents a single quota override for a project.
@@ -802,7 +880,9 @@ def register_models():
Network, SecurityGroup, SecurityGroupIngressRule,
SecurityGroupInstanceAssociation, AuthToken, User,
Project, Certificate, ConsolePool, Console, Zone,
- AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration)
+ VolumeMetadata, VolumeTypes, VolumeTypeExtraSpecs,
+ AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration,
+ VirtualStorageArray)
engine = create_engine(FLAGS.sql_connection, echo=False)
for model in models:
model.metadata.create_all(engine)