Skip to content

Commit

Permalink
Add instance_extra table and related objects
Browse files Browse the repository at this point in the history
This patch adds the table for storing the instance NUMA topology, as
adding it on the model itself as a text field is thought to be bad for
perfomance.

We need to add it as scheduling based on it and tracking resources
proves to be impossible otherwise as we would have to rely on
re-calculating it from data stashed in system_metadata table, which is
deemed even more inefficient if it needs to be done in a periodic task.

We add the objects so that we can make sure that data is versioned, and
the relevant database models methods and the migration for adding the
table.

Blueprint: virt-driver-numa-placement
Change-Id: I724ed7089044177ec4fecdf4fdcefad96a2466b6
  • Loading branch information
djipko committed Aug 29, 2014
1 parent 143de6e commit 6d3c920
Show file tree
Hide file tree
Showing 16 changed files with 452 additions and 23 deletions.
17 changes: 17 additions & 0 deletions nova/db/api.py
Expand Up @@ -919,6 +919,23 @@ def instance_info_cache_delete(context, instance_uuid):
###################


def instance_extra_create(context, values):
"""Create the instance extra data record
"""
return IMPL.instance_extra_create(context, values)


def instance_extra_get_by_instance_uuid(context, instance_uuid):
"""Get the instance extra record
:param instance_uuid: = uuid of the instance tied to the topology record
"""
return IMPL.instance_extra_get_by_instance_uuid(context, instance_uuid)


###################


def key_pair_create(context, values):
"""Create a key_pair from the values dictionary."""
return IMPL.key_pair_create(context, values)
Expand Down
25 changes: 25 additions & 0 deletions nova/db/sqlalchemy/api.py
Expand Up @@ -1699,6 +1699,9 @@ def instance_destroy(context, instance_uuid, constraint=None):
model_query(context, models.InstanceFault, session=session).\
filter_by(instance_uuid=instance_uuid).\
soft_delete()
model_query(context, models.InstanceExtra, session=session).\
filter_by(instance_uuid=instance_uuid).\
soft_delete()
return instance_ref


Expand Down Expand Up @@ -2401,6 +2404,28 @@ def instance_info_cache_delete(context, instance_uuid):
###################


def instance_extra_create(context, values):
inst_extra_ref = models.InstanceExtra()
inst_extra_ref.update(values)
inst_extra_ref.save()
return inst_extra_ref


def _instance_extra_get_by_instance_uuid_query(context, instance_uuid):
return (model_query(context, models.InstanceExtra)
.filter_by(instance_uuid=instance_uuid))


def instance_extra_get_by_instance_uuid(context, instance_uuid):
query = _instance_extra_get_by_instance_uuid_query(
context, instance_uuid)
instance_extra = query.first()
return instance_extra


###################


@require_context
def key_pair_create(context, values):
try:
Expand Down
@@ -0,0 +1,71 @@
# 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 migrate import ForeignKeyConstraint
from sqlalchemy import Column
from sqlalchemy import DateTime
from sqlalchemy import Index
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import Text


def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine

columns = [
(('created_at', DateTime), {}),
(('updated_at', DateTime), {}),
(('deleted_at', DateTime), {}),
(('deleted', Integer), {}),
(('id', Integer), dict(primary_key=True, nullable=False)),
(('instance_uuid', String(length=36)), dict(nullable=False)),
(('numa_topology', Text), dict(nullable=True)),
]
for prefix in ('', 'shadow_'):
instances = Table(prefix + 'instances', meta, autoload=True)
basename = prefix + 'instance_extra'
if migrate_engine.has_table(basename):
continue
_columns = tuple([Column(*args, **kwargs)
for args, kwargs in columns])
table = Table(basename, meta, *_columns, mysql_engine='InnoDB',
mysql_charset='utf8')
table.create()

# Index
instance_uuid_index = Index(basename + '_idx',
table.c.instance_uuid)
instance_uuid_index.create(migrate_engine)

