Add instance_system_metadata modeling.
Implements blueprint instance-system-metadata. Adds a new table to store system-owned metadata tied to an instance. The intent is to provide a key/value store that compute plugins can use to persist state. Change-Id: Ic446fea0f9c8a652f2ac6d554f4f85021ce34fb8
This commit is contained in:
parent
7d8231a58c
commit
066d4c0bf2
@ -1488,6 +1488,25 @@ def instance_metadata_update(context, instance_id, metadata, delete):
|
||||
####################
|
||||
|
||||
|
||||
def instance_system_metadata_get(context, instance_uuid):
|
||||
"""Get all system metadata for an instance."""
|
||||
return IMPL.instance_system_metadata_get(context, instance_uuid)
|
||||
|
||||
|
||||
def instance_system_metadata_delete(context, instance_uuid, key):
|
||||
"""Delete the given system metadata item."""
|
||||
IMPL.instance_system_metadata_delete(context, instance_uuid, key)
|
||||
|
||||
|
||||
def instance_system_metadata_update(context, instance_uuid, metadata, delete):
|
||||
"""Update metadata if it exists, otherwise create it."""
|
||||
IMPL.instance_system_metadata_update(
|
||||
context, instance_uuid, metadata, delete)
|
||||
|
||||
|
||||
####################
|
||||
|
||||
|
||||
def agent_build_create(context, values):
|
||||
"""Create a new agent build entry."""
|
||||
return IMPL.agent_build_create(context, values)
|
||||
|
@ -136,11 +136,25 @@ def require_instance_exists(f):
|
||||
Requires the wrapped function to use context and instance_id as
|
||||
their first two arguments.
|
||||
"""
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrapper(context, instance_id, *args, **kwargs):
|
||||
db.instance_get(context, instance_id)
|
||||
return f(context, instance_id, *args, **kwargs)
|
||||
wrapper.__name__ = f.__name__
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def require_instance_exists_using_uuid(f):
|
||||
"""Decorator to require the specified instance to exist.
|
||||
|
||||
Requires the wrapped function to use context and instance_uuid as
|
||||
their first two arguments.
|
||||
"""
|
||||
@functools.wraps(f)
|
||||
def wrapper(context, instance_uuid, *args, **kwargs):
|
||||
db.instance_get_by_uuid(context, instance_uuid)
|
||||
return f(context, instance_uuid, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@ -1259,8 +1273,12 @@ def instance_create(context, values):
|
||||
values - dict containing column values.
|
||||
"""
|
||||
values = values.copy()
|
||||
values['metadata'] = _metadata_refs(values.get('metadata'),
|
||||
models.InstanceMetadata)
|
||||
values['metadata'] = _metadata_refs(
|
||||
values.get('metadata'), models.InstanceMetadata)
|
||||
|
||||
values['system_metadata'] = _metadata_refs(
|
||||
values.get('system_metadata'), models.InstanceSystemMetadata)
|
||||
|
||||
instance_ref = models.Instance()
|
||||
if not values.get('uuid'):
|
||||
values['uuid'] = str(utils.gen_uuid())
|
||||
@ -1620,10 +1638,15 @@ def instance_update(context, instance_id, values):
|
||||
|
||||
metadata = values.get('metadata')
|
||||
if metadata is not None:
|
||||
instance_metadata_update(context,
|
||||
instance_ref['id'],
|
||||
values.pop('metadata'),
|
||||
delete=True)
|
||||
instance_metadata_update(
|
||||
context, instance_ref['id'], values.pop('metadata'), delete=True)
|
||||
|
||||
system_metadata = values.get('system_metadata')
|
||||
if system_metadata is not None:
|
||||
instance_system_metadata_update(
|
||||
context, instance_ref['uuid'], values.pop('system_metadata'),
|
||||
delete=True)
|
||||
|
||||
with session.begin():
|
||||
instance_ref.update(values)
|
||||
instance_ref.save(session=session)
|
||||
@ -3682,8 +3705,8 @@ def cell_get_all(context):
|
||||
return model_query(context, models.Cell, read_deleted="no").all()
|
||||
|
||||
|
||||
####################
|
||||
|
||||
########################
|
||||
# User-provided metadata
|
||||
|
||||
def _instance_metadata_get_query(context, instance_id, session=None):
|
||||
return model_query(context, models.InstanceMetadata, session=session,
|
||||
@ -3764,6 +3787,88 @@ def instance_metadata_update(context, instance_id, metadata, delete):
|
||||
return metadata
|
||||
|
||||
|
||||
#######################
|
||||
# System-owned metadata
|
||||
|
||||
def _instance_system_metadata_get_query(context, instance_uuid, session=None):
|
||||
return model_query(context, models.InstanceSystemMetadata, session=session,
|
||||
read_deleted="no").\
|
||||
filter_by(instance_uuid=instance_uuid)
|
||||
|
||||
|
||||
@require_context
|
||||
@require_instance_exists_using_uuid
|
||||
def instance_system_metadata_get(context, instance_uuid):
|
||||
rows = _instance_system_metadata_get_query(context, instance_uuid).all()
|
||||
|
||||
result = {}
|
||||
for row in rows:
|
||||
result[row['key']] = row['value']
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
@require_instance_exists_using_uuid
|
||||
def instance_system_metadata_delete(context, instance_uuid, key):
|
||||
_instance_system_metadata_get_query(context, instance_uuid).\
|
||||
filter_by(key=key).\
|
||||
update({'deleted': True,
|
||||
'deleted_at': utils.utcnow(),
|
||||
'updated_at': literal_column('updated_at')})
|
||||
|
||||
|
||||
def _instance_system_metadata_get_item(context, instance_uuid, key,
|
||||
session=None):
|
||||
result = _instance_system_metadata_get_query(
|
||||
context, instance_uuid, session=session).\
|
||||
filter_by(key=key).\
|
||||
first()
|
||||
|
||||
if not result:
|
||||
raise exception.InstanceSystemMetadataNotFound(
|
||||
metadata_key=key, instance_uuid=instance_uuid)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
@require_instance_exists_using_uuid
|
||||
def instance_system_metadata_update(context, instance_uuid, metadata, delete):
|
||||
session = get_session()
|
||||
|
||||
# Set existing metadata to deleted if delete argument is True
|
||||
if delete:
|
||||
original_metadata = instance_system_metadata_get(
|
||||
context, instance_uuid)
|
||||
for meta_key, meta_value in original_metadata.iteritems():
|
||||
if meta_key not in metadata:
|
||||
meta_ref = _instance_system_metadata_get_item(
|
||||
context, instance_uuid, 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 = _instance_system_metadata_get_item(
|
||||
context, instance_uuid, meta_key, session)
|
||||
except exception.InstanceSystemMetadataNotFound, e:
|
||||
meta_ref = models.InstanceSystemMetadata()
|
||||
item.update({"key": meta_key, "instance_uuid": instance_uuid})
|
||||
|
||||
meta_ref.update(item)
|
||||
meta_ref.save(session=session)
|
||||
|
||||
return metadata
|
||||
|
||||
|
||||
####################
|
||||
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 Openstack, LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer
|
||||
from sqlalchemy import MetaData, String, Table
|
||||
from nova import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
# Upgrade operations go here. Don't create your own engine;
|
||||
# bind migrate_engine to your metadata
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
# load tables for fk
|
||||
instances = Table('instances', meta, autoload=True)
|
||||
|
||||
instance_system_metadata = Table('instance_system_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('instance_uuid',
|
||||
String(36),
|
||||
ForeignKey('instances.uuid'),
|
||||
nullable=False),
|
||||
Column('key',
|
||||
String(length=255, convert_unicode=True,
|
||||
assert_unicode=None,
|
||||
unicode_error=None, _warn_on_bytestring=False),
|
||||
nullable=False),
|
||||
Column('value',
|
||||
String(length=255, convert_unicode=True,
|
||||
assert_unicode=None,
|
||||
unicode_error=None, _warn_on_bytestring=False)),
|
||||
mysql_engine='InnoDB')
|
||||
|
||||
try:
|
||||
instance_system_metadata.create()
|
||||
except Exception:
|
||||
LOG.error(_("Table |%s| not created!"), repr(instance_system_metadata))
|
||||
raise
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
# load tables for fk
|
||||
instances = Table('instances', meta, autoload=True)
|
||||
|
||||
instance_system_metadata = Table(
|
||||
'instance_system_metadata', meta, autoload=True)
|
||||
instance_system_metadata.drop()
|
@ -831,7 +831,7 @@ class Console(BASE, NovaBase):
|
||||
|
||||
|
||||
class InstanceMetadata(BASE, NovaBase):
|
||||
"""Represents a metadata key/value pair for an instance"""
|
||||
"""Represents a user-provided metadata key/value pair for an instance"""
|
||||
__tablename__ = 'instance_metadata'
|
||||
id = Column(Integer, primary_key=True)
|
||||
key = Column(String(255))
|
||||
@ -844,6 +844,23 @@ class InstanceMetadata(BASE, NovaBase):
|
||||
'InstanceMetadata.deleted == False)')
|
||||
|
||||
|
||||
class InstanceSystemMetadata(BASE, NovaBase):
|
||||
"""Represents a system-owned metadata key/value pair for an instance"""
|
||||
__tablename__ = 'instance_system_metadata'
|
||||
id = Column(Integer, primary_key=True)
|
||||
key = Column(String(255))
|
||||
value = Column(String(255))
|
||||
instance_uuid = Column(String(36),
|
||||
ForeignKey('instances.uuid'),
|
||||
nullable=False)
|
||||
|
||||
primary_join = ('and_(InstanceSystemMetadata.instance_uuid == '
|
||||
'Instance.uuid, InstanceSystemMetadata.deleted == False)')
|
||||
instance = relationship(Instance, backref="system_metadata",
|
||||
foreign_keys=instance_uuid,
|
||||
primaryjoin=primary_join)
|
||||
|
||||
|
||||
class InstanceTypeExtraSpecs(BASE, NovaBase):
|
||||
"""Represents additional specs as key/value pairs for an instance_type"""
|
||||
__tablename__ = 'instance_type_extra_specs'
|
||||
|
@ -811,6 +811,11 @@ class InstanceMetadataNotFound(NotFound):
|
||||
"key %(metadata_key)s.")
|
||||
|
||||
|
||||
class InstanceSystemMetadataNotFound(NotFound):
|
||||
message = _("Instance %(instance_uuid)s has no system metadata with "
|
||||
"key %(metadata_key)s.")
|
||||
|
||||
|
||||
class InstanceTypeExtraSpecsNotFound(NotFound):
|
||||
message = _("Instance Type %(instance_type_id)s has no extra specs with "
|
||||
"key %(extra_specs_key)s.")
|
||||
|
@ -168,37 +168,47 @@ class DbApiTestCase(test.TestCase):
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
# Create an instance with some metadata
|
||||
metadata = {'host': 'foo'}
|
||||
values = {'metadata': metadata}
|
||||
values = {'metadata': {'host': 'foo'},
|
||||
'system_metadata': {'original_image_ref': 'blah'}}
|
||||
instance = db.instance_create(ctxt, values)
|
||||
|
||||
# Update the metadata
|
||||
metadata = {'host': 'bar'}
|
||||
values = {'metadata': metadata}
|
||||
values = {'metadata': {'host': 'bar'},
|
||||
'system_metadata': {'original_image_ref': 'baz'}}
|
||||
db.instance_update(ctxt, instance.id, values)
|
||||
|
||||
# Retrieve the metadata to ensure it was successfully updated
|
||||
# Retrieve the user-provided metadata to ensure it was successfully
|
||||
# updated
|
||||
instance_meta = db.instance_metadata_get(ctxt, instance.id)
|
||||
self.assertEqual('bar', instance_meta['host'])
|
||||
|
||||
# Retrieve the system metadata to ensure it was successfully updated
|
||||
system_meta = db.instance_system_metadata_get(ctxt, instance.uuid)
|
||||
self.assertEqual('baz', system_meta['original_image_ref'])
|
||||
|
||||
def test_instance_update_with_instance_uuid(self):
|
||||
""" test instance_update() works when an instance UUID is passed """
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
# Create an instance with some metadata
|
||||
metadata = {'host': 'foo'}
|
||||
values = {'metadata': metadata}
|
||||
values = {'metadata': {'host': 'foo'},
|
||||
'system_metadata': {'original_image_ref': 'blah'}}
|
||||
instance = db.instance_create(ctxt, values)
|
||||
|
||||
# Update the metadata
|
||||
metadata = {'host': 'bar'}
|
||||
values = {'metadata': metadata}
|
||||
values = {'metadata': {'host': 'bar'},
|
||||
'system_metadata': {'original_image_ref': 'baz'}}
|
||||
db.instance_update(ctxt, instance.uuid, values)
|
||||
|
||||
# Retrieve the metadata to ensure it was successfully updated
|
||||
# Retrieve the user-provided metadata to ensure it was successfully
|
||||
# updated
|
||||
instance_meta = db.instance_metadata_get(ctxt, instance.id)
|
||||
self.assertEqual('bar', instance_meta['host'])
|
||||
|
||||
# Retrieve the system metadata to ensure it was successfully updated
|
||||
system_meta = db.instance_system_metadata_get(ctxt, instance.uuid)
|
||||
self.assertEqual('baz', system_meta['original_image_ref'])
|
||||
|
||||
def test_instance_fault_create(self):
|
||||
"""Ensure we can create an instance fault"""
|
||||
ctxt = context.get_admin_context()
|
||||
|
Loading…
Reference in New Issue
Block a user