Ejemplo n.º 1
0
class BuildRequest(base.NovaObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    fields = {
        'id': fields.IntegerField(),
        'project_id': fields.StringField(),
        'user_id': fields.StringField(),
        'display_name': fields.StringField(nullable=True),
        'instance_metadata': fields.DictOfStringsField(nullable=True),
        'progress': fields.IntegerField(nullable=True),
        'vm_state': fields.StringField(nullable=True),
        'task_state': fields.StringField(nullable=True),
        'image_ref': fields.StringField(nullable=True),
        'access_ip_v4': fields.IPV4AddressField(nullable=True),
        'access_ip_v6': fields.IPV6AddressField(nullable=True),
        'info_cache': fields.ObjectField('InstanceInfoCache', nullable=True),
        'security_groups': fields.ObjectField('SecurityGroupList'),
        'config_drive': fields.BooleanField(default=False),
        'key_name': fields.StringField(nullable=True),
        'locked_by': fields.EnumField(['owner', 'admin'], nullable=True),
        'request_spec': fields.ObjectField('RequestSpec'),
        # NOTE(alaski): Normally these would come from the NovaPersistentObject
        # mixin but they're being set explicitly because we only need
        # created_at/updated_at. There is no soft delete for this object.
        # These fields should be carried over to the instance when it is
        # scheduled and created in a cell database.
        'created_at': fields.DateTimeField(nullable=True),
        'updated_at': fields.DateTimeField(nullable=True),
    }

    def _load_request_spec(self, db_spec):
        self.request_spec = objects.RequestSpec._from_db_object(
            self._context, objects.RequestSpec(), db_spec)

    def _load_info_cache(self, db_info_cache):
        self.info_cache = objects.InstanceInfoCache.obj_from_primitive(
            jsonutils.loads(db_info_cache))

    def _load_security_groups(self, db_sec_group):
        self.security_groups = objects.SecurityGroupList.obj_from_primitive(
            jsonutils.loads(db_sec_group))

    @staticmethod
    def _from_db_object(context, req, db_req):
        for key in req.fields:
            if isinstance(req.fields[key], fields.ObjectField):
                try:
                    getattr(req, '_load_%s' % key)(db_req[key])
                except AttributeError:
                    LOG.exception(_LE('No load handler for %s'), key)
            elif key in JSON_FIELDS and db_req[key] is not None:
                setattr(req, key, jsonutils.loads(db_req[key]))
            else:
                setattr(req, key, db_req[key])
        req.obj_reset_changes()
        req._context = context
        return req

    @staticmethod
    @db.api_context_manager.reader
    def _get_by_instance_uuid_from_db(context, instance_uuid):
        db_req = (context.session.query(api_models.BuildRequest).options(
            joinedload('request_spec')).filter(
                api_models.RequestSpec.instance_uuid == instance_uuid)
                  ).first()
        if not db_req:
            raise exception.BuildRequestNotFound(uuid=instance_uuid)
        return db_req

    @base.remotable_classmethod
    def get_by_instance_uuid(cls, context, instance_uuid):
        db_req = cls._get_by_instance_uuid_from_db(context, instance_uuid)
        return cls._from_db_object(context, cls(), db_req)

    @staticmethod
    @db.api_context_manager.writer
    def _create_in_db(context, updates):
        db_req = api_models.BuildRequest()
        db_req.update(updates)
        db_req.save(context.session)
        # NOTE: This is done because a later access will trigger a lazy load
        # outside of the db session so it will fail. We don't lazy load
        # request_spec on the object later because we never need a BuildRequest
        # without the RequestSpec.
        db_req.request_spec
        return db_req

    def _get_update_primitives(self):
        updates = self.obj_get_changes()
        for key, value in six.iteritems(updates):
            if key in OBJECT_FIELDS and value is not None:
                updates[key] = jsonutils.dumps(value.obj_to_primitive())
            elif key in JSON_FIELDS and value is not None:
                updates[key] = jsonutils.dumps(value)
            elif key in IP_FIELDS and value is not None:
                # These are stored as a string in the db and must be converted
                updates[key] = str(value)
        req_spec_obj = updates.pop('request_spec', None)
        if req_spec_obj:
            updates['request_spec_id'] = req_spec_obj.id
        return updates

    @base.remotable
    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')

        updates = self._get_update_primitives()
        db_req = self._create_in_db(self._context, updates)
        self._from_db_object(self._context, self, db_req)

    @staticmethod
    @db.api_context_manager.writer
    def _destroy_in_db(context, id):
        context.session.query(
            api_models.BuildRequest).filter_by(id=id).delete()

    @base.remotable
    def destroy(self):
        self._destroy_in_db(self._context, self.id)
Ejemplo n.º 2
0
class InstanceActionVolumeSwapNotification(base.NotificationBase):
    # Version 1.0: Initial version
    VERSION = '1.0'

    fields = {'payload': fields.ObjectField('InstanceActionVolumeSwapPayload')}
Ejemplo n.º 3
0
class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject,
              obj_base.NovaObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Added virtual_interface field
    # Version 1.2: Instance version 1.14
    # Version 1.3: Instance 1.15
    # Version 1.4: Added default_route field
    # Version 1.5: Added floating_ips field
    # Version 1.6: Instance 1.16
    # Version 1.7: Instance 1.17
    # Version 1.8: Instance 1.18
    # Version 1.8: Instance 1.19
    VERSION = '1.9'

    fields = {
        'id': fields.IntegerField(),
        'address': fields.IPV4AndV6AddressField(),
        'network_id': fields.IntegerField(nullable=True),
        'virtual_interface_id': fields.IntegerField(nullable=True),
        'instance_uuid': fields.UUIDField(nullable=True),
        'allocated': fields.BooleanField(),
        'leased': fields.BooleanField(),
        'reserved': fields.BooleanField(),
        'host': fields.StringField(nullable=True),
        'default_route': fields.BooleanField(),
        'instance': fields.ObjectField('Instance', nullable=True),
        'network': fields.ObjectField('Network', nullable=True),
        'virtual_interface': fields.ObjectField('VirtualInterface',
                                                nullable=True),
        # NOTE(danms): This should not ever be made lazy-loadable
        # because it would create a bit of a loop between FixedIP
        # and FloatingIP
        'floating_ips': fields.ObjectField('FloatingIPList'),
        }

    obj_relationships = {
        'instance': [('1.0', '1.13'), ('1.2', '1.14'), ('1.3', '1.15'),
                     ('1.6', '1.16'), ('1.7', '1.17'), ('1.8', '1.18'),
                     ('1.9', '1.19')],
        'network': [('1.0', '1.2')],
        'virtual_interface': [('1.1', '1.0')],
        'floating_ips': [('1.5', '1.7')],
    }

    def obj_make_compatible(self, primitive, target_version):
        super(FixedIP, self).obj_make_compatible(primitive, target_version)
        target_version = utils.convert_version_to_tuple(target_version)
        if target_version < (1, 4) and 'default_route' in primitive:
            del primitive['default_route']

    @staticmethod
    def _from_db_object(context, fixedip, db_fixedip, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        for field in fixedip.fields:
            if field == 'default_route':
                # NOTE(danms): This field is only set when doing a
                # FixedIPList.get_by_network() because it's a relatively
                # special-case thing, so skip it here
                continue
            if field not in FIXED_IP_OPTIONAL_ATTRS:
                fixedip[field] = db_fixedip[field]
        # NOTE(danms): Instance could be deleted, and thus None
        if 'instance' in expected_attrs:
            fixedip.instance = objects.Instance._from_db_object(
                context,
                objects.Instance(context),
                db_fixedip['instance']) if db_fixedip['instance'] else None
        if 'network' in expected_attrs:
            fixedip.network = objects.Network._from_db_object(
                context,
                objects.Network(context),
                db_fixedip['network']) if db_fixedip['network'] else None
        if 'virtual_interface' in expected_attrs:
            db_vif = db_fixedip['virtual_interface']
            vif = objects.VirtualInterface._from_db_object(
                context,
                objects.VirtualInterface(context),
                db_fixedip['virtual_interface']) if db_vif else None
            fixedip.virtual_interface = vif
        if 'floating_ips' in expected_attrs:
            fixedip.floating_ips = obj_base.obj_make_list(
                    context, objects.FloatingIPList(context),
                    objects.FloatingIP, db_fixedip['floating_ips'])
        fixedip._context = context
        fixedip.obj_reset_changes()
        return fixedip

    @obj_base.remotable_classmethod
    def get_by_id(cls, context, id, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        get_network = 'network' in expected_attrs
        db_fixedip = db.fixed_ip_get(context, id, get_network=get_network)
        return cls._from_db_object(context, cls(context), db_fixedip,
                                   expected_attrs)

    @obj_base.remotable_classmethod
    def get_by_address(cls, context, address, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        db_fixedip = db.fixed_ip_get_by_address(context, str(address),
                                                columns_to_join=expected_attrs)
        return cls._from_db_object(context, cls(context), db_fixedip,
                                   expected_attrs)

    @obj_base.remotable_classmethod
    def get_by_floating_address(cls, context, address):
        db_fixedip = db.fixed_ip_get_by_floating_address(context, str(address))
        if db_fixedip is not None:
            return cls._from_db_object(context, cls(context), db_fixedip)

    @obj_base.remotable_classmethod
    def get_by_network_and_host(cls, context, network_id, host):
        db_fixedip = db.fixed_ip_get_by_network_host(context, network_id, host)
        return cls._from_db_object(context, cls(context), db_fixedip)

    @obj_base.remotable_classmethod
    def associate(cls, context, address, instance_uuid, network_id=None,
                  reserved=False):
        db_fixedip = db.fixed_ip_associate(context, address, instance_uuid,
                                           network_id=network_id,
                                           reserved=reserved)
        return cls._from_db_object(context, cls(context), db_fixedip)

    @obj_base.remotable_classmethod
    def associate_pool(cls, context, network_id, instance_uuid=None,
                       host=None):
        db_fixedip = db.fixed_ip_associate_pool(context, network_id,
                                                instance_uuid=instance_uuid,
                                                host=host)
        return cls._from_db_object(context, cls(context), db_fixedip)

    @obj_base.remotable_classmethod
    def disassociate_by_address(cls, context, address):
        db.fixed_ip_disassociate(context, address)

    @obj_base.remotable_classmethod
    def _disassociate_all_by_timeout(cls, context, host, time_str):
        time = timeutils.parse_isotime(time_str)
        return db.fixed_ip_disassociate_all_by_timeout(context, host, time)

    @classmethod
    def disassociate_all_by_timeout(cls, context, host, time):
        return cls._disassociate_all_by_timeout(context, host,
                                                timeutils.isotime(time))

    @obj_base.remotable
    def create(self):
        updates = self.obj_get_changes()
        if 'id' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        if 'address' in updates:
            updates['address'] = str(updates['address'])
        db_fixedip = db.fixed_ip_create(self._context, updates)
        self._from_db_object(self._context, self, db_fixedip)

    @obj_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.fixed_ip_update(self._context, str(self.address), updates)
        self.obj_reset_changes()

    @obj_base.remotable
    def disassociate(self):
        db.fixed_ip_disassociate(self._context, str(self.address))
        self.instance_uuid = None
        self.instance = None
        self.obj_reset_changes(['instance_uuid', 'instance'])
Ejemplo n.º 4
0
class InstanceNUMACell(base.NovaObject,
                       base.NovaObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Add pagesize field
    # Version 1.2: Add cpu_pinning_raw and topology fields
    # Version 1.3: Add cpu_policy and cpu_thread_policy fields
    # Version 1.4: Add cpuset_reserved field
    VERSION = '1.4'

    def obj_make_compatible(self, primitive, target_version):
        super(InstanceNUMACell, self).obj_make_compatible(primitive,
                                                        target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 4):
            primitive.pop('cpuset_reserved', None)

        if target_version < (1, 3):
            primitive.pop('cpu_policy', None)
            primitive.pop('cpu_thread_policy', None)

    fields = {
        'id': obj_fields.IntegerField(),
        'cpuset': obj_fields.SetOfIntegersField(),
        'memory': obj_fields.IntegerField(),
        'pagesize': obj_fields.IntegerField(nullable=True),
        'cpu_topology': obj_fields.ObjectField('VirtCPUTopology',
                                               nullable=True),
        'cpu_pinning_raw': obj_fields.DictOfIntegersField(nullable=True),
        'cpu_policy': obj_fields.CPUAllocationPolicyField(nullable=True),
        'cpu_thread_policy': obj_fields.CPUThreadAllocationPolicyField(
            nullable=True),
        # These physical CPUs are reserved for use by the hypervisor
        'cpuset_reserved': obj_fields.SetOfIntegersField(nullable=True),
    }

    cpu_pinning = obj_fields.DictProxyField('cpu_pinning_raw')

    def __init__(self, **kwargs):
        super(InstanceNUMACell, self).__init__(**kwargs)
        if 'pagesize' not in kwargs:
            self.pagesize = None
            self.obj_reset_changes(['pagesize'])
        if 'cpu_pinning' not in kwargs:
            self.cpu_pinning = None
            self.obj_reset_changes(['cpu_pinning_raw'])
        if 'cpu_policy' not in kwargs:
            self.cpu_policy = None
            self.obj_reset_changes(['cpu_policy'])
        if 'cpu_thread_policy' not in kwargs:
            self.cpu_thread_policy = None
            self.obj_reset_changes(['cpu_thread_policy'])
        if 'cpuset_reserved' not in kwargs:
            self.cpuset_reserved = None
            self.obj_reset_changes(['cpuset_reserved'])

    def __len__(self):
        return len(self.cpuset)

    @classmethod
    def _from_dict(cls, data_dict):
        # NOTE(sahid): Used as legacy, could be renamed in
        # _legacy_from_dict_ to the future to avoid confusing.
        cpuset = hardware.parse_cpu_spec(data_dict.get('cpus', ''))
        memory = data_dict.get('mem', {}).get('total', 0)
        cell_id = data_dict.get('id')
        pagesize = data_dict.get('pagesize')
        return cls(id=cell_id, cpuset=cpuset,
                   memory=memory, pagesize=pagesize)

    @property
    def siblings(self):
        cpu_list = sorted(list(self.cpuset))

        threads = 0
        if ('cpu_topology' in self) and self.cpu_topology:
            threads = self.cpu_topology.threads
        if threads == 1:
            threads = 0

        return list(map(set, zip(*[iter(cpu_list)] * threads)))

    @property
    def cpu_pinning_requested(self):
        return self.cpu_policy == obj_fields.CPUAllocationPolicy.DEDICATED

    def pin(self, vcpu, pcpu):
        if vcpu not in self.cpuset:
            return
        pinning_dict = self.cpu_pinning or {}
        pinning_dict[vcpu] = pcpu
        self.cpu_pinning = pinning_dict

    def pin_vcpus(self, *cpu_pairs):
        for vcpu, pcpu in cpu_pairs:
            self.pin(vcpu, pcpu)

    def clear_host_pinning(self):
        """Clear any data related to how this cell is pinned to the host.

        Needed for aborting claims as we do not want to keep stale data around.
        """
        self.id = -1
        self.cpu_pinning = {}
        return self
Ejemplo n.º 5
0
class BlockDeviceMapping(base.NovaPersistentObject, base.NovaObject,
                         base.NovaObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Add instance_uuid to get_by_volume_id method
    # Version 1.2: Instance version 1.14
    # Version 1.3: Instance version 1.15
    # Version 1.4: Instance version 1.16
    # Version 1.5: Instance version 1.17
    # Version 1.6: Instance version 1.18
    # Version 1.7: Add update_or_create method
    # Version 1.8: Instance version 1.19
    # Version 1.9: Instance version 1.20
    # Version 1.10: Changed source_type field to BlockDeviceSourceTypeField.
    # Version 1.11: Changed destination_type field to
    #               BlockDeviceDestinationTypeField.
    # Version 1.12: Changed device_type field to BlockDeviceTypeField.
    # Version 1.13: Instance version 1.21
    # Version 1.14: Instance version 1.22
    # Version 1.15: Instance version 1.23
    # Version 1.16: Deprecate get_by_volume_id(), add
    #               get_by_volume() and get_by_volume_and_instance()
    VERSION = '1.16'

    fields = {
        'id': fields.IntegerField(),
        'instance_uuid': fields.UUIDField(),
        'instance': fields.ObjectField('Instance', nullable=True),
        'source_type': fields.BlockDeviceSourceTypeField(nullable=True),
        'destination_type':
        fields.BlockDeviceDestinationTypeField(nullable=True),
        'guest_format': fields.StringField(nullable=True),
        'device_type': fields.BlockDeviceTypeField(nullable=True),
        'disk_bus': fields.StringField(nullable=True),
        'boot_index': fields.IntegerField(nullable=True),
        'device_name': fields.StringField(nullable=True),
        'delete_on_termination': fields.BooleanField(default=False),
        'snapshot_id': fields.StringField(nullable=True),
        'volume_id': fields.StringField(nullable=True),
        'volume_size': fields.IntegerField(nullable=True),
        'image_id': fields.StringField(nullable=True),
        'no_device': fields.BooleanField(default=False),
        'connection_info': fields.StringField(nullable=True),
    }

    @staticmethod
    def _from_db_object(context,
                        block_device_obj,
                        db_block_device,
                        expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        for key in block_device_obj.fields:
            if key in BLOCK_DEVICE_OPTIONAL_ATTRS:
                continue
            block_device_obj[key] = db_block_device[key]
        if 'instance' in expected_attrs:
            my_inst = objects.Instance(context)
            my_inst._from_db_object(context, my_inst,
                                    db_block_device['instance'])
            block_device_obj.instance = my_inst

        block_device_obj._context = context
        block_device_obj.obj_reset_changes()
        return block_device_obj

    def _create(self, context, update_or_create=False):
        """Create the block device record in the database.

        In case the id field is set on the object, and if the instance is set
        raise an ObjectActionError. Resets all the changes on the object.

        Returns None

        :param context: security context used for database calls
        :param update_or_create: consider existing block devices for the
                instance based on the device name and swap, and only update
                the ones that match. Normally only used when creating the
                instance for the first time.
        """
        cell_type = cells_opts.get_cell_type()
        if cell_type == 'api':
            raise exception.ObjectActionError(
                action='create',
                reason='BlockDeviceMapping cannot be '
                'created in the API cell.')

        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        updates = self.obj_get_changes()
        if 'instance' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason='instance assigned')

        cells_create = update_or_create or None
        if update_or_create:
            db_bdm = db.block_device_mapping_update_or_create(context,
                                                              updates,
                                                              legacy=False)
        else:
            db_bdm = db.block_device_mapping_create(context,
                                                    updates,
                                                    legacy=False)

        self._from_db_object(context, self, db_bdm)
        # NOTE(alaski): bdms are looked up by instance uuid and device_name
        # so if we sync up with no device_name an entry will be created that
        # will not be found on a later update_or_create call and a second bdm
        # create will occur.
        if cell_type == 'compute' and db_bdm.get('device_name') is not None:
            cells_api = cells_rpcapi.CellsAPI()
            cells_api.bdm_update_or_create_at_top(context,
                                                  self,
                                                  create=cells_create)

    @base.remotable
    def create(self):
        self._create(self._context)

    @base.remotable
    def update_or_create(self):
        self._create(self._context, update_or_create=True)

    @base.remotable
    def destroy(self):
        if not self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='destroy',
                                              reason='already destroyed')
        db.block_device_mapping_destroy(self._context, self.id)
        delattr(self, base.get_attrname('id'))

        cell_type = cells_opts.get_cell_type()
        if cell_type == 'compute':
            cells_api = cells_rpcapi.CellsAPI()
            cells_api.bdm_destroy_at_top(self._context,
                                         self.instance_uuid,
                                         device_name=self.device_name,
                                         volume_id=self.volume_id)

    @base.remotable
    def save(self):
        updates = self.obj_get_changes()
        if 'instance' in updates:
            raise exception.ObjectActionError(action='save',
                                              reason='instance changed')
        updates.pop('id', None)
        updated = db.block_device_mapping_update(self._context,
                                                 self.id,
                                                 updates,
                                                 legacy=False)
        if not updated:
            raise exception.BDMNotFound(id=self.id)
        self._from_db_object(self._context, self, updated)
        cell_type = cells_opts.get_cell_type()
        if cell_type == 'compute':
            create = False
            # NOTE(alaski): If the device name has just been set this bdm
            # likely does not exist in the parent cell and we should create it.
            # If this is a modification of the device name we should update
            # rather than create which is why None is used here instead of True
            if 'device_name' in updates:
                create = None
            cells_api = cells_rpcapi.CellsAPI()
            cells_api.bdm_update_or_create_at_top(self._context,
                                                  self,
                                                  create=create)

    # NOTE(danms): This method is deprecated and will be removed in
    # v2.0 of the object
    @base.remotable_classmethod
    def get_by_volume_id(cls,
                         context,
                         volume_id,
                         instance_uuid=None,
                         expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        db_bdms = db.block_device_mapping_get_all_by_volume_id(
            context, volume_id, _expected_cols(expected_attrs))
        if not db_bdms:
            raise exception.VolumeBDMNotFound(volume_id=volume_id)
        if len(db_bdms) > 1:
            LOG.warning(
                _LW('Legacy get_by_volume_id() call found multiple '
                    'BDMs for volume %(volume)s'), {'volume': volume_id})
        db_bdm = db_bdms[0]
        # NOTE (ndipanov): Move this to the db layer into a
        # get_by_instance_and_volume_id method
        if instance_uuid and instance_uuid != db_bdm['instance_uuid']:
            raise exception.InvalidVolume(
                reason=_("Volume does not belong to the "
                         "requested instance."))
        return cls._from_db_object(context,
                                   cls(),
                                   db_bdm,
                                   expected_attrs=expected_attrs)

    @base.remotable_classmethod
    def get_by_volume_and_instance(cls,
                                   context,
                                   volume_id,
                                   instance_uuid,
                                   expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        db_bdm = db.block_device_mapping_get_by_instance_and_volume_id(
            context, volume_id, instance_uuid, _expected_cols(expected_attrs))
        if not db_bdm:
            raise exception.VolumeBDMNotFound(volume_id=volume_id)
        return cls._from_db_object(context,
                                   cls(),
                                   db_bdm,
                                   expected_attrs=expected_attrs)

    @base.remotable_classmethod
    def get_by_volume(cls, context, volume_id, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        db_bdms = db.block_device_mapping_get_all_by_volume_id(
            context, volume_id, _expected_cols(expected_attrs))
        if not db_bdms:
            raise exception.VolumeBDMNotFound(volume_id=volume_id)
        if len(db_bdms) > 1:
            raise exception.VolumeBDMIsMultiAttach(volume_id=volume_id)
        return cls._from_db_object(context,
                                   cls(),
                                   db_bdms[0],
                                   expected_attrs=expected_attrs)

    @property
    def is_root(self):
        return self.boot_index == 0

    @property
    def is_volume(self):
        return (
            self.destination_type == fields.BlockDeviceDestinationType.VOLUME)

    @property
    def is_image(self):
        return self.source_type == fields.BlockDeviceSourceType.IMAGE

    def get_image_mapping(self):
        return block_device.BlockDeviceDict(self).get_image_mapping()

    def obj_load_attr(self, attrname):
        if attrname not in BLOCK_DEVICE_OPTIONAL_ATTRS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason='attribute %s not lazy-loadable' % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        LOG.debug("Lazy-loading '%(attr)s' on %(name)s uuid %(uuid)s", {
            'attr': attrname,
            'name': self.obj_name(),
            'uuid': self.uuid,
        })
        self.instance = objects.Instance.get_by_uuid(self._context,
                                                     self.instance_uuid)
        self.obj_reset_changes(fields=['instance'])
Ejemplo n.º 6
0
class InstanceMapping(base.NovaTimestampObject, base.NovaObject):
    # Version 1.0: Initial version
    # Version 1.1: Add queued_for_delete
    # Version 1.2: Add user_id
    VERSION = '1.2'

    fields = {
        'id': fields.IntegerField(read_only=True),
        'instance_uuid': fields.UUIDField(),
        'cell_mapping': fields.ObjectField('CellMapping', nullable=True),
        'project_id': fields.StringField(),
        'user_id': fields.StringField(),
        'queued_for_delete': fields.BooleanField(default=False),
        }

    def obj_make_compatible(self, primitive, target_version):
        super(InstanceMapping, self).obj_make_compatible(primitive,
                                                         target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 2) and 'user_id' in primitive:
            del primitive['user_id']
        if target_version < (1, 1):
            if 'queued_for_delete' in primitive:
                del primitive['queued_for_delete']

    def obj_load_attr(self, attrname):
        if attrname == 'user_id':
            LOG.error('The unset user_id attribute of an unmigrated instance '
                      'mapping should not be accessed.')
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason=_('attribute user_id is not lazy-loadable'))
        super(InstanceMapping, self).obj_load_attr(attrname)

    def _update_with_cell_id(self, updates):
        cell_mapping_obj = updates.pop("cell_mapping", None)
        if cell_mapping_obj:
            updates["cell_id"] = cell_mapping_obj.id
        return updates

    @staticmethod
    def _from_db_object(context, instance_mapping, db_instance_mapping):
        for key in instance_mapping.fields:
            db_value = db_instance_mapping.get(key)
            if key == 'cell_mapping':
                # cell_mapping can be None indicating that the instance has
                # not been scheduled yet.
                if db_value:
                    db_value = cell_mapping.CellMapping._from_db_object(
                        context, cell_mapping.CellMapping(), db_value)
            if key == 'user_id' and db_value is None:
                # NOTE(melwitt): If user_id is NULL, we can't set the field
                # because it's non-nullable. We don't plan for any code to read
                # the user_id field at this time, so skip setting it.
                continue
            setattr(instance_mapping, key, db_value)
        instance_mapping.obj_reset_changes()
        instance_mapping._context = context
        return instance_mapping

    @staticmethod
    @db_api.api_context_manager.reader
    def _get_by_instance_uuid_from_db(context, instance_uuid):
        db_mapping = (context.session.query(api_models.InstanceMapping)
                        .options(joinedload('cell_mapping'))
                        .filter(
                            api_models.InstanceMapping.instance_uuid ==
                            instance_uuid)).first()
        if not db_mapping:
            raise exception.InstanceMappingNotFound(uuid=instance_uuid)

        return db_mapping

    @base.remotable_classmethod
    def get_by_instance_uuid(cls, context, instance_uuid):
        db_mapping = cls._get_by_instance_uuid_from_db(context, instance_uuid)
        return cls._from_db_object(context, cls(), db_mapping)

    @staticmethod
    @db_api.api_context_manager.writer
    def _create_in_db(context, updates):
        db_mapping = api_models.InstanceMapping()
        db_mapping.update(updates)
        db_mapping.save(context.session)
        # NOTE: This is done because a later access will trigger a lazy load
        # outside of the db session so it will fail. We don't lazy load
        # cell_mapping on the object later because we never need an
        # InstanceMapping without the CellMapping.
        db_mapping.cell_mapping
        return db_mapping

    @base.remotable
    def create(self):
        changes = self.obj_get_changes()
        changes = self._update_with_cell_id(changes)
        if 'queued_for_delete' not in changes:
            # NOTE(danms): If we are creating a mapping, it should be
            # not queued_for_delete (unless we are being asked to
            # create one in deleted state for some reason).
            changes['queued_for_delete'] = False
        db_mapping = self._create_in_db(self._context, changes)
        self._from_db_object(self._context, self, db_mapping)

    @staticmethod
    @db_api.api_context_manager.writer
    def _save_in_db(context, instance_uuid, updates):
        db_mapping = context.session.query(
                api_models.InstanceMapping).filter_by(
                        instance_uuid=instance_uuid).first()
        if not db_mapping:
            raise exception.InstanceMappingNotFound(uuid=instance_uuid)

        db_mapping.update(updates)
        # NOTE: This is done because a later access will trigger a lazy load
        # outside of the db session so it will fail. We don't lazy load
        # cell_mapping on the object later because we never need an
        # InstanceMapping without the CellMapping.
        db_mapping.cell_mapping
        context.session.add(db_mapping)
        return db_mapping

    @base.remotable
    def save(self):
        changes = self.obj_get_changes()
        changes = self._update_with_cell_id(changes)
        try:
            db_mapping = self._save_in_db(self._context, self.instance_uuid,
                    changes)
        except orm_exc.StaleDataError:
            # NOTE(melwitt): If the instance mapping has been deleted out from
            # under us by conductor (delete requested while booting), we will
            # encounter a StaleDataError after we retrieved the row and try to
            # update it after it's been deleted. We can treat this like an
            # instance mapping not found and allow the caller to handle it.
            raise exception.InstanceMappingNotFound(uuid=self.instance_uuid)
        self._from_db_object(self._context, self, db_mapping)
        self.obj_reset_changes()

    @staticmethod
    @db_api.api_context_manager.writer
    def _destroy_in_db(context, instance_uuid):
        result = context.session.query(api_models.InstanceMapping).filter_by(
                instance_uuid=instance_uuid).delete()
        if not result:
            raise exception.InstanceMappingNotFound(uuid=instance_uuid)

    @base.remotable
    def destroy(self):
        self._destroy_in_db(self._context, self.instance_uuid)
class BlockDeviceMapping(base.NovaPersistentObject, base.NovaObject,
                         base.NovaObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Add instance_uuid to get_by_volume_id method
    # Version 1.2: Instance version 1.14
    # Version 1.3: Instance version 1.15
    # Version 1.4: Instance version 1.16
    # Version 1.5: Instance version 1.17
    # Version 1.6: Instance version 1.18
    # Version 1.7: Add update_or_create method
    # Version 1.8: Instance version 1.19
    # Version 1.9: Instance version 1.20
    # Version 1.10: Changed source_type field to BlockDeviceSourceTypeField.
    # Version 1.11: Changed destination_type field to
    #               BlockDeviceDestinationTypeField.
    # Version 1.12: Changed device_type field to BlockDeviceTypeField.
    # Version 1.13: Instance version 1.21
    # Version 1.14: Instance version 1.22
    # Version 1.15: Instance version 1.23
    # Version 1.16: Deprecate get_by_volume_id(), add
    #               get_by_volume() and get_by_volume_and_instance()
    # Version 1.17: Added tag field
    # Version 1.18: Added attachment_id
    # Version 1.19: Added uuid
    # Version 1.20: Added volume_type
    VERSION = '1.20'

    fields = {
        'id': fields.IntegerField(),
        'uuid': fields.UUIDField(),
        'instance_uuid': fields.UUIDField(),
        'instance': fields.ObjectField('Instance', nullable=True),
        'source_type': fields.BlockDeviceSourceTypeField(nullable=True),
        'destination_type': fields.BlockDeviceDestinationTypeField(
                                nullable=True),
        'guest_format': fields.StringField(nullable=True),
        'device_type': fields.BlockDeviceTypeField(nullable=True),
        'disk_bus': fields.StringField(nullable=True),
        'boot_index': fields.IntegerField(nullable=True),
        'device_name': fields.StringField(nullable=True),
        'delete_on_termination': fields.BooleanField(default=False),
        'snapshot_id': fields.StringField(nullable=True),
        'volume_id': fields.StringField(nullable=True),
        'volume_size': fields.IntegerField(nullable=True),
        'image_id': fields.StringField(nullable=True),
        'no_device': fields.BooleanField(default=False),
        'connection_info': fields.SensitiveStringField(nullable=True),
        'tag': fields.StringField(nullable=True),
        'attachment_id': fields.UUIDField(nullable=True),
        # volume_type field can be a volume type name or ID(UUID).
        'volume_type': 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, 20) and 'volume_type' in primitive:
            del primitive['volume_type']
        if target_version < (1, 19) and 'uuid' in primitive:
            del primitive['uuid']
        if target_version < (1, 18) and 'attachment_id' in primitive:
            del primitive['attachment_id']
        if target_version < (1, 17) and 'tag' in primitive:
            del primitive['tag']

    @classmethod
    def populate_uuids(cls, context, count):
        @db_api.pick_context_manager_reader
        def get_bdms_no_uuid(context):
            return context.session.query(db_models.BlockDeviceMapping).\
                    filter_by(uuid=None).limit(count).all()

        db_bdms = get_bdms_no_uuid(context)

        done = 0
        for db_bdm in db_bdms:
            cls._create_uuid(context, db_bdm['id'])
            done += 1

        return done, done

    @staticmethod
    @oslo_db_api.wrap_db_retry(max_retries=1, retry_on_deadlock=True)
    def _create_uuid(context, bdm_id):
        # NOTE(mdbooth): This method is only required until uuid is made
        # non-nullable in a future release.

        # NOTE(mdbooth): We wrap this method in a retry loop because it can
        # fail (safely) on multi-master galera if concurrent updates happen on
        # different masters. It will never fail on single-master. We can only
        # ever need one retry.

        uuid = uuidutils.generate_uuid()
        values = {'uuid': uuid}
        compare = db_models.BlockDeviceMapping(id=bdm_id, uuid=None)

        # NOTE(mdbooth): We explicitly use an independent transaction context
        # here so as not to fail if:
        # 1. We retry.
        # 2. We're in a read transaction.This is an edge case of what's
        #    normally a read operation. Forcing everything (transitively)
        #    which reads a BDM to be in a write transaction for a narrow
        #    temporary edge case is undesirable.
        tctxt = db_api.get_context_manager(context).writer.independent
        with tctxt.using(context):
            query = context.session.query(db_models.BlockDeviceMapping).\
                        filter_by(id=bdm_id)

            try:
                query.update_on_match(compare, 'id', values)
            except update_match.NoRowsMatched:
                # We can only get here if we raced, and another writer already
                # gave this bdm a uuid
                result = query.one()
                uuid = result['uuid']
                assert(uuid is not None)

        return uuid

    @classmethod
    def _from_db_object(cls, context, block_device_obj,
                        db_block_device, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        for key in block_device_obj.fields:
            if key in BLOCK_DEVICE_OPTIONAL_ATTRS:
                continue
            if key == 'uuid' and not db_block_device.get(key):
                # NOTE(danms): While the records could be nullable,
                # generate a UUID on read since the object requires it
                bdm_id = db_block_device['id']
                db_block_device[key] = cls._create_uuid(context, bdm_id)
            block_device_obj[key] = db_block_device[key]
        if 'instance' in expected_attrs:
            my_inst = objects.Instance(context)
            my_inst._from_db_object(context, my_inst,
                                    db_block_device['instance'])
            block_device_obj.instance = my_inst

        block_device_obj._context = context
        block_device_obj.obj_reset_changes()
        return block_device_obj

    def _create(self, context, update_or_create=False):
        """Create the block device record in the database.

        In case the id field is set on the object, and if the instance is set
        raise an ObjectActionError. Resets all the changes on the object.

        Returns None

        :param context: security context used for database calls
        :param update_or_create: consider existing block devices for the
                instance based on the device name and swap, and only update
                the ones that match. Normally only used when creating the
                instance for the first time.
        """
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        updates = self.obj_get_changes()
        if 'instance' in updates:
            raise exception.ObjectActionError(action='create',
                                              reason='instance assigned')

        if update_or_create:
            db_bdm = db.block_device_mapping_update_or_create(
                    context, updates, legacy=False)
        else:
            db_bdm = db.block_device_mapping_create(
                    context, updates, legacy=False)

        self._from_db_object(context, self, db_bdm)

    @base.remotable
    def create(self):
        self._create(self._context)

    @base.remotable
    def update_or_create(self):
        self._create(self._context, update_or_create=True)

    @base.remotable
    def destroy(self):
        if not self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='destroy',
                                              reason='already destroyed')
        db.block_device_mapping_destroy(self._context, self.id)
        delattr(self, base.get_attrname('id'))

    @base.remotable
    def save(self):
        updates = self.obj_get_changes()
        if 'instance' in updates:
            raise exception.ObjectActionError(action='save',
                                              reason='instance changed')
        updates.pop('id', None)
        updated = db.block_device_mapping_update(self._context, self.id,
                                                 updates, legacy=False)
        if not updated:
            raise exception.BDMNotFound(id=self.id)
        self._from_db_object(self._context, self, updated)

    # NOTE(danms): This method is deprecated and will be removed in
    # v2.0 of the object
    @base.remotable_classmethod
    def get_by_volume_id(cls, context, volume_id,
                         instance_uuid=None, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        db_bdms = db.block_device_mapping_get_all_by_volume_id(
                context, volume_id, _expected_cols(expected_attrs))
        if not db_bdms:
            raise exception.VolumeBDMNotFound(volume_id=volume_id)
        if len(db_bdms) > 1:
            LOG.warning('Legacy get_by_volume_id() call found multiple '
                        'BDMs for volume %(volume)s',
                        {'volume': volume_id})
        db_bdm = db_bdms[0]
        # NOTE (ndipanov): Move this to the db layer into a
        # get_by_instance_and_volume_id method
        if instance_uuid and instance_uuid != db_bdm['instance_uuid']:
            raise exception.InvalidVolume(
                    reason=_("Volume does not belong to the "
                             "requested instance."))
        return cls._from_db_object(context, cls(), db_bdm,
                                   expected_attrs=expected_attrs)

    @base.remotable_classmethod
    def get_by_volume_and_instance(cls, context, volume_id, instance_uuid,
                                   expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        db_bdm = db.block_device_mapping_get_by_instance_and_volume_id(
            context, volume_id, instance_uuid,
            _expected_cols(expected_attrs))
        if not db_bdm:
            raise exception.VolumeBDMNotFound(volume_id=volume_id)
        return cls._from_db_object(context, cls(), db_bdm,
                                   expected_attrs=expected_attrs)

    @base.remotable_classmethod
    def get_by_volume(cls, context, volume_id, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        db_bdms = db.block_device_mapping_get_all_by_volume_id(
                context, volume_id, _expected_cols(expected_attrs))
        if not db_bdms:
            raise exception.VolumeBDMNotFound(volume_id=volume_id)
        if len(db_bdms) > 1:
            raise exception.VolumeBDMIsMultiAttach(volume_id=volume_id)
        return cls._from_db_object(context, cls(), db_bdms[0],
                                   expected_attrs=expected_attrs)

    @property
    def is_root(self):
        return self.boot_index == 0

    @property
    def is_volume(self):
        return (self.destination_type ==
                    fields.BlockDeviceDestinationType.VOLUME)

    @property
    def is_image(self):
        return self.source_type == fields.BlockDeviceSourceType.IMAGE

    def get_image_mapping(self):
        return block_device.BlockDeviceDict(self).get_image_mapping()

    def obj_load_attr(self, attrname):
        if attrname not in BLOCK_DEVICE_OPTIONAL_ATTRS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason='attribute %s not lazy-loadable' % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        LOG.debug("Lazy-loading '%(attr)s' on %(name)s using uuid %(uuid)s",
                  {'attr': attrname,
                   'name': self.obj_name(),
                   'uuid': self.instance_uuid,
                   })
        self.instance = objects.Instance.get_by_uuid(self._context,
                                                     self.instance_uuid)
        self.obj_reset_changes(fields=['instance'])
Ejemplo n.º 8
0
class AggregateNotification(base.NotificationBase):
    # Version 1.0: Initial version
    VERSION = '1.0'

    fields = {'payload': fields.ObjectField('AggregatePayload')}
Ejemplo n.º 9
0
class ImageMeta(base.NovaObject):
    # Version 1.0: Initial version
    # Version 1.1: updated ImageMetaProps
    # Version 1.2: ImageMetaProps version 1.2
    # Version 1.3: ImageMetaProps version 1.3
    # Version 1.4: ImageMetaProps version 1.4
    # Version 1.5: ImageMetaProps version 1.5
    # Version 1.6: ImageMetaProps version 1.6
    # Version 1.7: ImageMetaProps version 1.7
    # Version 1.8: ImageMetaProps version 1.8
    VERSION = '1.8'

    # These are driven by what the image client API returns
    # to Nova from Glance. This is defined in the glance
    # code glance/api/v2/images.py get_base_properties()
    # method. A few things are currently left out:
    # self, file, schema - Nova does not appear to ever use
    # these field; locations - modelling the arbitrary
    # data in the 'metadata' subfield is non-trivial as
    # there's no clear spec.
    #
    # TODO(ft): In version 2.0, these fields should be nullable:
    # name, checksum, owner, size, virtual_size, container_format, disk_format
    #
    fields = {
        'id': fields.UUIDField(),
        'name': fields.StringField(),
        'status': fields.StringField(),
        'visibility': fields.StringField(),
        'protected': fields.FlexibleBooleanField(),
        'checksum': fields.StringField(),
        'owner': fields.StringField(),
        'size': fields.IntegerField(),
        'virtual_size': fields.IntegerField(),
        'container_format': fields.StringField(),
        'disk_format': fields.StringField(),
        'created_at': fields.DateTimeField(nullable=True),
        'updated_at': fields.DateTimeField(nullable=True),
        'tags': fields.ListOfStringsField(),
        'direct_url': fields.StringField(),
        'min_ram': fields.IntegerField(),
        'min_disk': fields.IntegerField(),
        'properties': fields.ObjectField('ImageMetaProps'),
    }

    @classmethod
    def from_dict(cls, image_meta):
        """Create instance from image metadata dict

        :param image_meta: image metadata dictionary

        Creates a new object instance, initializing from the
        properties associated with the image metadata instance

        :returns: an ImageMeta instance
        """
        if image_meta is None:
            image_meta = {}

        # We must turn 'properties' key dict into an object
        # so copy image_meta to avoid changing original
        image_meta = copy.deepcopy(image_meta)
        image_meta["properties"] = \
            objects.ImageMetaProps.from_dict(
                image_meta.get("properties", {}))

        # Some fields are nullable in Glance DB schema, but was not marked that
        # in ImageMeta initially by mistake. To keep compatibility with compute
        # nodes which are run with previous versions these fields are still
        # not nullable in ImageMeta, but the code below converts None to
        # approppriate empty values.
        for fld in NULLABLE_STRING_FIELDS:
            if fld in image_meta and image_meta[fld] is None:
                image_meta[fld] = ''
        for fld in NULLABLE_INTEGER_FIELDS:
            if fld in image_meta and image_meta[fld] is None:
                image_meta[fld] = 0

        return cls(**image_meta)

    @classmethod
    def from_instance(cls, instance):
        """Create instance from instance system metadata

        :param instance: Instance object

        Creates a new object instance, initializing from the
        system metadata "image_*" properties associated with
        instance

        :returns: an ImageMeta instance
        """
        sysmeta = utils.instance_sys_meta(instance)
        image_meta = utils.get_image_from_system_metadata(sysmeta)
        return cls.from_dict(image_meta)

    @classmethod
    def from_image_ref(cls, context, image_api, image_ref):
        """Create instance from glance image

        :param context: the request context
        :param image_api: the glance client API
        :param image_ref: the glance image identifier

        Creates a new object instance, initializing from the
        properties associated with a glance image

        :returns: an ImageMeta instance
        """

        image_meta = image_api.get(context, image_ref)
        image = cls.from_dict(image_meta)
        setattr(image, "id", image_ref)
        return image
Ejemplo n.º 10
0
class Service(base.NovaPersistentObject, base.NovaObject,
              base.NovaObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Added compute_node nested object
    # Version 1.2: String attributes updated to support unicode
    # Version 1.3: ComputeNode version 1.5
    # Version 1.4: Added use_slave to get_by_compute_host
    # Version 1.5: ComputeNode version 1.6
    # Version 1.6: ComputeNode version 1.7
    # Version 1.7: ComputeNode version 1.8
    # Version 1.8: ComputeNode version 1.9
    # Version 1.9: ComputeNode version 1.10
    # Version 1.10: Changes behaviour of loading compute_node
    # Version 1.11: Added get_by_host_and_binary
    # Version 1.12: ComputeNode version 1.11
    # Version 1.13: Added last_seen_up
    # Version 1.14: Added forced_down
    VERSION = '1.14'

    fields = {
        'id': fields.IntegerField(read_only=True),
        'host': fields.StringField(nullable=True),
        'binary': fields.StringField(nullable=True),
        'topic': fields.StringField(nullable=True),
        'report_count': fields.IntegerField(),
        'disabled': fields.BooleanField(),
        'disabled_reason': fields.StringField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),
        'compute_node': fields.ObjectField('ComputeNode'),
        'last_seen_up': fields.DateTimeField(nullable=True),
        'forced_down': fields.BooleanField(),
    }

    obj_relationships = {
        'compute_node': [('1.1', '1.4'), ('1.3', '1.5'), ('1.5', '1.6'),
                         ('1.7', '1.8'), ('1.8', '1.9'), ('1.9', '1.10'),
                         ('1.12', '1.11')],
    }

    def obj_make_compatible(self, primitive, target_version):
        _target_version = utils.convert_version_to_tuple(target_version)
        if _target_version < (1, 14) and 'forced_down' in primitive:
            del primitive['forced_down']
        if _target_version < (1, 13) and 'last_seen_up' in primitive:
            del primitive['last_seen_up']
        if _target_version < (1, 10):
            target_compute_version = self.obj_calculate_child_version(
                target_version, 'compute_node')
            # service.compute_node was not lazy-loaded, we need to provide it
            # when called
            self._do_compute_node(self._context, primitive,
                                  target_compute_version)
        super(Service, self).obj_make_compatible(primitive, target_version)

    def _do_compute_node(self, context, primitive, target_version):
        try:
            # NOTE(sbauza): Some drivers (VMware, Ironic) can have multiple
            # nodes for the same service, but for keeping same behaviour,
            # returning only the first elem of the list
            compute = objects.ComputeNodeList.get_all_by_host(
                context, primitive['host'])[0]
        except Exception:
            return
        primitive['compute_node'] = compute.obj_to_primitive(
            target_version=target_version)

    @staticmethod
    def _from_db_object(context, service, db_service):
        allow_missing = ('availability_zone', )
        for key in service.fields:
            if key in allow_missing and key not in db_service:
                continue
            if key == 'compute_node':
                #  NOTE(sbauza); We want to only lazy-load compute_node
                continue
            else:
                service[key] = db_service[key]
        service._context = context
        service.obj_reset_changes()
        return service

    def obj_load_attr(self, attrname):
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        LOG.debug("Lazy-loading `%(attr)s' on %(name)s id %(id)s", {
            'attr': attrname,
            'name': self.obj_name(),
            'id': self.id,
        })
        if attrname != 'compute_node':
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason='attribute %s not lazy-loadable' % attrname)
        if self.binary == 'nova-compute':
            # Only n-cpu services have attached compute_node(s)
            compute_nodes = objects.ComputeNodeList.get_all_by_host(
                self._context, self.host)
        else:
            # NOTE(sbauza); Previous behaviour was raising a ServiceNotFound,
            # we keep it for backwards compatibility
            raise exception.ServiceNotFound(service_id=self.id)
        # NOTE(sbauza): Some drivers (VMware, Ironic) can have multiple nodes
        # for the same service, but for keeping same behaviour, returning only
        # the first elem of the list
        self.compute_node = compute_nodes[0]

    @base.remotable_classmethod
    def get_by_id(cls, context, service_id):
        db_service = db.service_get(context, service_id)
        return cls._from_db_object(context, cls(), db_service)

    @base.remotable_classmethod
    def get_by_host_and_topic(cls, context, host, topic):
        db_service = db.service_get_by_host_and_topic(context, host, topic)
        return cls._from_db_object(context, cls(), db_service)

    @base.remotable_classmethod
    def get_by_host_and_binary(cls, context, host, binary):
        try:
            db_service = db.service_get_by_host_and_binary(
                context, host, binary)
        except exception.HostBinaryNotFound:
            return
        return cls._from_db_object(context, cls(), db_service)

    @base.remotable_classmethod
    def get_by_compute_host(cls, context, host, use_slave=False):
        db_service = db.service_get_by_compute_host(context, host)
        return cls._from_db_object(context, cls(), db_service)

    # NOTE(ndipanov): This is deprecated and should be removed on the next
    # major version bump
    @base.remotable_classmethod
    def get_by_args(cls, context, host, binary):
        db_service = db.service_get_by_host_and_binary(context, host, binary)
        return cls._from_db_object(context, cls(), db_service)

    @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_service = db.service_create(self._context, updates)
        self._from_db_object(self._context, self, db_service)

    @base.remotable
    def save(self):
        updates = self.obj_get_changes()
        updates.pop('id', None)
        db_service = db.service_update(self._context, self.id, updates)
        self._from_db_object(self._context, self, db_service)

    @base.remotable
    def destroy(self):
        db.service_destroy(self._context, self.id)
Ejemplo n.º 11
0
class InstanceNUMACell(base.NovaObject,
                       base.NovaObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Add pagesize field
    # Version 1.2: Add cpu_pinning_raw and topology fields
    VERSION = '1.2'

    fields = {
        'id': obj_fields.IntegerField(),
        'cpuset': obj_fields.SetOfIntegersField(),
        'memory': obj_fields.IntegerField(),
        'pagesize': obj_fields.IntegerField(nullable=True),
        'cpu_topology': obj_fields.ObjectField('VirtCPUTopology',
                                               nullable=True),
        'cpu_pinning_raw': obj_fields.DictOfIntegersField(nullable=True)
        }

    obj_relationships = {
        'cpu_topology': [('1.2', '1.0')]
    }

    cpu_pinning = obj_fields.DictProxyField('cpu_pinning_raw')

    def __init__(self, **kwargs):
        super(InstanceNUMACell, self).__init__(**kwargs)
        if 'pagesize' not in kwargs:
            self.pagesize = None
            self.obj_reset_changes(['pagesize'])
        if 'cpu_topology' not in kwargs:
            self.cpu_topology = None
            self.obj_reset_changes(['cpu_topology'])
        if 'cpu_pinning' not in kwargs:
            self.cpu_pinning = None
            self.obj_reset_changes(['cpu_pinning_raw'])

    def __len__(self):
        return len(self.cpuset)

    def _to_dict(self):
        # NOTE(sahid): Used as legacy, could be renamed in
        # _legacy_to_dict_ to the future to avoid confusing.
        return {'cpus': hardware.format_cpu_spec(self.cpuset,
                                                 allow_ranges=False),
                'mem': {'total': self.memory},
                'id': self.id,
                'pagesize': self.pagesize}

    @classmethod
    def _from_dict(cls, data_dict):
        # NOTE(sahid): Used as legacy, could be renamed in
        # _legacy_from_dict_ to the future to avoid confusing.
        cpuset = hardware.parse_cpu_spec(data_dict.get('cpus', ''))
        memory = data_dict.get('mem', {}).get('total', 0)
        cell_id = data_dict.get('id')
        pagesize = data_dict.get('pagesize')
        return cls(id=cell_id, cpuset=cpuset,
                   memory=memory, pagesize=pagesize)

    @property
    def siblings(self):
        cpu_list = sorted(list(self.cpuset))

        threads = 0
        if self.cpu_topology:
            threads = self.cpu_topology.threads
        if threads == 1:
            threads = 0

        return list(map(set, zip(*[iter(cpu_list)] * threads)))

    @property
    def cpu_pinning_requested(self):
        return self.cpu_pinning is not None

    def pin(self, vcpu, pcpu):
        if vcpu not in self.cpuset:
            return
        pinning_dict = self.cpu_pinning or {}
        pinning_dict[vcpu] = pcpu
        self.cpu_pinning = pinning_dict

    def pin_vcpus(self, *cpu_pairs):
        for vcpu, pcpu in cpu_pairs:
            self.pin(vcpu, pcpu)
Ejemplo n.º 12
0
class ImageMetaPayload(base.NotificationPayloadBase):
    # Version 1.0: Initial version
    VERSION = '1.0'

    SCHEMA = {
        'id': ('image_meta', 'id'),
        'name': ('image_meta', 'name'),
        'status': ('image_meta', 'status'),
        'visibility': ('image_meta', 'visibility'),
        'protected': ('image_meta', 'protected'),
        'checksum': ('image_meta', 'checksum'),
        'owner': ('image_meta', 'owner'),
        'size': ('image_meta', 'size'),
        'virtual_size': ('image_meta', 'virtual_size'),
        'container_format': ('image_meta', 'container_format'),
        'disk_format': ('image_meta', 'disk_format'),
        'created_at': ('image_meta', 'created_at'),
        'updated_at': ('image_meta', 'updated_at'),
        'tags': ('image_meta', 'tags'),
        'direct_url': ('image_meta', 'direct_url'),
        'min_ram': ('image_meta', 'min_ram'),
        'min_disk': ('image_meta', 'min_disk')
    }

    # NOTE(takashin): The reason that each field is nullable is as follows.
    #
    # a. It is defined as "The value might be null (JSON null data type)."
    #    in the "Show image" API (GET /v2/images/{image_id})
    #    in the glance API v2 Reference.
    #    (https://developer.openstack.org/api-ref/image/v2/index.html)
    #
    #   * checksum
    #   * container_format
    #   * disk_format
    #   * min_disk
    #   * min_ram
    #   * name
    #   * owner
    #   * size
    #   * updated_at
    #   * virtual_size
    #
    # b. It is optional in the response from glance.
    #   * direct_url
    #
    # a. It is defined as nullable in the ImageMeta object.
    #   * created_at
    #
    # c. It cannot be got in the boot from volume case.
    #    See VIM_IMAGE_ATTRIBUTES in nova/utils.py.
    #
    #   * id (not 'image_id')
    #   * visibility
    #   * protected
    #   * status
    #   * tags
    fields = {
        'id': fields.UUIDField(nullable=True),
        'name': fields.StringField(nullable=True),
        'status': fields.StringField(nullable=True),
        'visibility': fields.StringField(nullable=True),
        'protected': fields.FlexibleBooleanField(nullable=True),
        'checksum': fields.StringField(nullable=True),
        'owner': fields.StringField(nullable=True),
        'size': fields.IntegerField(nullable=True),
        'virtual_size': fields.IntegerField(nullable=True),
        'container_format': fields.StringField(nullable=True),
        'disk_format': fields.StringField(nullable=True),
        'created_at': fields.DateTimeField(nullable=True),
        'updated_at': fields.DateTimeField(nullable=True),
        'tags': fields.ListOfStringsField(nullable=True),
        'direct_url': fields.StringField(nullable=True),
        'min_ram': fields.IntegerField(nullable=True),
        'min_disk': fields.IntegerField(nullable=True),
        'properties': fields.ObjectField('ImageMetaPropsPayload')
    }

    def __init__(self, image_meta):
        super(ImageMetaPayload, self).__init__()
        self.properties = ImageMetaPropsPayload(
            image_meta_props=image_meta.properties)
        self.populate_schema(image_meta=image_meta)
Ejemplo n.º 13
0
class InstanceNUMACell(base.NovaEphemeralObject, base.NovaObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Add pagesize field
    # Version 1.2: Add cpu_pinning_raw and topology fields
    # Version 1.3: Add cpu_policy and cpu_thread_policy fields
    # Version 1.4: Add cpuset_reserved field
    # Version 1.5: Add pcpuset field
    # Version 1.6: Add 'mixed' to cpu_policy field
    VERSION = '1.6'

    def obj_make_compatible(self, primitive, target_version):
        super(InstanceNUMACell,
              self).obj_make_compatible(primitive, target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)
        # Instance with a 'mixed' CPU policy could not provide a backward
        # compatibility.
        if target_version < (1, 6):
            if primitive['cpu_policy'] == obj_fields.CPUAllocationPolicy.MIXED:
                raise exception.ObjectActionError(
                    action='obj_make_compatible',
                    reason=_('%s policy is not supported in version %s') %
                    (primitive['cpu_policy'], target_version))

        # NOTE(huaqiang): Since version 1.5, 'cpuset' is modified to track the
        # unpinned CPUs only, with pinned CPUs tracked via 'pcpuset' instead.
        # For a backward compatibility, move the 'dedicated' instance CPU list
        # from 'pcpuset' to 'cpuset'.
        if target_version < (1, 5):
            if (primitive['cpu_policy'] ==
                    obj_fields.CPUAllocationPolicy.DEDICATED):
                primitive['cpuset'] = primitive['pcpuset']
            primitive.pop('pcpuset', None)

        if target_version < (1, 4):
            primitive.pop('cpuset_reserved', None)

        if target_version < (1, 3):
            primitive.pop('cpu_policy', None)
            primitive.pop('cpu_thread_policy', None)

    fields = {
        'id':
        obj_fields.IntegerField(),
        'cpuset':
        obj_fields.SetOfIntegersField(),
        'pcpuset':
        obj_fields.SetOfIntegersField(),
        # These physical CPUs are reserved for use by the hypervisor
        'cpuset_reserved':
        obj_fields.SetOfIntegersField(nullable=True, default=None),
        'memory':
        obj_fields.IntegerField(),
        'pagesize':
        obj_fields.IntegerField(nullable=True, default=None),
        # TODO(sean-k-mooney): This is no longer used and should be
        # removed in v2
        'cpu_topology':
        obj_fields.ObjectField('VirtCPUTopology', nullable=True),
        'cpu_pinning_raw':
        obj_fields.DictOfIntegersField(nullable=True, default=None),
        'cpu_policy':
        obj_fields.CPUAllocationPolicyField(nullable=True, default=None),
        'cpu_thread_policy':
        obj_fields.CPUThreadAllocationPolicyField(nullable=True, default=None),
    }

    cpu_pinning = obj_fields.DictProxyField('cpu_pinning_raw')

    def __len__(self):
        return len(self.total_cpus)

    @property
    def total_cpus(self):
        return self.cpuset | self.pcpuset

    @property
    def siblings(self):
        cpu_list = sorted(list(self.total_cpus))

        threads = 0
        if ('cpu_topology' in self) and self.cpu_topology:
            threads = self.cpu_topology.threads
        if threads == 1:
            threads = 0

        return list(map(set, zip(*[iter(cpu_list)] * threads)))

    def pin(self, vcpu, pcpu):
        if vcpu not in self.pcpuset:
            return
        pinning_dict = self.cpu_pinning or {}
        pinning_dict[vcpu] = pcpu
        self.cpu_pinning = pinning_dict

    def pin_vcpus(self, *cpu_pairs):
        for vcpu, pcpu in cpu_pairs:
            self.pin(vcpu, pcpu)

    def clear_host_pinning(self):
        """Clear any data related to how this cell is pinned to the host.

        Needed for aborting claims as we do not want to keep stale data around.
        """
        self.id = -1
        self.cpu_pinning = {}
        return self
Ejemplo n.º 14
0
class InstanceMapping(base.NovaTimestampObject, base.NovaObject):
    # Version 1.0: Initial version
    VERSION = '1.0'

    fields = {
        'id': fields.IntegerField(read_only=True),
        'instance_uuid': fields.UUIDField(),
        'cell_mapping': fields.ObjectField('CellMapping', nullable=True),
        'project_id': fields.StringField(),
    }

    def _update_with_cell_id(self, updates):
        cell_mapping_obj = updates.pop("cell_mapping", None)
        if cell_mapping_obj:
            updates["cell_id"] = cell_mapping_obj.id
        return updates

    @staticmethod
    def _from_db_object(context, instance_mapping, db_instance_mapping):
        for key in instance_mapping.fields:
            db_value = db_instance_mapping.get(key)
            if key == 'cell_mapping':
                # cell_mapping can be None indicating that the instance has
                # not been scheduled yet.
                if db_value:
                    db_value = cell_mapping.CellMapping._from_db_object(
                        context, cell_mapping.CellMapping(), db_value)
            setattr(instance_mapping, key, db_value)
        instance_mapping.obj_reset_changes()
        instance_mapping._context = context
        return instance_mapping

    @staticmethod
    @db_api.api_context_manager.reader
    def _get_by_instance_uuid_from_db(context, instance_uuid):
        db_mapping = (context.session.query(
            api_models.InstanceMapping).options(
                joinedload('cell_mapping')).filter(
                    api_models.InstanceMapping.instance_uuid == instance_uuid)
                      ).first()
        if not db_mapping:
            raise exception.InstanceMappingNotFound(uuid=instance_uuid)

        return db_mapping

    @base.remotable_classmethod
    def get_by_instance_uuid(cls, context, instance_uuid):
        db_mapping = cls._get_by_instance_uuid_from_db(context, instance_uuid)
        return cls._from_db_object(context, cls(), db_mapping)

    @staticmethod
    @db_api.api_context_manager.writer
    def _create_in_db(context, updates):
        db_mapping = api_models.InstanceMapping()
        db_mapping.update(updates)
        db_mapping.save(context.session)
        # NOTE: This is done because a later access will trigger a lazy load
        # outside of the db session so it will fail. We don't lazy load
        # cell_mapping on the object later because we never need an
        # InstanceMapping without the CellMapping.
        db_mapping.cell_mapping
        return db_mapping

    @base.remotable
    def create(self):
        changes = self.obj_get_changes()
        changes = self._update_with_cell_id(changes)
        db_mapping = self._create_in_db(self._context, changes)
        self._from_db_object(self._context, self, db_mapping)

    @staticmethod
    @db_api.api_context_manager.writer
    def _save_in_db(context, instance_uuid, updates):
        db_mapping = context.session.query(
            api_models.InstanceMapping).filter_by(
                instance_uuid=instance_uuid).first()
        if not db_mapping:
            raise exception.InstanceMappingNotFound(uuid=instance_uuid)

        db_mapping.update(updates)
        # NOTE: This is done because a later access will trigger a lazy load
        # outside of the db session so it will fail. We don't lazy load
        # cell_mapping on the object later because we never need an
        # InstanceMapping without the CellMapping.
        db_mapping.cell_mapping
        context.session.add(db_mapping)
        return db_mapping

    @base.remotable
    def save(self):
        changes = self.obj_get_changes()
        changes = self._update_with_cell_id(changes)
        db_mapping = self._save_in_db(self._context, self.instance_uuid,
                                      changes)
        self._from_db_object(self._context, self, db_mapping)
        self.obj_reset_changes()

    @staticmethod
    @db_api.api_context_manager.writer
    def _destroy_in_db(context, instance_uuid):
        result = context.session.query(api_models.InstanceMapping).filter_by(
            instance_uuid=instance_uuid).delete()
        if not result:
            raise exception.InstanceMappingNotFound(uuid=instance_uuid)

    @base.remotable
    def destroy(self):
        self._destroy_in_db(self._context, self.instance_uuid)
Ejemplo n.º 15
0
class BuildRequest(base.NovaObject):
    # Version 1.0: Initial version
    # Version 1.1: Added block_device_mappings
    # Version 1.2: Added save() method
    # Version 1.3: Added tags
    VERSION = '1.3'

    fields = {
        'id': fields.IntegerField(),
        'instance_uuid': fields.UUIDField(),
        'project_id': fields.StringField(),
        'instance': fields.ObjectField('Instance'),
        'block_device_mappings': fields.ObjectField('BlockDeviceMappingList'),
        # NOTE(alaski): Normally these would come from the NovaPersistentObject
        # mixin but they're being set explicitly because we only need
        # created_at/updated_at. There is no soft delete for this object.
        'created_at': fields.DateTimeField(nullable=True),
        'updated_at': fields.DateTimeField(nullable=True),
        'tags': fields.ObjectField('TagList'),
    }

    def obj_make_compatible(self, primitive, target_version):
        super(BuildRequest, self).obj_make_compatible(primitive,
                                                      target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 1) and 'block_device_mappings' in primitive:
            del primitive['block_device_mappings']
        elif target_version < (1, 3) and 'tags' in primitive:
            del primitive['tags']

    def _load_instance(self, db_instance):
        # NOTE(alaski): Be very careful with instance loading because it
        # changes more than most objects.
        try:
            self.instance = objects.Instance.obj_from_primitive(
                jsonutils.loads(db_instance))
        except TypeError:
            LOG.debug(
                'Failed to load instance from BuildRequest with uuid '
                '%s because it is None', self.instance_uuid)
            raise exception.BuildRequestNotFound(uuid=self.instance_uuid)
        except ovoo_exc.IncompatibleObjectVersion as exc:
            # This should only happen if proper service upgrade strategies are
            # not followed. Log the exception and raise BuildRequestNotFound.
            # If the instance can't be loaded this object is useless and may
            # as well not exist.
            LOG.debug(
                'Could not deserialize instance store in BuildRequest '
                'with uuid %(instance_uuid)s. Found version %(version)s '
                'which is not supported here.',
                dict(instance_uuid=self.instance_uuid, version=exc.objver))
            LOG.exception('Could not deserialize instance in BuildRequest')
            raise exception.BuildRequestNotFound(uuid=self.instance_uuid)
        # NOTE(sbauza): The instance primitive should already have the deleted
        # field being set, so when hydrating it back here, we should get the
        # right value but in case we don't have it, let's suppose that the
        # instance is not deleted, which is the default value for that field.
        self.instance.obj_set_defaults('deleted')
        # NOTE(alaski): Set some fields on instance that are needed by the api,
        # not lazy-loadable, and don't change.
        self.instance.disable_terminate = False
        self.instance.terminated_at = None
        self.instance.host = None
        self.instance.node = None
        self.instance.launched_at = None
        self.instance.launched_on = None
        self.instance.cell_name = None
        # The fields above are not set until the instance is in a cell at
        # which point this BuildRequest will be gone. locked_by could
        # potentially be set by an update so it should not be overwritten.
        if not self.instance.obj_attr_is_set('locked_by'):
            self.instance.locked_by = None
        # created_at/updated_at are not on the serialized instance because it
        # was never persisted.
        self.instance.created_at = self.created_at
        self.instance.updated_at = self.updated_at
        self.instance.tags = self.tags

    def _load_block_device_mappings(self, db_bdms):
        # 'db_bdms' is a serialized BlockDeviceMappingList object. If it's None
        # we're in a mixed version nova-api scenario and can't retrieve the
        # actual list. Set it to an empty list here which will cause a
        # temporary API inconsistency that will be resolved as soon as the
        # instance is scheduled and on a compute.
        if db_bdms is None:
            LOG.debug(
                'Failed to load block_device_mappings from BuildRequest '
                'for instance %s because it is None', self.instance_uuid)
            self.block_device_mappings = objects.BlockDeviceMappingList()
            return

        self.block_device_mappings = (
            objects.BlockDeviceMappingList.obj_from_primitive(
                jsonutils.loads(db_bdms)))

    def _load_tags(self, db_tags):
        # 'db_tags' is a serialized TagList object. If it's None
        # we're in a mixed version nova-api scenario and can't retrieve the
        # actual list. Set it to an empty list here which will cause a
        # temporary API inconsistency that will be resolved as soon as the
        # instance is scheduled and on a compute.
        if db_tags is None:
            LOG.debug(
                'Failed to load tags from BuildRequest '
                'for instance %s because it is None', self.instance_uuid)
            self.tags = objects.TagList()
            return

        self.tags = (objects.TagList.obj_from_primitive(
            jsonutils.loads(db_tags)))

    @staticmethod
    def _from_db_object(context, req, db_req):
        # Set this up front so that it can be pulled for error messages or
        # logging at any point.
        req.instance_uuid = db_req['instance_uuid']

        for key in req.fields:
            if key == 'instance':
                continue
            elif isinstance(req.fields[key], fields.ObjectField):
                try:
                    getattr(req, '_load_%s' % key)(db_req[key])
                except AttributeError:
                    LOG.exception('No load handler for %s', key)
            else:
                setattr(req, key, db_req[key])
        # Load instance last because other fields on req may be referenced
        req._load_instance(db_req['instance'])
        req.obj_reset_changes(recursive=True)
        req._context = context
        return req

    @staticmethod
    @db.api_context_manager.reader
    def _get_by_instance_uuid_from_db(context, instance_uuid):
        db_req = context.session.query(api_models.BuildRequest).filter_by(
            instance_uuid=instance_uuid).first()
        if not db_req:
            raise exception.BuildRequestNotFound(uuid=instance_uuid)
        return db_req

    @base.remotable_classmethod
    def get_by_instance_uuid(cls, context, instance_uuid):
        db_req = cls._get_by_instance_uuid_from_db(context, instance_uuid)
        return cls._from_db_object(context, cls(), db_req)

    @staticmethod
    @db.api_context_manager.writer
    def _create_in_db(context, updates):
        db_req = api_models.BuildRequest()
        db_req.update(updates)
        db_req.save(context.session)
        return db_req

    def _get_update_primitives(self):
        updates = self.obj_get_changes()
        for key, value in updates.items():
            if isinstance(self.fields[key], fields.ObjectField):
                updates[key] = jsonutils.dumps(value.obj_to_primitive())
        return updates

    @base.remotable
    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        if not self.obj_attr_is_set('instance_uuid'):
            # We can't guarantee this is not null in the db so check here
            raise exception.ObjectActionError(
                action='create', reason='instance_uuid must be set')

        updates = self._get_update_primitives()
        db_req = self._create_in_db(self._context, updates)
        self._from_db_object(self._context, self, db_req)

    @staticmethod
    @db.api_context_manager.writer
    def _destroy_in_db(context, instance_uuid):
        result = context.session.query(api_models.BuildRequest).filter_by(
            instance_uuid=instance_uuid).delete()
        if not result:
            raise exception.BuildRequestNotFound(uuid=instance_uuid)

    @base.remotable
    def destroy(self):
        self._destroy_in_db(self._context, self.instance_uuid)

    @db.api_context_manager.writer
    def _save_in_db(self, context, req_id, updates):
        db_req = context.session.query(
            api_models.BuildRequest).filter_by(id=req_id).first()
        if not db_req:
            raise exception.BuildRequestNotFound(uuid=self.instance_uuid)

        db_req.update(updates)
        context.session.add(db_req)
        return db_req

    @base.remotable
    def save(self):
        updates = self._get_update_primitives()
        db_req = self._save_in_db(self._context, self.id, updates)
        self._from_db_object(self._context, self, db_req)

    def get_new_instance(self, context):
        # NOTE(danms): This is a hack to make sure that the returned
        # instance has all dirty fields. There are probably better
        # ways to do this, but they kinda involve o.vo internals
        # so this is okay for the moment.
        instance = objects.Instance(context)
        for field in self.instance.obj_fields:
            # NOTE(danms): Don't copy the defaulted tags field
            # as instance.create() won't handle it properly.
            # TODO(zhengzhenyu): Handle this when the API supports creating
            # servers with tags.
            if field == 'tags':
                continue
            if self.instance.obj_attr_is_set(field):
                setattr(instance, field, getattr(self.instance, field))
        return instance
Ejemplo n.º 16
0
class MigrationContext(base.NovaPersistentObject, base.NovaObject):
    """Data representing additional resources related to a migration.

    Some resources cannot be calculated from knowing the flavor alone for the
    purpose of resources tracking, but need to be persisted at the time the
    claim was made, for subsequent resource tracking runs to be consistent.
    MigrationContext objects are created when the claim is done and are there
    to facilitate resource tracking and final provisioning of the instance on
    the destination host.
    """

    # Version 1.0: Initial version
    # Version 1.1: Add old/new pci_devices and pci_requests
    # Version 1.2: Add old/new resources
    VERSION = '1.2'

    fields = {
        'instance_uuid':
        fields.UUIDField(),
        'migration_id':
        fields.IntegerField(),
        'new_numa_topology':
        fields.ObjectField('InstanceNUMATopology', nullable=True),
        'old_numa_topology':
        fields.ObjectField('InstanceNUMATopology', nullable=True),
        'new_pci_devices':
        fields.ObjectField('PciDeviceList', nullable=True),
        'old_pci_devices':
        fields.ObjectField('PciDeviceList', nullable=True),
        'new_pci_requests':
        fields.ObjectField('InstancePCIRequests', nullable=True),
        'old_pci_requests':
        fields.ObjectField('InstancePCIRequests', nullable=True),
        'new_resources':
        fields.ObjectField('ResourceList', nullable=True),
        'old_resources':
        fields.ObjectField('ResourceList', nullable=True),
    }

    @classmethod
    def obj_make_compatible(cls, primitive, target_version):
        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 2):
            primitive.pop('old_resources', None)
            primitive.pop('new_resources', None)
        if target_version < (1, 1):
            primitive.pop('old_pci_devices', None)
            primitive.pop('new_pci_devices', None)
            primitive.pop('old_pci_requests', None)
            primitive.pop('new_pci_requests', None)

    @classmethod
    def obj_from_db_obj(cls, db_obj):
        primitive = jsonutils.loads(db_obj)
        return cls.obj_from_primitive(primitive)

    @base.remotable_classmethod
    def get_by_instance_uuid(cls, context, instance_uuid):
        db_extra = db.instance_extra_get_by_instance_uuid(
            context, instance_uuid, columns=['migration_context'])
        if not db_extra:
            raise exception.MigrationContextNotFound(
                instance_uuid=instance_uuid)

        if db_extra['migration_context'] is None:
            return None

        return cls.obj_from_db_obj(db_extra['migration_context'])

    def get_pci_mapping_for_migration(self, revert):
        """Get the mapping between the old PCI devices and the new PCI
        devices that have been allocated during this migration.  The
        correlation is based on PCI request ID which is unique per PCI
        devices for SR-IOV ports.

        :param revert: If True, return a reverse mapping i.e
               mapping between new PCI devices and old PCI devices.
        :returns: dictionary of PCI mapping.
                  if revert==False:
                      {'<old pci address>': <New PciDevice>}
                  if revert==True:
                      {'<new pci address>': <Old PciDevice>}
        """
        step = -1 if revert else 1
        current_pci_devs, updated_pci_devs = (self.old_pci_devices,
                                              self.new_pci_devices)[::step]
        if current_pci_devs and updated_pci_devs:
            LOG.debug(
                "Determining PCI devices mapping using migration "
                "context: current_pci_devs: %(cur)s, "
                "updated_pci_devs: %(upd)s", {
                    'cur': [dev for dev in current_pci_devs],
                    'upd': [dev for dev in updated_pci_devs]
                })
            return {
                curr_dev.address: upd_dev
                for curr_dev in current_pci_devs
                for upd_dev in updated_pci_devs
                if curr_dev.request_id == upd_dev.request_id
            }
        return {}
Ejemplo n.º 17
0
 class TestNotification(notification.NotificationBase):
     VERSION = '1.0'
     fields = {'payload': fields.ObjectField('TestNotificationPayload')}
Ejemplo n.º 18
0
class InstancePayload(base.NotificationPayloadBase):
    SCHEMA = {
        'uuid': ('instance', 'uuid'),
        'user_id': ('instance', 'user_id'),
        'tenant_id': ('instance', 'project_id'),
        'reservation_id': ('instance', 'reservation_id'),
        'display_name': ('instance', 'display_name'),
        'display_description': ('instance', 'display_description'),
        'host_name': ('instance', 'hostname'),
        'host': ('instance', 'host'),
        'node': ('instance', 'node'),
        'os_type': ('instance', 'os_type'),
        'architecture': ('instance', 'architecture'),
        'availability_zone': ('instance', 'availability_zone'),

        'image_uuid': ('instance', 'image_ref'),

        'key_name': ('instance', 'key_name'),

        'kernel_id': ('instance', 'kernel_id'),
        'ramdisk_id': ('instance', 'ramdisk_id'),

        'created_at': ('instance', 'created_at'),
        'launched_at': ('instance', 'launched_at'),
        'terminated_at': ('instance', 'terminated_at'),
        'deleted_at': ('instance', 'deleted_at'),
        'updated_at': ('instance', 'updated_at'),

        'state': ('instance', 'vm_state'),
        'power_state': ('instance', 'power_state'),
        'task_state': ('instance', 'task_state'),
        'progress': ('instance', 'progress'),

        'metadata': ('instance', 'metadata'),
        'locked': ('instance', 'locked'),
        'auto_disk_config': ('instance', 'auto_disk_config')
    }
    # Version 1.0: Initial version
    # Version 1.1: add locked and display_description field
    # Version 1.2: Add auto_disk_config field
    # Version 1.3: Add key_name field
    # Version 1.4: Add BDM related data
    # Version 1.5: Add updated_at field
    VERSION = '1.5'
    fields = {
        'uuid': fields.UUIDField(),
        'user_id': fields.StringField(nullable=True),
        'tenant_id': fields.StringField(nullable=True),
        'reservation_id': fields.StringField(nullable=True),
        'display_name': fields.StringField(nullable=True),
        'display_description': fields.StringField(nullable=True),
        'host_name': fields.StringField(nullable=True),
        'host': fields.StringField(nullable=True),
        'node': fields.StringField(nullable=True),
        'os_type': fields.StringField(nullable=True),
        'architecture': fields.StringField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),

        'flavor': fields.ObjectField('FlavorPayload'),
        'image_uuid': fields.StringField(nullable=True),

        'key_name': fields.StringField(nullable=True),

        'kernel_id': fields.StringField(nullable=True),
        'ramdisk_id': fields.StringField(nullable=True),

        'created_at': fields.DateTimeField(nullable=True),
        'launched_at': fields.DateTimeField(nullable=True),
        'terminated_at': fields.DateTimeField(nullable=True),
        'deleted_at': fields.DateTimeField(nullable=True),
        'updated_at': fields.DateTimeField(nullable=True),

        'state': fields.InstanceStateField(nullable=True),
        'power_state': fields.InstancePowerStateField(nullable=True),
        'task_state': fields.InstanceTaskStateField(nullable=True),
        'progress': fields.IntegerField(nullable=True),

        'ip_addresses': fields.ListOfObjectsField('IpPayload'),
        'block_devices': fields.ListOfObjectsField('BlockDevicePayload',
                                                   nullable=True),

        'metadata': fields.DictOfStringsField(),
        'locked': fields.BooleanField(),
        'auto_disk_config': fields.DiskConfigField()
    }

    def __init__(self, instance, bdms=None):
        super(InstancePayload, self).__init__()
        network_info = instance.get_network_info()
        self.ip_addresses = IpPayload.from_network_info(network_info)
        self.flavor = flavor_payload.FlavorPayload(flavor=instance.flavor)
        if bdms is not None:
            self.block_devices = BlockDevicePayload.from_bdms(bdms)
        else:
            self.block_devices = BlockDevicePayload.from_instance(instance)

        self.populate_schema(instance=instance)
Ejemplo n.º 19
0
 class ParentObject(base.NovaObject):
     fields = {
         'foo': fields.IntegerField(),
         'bar': fields.ObjectField('MyObj'),
     }
Ejemplo n.º 20
0
class Instance(base.NovaPersistentObject, base.NovaObject):
    # Version 1.0: Initial version
    # Version 1.1: Added info_cache
    # Version 1.2: Added security_groups
    # Version 1.3: Added expected_vm_state and admin_state_reset to
    #              save()
    # Version 1.4: Added locked_by and deprecated locked
    # Version 1.5: Added cleaned
    # Version 1.6: Added pci_devices
    # Version 1.7: String attributes updated to support unicode
    # Version 1.8: 'security_groups' and 'pci_devices' cannot be None
    # Version 1.9: Make uuid a non-None real string
    # Version 1.10: Added use_slave to refresh and get_by_uuid
    # Version 1.11: Update instance from database during destroy
    # Version 1.12: Added ephemeral_key_uuid
    # Version 1.13: Added delete_metadata_key()
    # Version 1.14: Added numa_topology
    # Version 1.15: PciDeviceList 1.1
    # Version 1.16: Added pci_requests
    VERSION = '1.16'

    fields = {
        'id': fields.IntegerField(),
        'user_id': fields.StringField(nullable=True),
        'project_id': fields.StringField(nullable=True),
        'image_ref': fields.StringField(nullable=True),
        'kernel_id': fields.StringField(nullable=True),
        'ramdisk_id': fields.StringField(nullable=True),
        'hostname': fields.StringField(nullable=True),
        'launch_index': fields.IntegerField(nullable=True),
        'key_name': fields.StringField(nullable=True),
        'key_data': fields.StringField(nullable=True),
        'power_state': fields.IntegerField(nullable=True),
        'vm_state': fields.StringField(nullable=True),
        'task_state': fields.StringField(nullable=True),
        'memory_mb': fields.IntegerField(nullable=True),
        'vcpus': fields.IntegerField(nullable=True),
        'root_gb': fields.IntegerField(nullable=True),
        'ephemeral_gb': fields.IntegerField(nullable=True),
        'ephemeral_key_uuid': fields.UUIDField(nullable=True),
        'host': fields.StringField(nullable=True),
        'node': fields.StringField(nullable=True),
        'instance_type_id': fields.IntegerField(nullable=True),
        'user_data': fields.StringField(nullable=True),
        'reservation_id': fields.StringField(nullable=True),
        'scheduled_at': fields.DateTimeField(nullable=True),
        'launched_at': fields.DateTimeField(nullable=True),
        'terminated_at': fields.DateTimeField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),
        'display_name': fields.StringField(nullable=True),
        'display_description': fields.StringField(nullable=True),
        'launched_on': fields.StringField(nullable=True),

        # NOTE(jdillaman): locked deprecated in favor of locked_by,
        # to be removed in Icehouse
        'locked': fields.BooleanField(default=False),
        'locked_by': fields.StringField(nullable=True),
        'os_type': fields.StringField(nullable=True),
        'architecture': fields.StringField(nullable=True),
        'vm_mode': fields.StringField(nullable=True),
        'uuid': fields.UUIDField(),
        'root_device_name': fields.StringField(nullable=True),
        'default_ephemeral_device': fields.StringField(nullable=True),
        'default_swap_device': fields.StringField(nullable=True),
        'config_drive': fields.StringField(nullable=True),
        'access_ip_v4': fields.IPV4AddressField(nullable=True),
        'access_ip_v6': fields.IPV6AddressField(nullable=True),
        'auto_disk_config': fields.BooleanField(default=False),
        'progress': fields.IntegerField(nullable=True),
        'shutdown_terminate': fields.BooleanField(default=False),
        'disable_terminate': fields.BooleanField(default=False),
        'cell_name': fields.StringField(nullable=True),
        'metadata': fields.DictOfStringsField(),
        'system_metadata': fields.DictOfNullableStringsField(),
        'info_cache': fields.ObjectField('InstanceInfoCache', nullable=True),
        'security_groups': fields.ObjectField('SecurityGroupList'),
        'fault': fields.ObjectField('InstanceFault', nullable=True),
        'cleaned': fields.BooleanField(default=False),
        'pci_devices': fields.ObjectField('PciDeviceList', nullable=True),
        'numa_topology': fields.ObjectField('InstanceNUMATopology',
                                            nullable=True),
        'pci_requests': fields.ObjectField('InstancePCIRequests',
                                           nullable=True),
    }

    obj_extra_fields = ['name']

    def __init__(self, *args, **kwargs):
        super(Instance, self).__init__(*args, **kwargs)
        self._reset_metadata_tracking()

    def _reset_metadata_tracking(self, fields=None):
        if fields is None or 'system_metadata' in fields:
            self._orig_system_metadata = (dict(self.system_metadata)
                                          if 'system_metadata' in self else {})
        if fields is None or 'metadata' in fields:
            self._orig_metadata = (dict(self.metadata)
                                   if 'metadata' in self else {})

    def obj_reset_changes(self, fields=None):
        super(Instance, self).obj_reset_changes(fields)
        self._reset_metadata_tracking(fields=fields)

    def obj_what_changed(self):
        changes = super(Instance, self).obj_what_changed()
        if 'metadata' in self and self.metadata != self._orig_metadata:
            changes.add('metadata')
        if 'system_metadata' in self and (self.system_metadata !=
                                          self._orig_system_metadata):
            changes.add('system_metadata')
        return changes

    @classmethod
    def _obj_from_primitive(cls, context, objver, primitive):
        self = super(Instance,
                     cls)._obj_from_primitive(context, objver, primitive)
        self._reset_metadata_tracking()
        return self

    def obj_make_compatible(self, primitive, target_version):
        target_version = utils.convert_version_to_tuple(target_version)
        unicode_attributes = [
            'user_id', 'project_id', 'image_ref', 'kernel_id', 'ramdisk_id',
            'hostname', 'key_name', 'key_data', 'host', 'node', 'user_data',
            'availability_zone', 'display_name', 'display_description',
            'launched_on', 'locked_by', 'os_type', 'architecture', 'vm_mode',
            'root_device_name', 'default_ephemeral_device',
            'default_swap_device', 'config_drive', 'cell_name'
        ]
        if target_version < (1, 14) and 'numa_topology' in primitive:
            del primitive['numa_topology']
        if target_version < (1, 10) and 'info_cache' in primitive:
            # NOTE(danms): Instance <= 1.9 (havana) had info_cache 1.4
            self.info_cache.obj_make_compatible(
                primitive['info_cache']['nova_object.data'], '1.4')
            primitive['info_cache']['nova_object.version'] = '1.4'
        if target_version < (1, 7):
            # NOTE(danms): Before 1.7, we couldn't handle unicode in
            # string fields, so squash it here
            for field in [
                    x for x in unicode_attributes
                    if x in primitive and primitive[x] is not None
            ]:
                primitive[field] = primitive[field].encode('ascii', 'replace')
        if target_version < (1, 15) and 'pci_devices' in primitive:
            # NOTE(baoli): Instance <= 1.14 (icehouse) had PciDeviceList 1.0
            self.pci_devices.obj_make_compatible(
                primitive['pci_devices']['nova_object.data'], '1.0')
            primitive['pci_devices']['nova_object.version'] = '1.0'
        if target_version < (1, 6):
            # NOTE(danms): Before 1.6 there was no pci_devices list
            if 'pci_devices' in primitive:
                del primitive['pci_devices']
        if target_version < (1, 16) and 'pci_requests' in primitive:
            del primitive['pci_requests']

    @property
    def name(self):
        try:
            base_name = CONF.instance_name_template % self.id
        except TypeError:
            # Support templates like "uuid-%(uuid)s", etc.
            info = {}
            # NOTE(russellb): Don't use self.iteritems() here, as it will
            # result in infinite recursion on the name property.
            for key in self.fields:
                if key == 'name':
                    # NOTE(danms): prevent recursion
                    continue
                elif not self.obj_attr_is_set(key):
                    # NOTE(danms): Don't trigger lazy-loads
                    continue
                info[key] = self[key]
            try:
                base_name = CONF.instance_name_template % info
            except KeyError:
                base_name = self.uuid
        return base_name

    @staticmethod
    def _from_db_object(context, instance, db_inst, expected_attrs=None):
        """Method to help with migration to objects.

        Converts a database entity to a formal object.
        """
        instance._context = context
        if expected_attrs is None:
            expected_attrs = []
        # Most of the field names match right now, so be quick
        for field in instance.fields:
            if field in INSTANCE_OPTIONAL_ATTRS:
                continue
            elif field == 'deleted':
                instance.deleted = db_inst['deleted'] == db_inst['id']
            elif field == 'cleaned':
                instance.cleaned = db_inst['cleaned'] == 1
            else:
                instance[field] = db_inst[field]

        if 'metadata' in expected_attrs:
            instance['metadata'] = utils.instance_meta(db_inst)
        if 'system_metadata' in expected_attrs:
            instance['system_metadata'] = utils.instance_sys_meta(db_inst)
        if 'fault' in expected_attrs:
            instance['fault'] = (objects.InstanceFault.get_latest_for_instance(
                context, instance.uuid))
        if 'numa_topology' in expected_attrs:
            instance._load_numa_topology(
                db_inst.get('extra').get('numa_topology'))
        if 'pci_requests' in expected_attrs:
            instance._load_pci_requests(
                db_inst.get('extra').get('pci_requests'))

        if 'info_cache' in expected_attrs:
            if db_inst['info_cache'] is None:
                instance.info_cache = None
            elif not instance.obj_attr_is_set('info_cache'):
                # TODO(danms): If this ever happens on a backlevel instance
                # passed to us by a backlevel service, things will break
                instance.info_cache = objects.InstanceInfoCache(context)
            if instance.info_cache is not None:
                instance.info_cache._from_db_object(context,
                                                    instance.info_cache,
                                                    db_inst['info_cache'])

        # TODO(danms): If we are updating these on a backlevel instance,
        # we'll end up sending back new versions of these objects (see
        # above note for new info_caches
        if 'pci_devices' in expected_attrs:
            pci_devices = base.obj_make_list(context,
                                             objects.PciDeviceList(context),
                                             objects.PciDevice,
                                             db_inst['pci_devices'])
            instance['pci_devices'] = pci_devices
        if 'security_groups' in expected_attrs:
            sec_groups = base.obj_make_list(context,
                                            objects.SecurityGroupList(context),
                                            objects.SecurityGroup,
                                            db_inst['security_groups'])
            instance['security_groups'] = sec_groups

        instance.obj_reset_changes()
        return instance

    @base.remotable_classmethod
    def get_by_uuid(cls, context, uuid, expected_attrs=None, use_slave=False):
        if expected_attrs is None:
            expected_attrs = ['info_cache', 'security_groups']
        columns_to_join = _expected_cols(expected_attrs)
        db_inst = db.instance_get_by_uuid(context,
                                          uuid,
                                          columns_to_join=columns_to_join,
                                          use_slave=use_slave)
        return cls._from_db_object(context, cls(), db_inst, expected_attrs)

    @base.remotable_classmethod
    def get_by_id(cls, context, inst_id, expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = ['info_cache', 'security_groups']
        columns_to_join = _expected_cols(expected_attrs)
        db_inst = db.instance_get(context,
                                  inst_id,
                                  columns_to_join=columns_to_join)
        return cls._from_db_object(context, cls(), db_inst, expected_attrs)

    @base.remotable
    def create(self, context):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        updates = self.obj_get_changes()
        expected_attrs = [
            attr for attr in INSTANCE_DEFAULT_FIELDS if attr in updates
        ]
        if 'security_groups' in updates:
            updates['security_groups'] = [
                x.name for x in updates['security_groups']
            ]
        if 'info_cache' in updates:
            updates['info_cache'] = {
                'network_info': updates['info_cache'].network_info.json()
            }
        updates['extra'] = {}
        numa_topology = updates.pop('numa_topology', None)
        if numa_topology:
            expected_attrs.append('numa_topology')
            updates['extra']['numa_topology'] = numa_topology._to_json()
        pci_requests = updates.pop('pci_requests', None)
        if pci_requests:
            expected_attrs.append('pci_requests')
            updates['extra']['pci_requests'] = (pci_requests.to_json())
        db_inst = db.instance_create(context, updates)
        self._from_db_object(context, self, db_inst, expected_attrs)

    @base.remotable
    def destroy(self, context):
        if not self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='destroy',
                                              reason='already destroyed')
        if not self.obj_attr_is_set('uuid'):
            raise exception.ObjectActionError(action='destroy',
                                              reason='no uuid')
        if not self.obj_attr_is_set('host') or not self.host:
            # NOTE(danms): If our host is not set, avoid a race
            constraint = db.constraint(host=db.equal_any(None))
        else:
            constraint = None

        try:
            db_inst = db.instance_destroy(context,
                                          self.uuid,
                                          constraint=constraint)
            self._from_db_object(context, self, db_inst)
        except exception.ConstraintNotMet:
            raise exception.ObjectActionError(action='destroy',
                                              reason='host changed')
        delattr(self, base.get_attrname('id'))

    def _save_info_cache(self, context):
        if self.info_cache:
            self.info_cache.save(context)

    def _save_security_groups(self, context):
        security_groups = self.security_groups or []
        for secgroup in security_groups:
            secgroup.save(context)
        self.security_groups.obj_reset_changes()

    def _save_fault(self, context):
        # NOTE(danms): I don't think we need to worry about this, do we?
        pass

    def _save_numa_topology(self, context):
        if self.numa_topology:
            self.numa_topology.instance_uuid = self.uuid
            self.numa_topology._save(context)
        else:
            objects.InstanceNUMATopology.delete_by_instance_uuid(
                context, self.uuid)

    def _save_pci_requests(self, context):
        # NOTE(danms): No need for this yet.
        pass

    def _save_pci_devices(self, context):
        # NOTE(yjiang5): All devices held by PCI tracker, only PCI tracker
        # permitted to update the DB. all change to devices from here will
        # be dropped.
        pass

    @base.remotable
    def save(self,
             context,
             expected_vm_state=None,
             expected_task_state=None,
             admin_state_reset=False):
        """Save updates to this instance

        Column-wise updates will be made based on the result of
        self.what_changed(). If expected_task_state is provided,
        it will be checked against the in-database copy of the
        instance before updates are made.

        :param:context: Security context
        :param:expected_task_state: Optional tuple of valid task states
        for the instance to be in
        :param:expected_vm_state: Optional tuple of valid vm states
        for the instance to be in
        :param admin_state_reset: True if admin API is forcing setting
        of task_state/vm_state

        """

        cell_type = cells_opts.get_cell_type()
        if cell_type == 'api' and self.cell_name:
            # NOTE(comstud): We need to stash a copy of ourselves
            # before any updates are applied.  When we call the save
            # methods on nested objects, we will lose any changes to
            # them.  But we need to make sure child cells can tell
            # what is changed.
            #
            # We also need to nuke any updates to vm_state and task_state
            # unless admin_state_reset is True.  compute cells are
            # authoritative for their view of vm_state and task_state.
            stale_instance = self.obj_clone()

            def _handle_cell_update_from_api():
                cells_api = cells_rpcapi.CellsAPI()
                cells_api.instance_update_from_api(context, stale_instance,
                                                   expected_vm_state,
                                                   expected_task_state,
                                                   admin_state_reset)
        else:
            stale_instance = None

        updates = {}
        changes = self.obj_what_changed()

        for field in self.fields:
            if (self.obj_attr_is_set(field)
                    and isinstance(self.fields[field], fields.ObjectField)):
                try:
                    getattr(self, '_save_%s' % field)(context)
                except AttributeError:
                    LOG.exception(_LE('No save handler for %s'),
                                  field,
                                  instance=self)
            elif field in changes:
                updates[field] = self[field]

        if not updates:
            if stale_instance:
                _handle_cell_update_from_api()
            return

        # Cleaned needs to be turned back into an int here
        if 'cleaned' in updates:
            if updates['cleaned']:
                updates['cleaned'] = 1
            else:
                updates['cleaned'] = 0

        if expected_task_state is not None:
            if (self.VERSION == '1.9'
                    and expected_task_state == 'image_snapshot'):
                # NOTE(danms): Icehouse introduced a pending state which
                # Havana doesn't know about. If we're an old instance,
                # tolerate the pending state as well
                expected_task_state = [
                    expected_task_state, 'image_snapshot_pending'
                ]
            updates['expected_task_state'] = expected_task_state
        if expected_vm_state is not None:
            updates['expected_vm_state'] = expected_vm_state

        expected_attrs = [
            attr for attr in _INSTANCE_OPTIONAL_JOINED_FIELDS
            if self.obj_attr_is_set(attr)
        ]
        if 'pci_devices' in expected_attrs:
            # NOTE(danms): We don't refresh pci_devices on save right now
            expected_attrs.remove('pci_devices')

        # NOTE(alaski): We need to pull system_metadata for the
        # notification.send_update() below.  If we don't there's a KeyError
        # when it tries to extract the flavor.
        if 'system_metadata' not in expected_attrs:
            expected_attrs.append('system_metadata')
        old_ref, inst_ref = db.instance_update_and_get_original(
            context,
            self.uuid,
            updates,
            update_cells=False,
            columns_to_join=_expected_cols(expected_attrs))

        if stale_instance:
            _handle_cell_update_from_api()
        elif cell_type == 'compute':
            cells_api = cells_rpcapi.CellsAPI()
            cells_api.instance_update_at_top(context, inst_ref)

        self._from_db_object(context,
                             self,
                             inst_ref,
                             expected_attrs=expected_attrs)
        notifications.send_update(context, old_ref, self)
        self.obj_reset_changes()

    @base.remotable
    def refresh(self, context, use_slave=False):
        extra = [
            field for field in INSTANCE_OPTIONAL_ATTRS
            if self.obj_attr_is_set(field)
        ]
        current = self.__class__.get_by_uuid(context,
                                             uuid=self.uuid,
                                             expected_attrs=extra,
                                             use_slave=use_slave)
        # NOTE(danms): We orphan the instance copy so we do not unexpectedly
        # trigger a lazy-load (which would mean we failed to calculate the
        # expected_attrs properly)
        current._context = None

        for field in self.fields:
            if self.obj_attr_is_set(field):
                if field == 'info_cache':
                    self.info_cache.refresh()
                    # NOTE(danms): Make sure this shows up as touched
                    self.info_cache = self.info_cache
                elif self[field] != current[field]:
                    self[field] = current[field]
        self.obj_reset_changes()

    def _load_generic(self, attrname):
        instance = self.__class__.get_by_uuid(self._context,
                                              uuid=self.uuid,
                                              expected_attrs=[attrname])

        # NOTE(danms): Never allow us to recursively-load
        if instance.obj_attr_is_set(attrname):
            self[attrname] = instance[attrname]
        else:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason='loading %s requires recursion' % attrname)

    def _load_fault(self):
        self.fault = objects.InstanceFault.get_latest_for_instance(
            self._context, self.uuid)

    def _load_numa_topology(self, db_topology=None):
        if db_topology is not None:
            self.numa_topology = \
                objects.InstanceNUMATopology.obj_from_db_obj(self.uuid,
                                                             db_topology)
        else:
            try:
                self.numa_topology = \
                    objects.InstanceNUMATopology.get_by_instance_uuid(
                        self._context, self.uuid)
            except exception.NumaTopologyNotFound:
                self.numa_topology = None

    def _load_pci_requests(self, db_requests=None):
        # FIXME: also do this if none!
        if db_requests is not None:
            self.pci_requests = objects.InstancePCIRequests.obj_from_db(
                self._context, self.uuid, db_requests)
        else:
            self.pci_requests = \
                objects.InstancePCIRequests.get_by_instance_uuid(
                    self._context, self.uuid)

    def obj_load_attr(self, attrname):
        if attrname not in INSTANCE_OPTIONAL_ATTRS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason='attribute %s not lazy-loadable' % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        LOG.debug("Lazy-loading `%(attr)s' on %(name)s uuid %(uuid)s", {
            'attr': attrname,
            'name': self.obj_name(),
            'uuid': self.uuid,
        })

        # NOTE(danms): We handle some fields differently here so that we
        # can be more efficient
        if attrname == 'fault':
            self._load_fault()
        elif attrname == 'numa_topology':
            self._load_numa_topology()
        elif attrname == 'pci_requests':
            self._load_pci_requests()
        else:
            # FIXME(comstud): This should be optimized to only load the attr.
            self._load_generic(attrname)
        self.obj_reset_changes([attrname])

    def get_flavor(self, namespace=None):
        prefix = ('%s_' % namespace) if namespace is not None else ''

        db_flavor = flavors.extract_flavor(self, prefix)
        flavor = objects.Flavor(self._context)
        for key in flavors.system_metadata_flavor_props:
            flavor[key] = db_flavor[key]
        return flavor

    def set_flavor(self, flavor, namespace=None):
        prefix = ('%s_' % namespace) if namespace is not None else ''

        self.system_metadata = flavors.save_flavor_info(
            self.system_metadata, flavor, prefix)
        self.save()

    def delete_flavor(self, namespace):
        self.system_metadata = flavors.delete_flavor_info(
            self.system_metadata, "%s_" % namespace)
        self.save()

    @base.remotable
    def delete_metadata_key(self, context, key):
        """Optimized metadata delete method.

        This provides a more efficient way to delete a single metadata
        key, instead of just calling instance.save(). This should be called
        with the key still present in self.metadata, which it will update
        after completion.
        """
        db.instance_metadata_delete(context, self.uuid, key)
        md_was_changed = 'metadata' in self.obj_what_changed()
        del self.metadata[key]
        self._orig_metadata.pop(key, None)
        notifications.send_update(context, self, self)
        if not md_was_changed:
            self.obj_reset_changes(['metadata'])
Ejemplo n.º 21
0
class Service(base.NovaPersistentObject, base.NovaObject,
              base.NovaObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Added compute_node nested object
    # Version 1.2: String attributes updated to support unicode
    # Version 1.3: ComputeNode version 1.5
    # Version 1.4: Added use_slave to get_by_compute_host
    # Version 1.5: ComputeNode version 1.6
    # Version 1.6: ComputeNode version 1.7
    # Version 1.7: ComputeNode version 1.8
    # Version 1.8: ComputeNode version 1.9
    # Version 1.9: ComputeNode version 1.10
    # Version 1.10: Changes behaviour of loading compute_node
    # Version 1.11: Added get_by_host_and_binary
    # Version 1.12: ComputeNode version 1.11
    # Version 1.13: Added last_seen_up
    # Version 1.14: Added forced_down
    # Version 1.15: ComputeNode version 1.12
    # Version 1.16: Added version
    # Version 1.17: ComputeNode version 1.13
    VERSION = '1.17'

    fields = {
        'id': fields.IntegerField(read_only=True),
        'host': fields.StringField(nullable=True),
        'binary': fields.StringField(nullable=True),
        'topic': fields.StringField(nullable=True),
        'report_count': fields.IntegerField(),
        'disabled': fields.BooleanField(),
        'disabled_reason': fields.StringField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),
        'compute_node': fields.ObjectField('ComputeNode'),
        'last_seen_up': fields.DateTimeField(nullable=True),
        'forced_down': fields.BooleanField(),
        'version': fields.IntegerField(),
    }

    obj_relationships = {
        'compute_node': [('1.1', '1.4'), ('1.3', '1.5'), ('1.5', '1.6'),
                         ('1.7', '1.8'), ('1.8', '1.9'), ('1.9', '1.10'),
                         ('1.12', '1.11'), ('1.15', '1.12'), ('1.17', '1.13')],
    }

    def __init__(self, *args, **kwargs):
        # NOTE(danms): We're going against the rules here and overriding
        # init. The reason is that we want to *ensure* that we're always
        # setting the current service version on our objects, overriding
        # whatever else might be set in the database, or otherwise (which
        # is the normal reason not to override init).
        #
        # We also need to do this here so that it's set on the client side
        # all the time, such that create() and save() operations will
        # include the current service version.
        if 'version' in kwargs:
            raise exception.ObjectActionError(
                action='init',
                reason='Version field is immutable')

        super(Service, self).__init__(*args, **kwargs)
        self.version = SERVICE_VERSION

    def obj_make_compatible(self, primitive, target_version):
        super(Service, self).obj_make_compatible(primitive, target_version)
        _target_version = utils.convert_version_to_tuple(target_version)
        if _target_version < (1, 16) and 'version' in primitive:
            del primitive['version']
        if _target_version < (1, 14) and 'forced_down' in primitive:
            del primitive['forced_down']
        if _target_version < (1, 13) and 'last_seen_up' in primitive:
            del primitive['last_seen_up']
        if _target_version < (1, 10):
            target_compute_version = self.obj_calculate_child_version(
                target_version, 'compute_node')
            # service.compute_node was not lazy-loaded, we need to provide it
            # when called
            self._do_compute_node(self._context, primitive,
                                  target_compute_version)

    def _do_compute_node(self, context, primitive, target_version):
        try:
            # NOTE(sbauza): Some drivers (VMware, Ironic) can have multiple
            # nodes for the same service, but for keeping same behaviour,
            # returning only the first elem of the list
            compute = objects.ComputeNodeList.get_all_by_host(
                context, primitive['host'])[0]
        except Exception:
            return
        primitive['compute_node'] = compute.obj_to_primitive(
            target_version=target_version)

    @staticmethod
    def _from_db_object(context, service, db_service):
        allow_missing = ('availability_zone',)
        for key in service.fields:
            if key in allow_missing and key not in db_service:
                continue
            if key == 'compute_node':
                #  NOTE(sbauza); We want to only lazy-load compute_node
                continue
            elif key == 'version':
                # NOTE(danms): Special handling of the version field, since
                # it is read_only and set in our init.
                setattr(service, base.get_attrname(key), db_service[key])
            else:
                service[key] = db_service[key]
        service._context = context
        service.obj_reset_changes()
        return service

    def obj_load_attr(self, attrname):
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        LOG.debug("Lazy-loading `%(attr)s' on %(name)s id %(id)s",
                  {'attr': attrname,
                   'name': self.obj_name(),
                   'id': self.id,
                   })
        if attrname != 'compute_node':
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason='attribute %s not lazy-loadable' % attrname)
        if self.binary == 'nova-compute':
            # Only n-cpu services have attached compute_node(s)
            compute_nodes = objects.ComputeNodeList.get_all_by_host(
                self._context, self.host)
        else:
            # NOTE(sbauza); Previous behaviour was raising a ServiceNotFound,
            # we keep it for backwards compatibility
            raise exception.ServiceNotFound(service_id=self.id)
        # NOTE(sbauza): Some drivers (VMware, Ironic) can have multiple nodes
        # for the same service, but for keeping same behaviour, returning only
        # the first elem of the list
        self.compute_node = compute_nodes[0]

    @base.remotable_classmethod
    def get_by_id(cls, context, service_id):
        db_service = db.service_get(context, service_id)
        return cls._from_db_object(context, cls(), db_service)

    @base.remotable_classmethod
    def get_by_host_and_topic(cls, context, host, topic):
        db_service = db.service_get_by_host_and_topic(context, host, topic)
        return cls._from_db_object(context, cls(), db_service)

    @base.remotable_classmethod
    def get_by_host_and_binary(cls, context, host, binary):
        try:
            db_service = db.service_get_by_host_and_binary(context,
                                                           host, binary)
        except exception.HostBinaryNotFound:
            return
        return cls._from_db_object(context, cls(), db_service)

    @base.remotable_classmethod
    def get_by_compute_host(cls, context, host, use_slave=False):
        db_service = db.service_get_by_compute_host(context, host)
        return cls._from_db_object(context, cls(), db_service)

    # NOTE(ndipanov): This is deprecated and should be removed on the next
    # major version bump
    @base.remotable_classmethod
    def get_by_args(cls, context, host, binary):
        db_service = db.service_get_by_host_and_binary(context, host, binary)
        return cls._from_db_object(context, cls(), db_service)

    @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_service = db.service_create(self._context, updates)
        self._from_db_object(self._context, self, db_service)

    @base.remotable
    def save(self):
        updates = self.obj_get_changes()
        updates.pop('id', None)
        if list(updates.keys()) == ['version']:
            # NOTE(danms): Since we set/dirty version in init, don't
            # do a save if that's all that has changed. This keeps the
            # "save is a no-op if nothing has changed" behavior.
            return
        db_service = db.service_update(self._context, self.id, updates)
        self._from_db_object(self._context, self, db_service)

    @base.remotable
    def destroy(self):
        db.service_destroy(self._context, self.id)
Ejemplo n.º 22
0
class Service(base.NovaPersistentObject, base.NovaObject,
              base.NovaObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Added compute_node nested object
    # Version 1.2: String attributes updated to support unicode
    # Version 1.3: ComputeNode version 1.5
    # Version 1.4: Added use_slave to get_by_compute_host
    # Version 1.5: ComputeNode version 1.6
    # Version 1.6: ComputeNode version 1.7
    # Version 1.7: ComputeNode version 1.8
    # Version 1.8: ComputeNode version 1.9
    # Version 1.9: ComputeNode version 1.10
    # Version 1.10: Changes behaviour of loading compute_node
    # Version 1.11: Added get_by_host_and_binary
    # Version 1.12: ComputeNode version 1.11
    # Version 1.13: Added last_seen_up
    # Version 1.14: Added forced_down
    # Version 1.15: ComputeNode version 1.12
    # Version 1.16: Added version
    # Version 1.17: ComputeNode version 1.13
    # Version 1.18: ComputeNode version 1.14
    # Version 1.19: Added get_minimum_version()
    # Version 1.20: Added get_minimum_version_multi()
    # Version 1.21: Added uuid
    # Version 1.22: Added get_by_uuid()
    VERSION = '1.22'

    fields = {
        'id': fields.IntegerField(read_only=True),
        'uuid': fields.UUIDField(),
        'host': fields.StringField(nullable=True),
        'binary': fields.StringField(nullable=True),
        'topic': fields.StringField(nullable=True),
        'report_count': fields.IntegerField(),
        'disabled': fields.BooleanField(),
        'disabled_reason': fields.StringField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),
        'compute_node': fields.ObjectField('ComputeNode'),
        'last_seen_up': fields.DateTimeField(nullable=True),
        'forced_down': fields.BooleanField(),
        'version': fields.IntegerField(),
    }

    _MIN_VERSION_CACHE = {}
    _SERVICE_VERSION_CACHING = False

    def __init__(self, *args, **kwargs):
        # NOTE(danms): We're going against the rules here and overriding
        # init. The reason is that we want to *ensure* that we're always
        # setting the current service version on our objects, overriding
        # whatever else might be set in the database, or otherwise (which
        # is the normal reason not to override init).
        #
        # We also need to do this here so that it's set on the client side
        # all the time, such that create() and save() operations will
        # include the current service version.
        if 'version' in kwargs:
            raise exception.ObjectActionError(
                action='init', reason='Version field is immutable')

        super(Service, self).__init__(*args, **kwargs)
        self.version = SERVICE_VERSION

    def obj_make_compatible_from_manifest(self, primitive, target_version,
                                          version_manifest):
        super(Service,
              self).obj_make_compatible_from_manifest(primitive,
                                                      target_version,
                                                      version_manifest)
        _target_version = versionutils.convert_version_to_tuple(target_version)
        if _target_version < (1, 21) and 'uuid' in primitive:
            del primitive['uuid']
        if _target_version < (1, 16) and 'version' in primitive:
            del primitive['version']
        if _target_version < (1, 14) and 'forced_down' in primitive:
            del primitive['forced_down']
        if _target_version < (1, 13) and 'last_seen_up' in primitive:
            del primitive['last_seen_up']
        if _target_version < (1, 10):
            # service.compute_node was not lazy-loaded, we need to provide it
            # when called
            self._do_compute_node(self._context, primitive, version_manifest)

    def _do_compute_node(self, context, primitive, version_manifest):
        try:
            target_version = version_manifest['ComputeNode']
            # NOTE(sbauza): Ironic deployments can have multiple
            # nodes for the same service, but for keeping same behaviour,
            # returning only the first elem of the list
            compute = objects.ComputeNodeList.get_all_by_host(
                context, primitive['host'])[0]
        except Exception:
            return
        primitive['compute_node'] = compute.obj_to_primitive(
            target_version=target_version, version_manifest=version_manifest)

    @staticmethod
    def _from_db_object(context, service, db_service):
        allow_missing = ('availability_zone', )
        for key in service.fields:
            if key in allow_missing and key not in db_service:
                continue
            if key == 'compute_node':
                #  NOTE(sbauza); We want to only lazy-load compute_node
                continue
            elif key == 'version':
                # NOTE(danms): Special handling of the version field, since
                # it is read_only and set in our init.
                setattr(service, base.get_attrname(key), db_service[key])
            elif key == 'uuid' and not db_service.get(key):
                # Leave uuid off the object if undefined in the database
                # so that it will be generated below.
                continue
            else:
                service[key] = db_service[key]

        service._context = context
        service.obj_reset_changes()

        return service

    def obj_load_attr(self, attrname):
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())

        LOG.debug("Lazy-loading '%(attr)s' on %(name)s id %(id)s", {
            'attr': attrname,
            'name': self.obj_name(),
            'id': self.id,
        })
        if attrname != 'compute_node':
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason='attribute %s not lazy-loadable' % attrname)
        if self.binary == 'nova-compute':
            # Only n-cpu services have attached compute_node(s)
            compute_nodes = objects.ComputeNodeList.get_all_by_host(
                self._context, self.host)
        else:
            # NOTE(sbauza); Previous behaviour was raising a ServiceNotFound,
            # we keep it for backwards compatibility
            raise exception.ServiceNotFound(service_id=self.id)
        # NOTE(sbauza): Ironic deployments can have multiple nodes
        # for the same service, but for keeping same behaviour, returning only
        # the first elem of the list
        self.compute_node = compute_nodes[0]

    @base.remotable_classmethod
    def get_by_id(cls, context, service_id):
        db_service = db.service_get(context, service_id)
        return cls._from_db_object(context, cls(), db_service)

    @base.remotable_classmethod
    def get_by_uuid(cls, context, service_uuid):
        db_service = db.service_get_by_uuid(context, service_uuid)
        return cls._from_db_object(context, cls(), db_service)

    @base.remotable_classmethod
    def get_by_host_and_topic(cls, context, host, topic):
        db_service = db.service_get_by_host_and_topic(context, host, topic)
        return cls._from_db_object(context, cls(), db_service)

    @base.remotable_classmethod
    def get_by_host_and_binary(cls, context, host, binary):
        try:
            db_service = db.service_get_by_host_and_binary(
                context, host, binary)
        except exception.HostBinaryNotFound:
            return
        return cls._from_db_object(context, cls(), db_service)

    @staticmethod
    @db.select_db_reader_mode
    def _db_service_get_by_compute_host(context, host, use_slave=False):
        return db.service_get_by_compute_host(context, host)

    @base.remotable_classmethod
    def get_by_compute_host(cls, context, host, use_slave=False):
        db_service = cls._db_service_get_by_compute_host(context,
                                                         host,
                                                         use_slave=use_slave)
        return cls._from_db_object(context, cls(), db_service)

    # NOTE(ndipanov): This is deprecated and should be removed on the next
    # major version bump
    @base.remotable_classmethod
    def get_by_args(cls, context, host, binary):
        db_service = db.service_get_by_host_and_binary(context, host, binary)
        return cls._from_db_object(context, cls(), db_service)

    def _check_minimum_version(self):
        """Enforce that we are not older that the minimum version.

        This is a loose check to avoid creating or updating our service
        record if we would do so with a version that is older that the current
        minimum of all services. This could happen if we were started with
        older code by accident, either due to a rollback or an old and
        un-updated node suddenly coming back onto the network.

        There is technically a race here between the check and the update,
        but since the minimum version should always roll forward and never
        backwards, we don't need to worry about doing it atomically. Further,
        the consequence for getting this wrong is minor, in that we'll just
        fail to send messages that other services understand.
        """
        if not self.obj_attr_is_set('version'):
            return
        if not self.obj_attr_is_set('binary'):
            return
        minver = self.get_minimum_version(self._context, self.binary)
        if minver > self.version:
            raise exception.ServiceTooOld(thisver=self.version, minver=minver)

    @base.remotable
    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        self._check_minimum_version()
        updates = self.obj_get_changes()

        if 'uuid' not in updates:
            updates['uuid'] = uuidutils.generate_uuid()
            self.uuid = updates['uuid']

        db_service = db.service_create(self._context, updates)
        self._from_db_object(self._context, self, db_service)
        self._send_notification(fields.NotificationAction.CREATE)

    @base.remotable
    def save(self):
        updates = self.obj_get_changes()
        updates.pop('id', None)
        self._check_minimum_version()
        db_service = db.service_update(self._context, self.id, updates)
        self._from_db_object(self._context, self, db_service)

        self._send_status_update_notification(updates)

    def _send_status_update_notification(self, updates):
        # Note(gibi): We do not trigger notification on version as that field
        # is always dirty, which would cause that nova sends notification on
        # every other field change. See the comment in save() too.
        if set(updates.keys()).intersection(
            {'disabled', 'disabled_reason', 'forced_down'}):
            self._send_notification(fields.NotificationAction.UPDATE)

    def _send_notification(self, action):
        payload = service_notification.ServiceStatusPayload(self)
        service_notification.ServiceStatusNotification(
            publisher=notification.NotificationPublisher.from_service_obj(
                self),
            event_type=notification.EventType(object='service', action=action),
            priority=fields.NotificationPriority.INFO,
            payload=payload).emit(self._context)

    @base.remotable
    def destroy(self):
        db.service_destroy(self._context, self.id)
        self._send_notification(fields.NotificationAction.DELETE)

    @classmethod
    def enable_min_version_cache(cls):
        cls.clear_min_version_cache()
        cls._SERVICE_VERSION_CACHING = True

    @classmethod
    def clear_min_version_cache(cls):
        cls._MIN_VERSION_CACHE = {}

    @staticmethod
    @db.select_db_reader_mode
    def _db_service_get_minimum_version(context, binaries, use_slave=False):
        return db.service_get_minimum_version(context, binaries)

    @base.remotable_classmethod
    def get_minimum_version_multi(cls, context, binaries, use_slave=False):
        if not all(binary.startswith('nova-') for binary in binaries):
            LOG.warning(
                'get_minimum_version called with likely-incorrect '
                'binaries `%s\'', ','.join(binaries))
            raise exception.ObjectActionError(action='get_minimum_version',
                                              reason='Invalid binary prefix')

        if (not cls._SERVICE_VERSION_CACHING
                or any(binary not in cls._MIN_VERSION_CACHE
                       for binary in binaries)):
            min_versions = cls._db_service_get_minimum_version(
                context, binaries, use_slave=use_slave)
            if min_versions:
                min_versions = {
                    binary: version or 0
                    for binary, version in min_versions.items()
                }
                cls._MIN_VERSION_CACHE.update(min_versions)
        else:
            min_versions = {
                binary: cls._MIN_VERSION_CACHE[binary]
                for binary in binaries
            }

        if min_versions:
            version = min(min_versions.values())
        else:
            version = 0
        # NOTE(danms): Since our return value is not controlled by object
        # schema, be explicit here.
        version = int(version)

        return version

    @base.remotable_classmethod
    def get_minimum_version(cls, context, binary, use_slave=False):
        return cls.get_minimum_version_multi(context, [binary],
                                             use_slave=use_slave)
Ejemplo n.º 23
0
class ServiceStatusNotification(base.NotificationBase):
    # Version 1.0: Initial version
    VERSION = '1.0'

    fields = {'payload': fields.ObjectField('ServiceStatusPayload')}
Ejemplo n.º 24
0
class FloatingIP(obj_base.NovaPersistentObject, obj_base.NovaObject,
                 obj_base.NovaObjectDictCompat):
    # Version 1.0: Initial version
    # Version 1.1: Added _get_addresses_by_instance_uuid()
    # Version 1.2: FixedIP <= version 1.2
    # Version 1.3: FixedIP <= version 1.3
    # Version 1.4: FixedIP <= version 1.4
    # Version 1.5: FixedIP <= version 1.5
    # Version 1.6: FixedIP <= version 1.6
    # Version 1.7: FixedIP <= version 1.11
    # Version 1.8: FixedIP <= version 1.12
    # Version 1.9: FixedIP <= version 1.13
    # Version 1.10: FixedIP <= version 1.14
    VERSION = '1.10'
    fields = {
        'id': fields.IntegerField(),
        'address': fields.IPAddressField(),
        'fixed_ip_id': fields.IntegerField(nullable=True),
        'project_id': fields.UUIDField(nullable=True),
        'host': fields.StringField(nullable=True),
        'auto_assigned': fields.BooleanField(),
        'pool': fields.StringField(nullable=True),
        'interface': fields.StringField(nullable=True),
        'fixed_ip': fields.ObjectField('FixedIP', nullable=True),
    }

    obj_relationships = {
        'fixed_ip': [('1.0', '1.1'), ('1.2', '1.2'), ('1.3', '1.3'),
                     ('1.4', '1.4'), ('1.5', '1.5'), ('1.6', '1.6'),
                     ('1.7', '1.11'), ('1.8', '1.12'), ('1.9', '1.13'),
                     ('1.10', '1.14')],
    }

    @staticmethod
    def _from_db_object(context,
                        floatingip,
                        db_floatingip,
                        expected_attrs=None):
        if expected_attrs is None:
            expected_attrs = []
        for field in floatingip.fields:
            if field not in FLOATING_IP_OPTIONAL_ATTRS:
                floatingip[field] = db_floatingip[field]
        if ('fixed_ip' in expected_attrs
                and db_floatingip['fixed_ip'] is not None):
            floatingip.fixed_ip = objects.FixedIP._from_db_object(
                context, objects.FixedIP(context), db_floatingip['fixed_ip'])
        floatingip._context = context
        floatingip.obj_reset_changes()
        return floatingip

    def obj_load_attr(self, attrname):
        if attrname not in FLOATING_IP_OPTIONAL_ATTRS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason='attribute %s is not lazy-loadable' % attrname)
        if not self._context:
            raise exception.OrphanedObjectError(method='obj_load_attr',
                                                objtype=self.obj_name())
        if self.fixed_ip_id is not None:
            self.fixed_ip = objects.FixedIP.get_by_id(
                self._context, self.fixed_ip_id, expected_attrs=['network'])
        else:
            self.fixed_ip = None

    @obj_base.remotable_classmethod
    def get_by_id(cls, context, id):
        db_floatingip = db.floating_ip_get(context, id)
        # XXX joins fixed.instance
        return cls._from_db_object(context,
                                   cls(context),
                                   db_floatingip,
                                   expected_attrs=['fixed_ip'])

    @obj_base.remotable_classmethod
    def get_by_address(cls, context, address):
        db_floatingip = db.floating_ip_get_by_address(context, str(address))
        return cls._from_db_object(context, cls(context), db_floatingip)

    @obj_base.remotable_classmethod
    def get_pool_names(cls, context):
        return [x['name'] for x in db.floating_ip_get_pools(context)]

    @obj_base.remotable_classmethod
    def allocate_address(cls, context, project_id, pool, auto_assigned=False):
        return db.floating_ip_allocate_address(context,
                                               project_id,
                                               pool,
                                               auto_assigned=auto_assigned)

    @obj_base.remotable_classmethod
    def associate(cls, context, floating_address, fixed_address, host):
        db_fixed = db.floating_ip_fixed_ip_associate(context,
                                                     str(floating_address),
                                                     str(fixed_address), host)
        if db_fixed is None:
            return None

        floating = FloatingIP(context=context,
                              address=floating_address,
                              host=host,
                              fixed_ip_id=db_fixed['id'],
                              fixed_ip=objects.FixedIP._from_db_object(
                                  context,
                                  objects.FixedIP(context),
                                  db_fixed,
                                  expected_attrs=['network']))
        return floating

    @obj_base.remotable_classmethod
    def deallocate(cls, context, address):
        return db.floating_ip_deallocate(context, str(address))

    @obj_base.remotable_classmethod
    def destroy(cls, context, address):
        db.floating_ip_destroy(context, str(address))

    @obj_base.remotable_classmethod
    def disassociate(cls, context, address):
        db_fixed = db.floating_ip_disassociate(context, str(address))

        return cls(context=context,
                   address=address,
                   fixed_ip_id=db_fixed['id'],
                   fixed_ip=objects.FixedIP._from_db_object(
                       context,
                       objects.FixedIP(context),
                       db_fixed,
                       expected_attrs=['network']))

    @obj_base.remotable_classmethod
    def _get_addresses_by_instance_uuid(cls, context, instance_uuid):
        return db.instance_floating_address_get_all(context, instance_uuid)

    @classmethod
    def get_addresses_by_instance(cls, context, instance):
        return cls._get_addresses_by_instance_uuid(context, instance['uuid'])

    @obj_base.remotable
    def save(self):
        updates = self.obj_get_changes()
        if 'address' in updates:
            raise exception.ObjectActionError(action='save',
                                              reason='address is not mutable')
        if 'fixed_ip_id' in updates:
            reason = 'fixed_ip_id is not mutable'
            raise exception.ObjectActionError(action='save', reason=reason)

        # NOTE(danms): Make sure we don't pass the calculated fixed_ip
        # relationship to the DB update method
        updates.pop('fixed_ip', None)

        db_floatingip = db.floating_ip_update(self._context, str(self.address),
                                              updates)
        self._from_db_object(self._context, self, db_floatingip)
Ejemplo n.º 25
0
class InstanceUpdateNotification(base.NotificationBase):
    # Version 1.0: Initial version
    VERSION = '1.0'

    fields = {'payload': fields.ObjectField('InstanceUpdatePayload')}
Ejemplo n.º 26
0
class NUMACell(base.NovaObject):
    # Version 1.0: Initial version
    # Version 1.1: Added pinned_cpus and siblings fields
    # Version 1.2: Added mempages field
    # Version 1.3: Add network_metadata field
    # Version 1.4: Add pcpuset
    # Version 1.5: Add socket
    VERSION = '1.5'

    fields = {
        'id': obj_fields.IntegerField(read_only=True),
        'cpuset': obj_fields.SetOfIntegersField(),
        'pcpuset': obj_fields.SetOfIntegersField(),
        'memory': obj_fields.IntegerField(),
        'cpu_usage': obj_fields.IntegerField(default=0),
        'memory_usage': obj_fields.IntegerField(default=0),
        'pinned_cpus': obj_fields.SetOfIntegersField(),
        'siblings': obj_fields.ListOfSetsOfIntegersField(),
        'mempages': obj_fields.ListOfObjectsField('NUMAPagesTopology'),
        'network_metadata': obj_fields.ObjectField('NetworkMetadata'),
        'socket': obj_fields.IntegerField(nullable=True),
    }

    def obj_make_compatible(self, primitive, target_version):
        super(NUMACell, self).obj_make_compatible(primitive, target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 5):
            primitive.pop('socket', None)
        if target_version < (1, 4):
            primitive.pop('pcpuset', None)
        if target_version < (1, 3):
            primitive.pop('network_metadata', None)

    def __eq__(self, other):
        return base.all_things_equal(self, other)

    def __ne__(self, other):
        return not (self == other)

    @property
    def free_pcpus(self):
        """Return available dedicated CPUs."""
        return self.pcpuset - self.pinned_cpus or set()

    @property
    def free_siblings(self):
        """Return available dedicated CPUs in their sibling set form."""
        return [sibling_set & self.free_pcpus for sibling_set in self.siblings]

    @property
    def avail_pcpus(self):
        """Return number of available dedicated CPUs."""
        return len(self.free_pcpus)

    @property
    def avail_memory(self):
        return self.memory - self.memory_usage

    @property
    def has_threads(self):
        """Check if SMT threads, a.k.a. HyperThreads, are present."""
        return any(len(sibling_set) > 1 for sibling_set in self.siblings)

    def pin_cpus(self, cpus):
        if cpus - self.pcpuset:
            raise exception.CPUPinningUnknown(requested=list(cpus),
                                              available=list(self.pcpuset))

        if self.pinned_cpus & cpus:
            available = list(self.pcpuset - self.pinned_cpus)
            raise exception.CPUPinningInvalid(requested=list(cpus),
                                              available=available)

        self.pinned_cpus |= cpus

    def unpin_cpus(self, cpus):
        if cpus - self.pcpuset:
            raise exception.CPUUnpinningUnknown(requested=list(cpus),
                                                available=list(self.pcpuset))

        if (self.pinned_cpus & cpus) != cpus:
            raise exception.CPUUnpinningInvalid(requested=list(cpus),
                                                available=list(
                                                    self.pinned_cpus))

        self.pinned_cpus -= cpus

    def pin_cpus_with_siblings(self, cpus):
        """Pin (consume) both thread siblings if one of them is requested to
        be pinned.

        :param cpus: set of CPUs to pin
        """
        pin_siblings = set()
        for sib in self.siblings:
            if cpus & sib:
                # NOTE(artom) If the intersection between cpus and sib is not
                # empty - IOW, the CPU we want to pin has sibligns - pin the
                # sibling as well. This is because we normally got here because
                # the `isolate` CPU thread policy is set, so we don't want to
                # place guest CPUs on host thread siblings.
                pin_siblings.update(sib)
        self.pin_cpus(pin_siblings)

    def unpin_cpus_with_siblings(self, cpus):
        """Unpin (free up) both thread siblings if one of them is requested to
        be freed.

        :param cpus: set of CPUs to unpin.
        """
        pin_siblings = set()
        for sib in self.siblings:
            if cpus & sib:
                # NOTE(artom) This is the inverse operation of
                # pin_cpus_with_siblings() - see the NOTE there. If the CPU
                # we're unpinning has siblings, unpin the sibling as well.
                pin_siblings.update(sib)
        self.unpin_cpus(pin_siblings)

    def can_fit_pagesize(self, pagesize, memory, use_free=True):
        """Returns whether memory can fit into a given pagesize.

        :param pagesize: a page size in KibB
        :param memory: a memory size asked to fit in KiB
        :param use_free: if true, assess based on free memory rather than total
            memory. This means overcommit is not allowed, which should be the
            case for hugepages since these are memlocked by the kernel and
            can't be swapped out.

        :returns: whether memory can fit in hugepages
        :raises: MemoryPageSizeNotSupported if page size not supported
        """
        for pages in self.mempages:
            avail_kb = pages.free_kb if use_free else pages.total_kb
            if pages.size_kb == pagesize:
                return memory <= avail_kb and (memory % pages.size_kb) == 0
        raise exception.MemoryPageSizeNotSupported(pagesize=pagesize)
Ejemplo n.º 27
0
class ComputeNode(base.NovaPersistentObject, base.NovaObject):
    # Version 1.0: Initial version
    # Version 1.1: Added get_by_service_id()
    # Version 1.2: String attributes updated to support unicode
    # Version 1.3: Added stats field
    # Version 1.4: Added host ip field
    # Version 1.5: Added numa_topology field
    # Version 1.6: Added supported_hv_specs
    # Version 1.7: Added host field
    # Version 1.8: Added get_by_host_and_nodename()
    # Version 1.9: Added pci_device_pools
    # Version 1.10: Added get_first_node_by_host_for_old_compat()
    # Version 1.11: PciDevicePoolList version 1.1
    # Version 1.12: HVSpec version 1.1
    # Version 1.13: Changed service_id field to be nullable
    # Version 1.14: Added cpu_allocation_ratio and ram_allocation_ratio
    # Version 1.15: Added uuid
    # Version 1.16: Added disk_allocation_ratio
    # Version 1.17: Added mapped
    # Version 1.18: Added get_by_uuid().
    VERSION = '1.18'

    fields = {
        'id': fields.IntegerField(read_only=True),
        'uuid': fields.UUIDField(read_only=True),
        'service_id': fields.IntegerField(nullable=True),
        'host': fields.StringField(nullable=True),
        'vcpus': fields.IntegerField(),
        'memory_mb': fields.IntegerField(),
        'local_gb': fields.IntegerField(),
        'vcpus_used': fields.IntegerField(),
        'memory_mb_used': fields.IntegerField(),
        'local_gb_used': fields.IntegerField(),
        'hypervisor_type': fields.StringField(),
        'hypervisor_version': fields.IntegerField(),
        'hypervisor_hostname': fields.StringField(nullable=True),
        'free_ram_mb': fields.IntegerField(nullable=True),
        'free_disk_gb': fields.IntegerField(nullable=True),
        'current_workload': fields.IntegerField(nullable=True),
        'running_vms': fields.IntegerField(nullable=True),
        # TODO(melwitt): cpu_info is non-nullable in the schema but we must
        # wait until version 2.0 of ComputeNode to change it to non-nullable
        'cpu_info': fields.StringField(nullable=True),
        'disk_available_least': fields.IntegerField(nullable=True),
        'metrics': fields.StringField(nullable=True),
        'stats': fields.DictOfNullableStringsField(nullable=True),
        'host_ip': fields.IPAddressField(nullable=True),
        # TODO(rlrossit): because of history, numa_topology is held here as a
        # StringField, not a NUMATopology object. In version 2 of ComputeNode
        # this will be converted over to a fields.ObjectField('NUMATopology')
        'numa_topology': fields.StringField(nullable=True),
        # NOTE(pmurray): the supported_hv_specs field maps to the
        # supported_instances field in the database
        'supported_hv_specs': fields.ListOfObjectsField('HVSpec'),
        # NOTE(pmurray): the pci_device_pools field maps to the
        # pci_stats field in the database
        'pci_device_pools': fields.ObjectField('PciDevicePoolList',
                                               nullable=True),
        'cpu_allocation_ratio': fields.FloatField(),
        'ram_allocation_ratio': fields.FloatField(),
        'disk_allocation_ratio': fields.FloatField(),
        'mapped': fields.IntegerField(),
        }

    def obj_make_compatible(self, primitive, target_version):
        super(ComputeNode, self).obj_make_compatible(primitive, target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 17):
            if 'mapped' in primitive:
                del primitive['mapped']
        if target_version < (1, 16):
            if 'disk_allocation_ratio' in primitive:
                del primitive['disk_allocation_ratio']
        if target_version < (1, 15):
            if 'uuid' in primitive:
                del primitive['uuid']
        if target_version < (1, 14):
            if 'ram_allocation_ratio' in primitive:
                del primitive['ram_allocation_ratio']
            if 'cpu_allocation_ratio' in primitive:
                del primitive['cpu_allocation_ratio']
        if target_version < (1, 13) and primitive.get('service_id') is None:
            # service_id is non-nullable in versions before 1.13
            try:
                service = objects.Service.get_by_compute_host(
                    self._context, primitive['host'])
                primitive['service_id'] = service.id
            except (exception.ComputeHostNotFound, KeyError):
                # NOTE(hanlind): In case anything goes wrong like service not
                # found or host not being set, catch and set a fake value just
                # to allow for older versions that demand a value to work.
                # Setting to -1 will, if value is later used result in a
                # ServiceNotFound, so should be safe.
                primitive['service_id'] = -1
        if target_version < (1, 7) and 'host' in primitive:
            del primitive['host']
        if target_version < (1, 5) and 'numa_topology' in primitive:
            del primitive['numa_topology']
        if target_version < (1, 4) and 'host_ip' in primitive:
            del primitive['host_ip']
        if target_version < (1, 3) and 'stats' in primitive:
            # pre 1.3 version does not have a stats field
            del primitive['stats']

    @staticmethod
    def _host_from_db_object(compute, db_compute):
        if (('host' not in db_compute or db_compute['host'] is None)
                and 'service_id' in db_compute
                and db_compute['service_id'] is not None):
            # FIXME(sbauza) : Unconverted compute record, provide compatibility
            # This has to stay until we can be sure that any/all compute nodes
            # in the database have been converted to use the host field

            # Service field of ComputeNode could be deprecated in a next patch,
            # so let's use directly the Service object
            try:
                service = objects.Service.get_by_id(
                    compute._context, db_compute['service_id'])
            except exception.ServiceNotFound:
                compute.host = None
                return
            try:
                compute.host = service.host
            except (AttributeError, exception.OrphanedObjectError):
                # Host can be nullable in Service
                compute.host = None
        elif 'host' in db_compute and db_compute['host'] is not None:
            # New-style DB having host as a field
            compute.host = db_compute['host']
        else:
            # We assume it should not happen but in case, let's set it to None
            compute.host = None

    @staticmethod
    def _from_db_object(context, compute, db_compute):
        special_cases = set([
            'stats',
            'supported_hv_specs',
            'host',
            'pci_device_pools',
            ])
        fields = set(compute.fields) - special_cases
        for key in fields:
            value = db_compute[key]
            # NOTE(sbauza): Since all compute nodes don't possibly run the
            # latest RT code updating allocation ratios, we need to provide
            # a backwards compatible way of hydrating them.
            # As we want to care about our operators and since we don't want to
            # ask them to change their configuration files before upgrading, we
            # prefer to hardcode the default values for the ratios here until
            # the next release (Newton) where the opt default values will be
            # restored for both cpu (16.0), ram (1.5) and disk (1.0)
            # allocation ratios.
            # TODO(sbauza): Remove that in the next major version bump where
            # we break compatibility with old Liberty computes
            if (key == 'cpu_allocation_ratio' or key == 'ram_allocation_ratio'
                or key == 'disk_allocation_ratio'):
                if value == 0.0:
                    # Operator has not yet provided a new value for that ratio
                    # on the compute node
                    value = None
                if value is None:
                    # ResourceTracker is not updating the value (old node)
                    # or the compute node is updated but the default value has
                    # not been changed
                    value = getattr(CONF, key)
                    if value == 0.0 and key == 'cpu_allocation_ratio':
                        # It's not specified either on the controller
                        value = 16.0
                    if value == 0.0 and key == 'ram_allocation_ratio':
                        # It's not specified either on the controller
                        value = 1.5
                    if value == 0.0 and key == 'disk_allocation_ratio':
                        # It's not specified either on the controller
                        value = 1.0
            elif key == 'mapped':
                value = 0 if value is None else value

            setattr(compute, key, value)

        stats = db_compute['stats']
        if stats:
            compute.stats = jsonutils.loads(stats)

        sup_insts = db_compute.get('supported_instances')
        if sup_insts:
            hv_specs = jsonutils.loads(sup_insts)
            hv_specs = [objects.HVSpec.from_list(hv_spec)
                        for hv_spec in hv_specs]
            compute.supported_hv_specs = hv_specs

        pci_stats = db_compute.get('pci_stats')
        if pci_stats is not None:
            pci_stats = pci_device_pool.from_pci_stats(pci_stats)
        compute.pci_device_pools = pci_stats
        compute._context = context

        # Make sure that we correctly set the host field depending on either
        # host column is present in the table or not
        compute._host_from_db_object(compute, db_compute)

        compute.obj_reset_changes()

        return compute

    @base.remotable_classmethod
    def get_by_id(cls, context, compute_id):
        db_compute = db.compute_node_get(context, compute_id)
        return cls._from_db_object(context, cls(), db_compute)

    @base.remotable_classmethod
    def get_by_uuid(cls, context, compute_uuid):
        nodes = ComputeNodeList.get_all_by_uuids(context, [compute_uuid])
        # We have a unique index on the uuid column so we can get back 0 or 1.
        if not nodes:
            raise exception.ComputeHostNotFound(host=compute_uuid)
        return nodes[0]

    # NOTE(hanlind): This is deprecated and should be removed on the next
    # major version bump
    @base.remotable_classmethod
    def get_by_service_id(cls, context, service_id):
        db_computes = db.compute_nodes_get_by_service_id(context, service_id)
        # NOTE(sbauza): Old version was returning an item, we need to keep this
        # behaviour for backwards compatibility
        db_compute = db_computes[0]
        return cls._from_db_object(context, cls(), db_compute)

    @base.remotable_classmethod
    def get_by_host_and_nodename(cls, context, host, nodename):
        db_compute = db.compute_node_get_by_host_and_nodename(
            context, host, nodename)
        return cls._from_db_object(context, cls(), db_compute)

    # TODO(pkholkin): Remove this method in the next major version bump
    @base.remotable_classmethod
    def get_first_node_by_host_for_old_compat(cls, context, host,
                                              use_slave=False):
        computes = ComputeNodeList.get_all_by_host(context, host, use_slave)
        # FIXME(sbauza): Some hypervisors (VMware, Ironic) can return multiple
        # nodes per host, we should return all the nodes and modify the callers
        # instead.
        # Arbitrarily returning the first node.
        return computes[0]

    @staticmethod
    def _convert_stats_to_db_format(updates):
        stats = updates.pop('stats', None)
        if stats is not None:
            updates['stats'] = jsonutils.dumps(stats)

    @staticmethod
    def _convert_host_ip_to_db_format(updates):
        host_ip = updates.pop('host_ip', None)
        if host_ip:
            updates['host_ip'] = str(host_ip)

    @staticmethod
    def _convert_supported_instances_to_db_format(updates):
        hv_specs = updates.pop('supported_hv_specs', None)
        if hv_specs is not None:
            hv_specs = [hv_spec.to_list() for hv_spec in hv_specs]
            updates['supported_instances'] = jsonutils.dumps(hv_specs)

    @staticmethod
    def _convert_pci_stats_to_db_format(updates):
        if 'pci_device_pools' in updates:
            pools = updates.pop('pci_device_pools')
            if pools is not None:
                pools = jsonutils.dumps(pools.obj_to_primitive())
            updates['pci_stats'] = pools

    @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()
        if 'uuid' not in updates:
            updates['uuid'] = uuidutils.generate_uuid()
            self.uuid = updates['uuid']

        self._convert_stats_to_db_format(updates)
        self._convert_host_ip_to_db_format(updates)
        self._convert_supported_instances_to_db_format(updates)
        self._convert_pci_stats_to_db_format(updates)

        db_compute = db.compute_node_create(self._context, updates)
        self._from_db_object(self._context, self, db_compute)

    @base.remotable
    def save(self, prune_stats=False):
        # NOTE(belliott) ignore prune_stats param, no longer relevant

        updates = self.obj_get_changes()
        updates.pop('id', None)
        self._convert_stats_to_db_format(updates)
        self._convert_host_ip_to_db_format(updates)
        self._convert_supported_instances_to_db_format(updates)
        self._convert_pci_stats_to_db_format(updates)

        db_compute = db.compute_node_update(self._context, self.id, updates)
        self._from_db_object(self._context, self, db_compute)

    @base.remotable
    def destroy(self):
        db.compute_node_delete(self._context, self.id)

    def update_from_virt_driver(self, resources):
        # NOTE(pmurray): the virt driver provides a dict of values that
        # can be copied into the compute node. The names and representation
        # do not exactly match.
        # TODO(pmurray): the resources dict should be formalized.
        keys = ["vcpus", "memory_mb", "local_gb", "cpu_info",
                "vcpus_used", "memory_mb_used", "local_gb_used",
                "numa_topology", "hypervisor_type",
                "hypervisor_version", "hypervisor_hostname",
                "disk_available_least", "host_ip"]
        for key in keys:
            if key in resources:
                setattr(self, key, resources[key])

        # supported_instances has a different name in compute_node
        if 'supported_instances' in resources:
            si = resources['supported_instances']
            self.supported_hv_specs = [objects.HVSpec.from_list(s) for s in si]
Ejemplo n.º 28
0
class InstancePayload(base.NotificationPayloadBase):
    SCHEMA = {
        'uuid': ('instance', 'uuid'),
        'user_id': ('instance', 'user_id'),
        'tenant_id': ('instance', 'project_id'),
        'reservation_id': ('instance', 'reservation_id'),
        'display_name': ('instance', 'display_name'),
        'host_name': ('instance', 'hostname'),
        'host': ('instance', 'host'),
        'node': ('instance', 'node'),
        'os_type': ('instance', 'os_type'),
        'architecture': ('instance', 'architecture'),
        'availability_zone': ('instance', 'availability_zone'),

        'image_uuid': ('instance', 'image_ref'),

        'kernel_id': ('instance', 'kernel_id'),
        'ramdisk_id': ('instance', 'ramdisk_id'),

        'created_at': ('instance', 'created_at'),
        'launched_at': ('instance', 'launched_at'),
        'terminated_at': ('instance', 'terminated_at'),
        'deleted_at': ('instance', 'deleted_at'),

        'state': ('instance', 'vm_state'),
        'power_state': ('instance', 'power_state'),
        'task_state': ('instance', 'task_state'),
        'progress': ('instance', 'progress'),

        'metadata': ('instance', 'metadata'),
    }
    # Version 1.0: Initial version
    VERSION = '1.0'
    fields = {
        'uuid': fields.UUIDField(),
        'user_id': fields.StringField(nullable=True),
        'tenant_id': fields.StringField(nullable=True),
        'reservation_id': fields.StringField(nullable=True),
        'display_name': fields.StringField(nullable=True),
        'host_name': fields.StringField(nullable=True),
        'host': fields.StringField(nullable=True),
        'node': fields.StringField(nullable=True),
        'os_type': fields.StringField(nullable=True),
        'architecture': fields.StringField(nullable=True),
        'availability_zone': fields.StringField(nullable=True),

        'flavor': fields.ObjectField('FlavorPayload'),
        'image_uuid': fields.StringField(nullable=True),

        'kernel_id': fields.StringField(nullable=True),
        'ramdisk_id': fields.StringField(nullable=True),

        'created_at': fields.DateTimeField(nullable=True),
        'launched_at': fields.DateTimeField(nullable=True),
        'terminated_at': fields.DateTimeField(nullable=True),
        'deleted_at': fields.DateTimeField(nullable=True),

        'state': fields.InstanceStateField(nullable=True),
        'power_state': fields.InstancePowerStateField(nullable=True),
        'task_state': fields.InstanceTaskStateField(nullable=True),
        'progress': fields.IntegerField(nullable=True),

        'ip_addresses': fields.ListOfObjectsField('IpPayload'),

        'metadata': fields.DictOfStringsField(),
    }

    def __init__(self, instance, **kwargs):
        super(InstancePayload, self).__init__(**kwargs)
        self.populate_schema(instance=instance)
Ejemplo n.º 29
0
class RequestSpec(base.NovaObject):
    # Version 1.0: Initial version
    # Version 1.1: ImageMeta version 1.6
    # Version 1.2: SchedulerRetries version 1.1
    # Version 1.3: InstanceGroup version 1.10
    # Version 1.4: ImageMeta version 1.7
    # Version 1.5: Added get_by_instance_uuid(), create(), save()
    # Version 1.6: Added requested_destination
    # Version 1.7: Added destroy()
    # Version 1.8: Added security_groups
    #              WRS: add min_num_instances
    #              WRS: add display_name
    #              WRS: add name
    #              WRS: add reject_map
    #              WRS: add offline_cpus
    VERSION = '1.8'

    fields = {
        'id':
        fields.IntegerField(),
        'image':
        fields.ObjectField('ImageMeta', nullable=True),
        'numa_topology':
        fields.ObjectField('InstanceNUMATopology', nullable=True),
        'pci_requests':
        fields.ObjectField('InstancePCIRequests', nullable=True),
        # TODO(mriedem): The project_id shouldn't be nullable since the
        # scheduler relies on it being set.
        'project_id':
        fields.StringField(nullable=True),
        'availability_zone':
        fields.StringField(nullable=True),
        'flavor':
        fields.ObjectField('Flavor', nullable=False),
        'num_instances':
        fields.IntegerField(default=1),
        'ignore_hosts':
        fields.ListOfStringsField(nullable=True),
        # NOTE(mriedem): In reality, you can only ever have one
        # host in the force_hosts list. The fact this is a list
        # is a mistake perpetuated over time.
        'force_hosts':
        fields.ListOfStringsField(nullable=True),
        # NOTE(mriedem): In reality, you can only ever have one
        # node in the force_nodes list. The fact this is a list
        # is a mistake perpetuated over time.
        'force_nodes':
        fields.ListOfStringsField(nullable=True),
        'requested_destination':
        fields.ObjectField('Destination', nullable=True, default=None),
        'retry':
        fields.ObjectField('SchedulerRetries', nullable=True),
        'limits':
        fields.ObjectField('SchedulerLimits', nullable=True),
        'instance_group':
        fields.ObjectField('InstanceGroup', nullable=True),
        # NOTE(sbauza): Since hints are depending on running filters, we prefer
        # to leave the API correctly validating the hints per the filters and
        # just provide to the RequestSpec object a free-form dictionary
        'scheduler_hints':
        fields.DictOfListOfStringsField(nullable=True),
        'instance_uuid':
        fields.UUIDField(),
        'security_groups':
        fields.ObjectField('SecurityGroupList'),
        'min_num_instances':
        fields.IntegerField(default=1),
        'display_name':
        fields.StringField(nullable=True),
        'name':
        fields.StringField(nullable=True),
        'reject_map':
        fields.DictOfListOfStringsField(nullable=True),
        'offline_cpus':
        fields.IntegerField(default=0),
    }

    def obj_make_compatible(self, primitive, target_version):
        super(RequestSpec, self).obj_make_compatible(primitive, target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)
        if target_version < (1, 8):
            if 'security_groups' in primitive:
                del primitive['security_groups']
        if target_version < (1, 6):
            if 'requested_destination' in primitive:
                del primitive['requested_destination']

    def obj_load_attr(self, attrname):
        if attrname not in REQUEST_SPEC_OPTIONAL_ATTRS:
            raise exception.ObjectActionError(
                action='obj_load_attr',
                reason='attribute %s not lazy-loadable' % attrname)

        if attrname == 'security_groups':
            self.security_groups = objects.SecurityGroupList(objects=[])
            return

        # NOTE(sbauza): In case the primitive was not providing that field
        # because of a previous RequestSpec version, we want to default
        # that field in order to have the same behaviour.
        self.obj_set_defaults(attrname)

    @property
    def vcpus(self):
        return self.flavor.vcpus

    @property
    def memory_mb(self):
        return self.flavor.memory_mb

    @property
    def root_gb(self):
        return self.flavor.root_gb

    @property
    def ephemeral_gb(self):
        return self.flavor.ephemeral_gb

    @property
    def swap(self):
        return self.flavor.swap

    def _image_meta_from_image(self, image):
        if isinstance(image, objects.ImageMeta):
            self.image = image
        elif isinstance(image, dict):
            # NOTE(sbauza): Until Nova is fully providing an ImageMeta object
            # for getting properties, we still need to hydrate it here
            # TODO(sbauza): To be removed once all RequestSpec hydrations are
            # done on the conductor side and if the image is an ImageMeta
            self.image = objects.ImageMeta.from_dict(image)
        else:
            self.image = None

    def _from_instance(self, instance):
        if isinstance(instance, obj_instance.Instance):
            # NOTE(sbauza): Instance should normally be a NovaObject...
            getter = getattr
        elif isinstance(instance, dict):
            # NOTE(sbauza): ... but there are some cases where request_spec
            # has an instance key as a dictionary, just because
            # select_destinations() is getting a request_spec dict made by
            # sched_utils.build_request_spec()
            # TODO(sbauza): To be removed once all RequestSpec hydrations are
            # done on the conductor side
            getter = lambda x, y: x.get(y)
        else:
            # If the instance is None, there is no reason to set the fields
            return

        instance_fields = [
            'numa_topology', 'pci_requests', 'uuid', 'project_id',
            'availability_zone'
        ]
        # WRS extension
        instance_fields.extend(['display_name', 'name'])
        for field in instance_fields:
            if field == 'uuid':
                setattr(self, 'instance_uuid', getter(instance, field))
            elif field == 'pci_requests':
                self._from_instance_pci_requests(getter(instance, field))
            elif field == 'numa_topology':
                self._from_instance_numa_topology(getter(instance, field))
            else:
                setattr(self, field, getter(instance, field))

    def _from_instance_pci_requests(self, pci_requests):
        if isinstance(pci_requests, dict):
            pci_req_cls = objects.InstancePCIRequests
            self.pci_requests = pci_req_cls.from_request_spec_instance_props(
                pci_requests)
        else:
            self.pci_requests = pci_requests

    def _from_instance_numa_topology(self, numa_topology):
        if isinstance(numa_topology, dict):
            self.numa_topology = hardware.instance_topology_from_instance(
                dict(numa_topology=numa_topology))
        else:
            self.numa_topology = numa_topology

    def _from_flavor(self, flavor):
        if isinstance(flavor, objects.Flavor):
            self.flavor = flavor
        elif isinstance(flavor, dict):
            # NOTE(sbauza): Again, request_spec is primitived by
            # sched_utils.build_request_spec() and passed to
            # select_destinations() like this
            # TODO(sbauza): To be removed once all RequestSpec hydrations are
            # done on the conductor side
            self.flavor = objects.Flavor(**flavor)

    def _from_retry(self, retry_dict):
        self.retry = (SchedulerRetries.from_dict(self._context, retry_dict)
                      if retry_dict else None)

    def _populate_group_info(self, filter_properties):
        if filter_properties.get('instance_group'):
            # New-style group information as a NovaObject, we can directly set
            # the field
            self.instance_group = filter_properties.get('instance_group')
        elif filter_properties.get('group_updated') is True:
            # Old-style group information having ugly dict keys containing sets
            # NOTE(sbauza): Can be dropped once select_destinations is removed
            policies = list(filter_properties.get('group_policies'))
            hosts = list(filter_properties.get('group_hosts'))
            members = list(filter_properties.get('group_members'))
            name = filter_properties.get('group_name')
            md = filter_properties.get('group_metadetails')
            self.instance_group = objects.InstanceGroup(policies=policies,
                                                        hosts=hosts,
                                                        members=members,
                                                        name=name,
                                                        metadetails=md)
            # hosts has to be not part of the updates for saving the object
            self.instance_group.obj_reset_changes(['hosts'])
        else:
            # Set the value anyway to avoid any call to obj_attr_is_set for it
            self.instance_group = None

    def _from_limits(self, limits_dict):
        self.limits = SchedulerLimits.from_dict(limits_dict)

    def _from_hints(self, hints_dict):
        if hints_dict is None:
            self.scheduler_hints = None
            return
        self.scheduler_hints = {
            hint: value if isinstance(value, list) else [value]
            for hint, value in hints_dict.items()
        }

    @classmethod
    def from_primitives(cls, context, request_spec, filter_properties):
        """Returns a new RequestSpec object by hydrating it from legacy dicts.

        Deprecated.  A RequestSpec object is created early in the boot process
        using the from_components method.  That object will either be passed to
        places that require it, or it can be looked up with
        get_by_instance_uuid.  This method can be removed when there are no
        longer any callers.  Because the method is not remotable it is not tied
        to object versioning.

        That helper is not intended to leave the legacy dicts kept in the nova
        codebase, but is rather just for giving a temporary solution for
        populating the Spec object until we get rid of scheduler_utils'
        build_request_spec() and the filter_properties hydratation in the
        conductor.

        :param context: a context object
        :param request_spec: An old-style request_spec dictionary
        :param filter_properties: An old-style filter_properties dictionary
        """
        num_instances = request_spec.get('num_instances', 1)
        spec = cls(context, num_instances=num_instances)
        # Hydrate from request_spec first
        image = request_spec.get('image')
        spec._image_meta_from_image(image)
        instance = request_spec.get('instance_properties')
        spec._from_instance(instance)
        flavor = request_spec.get('instance_type')
        spec._from_flavor(flavor)
        # Hydrate now from filter_properties
        spec.ignore_hosts = filter_properties.get('ignore_hosts')
        spec.force_hosts = filter_properties.get('force_hosts')
        spec.force_nodes = filter_properties.get('force_nodes')
        retry = filter_properties.get('retry', {})
        spec._from_retry(retry)
        limits = filter_properties.get('limits', {})
        spec._from_limits(limits)
        spec._populate_group_info(filter_properties)
        scheduler_hints = filter_properties.get('scheduler_hints', {})
        spec._from_hints(scheduler_hints)
        spec.requested_destination = filter_properties.get(
            'requested_destination')
        spec.min_num_instances = filter_properties.get('min_num_instances',
                                                       num_instances)
        spec.reject_map = filter_properties.get('reject_map', {})
        spec.offline_cpus = request_spec.get('offline_cpus', 0)

        # NOTE(sbauza): Default the other fields that are not part of the
        # original contract
        spec.obj_set_defaults()

        return spec

    def get_scheduler_hint(self, hint_name, default=None):
        """Convenient helper for accessing a particular scheduler hint since
        it is hydrated by putting a single item into a list.

        In order to reduce the complexity, that helper returns a string if the
        requested hint is a list of only one value, and if not, returns the
        value directly (ie. the list). If the hint is not existing (or
        scheduler_hints is None), then it returns the default value.

        :param hint_name: name of the hint
        :param default: the default value if the hint is not there
        """
        if (not self.obj_attr_is_set('scheduler_hints')
                or self.scheduler_hints is None):
            return default
        hint_val = self.scheduler_hints.get(hint_name, default)
        return (hint_val[0] if isinstance(hint_val, list)
                and len(hint_val) == 1 else hint_val)

    def _to_legacy_image(self):
        return base.obj_to_primitive(self.image) if (
            self.obj_attr_is_set('image') and self.image) else {}

    def _to_legacy_instance(self):
        # NOTE(sbauza): Since the RequestSpec only persists a few Instance
        # fields, we can only return a dict.
        instance = {}
        instance_fields = [
            'numa_topology', 'pci_requests', 'project_id', 'availability_zone',
            'instance_uuid'
        ]
        # WRS extension
        instance_fields.extend(['display_name', 'name', 'reject_map'])
        for field in instance_fields:
            if not self.obj_attr_is_set(field):
                continue
            if field == 'instance_uuid':
                instance['uuid'] = getattr(self, field)
            else:
                instance[field] = getattr(self, field)
        flavor_fields = ['root_gb', 'ephemeral_gb', 'memory_mb', 'vcpus']
        if not self.obj_attr_is_set('flavor'):
            return instance
        for field in flavor_fields:
            instance[field] = getattr(self.flavor, field)
        return instance

    def _to_legacy_group_info(self):
        # NOTE(sbauza): Since this is only needed until the AffinityFilters are
        # modified by using directly the RequestSpec object, we need to keep
        # the existing dictionary as a primitive.
        # WRS: add metadetails as this will be needed by
        # _populate_group_info() when called by the scheduler during upgrade
        # from 15.12.
        return {
            'group_updated': True,
            'group_hosts': set(self.instance_group.hosts),
            'group_policies': set(self.instance_group.policies),
            'group_members': set(self.instance_group.members),
            'group_metadetails': self.instance_group.metadetails
        }

    def to_legacy_request_spec_dict(self):
        """Returns a legacy request_spec dict from the RequestSpec object.

        Since we need to manage backwards compatibility and rolling upgrades
        within our RPC API, we need to accept to provide an helper for
        primitiving the right RequestSpec object into a legacy dict until we
        drop support for old Scheduler RPC API versions.
        If you don't understand why this method is needed, please don't use it.
        """
        req_spec = {}
        if not self.obj_attr_is_set('num_instances'):
            req_spec['num_instances'] = self.fields['num_instances'].default
        else:
            req_spec['num_instances'] = self.num_instances
        req_spec['image'] = self._to_legacy_image()
        req_spec['instance_properties'] = self._to_legacy_instance()
        if self.obj_attr_is_set('flavor'):
            req_spec['instance_type'] = self.flavor
        else:
            req_spec['instance_type'] = {}
        if self.obj_attr_is_set('offline_cpus'):
            req_spec['offline_cpus'] = self.offline_cpus
        else:
            req_spec['offline_cpus'] = 0
        if self.obj_attr_is_set('min_num_instances'):
            req_spec['min_num_instances'] = self.min_num_instances
        return req_spec

    def to_legacy_filter_properties_dict(self):
        """Returns a legacy filter_properties dict from the RequestSpec object.

        Since we need to manage backwards compatibility and rolling upgrades
        within our RPC API, we need to accept to provide an helper for
        primitiving the right RequestSpec object into a legacy dict until we
        drop support for old Scheduler RPC API versions.
        If you don't understand why this method is needed, please don't use it.
        """
        filt_props = {}
        if self.obj_attr_is_set('ignore_hosts') and self.ignore_hosts:
            filt_props['ignore_hosts'] = self.ignore_hosts
        if self.obj_attr_is_set('force_hosts') and self.force_hosts:
            filt_props['force_hosts'] = self.force_hosts
        if self.obj_attr_is_set('force_nodes') and self.force_nodes:
            filt_props['force_nodes'] = self.force_nodes
        if self.obj_attr_is_set('retry') and self.retry:
            filt_props['retry'] = self.retry.to_dict()
        if self.obj_attr_is_set('limits') and self.limits:
            filt_props['limits'] = self.limits.to_dict()
        if self.obj_attr_is_set('instance_group') and self.instance_group:
            filt_props.update(self._to_legacy_group_info())
        if self.obj_attr_is_set('scheduler_hints') and self.scheduler_hints:
            # NOTE(sbauza): We need to backport all the hints correctly since
            # we had to hydrate the field by putting a single item into a list.
            filt_props['scheduler_hints'] = {
                hint: self.get_scheduler_hint(hint)
                for hint in self.scheduler_hints
            }
        if self.obj_attr_is_set(
                'requested_destination') and self.requested_destination:
            filt_props['requested_destination'] = self.requested_destination
        if self.obj_attr_is_set('reject_map') and self.reject_map:
            filt_props['reject_map'] = self.reject_map
        return filt_props

    @classmethod
    def from_components(cls,
                        context,
                        instance_uuid,
                        image,
                        flavor,
                        numa_topology,
                        pci_requests,
                        filter_properties,
                        instance_group,
                        availability_zone,
                        security_groups=None,
                        project_id=None):
        """Returns a new RequestSpec object hydrated by various components.

        This helper is useful in creating the RequestSpec from the various
        objects that are assembled early in the boot process.  This method
        creates a complete RequestSpec object with all properties set or
        intentionally left blank.

        :param context: a context object
        :param instance_uuid: the uuid of the instance to schedule
        :param image: a dict of properties for an image or volume
        :param flavor: a flavor NovaObject
        :param numa_topology: InstanceNUMATopology or None
        :param pci_requests: InstancePCIRequests
        :param filter_properties: a dict of properties for scheduling
        :param instance_group: None or an instance group NovaObject
        :param availability_zone: an availability_zone string
        :param security_groups: A SecurityGroupList object. If None, don't
                                set security_groups on the resulting object.
        :param project_id: The project_id for the requestspec (should match
                           the instance project_id).
        """
        spec_obj = cls(context)
        spec_obj.num_instances = 1
        spec_obj.instance_uuid = instance_uuid
        spec_obj.instance_group = instance_group
        if spec_obj.instance_group is None and filter_properties:
            spec_obj._populate_group_info(filter_properties)
        spec_obj.project_id = project_id or context.project_id
        spec_obj._image_meta_from_image(image)
        spec_obj._from_flavor(flavor)
        spec_obj._from_instance_pci_requests(pci_requests)
        spec_obj._from_instance_numa_topology(numa_topology)
        spec_obj.ignore_hosts = filter_properties.get('ignore_hosts')
        spec_obj.force_hosts = filter_properties.get('force_hosts')
        spec_obj.force_nodes = filter_properties.get('force_nodes')
        spec_obj._from_retry(filter_properties.get('retry', {}))
        spec_obj._from_limits(filter_properties.get('limits', {}))
        spec_obj._from_hints(filter_properties.get('scheduler_hints', {}))
        spec_obj.availability_zone = availability_zone
        if security_groups is not None:
            spec_obj.security_groups = security_groups
        spec_obj.requested_destination = filter_properties.get(
            'requested_destination')
        spec_obj.min_num_instances = filter_properties.get(
            'min_num_instances', spec_obj.num_instances)
        spec_obj.display_name = ''
        spec_obj.name = ''
        spec_obj.reject_map = filter_properties.get('reject_map', {})
        spec_obj.offline_cpus = 0
        # NOTE(sbauza): Default the other fields that are not part of the
        # original contract
        spec_obj.obj_set_defaults()
        return spec_obj

    def ensure_project_id(self, instance):
        if 'project_id' not in self or self.project_id is None:
            self.project_id = instance.project_id

    @staticmethod
    def _from_db_object(context, spec, db_spec):
        spec_obj = spec.obj_from_primitive(jsonutils.loads(db_spec['spec']))
        for key in spec.fields:
            # Load these from the db model not the serialized object within,
            # though they should match.
            if key in ['id', 'instance_uuid']:
                setattr(spec, key, db_spec[key])
            else:
                setattr(spec, key, getattr(spec_obj, key))
        spec._context = context
        spec.obj_reset_changes()
        return spec

    @staticmethod
    @db.api_context_manager.reader
    def _get_by_instance_uuid_from_db(context, instance_uuid):
        db_spec = context.session.query(api_models.RequestSpec).filter_by(
            instance_uuid=instance_uuid).first()
        if not db_spec:
            raise exception.RequestSpecNotFound(instance_uuid=instance_uuid)
        return db_spec

    @base.remotable_classmethod
    def get_by_instance_uuid(cls, context, instance_uuid):
        db_spec = cls._get_by_instance_uuid_from_db(context, instance_uuid)
        return cls._from_db_object(context, cls(), db_spec)

    @staticmethod
    @db.api_context_manager.writer
    def _create_in_db(context, updates):
        db_spec = api_models.RequestSpec()
        db_spec.update(updates)
        db_spec.save(context.session)
        return db_spec

    def _get_update_primitives(self):
        """Serialize object to match the db model.

        We store copies of embedded objects rather than
        references to these objects because we want a snapshot of the request
        at this point.  If the references changed or were deleted we would
        not be able to reschedule this instance under the same conditions as
        it was originally scheduled with.
        """
        updates = self.obj_get_changes()
        # NOTE(alaski): The db schema is the full serialized object in a
        # 'spec' column.  If anything has changed we rewrite the full thing.
        if updates:
            db_updates = {'spec': jsonutils.dumps(self.obj_to_primitive())}
            if 'instance_uuid' in updates:
                db_updates['instance_uuid'] = updates['instance_uuid']
        return db_updates

    @base.remotable
    def create(self):
        if self.obj_attr_is_set('id'):
            raise exception.ObjectActionError(action='create',
                                              reason='already created')

        updates = self._get_update_primitives()

        db_spec = self._create_in_db(self._context, updates)
        self._from_db_object(self._context, self, db_spec)

    @staticmethod
    @db.api_context_manager.writer
    def _save_in_db(context, instance_uuid, updates):
        # FIXME(sbauza): Provide a classmethod when oslo.db bug #1520195 is
        # fixed and released
        db_spec = RequestSpec._get_by_instance_uuid_from_db(
            context, instance_uuid)
        db_spec.update(updates)
        db_spec.save(context.session)
        return db_spec

    @base.remotable
    def save(self):
        updates = self._get_update_primitives()
        db_spec = self._save_in_db(self._context, self.instance_uuid, updates)
        self._from_db_object(self._context, self, db_spec)
        self.obj_reset_changes()

    @staticmethod
    @db.api_context_manager.writer
    def _destroy_in_db(context, instance_uuid):
        result = context.session.query(api_models.RequestSpec).filter_by(
            instance_uuid=instance_uuid).delete()
        if not result:
            raise exception.RequestSpecNotFound(instance_uuid=instance_uuid)

    @base.remotable
    def destroy(self):
        self._destroy_in_db(self._context, self.instance_uuid)

    def reset_forced_destinations(self):
        """Clears the forced destination fields from the RequestSpec object.

        This method is for making sure we don't ask the scheduler to give us
        again the same destination(s) without persisting the modifications.
        """
        self.force_hosts = None
        self.force_nodes = None
        # NOTE(sbauza): Make sure we don't persist this, we need to keep the
        # original request for the forced hosts
        self.obj_reset_changes(['force_hosts', 'force_nodes'])
Ejemplo n.º 30
0
class LibvirtLiveMigrateData(LiveMigrateData):
    # Version 1.0: Initial version
    # Version 1.1: Added target_connect_addr
    # Version 1.2: Added 'serial_listen_ports' to allow live migration with
    #              serial console.
    # Version 1.3: Added 'supported_perf_events'
    # Version 1.4: Added old_vol_attachment_ids
    # Version 1.5: Added src_supports_native_luks
    # Version 1.6: Added wait_for_vif_plugged
    # Version 1.7: Added dst_wants_file_backed_memory
    # Version 1.8: Added file_backed_memory_discard
    # Version 1.9: Inherited vifs from LiveMigrateData
    # Version 1.10: Added dst_numa_info, src_supports_numa_live_migration, and
    #               dst_supports_numa_live_migration fields
    VERSION = '1.10'

    fields = {
        'filename': fields.StringField(),
        # FIXME: image_type should be enum?
        'image_type': fields.StringField(),
        'block_migration': fields.BooleanField(),
        'disk_over_commit': fields.BooleanField(),
        'disk_available_mb': fields.IntegerField(nullable=True),
        'is_shared_instance_path': fields.BooleanField(),
        'is_shared_block_storage': fields.BooleanField(),
        'instance_relative_path': fields.StringField(),
        'graphics_listen_addr_vnc': fields.IPAddressField(nullable=True),
        'graphics_listen_addr_spice': fields.IPAddressField(nullable=True),
        'serial_listen_addr': fields.StringField(nullable=True),
        'serial_listen_ports': fields.ListOfIntegersField(),
        'bdms': fields.ListOfObjectsField('LibvirtLiveMigrateBDMInfo'),
        'target_connect_addr': fields.StringField(nullable=True),
        'supported_perf_events': fields.ListOfStringsField(),
        # TODO(lyarwood): No longer used, drop in version 2.0
        'src_supports_native_luks': fields.BooleanField(),
        'dst_wants_file_backed_memory': fields.BooleanField(),
        # TODO(lyarwood): No longer used, drop in version 2.0
        'file_backed_memory_discard': fields.BooleanField(),
        # TODO(artom) (src|dst)_supports_numa_live_migration are only used as
        # flags to indicate that the compute host is new enough to perform a
        # NUMA-aware live migration. Remove in version 2.0.
        'src_supports_numa_live_migration': fields.BooleanField(),
        'dst_supports_numa_live_migration': fields.BooleanField(),
        'dst_numa_info': fields.ObjectField('LibvirtLiveMigrateNUMAInfo'),
    }

    def obj_make_compatible(self, primitive, target_version):
        super(LibvirtLiveMigrateData,
              self).obj_make_compatible(primitive, target_version)
        target_version = versionutils.convert_version_to_tuple(target_version)
        if (target_version < (1, 10)
                and 'src_supports_numa_live_migration' in primitive):
            del primitive['src_supports_numa_live_migration']
        if target_version < (1, 10) and 'dst_numa_info' in primitive:
            del primitive['dst_numa_info']
        if target_version < (1, 9) and 'vifs' in primitive:
            del primitive['vifs']
        if target_version < (1, 8):
            if 'file_backed_memory_discard' in primitive:
                del primitive['file_backed_memory_discard']
        if target_version < (1, 7):
            if 'dst_wants_file_backed_memory' in primitive:
                del primitive['dst_wants_file_backed_memory']
        if target_version < (1, 6) and 'wait_for_vif_plugged' in primitive:
            del primitive['wait_for_vif_plugged']
        if target_version < (1, 5):
            if 'src_supports_native_luks' in primitive:
                del primitive['src_supports_native_luks']
        if target_version < (1, 4):
            if 'old_vol_attachment_ids' in primitive:
                del primitive['old_vol_attachment_ids']
        if target_version < (1, 3):
            if 'supported_perf_events' in primitive:
                del primitive['supported_perf_events']
        if target_version < (1, 2):
            if 'serial_listen_ports' in primitive:
                del primitive['serial_listen_ports']
        if target_version < (1, 1) and 'target_connect_addr' in primitive:
            del primitive['target_connect_addr']

    def is_on_shared_storage(self):
        return self.is_shared_block_storage or self.is_shared_instance_path