# Foreign key
if not prefix:
fkey_columns = [table.c.instance_uuid]
fkey_refcolumns = [instances.c.uuid]
instance_fkey = ForeignKeyConstraint(
columns=fkey_columns, refcolumns=fkey_refcolumns)
instance_fkey.create()


def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine

for prefix in ('', 'shadow_'):
table_name = prefix + 'instance_extra'
if migrate_engine.has_table(table_name):
instance_extra = Table(table_name, meta, autoload=True)
instance_extra.drop()
15 changes: 15 additions & 0 deletions nova/db/sqlalchemy/models.py
Expand Up @@ -314,6 +314,21 @@ class InstanceInfoCache(BASE, NovaBase):
primaryjoin=instance_uuid == Instance.uuid)


class InstanceExtra(BASE, NovaBase):
__tablename__ = 'instance_extra'
__table_args__ = (
Index('instance_extra_idx', 'instance_uuid'),)
id = Column(Integer, primary_key=True, autoincrement=True)
instance_uuid = Column(String(36), ForeignKey('instances.uuid'),
nullable=False)
numa_topology = Column(Text)
instance = orm.relationship(Instance,
backref=orm.backref('numa_topology',
uselist=False),
foreign_keys=instance_uuid,
primaryjoin=instance_uuid == Instance.uuid)


