nova/nova/objects/virtual_interface.py

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