diff options
| author | danwent@gmail.com <> | 2011-08-28 20:06:55 -0700 |
|---|---|---|
| committer | danwent@gmail.com <> | 2011-08-28 20:06:55 -0700 |
| commit | a4bd14f86f31ea82daad8e194b26e86e6dd37398 (patch) | |
| tree | 3807a1131dc6a7ba7119782f356eb365f28d8b6c /nova/db | |
| parent | 605fe4f19af3af830a2a8c82809e9ce5909c602d (diff) | |
| parent | 0ef2581749f39fa4fd41c2376186418e730f0afb (diff) | |
merge trunk
Diffstat (limited to 'nova/db')
| -rw-r--r-- | nova/db/api.py | 112 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 424 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py | 18 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py | 12 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py | 38 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/migrate_repo/versions/042_add_volume_types_and_extradata.py | 115 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/migrate_repo/versions/043_add_vsa_data.py | 75 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/migration.py | 4 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/models.py | 82 |
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) |