class InstanceTypes(BASE, NovaBase):
"""Represents possible flavors for instances.
Expand Down
4 changes: 4 additions & 0 deletions nova/exception.py
Expand Up @@ -1700,3 +1700,7 @@ class ImageNUMATopologyMemoryOutOfRange(Invalid):

class InvalidHostname(Invalid):
msg_fmt = _("Invalid characters in hostname '%(hostname)s'")


class NumaTopologyNotFound(NotFound):
msg_fmt = _("Instance %(instance_uuid)s does not specify a NUMA topology")
1 change: 1 addition & 0 deletions nova/objects/__init__.py
Expand Up @@ -39,6 +39,7 @@ def register_all():
__import__('nova.objects.instance_fault')
__import__('nova.objects.instance_group')
__import__('nova.objects.instance_info_cache')
__import__('nova.objects.instance_numa_topology')
__import__('nova.objects.keypair')
__import__('nova.objects.migration')
__import__('nova.objects.network')
Expand Down
15 changes: 13 additions & 2 deletions nova/objects/block_device.py
Expand Up @@ -22,6 +22,7 @@
from nova.objects import base
from nova.objects import fields
from nova.openstack.common import log as logging
from nova import utils


LOG = logging.getLogger(__name__)
Expand All @@ -39,7 +40,8 @@ def _expected_cols(expected_attrs):
class BlockDeviceMapping(base.NovaPersistentObject, base.NovaObject):
# Version 1.0: Initial version
# Version 1.1: Add instance_uuid to get_by_volume_id method
VERSION = '1.1'
# Version 1.2: Instance version 1.14
VERSION = '1.2'

fields = {
'id': fields.IntegerField(),
Expand All @@ -61,6 +63,13 @@ class BlockDeviceMapping(base.NovaPersistentObject, base.NovaObject):
'connection_info': fields.StringField(nullable=True),
}

def obj_make_compatible(self, primitive, target_version):
target_version = utils.convert_version_to_tuple(target_version)
if target_version < (1, 2) and 'instance' in primitive:
primitive['instance'] = (
objects.Instance().object_make_compatible(
primitive['instance']['nova_object.data'], '1.13'))

@staticmethod
def _from_db_object(context, block_device_obj,
db_block_device, expected_attrs=None):
Expand Down Expand Up @@ -189,7 +198,8 @@ class BlockDeviceMappingList(base.ObjectListBase, base.NovaObject):
# Version 1.0: Initial version
# Version 1.1: BlockDeviceMapping <= version 1.1
# Version 1.2: Added use_slave to get_by_instance_uuid
VERSION = '1.2'
# Version 1.3: BlockDeviceMapping <= version 1.2
VERSION = '1.3'

fields = {
'objects': fields.ListOfObjectsField('BlockDeviceMapping'),
Expand All @@ -198,6 +208,7 @@ class BlockDeviceMappingList(base.ObjectListBase, base.NovaObject):
'1.0': '1.0',
'1.1': '1.1',
'1.2': '1.1',
'1.3': '1.2',
}

@base.remotable_classmethod
Expand Down
15 changes: 13 additions & 2 deletions nova/objects/fixed_ip.py
Expand Up @@ -18,6 +18,7 @@
from nova.objects import base as obj_base
from nova.objects import fields
from nova.openstack.common import timeutils
from nova import utils


FIXED_IP_OPTIONAL_ATTRS = ['instance', 'network']
Expand All @@ -26,7 +27,8 @@
class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
# Version 1.0: Initial version
# Version 1.1: Added virtual_interface field
VERSION = '1.1'
# Version 1.2: Instance version 1.14
VERSION = '1.2'

fields = {
'id': fields.IntegerField(),
Expand All @@ -44,6 +46,13 @@ class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
nullable=True),
}

def obj_make_compatible(self, primitive, target_version):
target_version = utils.convert_version_to_tuple(target_version)
if target_version < (1, 2) and 'instance' in primitive:
primitive['instance'] = (
objects.Instance().object_make_compatible(
primitive['instance']['nova_object.data'], '1.13'))

@property
def floating_ips(self):
return objects.FloatingIPList.get_by_fixed_ip_id(self._context,
Expand Down Expand Up @@ -164,14 +173,16 @@ def disassociate(self, context):
class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject):
# Version 1.0: Initial version
# Version 1.1: Added get_by_network()
VERSION = '1.1'
# Version 1.2: FixedIP <= version 1.2
VERSION = '1.2'

fields = {
'objects': fields.ListOfObjectsField('FixedIP'),
}
child_versions = {
'1.0': '1.0',
'1.1': '1.1',
'1.2': '1.2',
}

@obj_base.remotable_classmethod
Expand Down
14 changes: 12 additions & 2 deletions nova/objects/floating_ip.py
Expand Up @@ -17,14 +17,16 @@
from nova import objects
from nova.objects import base as obj_base
from nova.objects import fields
from nova import utils

FLOATING_IP_OPTIONAL_ATTRS = ['fixed_ip']


class FloatingIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
# Version 1.0: Initial version
# Version 1.1: Added _get_addresses_by_instance_uuid()
VERSION = '1.1'
# Version 1.2: FixedIP <= version 1.2
VERSION = '1.2'
fields = {
'id': fields.IntegerField(),
'address': fields.IPAddressField(),
Expand All @@ -37,6 +39,13 @@ class FloatingIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
'fixed_ip': fields.ObjectField('FixedIP', nullable=True),
}

def obj_make_compatible(self, primitive, target_version):
target_version = utils.convert_version_to_tuple(target_version)
if target_version < (1, 2) and 'fixed_ip' in primitive:
primitive['fixed_ip'] = (
objects.FixedIP().object_make_compatible(
primitive['fixed_ip']['nova_object.data'], '1.1'))

@staticmethod
def _from_db_object(context, floatingip, db_floatingip,
expected_attrs=None):
Expand Down Expand Up @@ -158,8 +167,9 @@ class FloatingIPList(obj_base.ObjectListBase, obj_base.NovaObject):
'1.0': '1.0',
'1.1': '1.1',
'1.2': '1.1',
'1.3': '1.2',
}
VERSION = '1.2'
VERSION = '1.3'

@obj_base.remotable_classmethod
def get_all(cls, context):
Expand Down

0 comments on commit 6d3c920

Please sign in to comment.