class ServiceStatusPayload(notification.NotificationPayloadBase): SCHEMA = { 'host': ('service', 'host'), 'binary': ('service', 'binary'), 'topic': ('service', 'topic'), 'report_count': ('service', 'report_count'), 'disabled': ('service', 'disabled'), 'disabled_reason': ('service', 'disabled_reason'), 'availability_zone': ('service', 'availability_zone'), 'last_seen_up': ('service', 'last_seen_up'), 'forced_down': ('service', 'forced_down'), 'version': ('service', 'version') } # Version 1.0: Initial version VERSION = '1.0' fields = { '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), 'last_seen_up': fields.DateTimeField(nullable=True), 'forced_down': fields.BooleanField(), 'version': fields.IntegerField(), } def __init__(self, service): super(ServiceStatusPayload, self).__init__() self.populate_schema(service=service)
class LiveMigrateData(obj_base.NovaObject): fields = { 'is_volume_backed': fields.BooleanField(), 'migration': fields.ObjectField('Migration'), } def to_legacy_dict(self, pre_migration_result=False): legacy = {} if self.obj_attr_is_set('is_volume_backed'): legacy['is_volume_backed'] = self.is_volume_backed if self.obj_attr_is_set('migration'): legacy['migration'] = self.migration if pre_migration_result: legacy['pre_live_migration_result'] = {} return legacy def from_legacy_dict(self, legacy): if 'is_volume_backed' in legacy: self.is_volume_backed = legacy['is_volume_backed'] if 'migration' in legacy: self.migration = legacy['migration'] @classmethod def detect_implementation(cls, legacy_dict): if 'instance_relative_path' in legacy_dict: obj = LibvirtLiveMigrateData() elif 'image_type' in legacy_dict: obj = LibvirtLiveMigrateData() elif 'migrate_data' in legacy_dict: obj = XenapiLiveMigrateData() else: obj = LiveMigrateData() obj.from_legacy_dict(legacy_dict) return obj
class InstancePCIRequest(base.NovaObject, base.NovaObjectDictCompat): # Version 1.0: Initial version # Version 1.1: Add request_id VERSION = '1.1' fields = { 'count': fields.IntegerField(), 'spec': fields.ListOfDictOfNullableStringsField(), 'alias_name': fields.StringField(nullable=True), # A stashed request related to a resize, not current 'is_new': fields.BooleanField(default=False), 'request_id': fields.UUIDField(nullable=True), } def obj_load_attr(self, attr): setattr(self, attr, None) # NOTE(danms): The dict that this object replaces uses a key of 'new' # so we translate it here to our more appropropriately-named 'is_new'. # This is not something that affects the obect version, so we could # remove this later when all dependent code is fixed. @property def new(self): return self.is_new def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 1) and 'request_id' in primitive: del primitive['request_id']
class NovaPersistentObject(object): """Mixin class for Persistent objects. This adds the fields that we use in common for most persistent objects. """ fields = { 'created_at': obj_fields.DateTimeField(nullable=True), 'updated_at': obj_fields.DateTimeField(nullable=True), 'deleted_at': obj_fields.DateTimeField(nullable=True), 'deleted': obj_fields.BooleanField(default=False), }
class XenapiLiveMigrateData(LiveMigrateData): VERSION = '1.0' fields = { 'block_migration': fields.BooleanField(nullable=True), 'destination_sr_ref': fields.StringField(nullable=True), 'migrate_send_data': fields.DictOfStringsField(nullable=True), 'sr_uuid_map': fields.DictOfStringsField(), 'kernel_file': fields.StringField(), 'ramdisk_file': fields.StringField(), } def to_legacy_dict(self, pre_migration_result=False): legacy = super(XenapiLiveMigrateData, self).to_legacy_dict() if self.obj_attr_is_set('block_migration'): legacy['block_migration'] = self.block_migration if self.obj_attr_is_set('migrate_send_data'): legacy['migrate_data'] = { 'migrate_send_data': self.migrate_send_data, 'destination_sr_ref': self.destination_sr_ref, } live_result = { 'sr_uuid_map': ('sr_uuid_map' in self and self.sr_uuid_map or {}), } if pre_migration_result: legacy['pre_live_migration_result'] = live_result return legacy def from_legacy_dict(self, legacy): super(XenapiLiveMigrateData, self).from_legacy_dict(legacy) if 'block_migration' in legacy: self.block_migration = legacy['block_migration'] else: self.block_migration = False if 'migrate_data' in legacy: self.migrate_send_data = \ legacy['migrate_data']['migrate_send_data'] self.destination_sr_ref = \ legacy['migrate_data']['destination_sr_ref'] if 'pre_live_migration_result' in legacy: self.sr_uuid_map = \ legacy['pre_live_migration_result']['sr_uuid_map']
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.19' 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(), 'rpc_current_version': fields.StringField(nullable=True), 'object_current_version': fields.StringField(nullable=True), } _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, 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): 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, 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]) 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) @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() 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 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 compute sends notification on # every other field change. See the comment in save() too. if set(updates.keys()).intersection( {'disabled', 'disabled_reason', 'forced_down'}): payload = ServiceStatusPayload(self) ServiceStatusNotification( publisher=notification.NotificationPublisher.from_service_obj( self), event_type=notification.EventType( object='service', action=fields.NotificationAction.UPDATE), priority=fields.NotificationPriority.INFO, payload=payload).emit(self._context) @base.remotable def destroy(self): db.service_destroy(self._context, self.id) @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, binary, use_slave=False): return db.service_get_minimum_version(context, binary) @base.remotable_classmethod def get_minimum_version(cls, context, binary, use_slave=False): if not binary.startswith('nova-') and not binary.startswith('jacket-'): LOG.warning(_LW('get_minimum_version called with likely-incorrect ' 'binary `%s\''), binary) raise exception.ObjectActionError(action='get_minimum_version', reason='Invalid binary prefix') if cls._SERVICE_VERSION_CACHING: cached_version = cls._MIN_VERSION_CACHE.get(binary) if cached_version: return cached_version version = cls._db_service_get_minimum_version(context, binary, use_slave=use_slave) if version is None: return 0 # NOTE(danms): Since our return value is not controlled by object # schema, be explicit here. version = int(version) cls._MIN_VERSION_CACHE[binary] = version return version @classmethod def _get_minimum_version(cls, attribute, context, binary): services = ServiceList.get_by_binary(context, binary) min_ver = None min_ver_str = None for s in services: ver_str = getattr(s, attribute) if ver_str is None: # FIXME(dulek) None in *_current_version means that this # service is in Liberty version, so we must assume this is the # lowest one. We use handy and easy to remember token to # indicate that. This may go away as soon as we drop # compatibility with Liberty, possibly in early N. return 'liberty' ver = versionutils.convert_version_to_int(ver_str) if min_ver is None or ver < min_ver: min_ver = ver min_ver_str = ver_str return min_ver_str @base.remotable_classmethod def get_minimum_rpc_version(cls, context, binary): return cls._get_minimum_version('rpc_current_version', context, binary) @base.remotable_classmethod def get_minimum_obj_version(cls, context, binary): return cls._get_minimum_version('object_current_version', context, binary)
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.9: Instance 1.19 # Version 1.10: Instance 1.20 # Version 1.11: Instance 1.21 # Version 1.12: Instance 1.22, FloatingIPList 1.9 # Version 1.13: Instance 1.23, FloatingIPList 1.10 # Version 1.14: Added vif_id kwarg to associate(_pool), FloatingIPList 1.11 VERSION = '1.14' 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'), } def obj_make_compatible(self, primitive, target_version): super(FixedIP, self).obj_make_compatible(primitive, target_version) target_version = versionutils.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, vif_id=None): db_fixedip = db.fixed_ip_associate(context, address, instance_uuid, network_id=network_id, reserved=reserved, virtual_interface_id=vif_id) 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, vif_id=None): db_fixedip = db.fixed_ip_associate_pool(context, network_id, instance_uuid=instance_uuid, host=host, virtual_interface_id=vif_id) 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, utils.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'])
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.SensitiveStringField(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'])
class Migration(base.NovaPersistentObject, base.NovaObject, base.NovaObjectDictCompat): # Version 1.0: Initial version # Version 1.1: String attributes updated to support unicode # Version 1.2: Added migration_type and hidden # Version 1.3: Added get_by_id_and_instance() # Version 1.4: Added migration progress detail VERSION = '1.4' fields = { 'id': fields.IntegerField(), 'source_compute': fields.StringField(nullable=True), 'dest_compute': fields.StringField(nullable=True), 'source_node': fields.StringField(nullable=True), 'dest_node': fields.StringField(nullable=True), 'dest_host': fields.StringField(nullable=True), 'old_instance_type_id': fields.IntegerField(nullable=True), 'new_instance_type_id': fields.IntegerField(nullable=True), 'instance_uuid': fields.StringField(nullable=True), 'status': fields.StringField(nullable=True), 'migration_type': fields.EnumField( ['migration', 'resize', 'live-migration', 'evacuation'], nullable=False), 'hidden': fields.BooleanField(nullable=False, default=False), 'memory_total': fields.IntegerField(nullable=True), 'memory_processed': fields.IntegerField(nullable=True), 'memory_remaining': fields.IntegerField(nullable=True), 'disk_total': fields.IntegerField(nullable=True), 'disk_processed': fields.IntegerField(nullable=True), 'disk_remaining': fields.IntegerField(nullable=True), } @staticmethod def _from_db_object(context, migration, db_migration): for key in migration.fields: value = db_migration[key] if key == 'migration_type' and value is None: value = determine_migration_type(db_migration) migration[key] = value migration._context = context migration.obj_reset_changes() return migration def obj_make_compatible(self, primitive, target_version): super(Migration, self).obj_make_compatible(primitive, target_version) target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 2): if 'migration_type' in primitive: del primitive['migration_type'] del primitive['hidden'] if target_version < (1, 4): if 'memory_total' in primitive: del primitive['memory_total'] del primitive['memory_processed'] del primitive['memory_remaining'] del primitive['disk_total'] del primitive['disk_processed'] del primitive['disk_remaining'] def obj_load_attr(self, attrname): if attrname == 'migration_type': # NOTE(danms): The only reason we'd need to load this is if # some older node sent us one. So, guess the type. self.migration_type = determine_migration_type(self) elif attrname == 'hidden': self.hidden = False else: super(Migration, self).obj_load_attr(attrname) @base.remotable_classmethod def get_by_id(cls, context, migration_id): db_migration = db.migration_get(context, migration_id) return cls._from_db_object(context, cls(), db_migration) @base.remotable_classmethod def get_by_id_and_instance(cls, context, migration_id, instance_uuid): db_migration = db.migration_get_by_id_and_instance( context, migration_id, instance_uuid) return cls._from_db_object(context, cls(), db_migration) @base.remotable_classmethod def get_by_instance_and_status(cls, context, instance_uuid, status): db_migration = db.migration_get_by_instance_and_status( context, instance_uuid, status) return cls._from_db_object(context, cls(), db_migration) @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 'migration_type' not in updates: raise exception.ObjectActionError( action="create", reason="cannot create a Migration object without a " "migration_type set") db_migration = db.migration_create(self._context, updates) self._from_db_object(self._context, self, db_migration) @base.remotable def save(self): updates = self.obj_get_changes() updates.pop('id', None) db_migration = db.migration_update(self._context, self.id, updates) self._from_db_object(self._context, self, db_migration) self.obj_reset_changes() @property def instance(self): if not hasattr(self, '_cached_instance'): self._cached_instance = objects.Instance.get_by_uuid( self._context, self.instance_uuid) return self._cached_instance @instance.setter def instance(self, instance): self._cached_instance = instance
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), } @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)
class Network(obj_base.NovaPersistentObject, obj_base.NovaObject, obj_base.NovaObjectDictCompat): # Version 1.0: Initial version # Version 1.1: Added in_use_on_host() # Version 1.2: Added mtu, dhcp_server, enable_dhcp, share_address VERSION = '1.2' fields = { 'id': fields.IntegerField(), 'label': fields.StringField(), 'injected': fields.BooleanField(), 'cidr': fields.IPV4NetworkField(nullable=True), 'cidr_v6': fields.IPV6NetworkField(nullable=True), 'multi_host': fields.BooleanField(), 'netmask': fields.IPV4AddressField(nullable=True), 'gateway': fields.IPV4AddressField(nullable=True), 'broadcast': fields.IPV4AddressField(nullable=True), 'netmask_v6': fields.IPV6AddressField(nullable=True), 'gateway_v6': fields.IPV6AddressField(nullable=True), 'bridge': fields.StringField(nullable=True), 'bridge_interface': fields.StringField(nullable=True), 'dns1': fields.IPAddressField(nullable=True), 'dns2': fields.IPAddressField(nullable=True), 'vlan': fields.IntegerField(nullable=True), 'vpn_public_address': fields.IPAddressField(nullable=True), 'vpn_public_port': fields.IntegerField(nullable=True), 'vpn_private_address': fields.IPAddressField(nullable=True), 'dhcp_start': fields.IPV4AddressField(nullable=True), 'rxtx_base': fields.IntegerField(nullable=True), 'project_id': fields.UUIDField(nullable=True), 'priority': fields.IntegerField(nullable=True), 'host': fields.StringField(nullable=True), 'uuid': fields.UUIDField(), 'mtu': fields.IntegerField(nullable=True), 'dhcp_server': fields.IPAddressField(nullable=True), 'enable_dhcp': fields.BooleanField(), 'share_address': fields.BooleanField(), } @staticmethod def _convert_legacy_ipv6_netmask(netmask): """Handle netmask_v6 possibilities from the database. Historically, this was stored as just an integral CIDR prefix, but in the future it should be stored as an actual netmask. Be tolerant of either here. """ try: prefix = int(netmask) return netaddr.IPNetwork('1::/%i' % prefix).netmask except ValueError: pass try: return netaddr.IPNetwork(netmask).netmask except netaddr.AddrFormatError: raise ValueError( _('IPv6 netmask "%s" must be a netmask ' 'or integral prefix') % netmask) def obj_make_compatible(self, primitive, target_version): target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 2): if 'mtu' in primitive: del primitive['mtu'] if 'enable_dhcp' in primitive: del primitive['enable_dhcp'] if 'dhcp_server' in primitive: del primitive['dhcp_server'] if 'share_address' in primitive: del primitive['share_address'] @staticmethod def _from_db_object(context, network, db_network): for field in network.fields: db_value = db_network[field] if field is 'netmask_v6' and db_value is not None: db_value = network._convert_legacy_ipv6_netmask(db_value) if field is 'mtu' and db_value is None: db_value = CONF.network_device_mtu if field is 'dhcp_server' and db_value is None: db_value = db_network['gateway'] if field is 'share_address' and CONF.share_dhcp_address: db_value = CONF.share_dhcp_address network[field] = db_value network._context = context network.obj_reset_changes() return network @obj_base.remotable_classmethod def get_by_id(cls, context, network_id, project_only='allow_none'): db_network = db.network_get(context, network_id, project_only=project_only) return cls._from_db_object(context, cls(), db_network) @obj_base.remotable_classmethod def get_by_uuid(cls, context, network_uuid): db_network = db.network_get_by_uuid(context, network_uuid) return cls._from_db_object(context, cls(), db_network) @obj_base.remotable_classmethod def get_by_cidr(cls, context, cidr): db_network = db.network_get_by_cidr(context, cidr) return cls._from_db_object(context, cls(), db_network) @obj_base.remotable_classmethod def associate(cls, context, project_id, network_id=None, force=False): db.network_associate(context, project_id, network_id=network_id, force=force) @obj_base.remotable_classmethod def disassociate(cls, context, network_id, host=False, project=False): db.network_disassociate(context, network_id, host, project) @obj_base.remotable_classmethod def in_use_on_host(cls, context, network_id, host): return db.network_in_use_on_host(context, network_id, host) def _get_primitive_changes(self): changes = {} for key, value in self.obj_get_changes().items(): if isinstance(value, netaddr.IPAddress): changes[key] = str(value) else: changes[key] = value return changes @obj_base.remotable def create(self): updates = self._get_primitive_changes() if 'id' in updates: raise exception.ObjectActionError(action='create', reason='already created') db_network = db.network_create_safe(self._context, updates) self._from_db_object(self._context, self, db_network) @obj_base.remotable def destroy(self): db.network_delete_safe(self._context, self.id) self.deleted = True self.obj_reset_changes(['deleted']) @obj_base.remotable def save(self): context = self._context updates = self._get_primitive_changes() if 'netmask_v6' in updates: # NOTE(danms): For some reason, historical code stores the # IPv6 netmask as just the CIDR mask length, so convert that # back here before saving for now. updates['netmask_v6'] = netaddr.IPNetwork( updates['netmask_v6']).netmask set_host = 'host' in updates if set_host: db.network_set_host(context, self.id, updates.pop('host')) if updates: db_network = db.network_update(context, self.id, updates) elif set_host: db_network = db.network_get(context, self.id) else: db_network = None if db_network is not None: self._from_db_object(context, self, db_network)
class LibvirtLiveMigrateData(LiveMigrateData): # Version 1.0: Initial version # Version 1.1: Added target_connect_addr VERSION = '1.1' 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), 'bdms': fields.ListOfObjectsField('LibvirtLiveMigrateBDMInfo'), 'target_connect_addr': fields.StringField(nullable=True), } 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, 1) and 'target_connect_addr' in primitive: del primitive['target_connect_addr'] def _bdms_to_legacy(self, legacy): if not self.obj_attr_is_set('bdms'): return legacy['volume'] = {} for bdmi in self.bdms: legacy['volume'][bdmi.serial] = { 'disk_info': bdmi.as_disk_info(), 'connection_info': bdmi.connection_info} def _bdms_from_legacy(self, legacy_pre_result): self.bdms = [] volume = legacy_pre_result.get('volume', {}) for serial in volume: vol = volume[serial] bdmi = objects.LibvirtLiveMigrateBDMInfo(serial=serial) bdmi.connection_info = vol['connection_info'] bdmi.bus = vol['disk_info']['bus'] bdmi.dev = vol['disk_info']['dev'] bdmi.type = vol['disk_info']['type'] if 'format' in vol: bdmi.format = vol['disk_info']['format'] if 'boot_index' in vol: bdmi.boot_index = int(vol['disk_info']['boot_index']) self.bdms.append(bdmi) def to_legacy_dict(self, pre_migration_result=False): LOG.debug('Converting to legacy: %s' % self) legacy = super(LibvirtLiveMigrateData, self).to_legacy_dict() keys = (set(self.fields.keys()) - set(LiveMigrateData.fields.keys()) - {'bdms'}) legacy.update({k: getattr(self, k) for k in keys if self.obj_attr_is_set(k)}) graphics_vnc = legacy.pop('graphics_listen_addr_vnc', None) graphics_spice = legacy.pop('graphics_listen_addr_spice', None) transport_target = legacy.pop('target_connect_addr', None) live_result = { 'graphics_listen_addrs': { 'vnc': graphics_vnc and str(graphics_vnc), 'spice': graphics_spice and str(graphics_spice), }, 'serial_listen_addr': legacy.pop('serial_listen_addr', None), 'target_connect_addr': transport_target, } if pre_migration_result: legacy['pre_live_migration_result'] = live_result self._bdms_to_legacy(live_result) LOG.debug('Legacy result: %s' % legacy) return legacy def from_legacy_dict(self, legacy): LOG.debug('Converting legacy dict to obj: %s' % legacy) super(LibvirtLiveMigrateData, self).from_legacy_dict(legacy) keys = set(self.fields.keys()) - set(LiveMigrateData.fields.keys()) for k in keys - {'bdms'}: if k in legacy: setattr(self, k, legacy[k]) if 'pre_live_migration_result' in legacy: pre_result = legacy['pre_live_migration_result'] self.graphics_listen_addr_vnc = \ pre_result['graphics_listen_addrs'].get('vnc') self.graphics_listen_addr_spice = \ pre_result['graphics_listen_addrs'].get('spice') self.target_connect_addr = pre_result.get('target_connect_addr') if 'serial_listen_addr' in pre_result: self.serial_listen_addr = pre_result['serial_listen_addr'] self._bdms_from_legacy(pre_result) LOG.debug('Converted object: %s' % self) def is_on_shared_storage(self): return self.is_shared_block_storage or self.is_shared_instance_path
class Flavor(base.NovaPersistentObject, base.NovaObject, base.NovaObjectDictCompat): # Version 1.0: Initial version # Version 1.1: Added save_projects(), save_extra_specs(), removed # remoteable from save() VERSION = '1.1' fields = { 'id': fields.IntegerField(), 'name': fields.StringField(nullable=True), 'memory_mb': fields.IntegerField(), 'vcpus': fields.IntegerField(), 'root_gb': fields.IntegerField(), 'ephemeral_gb': fields.IntegerField(), 'flavorid': fields.StringField(), 'swap': fields.IntegerField(), 'rxtx_factor': fields.FloatField(nullable=True, default=1.0), 'vcpu_weight': fields.IntegerField(nullable=True), 'disabled': fields.BooleanField(), 'is_public': fields.BooleanField(), 'extra_specs': fields.DictOfStringsField(), 'projects': fields.ListOfStringsField(), } def __init__(self, *args, **kwargs): super(Flavor, self).__init__(*args, **kwargs) self._orig_extra_specs = {} self._orig_projects = [] @staticmethod def _from_db_object(context, flavor, db_flavor, expected_attrs=None): if expected_attrs is None: expected_attrs = [] flavor._context = context for name, field in flavor.fields.items(): if name in OPTIONAL_FIELDS: continue value = db_flavor[name] if isinstance(field, fields.IntegerField): value = value if value is not None else 0 flavor[name] = value if 'extra_specs' in expected_attrs: flavor.extra_specs = db_flavor['extra_specs'] if 'projects' in expected_attrs: flavor._load_projects() flavor.obj_reset_changes() return flavor @base.remotable def _load_projects(self): self.projects = [ x['project_id'] for x in db.flavor_access_get_by_flavor_id( self._context, self.flavorid) ] self.obj_reset_changes(['projects']) def obj_load_attr(self, attrname): # NOTE(danms): Only projects could be lazy-loaded right now if attrname != 'projects': raise exception.ObjectActionError(action='obj_load_attr', reason='unable to load %s' % attrname) self._load_projects() def obj_reset_changes(self, fields=None, recursive=False): super(Flavor, self).obj_reset_changes(fields=fields, recursive=recursive) if fields is None or 'extra_specs' in fields: self._orig_extra_specs = (dict(self.extra_specs) if self.obj_attr_is_set('extra_specs') else {}) if fields is None or 'projects' in fields: self._orig_projects = (list(self.projects) if self.obj_attr_is_set('projects') else []) def obj_what_changed(self): changes = super(Flavor, self).obj_what_changed() if ('extra_specs' in self and self.extra_specs != self._orig_extra_specs): changes.add('extra_specs') if 'projects' in self and self.projects != self._orig_projects: changes.add('projects') return changes @classmethod def _obj_from_primitive(cls, context, objver, primitive): self = super(Flavor, cls)._obj_from_primitive(context, objver, primitive) changes = self.obj_what_changed() if 'extra_specs' not in changes: # This call left extra_specs "clean" so update our tracker self._orig_extra_specs = (dict(self.extra_specs) if self.obj_attr_is_set('extra_specs') else {}) if 'projects' not in changes: # This call left projects "clean" so update our tracker self._orig_projects = (list(self.projects) if self.obj_attr_is_set('projects') else []) return self @base.remotable_classmethod def get_by_id(cls, context, id): db_flavor = db.flavor_get(context, id) return cls._from_db_object(context, cls(context), db_flavor, expected_attrs=['extra_specs']) @base.remotable_classmethod def get_by_name(cls, context, name): db_flavor = db.flavor_get_by_name(context, name) return cls._from_db_object(context, cls(context), db_flavor, expected_attrs=['extra_specs']) @base.remotable_classmethod def get_by_flavor_id(cls, context, flavor_id, read_deleted=None): db_flavor = db.flavor_get_by_flavor_id(context, flavor_id, read_deleted) return cls._from_db_object(context, cls(context), db_flavor, expected_attrs=['extra_specs']) @base.remotable def add_access(self, project_id): if 'projects' in self.obj_what_changed(): raise exception.ObjectActionError(action='add_access', reason='projects modified') db.flavor_access_add(self._context, self.flavorid, project_id) self._load_projects() @base.remotable def remove_access(self, project_id): if 'projects' in self.obj_what_changed(): raise exception.ObjectActionError(action='remove_access', reason='projects modified') db.flavor_access_remove(self._context, self.flavorid, project_id) self._load_projects() @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() expected_attrs = [] for attr in OPTIONAL_FIELDS: if attr in updates: expected_attrs.append(attr) projects = updates.pop('projects', []) db_flavor = db.flavor_create(self._context, updates, projects=projects) self._from_db_object(self._context, self, db_flavor, expected_attrs=expected_attrs) @base.remotable def save_projects(self, to_add=None, to_delete=None): """Add or delete projects. :param:to_add: A list of projects to add :param:to_delete: A list of projects to remove """ to_add = to_add if to_add is not None else [] to_delete = to_delete if to_delete is not None else [] for project_id in to_add: db.flavor_access_add(self._context, self.flavorid, project_id) for project_id in to_delete: db.flavor_access_remove(self._context, self.flavorid, project_id) self.obj_reset_changes(['projects']) @base.remotable def save_extra_specs(self, to_add=None, to_delete=None): """Add or delete extra_specs. :param:to_add: A dict of new keys to add/update :param:to_delete: A list of keys to remove """ to_add = to_add if to_add is not None else {} to_delete = to_delete if to_delete is not None else [] if to_add: db.flavor_extra_specs_update_or_create(self._context, self.flavorid, to_add) for key in to_delete: db.flavor_extra_specs_delete(self._context, self.flavorid, key) self.obj_reset_changes(['extra_specs']) def save(self): updates = self.obj_get_changes() projects = updates.pop('projects', None) extra_specs = updates.pop('extra_specs', None) if updates: raise exception.ObjectActionError( action='save', reason='read-only fields were changed') if extra_specs is not None: deleted_keys = (set(self._orig_extra_specs.keys()) - set(extra_specs.keys())) added_keys = self.extra_specs else: added_keys = deleted_keys = None if projects is not None: deleted_projects = set(self._orig_projects) - set(projects) added_projects = set(projects) - set(self._orig_projects) else: added_projects = deleted_projects = None # NOTE(danms): The first remotable method we call will reset # our of the original values for projects and extra_specs. Thus, # we collect the added/deleted lists for both above and /then/ # call these methods to update them. if added_keys or deleted_keys: self.save_extra_specs(self.extra_specs, deleted_keys) if added_projects or deleted_projects: self.save_projects(added_projects, deleted_projects) @base.remotable def destroy(self): db.flavor_destroy(self._context, self.name)
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) .join(api_models.RequestSpec) .with_entities(api_models.BuildRequest, api_models.RequestSpec) .filter( api_models.RequestSpec.instance_uuid == instance_uuid) ).first() if not db_req: raise exception.BuildRequestNotFound(uuid=instance_uuid) # db_req is a tuple (api_models.BuildRequest, api_models.RequestSpect) build_req = db_req[0] build_req['request_spec'] = db_req[1] return build_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)