a7de4917a0
The InstanceMapping user_id field is a new, non-nullable field representing the user_id for the instance. When new instance create requests come in, we create the instance mapping. We will set user_id here before creating the record. Some virtual interface online data migration and map_instances routine create InstanceMapping records and since the user_id field did not previously exist, they were not setting it. We will populate user_id in these cases. Finally, whenever an API does a compute_api.get(), we can opportunistically set and save user_id on the instance mapping if it is not set. Part of blueprint count-quota-usage-from-placement Change-Id: Ic4bb7b49b90a3d6d7ce6c6c62d87836f96309f06
326 lines
13 KiB
Python
326 lines
13 KiB
Python
# Copyright (C) 2014, Red Hat, 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 oslo_log import log as logging
|
|
from oslo_utils import versionutils
|
|
|
|
from nova import context as nova_context
|
|
from nova.db import api as db
|
|
from nova.db.sqlalchemy import api as db_api
|
|
from nova.db.sqlalchemy import models
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova.objects import base
|
|
from nova.objects import fields
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
VIF_OPTIONAL_FIELDS = ['network_id']
|
|
FAKE_UUID = '00000000-0000-0000-0000-000000000000'
|
|
|
|
|
|
@base.NovaObjectRegistry.register
|
|
class VirtualInterface(base.NovaPersistentObject, base.NovaObject):
|
|
# Version 1.0: Initial version
|
|
# Version 1.1: Add tag field
|
|
# Version 1.2: Adding a save method
|
|
# Version 1.3: Added destroy() method
|
|
VERSION = '1.3'
|
|
|
|
fields = {
|
|
'id': fields.IntegerField(),
|
|
# This is a MAC address.
|
|
'address': fields.StringField(nullable=True),
|
|
'network_id': fields.IntegerField(),
|
|
'instance_uuid': fields.UUIDField(),
|
|
'uuid': fields.UUIDField(),
|
|
'tag': fields.StringField(nullable=True),
|
|
}
|
|
|
|
def obj_make_compatible(self, primitive, target_version):
|
|
target_version = versionutils.convert_version_to_tuple(target_version)
|
|
if target_version < (1, 1) and 'tag' in primitive:
|
|
del primitive['tag']
|
|
|
|
@staticmethod
|
|
def _from_db_object(context, vif, db_vif):
|
|
for field in vif.fields:
|
|
if not db_vif[field] and field in VIF_OPTIONAL_FIELDS:
|
|
continue
|
|
else:
|
|
setattr(vif, field, db_vif[field])
|
|
# NOTE(danms): The neutronv2 module namespaces mac addresses
|
|
# with port id to avoid uniqueness constraints currently on
|
|
# our table. Strip that out here so nobody else needs to care.
|
|
if 'address' in vif and '/' in vif.address:
|
|
vif.address, _ = vif.address.split('/', 1)
|
|
vif._context = context
|
|
vif.obj_reset_changes()
|
|
return vif
|
|
|
|
@base.remotable_classmethod
|
|
def get_by_id(cls, context, vif_id):
|
|
db_vif = db.virtual_interface_get(context, vif_id)
|
|
if db_vif:
|
|
return cls._from_db_object(context, cls(), db_vif)
|
|
|
|
@base.remotable_classmethod
|
|
def get_by_uuid(cls, context, vif_uuid):
|
|
db_vif = db.virtual_interface_get_by_uuid(context, vif_uuid)
|
|
if db_vif:
|
|
return cls._from_db_object(context, cls(), db_vif)
|
|
|
|
@base.remotable_classmethod
|
|
def get_by_address(cls, context, address):
|
|
db_vif = db.virtual_interface_get_by_address(context, address)
|
|
if db_vif:
|
|
return cls._from_db_object(context, cls(), db_vif)
|
|
|
|
@base.remotable_classmethod
|
|
def get_by_instance_and_network(cls, context, instance_uuid, network_id):
|
|
db_vif = db.virtual_interface_get_by_instance_and_network(context,
|
|
instance_uuid, network_id)
|
|
if db_vif:
|
|
return cls._from_db_object(context, cls(), db_vif)
|
|
|
|
@base.remotable
|
|
def create(self):
|
|
if self.obj_attr_is_set('id'):
|
|
raise exception.ObjectActionError(action='create',
|
|
reason='already created')
|
|
updates = self.obj_get_changes()
|
|
db_vif = db.virtual_interface_create(self._context, updates)
|
|
self._from_db_object(self._context, self, db_vif)
|
|
|
|
@base.remotable
|
|
def save(self):
|
|
updates = self.obj_get_changes()
|
|
if 'address' in updates:
|
|
raise exception.ObjectActionError(action='save',
|
|
reason='address is not mutable')
|
|
db_vif = db.virtual_interface_update(self._context, self.address,
|
|
updates)
|
|
return self._from_db_object(self._context, self, db_vif)
|
|
|
|
@base.remotable_classmethod
|
|
def delete_by_instance_uuid(cls, context, instance_uuid):
|
|
db.virtual_interface_delete_by_instance(context, instance_uuid)
|
|
|
|
@base.remotable
|
|
def destroy(self):
|
|
db.virtual_interface_delete(self._context, self.id)
|
|
|
|
|
|
@base.NovaObjectRegistry.register
|
|
class VirtualInterfaceList(base.ObjectListBase, base.NovaObject):
|
|
# Version 1.0: Initial version
|
|
VERSION = '1.0'
|
|
fields = {
|
|
'objects': fields.ListOfObjectsField('VirtualInterface'),
|
|
}
|
|
|
|
@base.remotable_classmethod
|
|
def get_all(cls, context):
|
|
db_vifs = db.virtual_interface_get_all(context)
|
|
return base.obj_make_list(context, cls(context),
|
|
objects.VirtualInterface, db_vifs)
|
|
|
|
@staticmethod
|
|
@db.select_db_reader_mode
|
|
def _db_virtual_interface_get_by_instance(context, instance_uuid,
|
|
use_slave=False):
|
|
return db.virtual_interface_get_by_instance(context, instance_uuid)
|
|
|
|
@base.remotable_classmethod
|
|
def get_by_instance_uuid(cls, context, instance_uuid, use_slave=False):
|
|
db_vifs = cls._db_virtual_interface_get_by_instance(
|
|
context, instance_uuid, use_slave=use_slave)
|
|
return base.obj_make_list(context, cls(context),
|
|
objects.VirtualInterface, db_vifs)
|
|
|
|
|
|
@db_api.api_context_manager.writer
|
|
def fill_virtual_interface_list(context, max_count):
|
|
"""This fills missing VirtualInterface Objects in Nova DB"""
|
|
count_hit = 0
|
|
count_all = 0
|
|
|
|
def _regenerate_vif_list_base_on_cache(context,
|
|
instance,
|
|
old_vif_list,
|
|
nw_info):
|
|
# Set old VirtualInterfaces as deleted.
|
|
for vif in old_vif_list:
|
|
vif.destroy()
|
|
|
|
# Generate list based on current cache:
|
|
for vif in nw_info:
|
|
vif_obj = objects.VirtualInterface(context)
|
|
vif_obj.uuid = vif['id']
|
|
vif_obj.address = "%s/%s" % (vif['address'], vif['id'])
|
|
vif_obj.instance_uuid = instance['uuid']
|
|
# Find tag from previous VirtualInterface object if exist.
|
|
old_vif = [x for x in old_vif_list if x.uuid == vif['id']]
|
|
vif_obj.tag = old_vif[0].tag if len(old_vif) > 0 else None
|
|
vif_obj.create()
|
|
|
|
cells = objects.CellMappingList.get_all(context)
|
|
for cell in cells:
|
|
if count_all == max_count:
|
|
# We reached the limit of checked instances per
|
|
# this function run.
|
|
# Stop, do not go to other cell.
|
|
break
|
|
|
|
with nova_context.target_cell(context, cell) as cctxt:
|
|
marker = _get_marker_for_migrate_instances(cctxt)
|
|
filters = {'deleted': False}
|
|
|
|
# Adjust the limit of migrated instances.
|
|
# If user wants to process a total of 100 instances
|
|
# and we did a 75 in cell1, then we only need to
|
|
# verify 25 more in cell2, no more.
|
|
adjusted_limit = max_count - count_all
|
|
|
|
instances = objects.InstanceList.get_by_filters(
|
|
cctxt,
|
|
filters=filters,
|
|
sort_key='created_at',
|
|
sort_dir='asc',
|
|
marker=marker,
|
|
limit=adjusted_limit)
|
|
|
|
for instance in instances:
|
|
# We don't want to fill vif for FAKE instance.
|
|
if instance.uuid == FAKE_UUID:
|
|
continue
|
|
|
|
try:
|
|
info_cache = objects.InstanceInfoCache.\
|
|
get_by_instance_uuid(cctxt, instance.get('uuid'))
|
|
if not info_cache.network_info:
|
|
LOG.info('InstanceInfoCache object has not set '
|
|
'NetworkInfo field. '
|
|
'Skipping build of VirtualInterfaceList.')
|
|
continue
|
|
except exception.InstanceInfoCacheNotFound:
|
|
LOG.info('Instance has no InstanceInfoCache object. '
|
|
'Skipping build of VirtualInterfaceList for it.')
|
|
continue
|
|
|
|
# It by design filters out deleted vifs.
|
|
vif_list = VirtualInterfaceList.\
|
|
get_by_instance_uuid(cctxt, instance.get('uuid'))
|
|
|
|
nw_info = info_cache.network_info
|
|
# This should be list with proper order of vifs,
|
|
# but we're not sure about that.
|
|
cached_vif_ids = [vif['id'] for vif in nw_info]
|
|
# This is ordered list of vifs taken from db.
|
|
db_vif_ids = [vif.uuid for vif in vif_list]
|
|
|
|
count_all += 1
|
|
if cached_vif_ids == db_vif_ids:
|
|
# The list of vifs and its order in cache and in
|
|
# virtual_interfaces is the same. So we could end here.
|
|
continue
|
|
elif len(db_vif_ids) < len(cached_vif_ids):
|
|
# Seems to be an instance from release older than
|
|
# Newton and we don't have full VirtualInterfaceList for
|
|
# it. Rewrite whole VirtualInterfaceList using interface
|
|
# order from InstanceInfoCache.
|
|
count_hit += 1
|
|
LOG.info('Got an instance %s with less VIFs defined in DB '
|
|
'than in cache. Could be Pre-Newton instance. '
|
|
'Building new VirtualInterfaceList for it.',
|
|
instance.uuid)
|
|
_regenerate_vif_list_base_on_cache(cctxt,
|
|
instance,
|
|
vif_list,
|
|
nw_info)
|
|
elif len(db_vif_ids) > len(cached_vif_ids):
|
|
# Seems vif list is inconsistent with cache.
|
|
# it could be a broken cache or interface
|
|
# during attach. Do nothing.
|
|
LOG.info('Got an unexpected number of VIF records in the '
|
|
'database compared to what was stored in the '
|
|
'instance_info_caches table for instance %s. '
|
|
'Perhaps it is an instance during interface '
|
|
'attach. Do nothing.', instance.uuid)
|
|
continue
|
|
else:
|
|
# The order is different between lists.
|
|
# We need a source of truth, so rebuild order
|
|
# from cache.
|
|
count_hit += 1
|
|
LOG.info('Got an instance %s with different order of '
|
|
'VIFs between DB and cache. '
|
|
'We need a source of truth, so rebuild order '
|
|
'from cache.', instance.uuid)
|
|
_regenerate_vif_list_base_on_cache(cctxt,
|
|
instance,
|
|
vif_list,
|
|
nw_info)
|
|
|
|
# Set marker to point last checked instance.
|
|
if instances:
|
|
marker = instances[-1].uuid
|
|
_set_or_delete_marker_for_migrate_instances(cctxt, marker)
|
|
|
|
return count_all, count_hit
|
|
|
|
|
|
# NOTE(mjozefcz): This is similiar to marker mechanism made for
|
|
# RequestSpecs object creation.
|
|
# Since we have a lot of instances to be check this
|
|
# will add a FAKE row that points to last instance
|
|
# we checked.
|
|
# Please notice that because of virtual_interfaces_instance_uuid_fkey
|
|
# we need to have FAKE_UUID instance object, even deleted one.
|
|
@db_api.pick_context_manager_writer
|
|
def _set_or_delete_marker_for_migrate_instances(context, marker=None):
|
|
context.session.query(models.VirtualInterface).filter_by(
|
|
instance_uuid=FAKE_UUID).delete()
|
|
|
|
# Create FAKE_UUID instance objects, only for marker, if doesn't exist.
|
|
# It is needed due constraint: virtual_interfaces_instance_uuid_fkey
|
|
instance = context.session.query(models.Instance).filter_by(
|
|
uuid=FAKE_UUID).first()
|
|
if not instance:
|
|
instance = objects.Instance(context)
|
|
instance.uuid = FAKE_UUID
|
|
instance.project_id = FAKE_UUID
|
|
instance.user_id = FAKE_UUID
|
|
instance.create()
|
|
# Thats fake instance, lets destroy it.
|
|
# We need only its row to solve constraint issue.
|
|
instance.destroy()
|
|
|
|
if marker is not None:
|
|
# ... but there can be a new marker to set
|
|
db_mapping = objects.VirtualInterface(context)
|
|
db_mapping.instance_uuid = FAKE_UUID
|
|
db_mapping.uuid = FAKE_UUID
|
|
db_mapping.tag = marker
|
|
db_mapping.address = 'ff:ff:ff:ff:ff:ff/%s' % FAKE_UUID
|
|
db_mapping.create()
|
|
|
|
|
|
@db_api.pick_context_manager_reader
|
|
def _get_marker_for_migrate_instances(context):
|
|
vif = (context.session.query(models.VirtualInterface).filter_by(
|
|
instance_uuid=FAKE_UUID)).first()
|
|
marker = vif['tag'] if vif else None
|
|
return marker
|