def setUp(self): super(TestBoolean, self).setUp() self.field = fields.BooleanField() self.coerce_good_values = [(True, True), (False, False), (1, True), ('foo', True), (0, False), ('', False)] self.coerce_bad_values = [] self.to_primitive_values = self.coerce_good_values[0:2] self.from_primitive_values = self.coerce_good_values[0:2]
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 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 = utils.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 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.12' 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'), } 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, 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 == 'patron-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)
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): super(Flavor, self).obj_reset_changes(fields=fields) 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 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'])
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.8' fields = { 'id': fields.IntegerField(), 'instance_uuid': fields.UUIDField(), 'instance': fields.ObjectField('Instance', nullable=True), 'source_type': fields.StringField(nullable=True), 'destination_type': fields.StringField(nullable=True), 'guest_format': fields.StringField(nullable=True), 'device_type': fields.StringField(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), } obj_relationships = { 'instance': [('1.0', '1.13'), ('1.2', '1.14'), ('1.3', '1.15'), ('1.4', '1.16'), ('1.5', '1.17'), ('1.6', '1.18'), ('1.8', '1.19')], } @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) if cell_type == 'compute': 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) self._from_db_object(self._context, self, updated) cell_type = cells_opts.get_cell_type() if cell_type == 'compute': cells_api = cells_rpcapi.CellsAPI() cells_api.bdm_update_or_create_at_top(self._context, self) @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_bdm = db.block_device_mapping_get_by_volume_id( context, volume_id, _expected_cols(expected_attrs)) if not db_bdm: raise exception.VolumeBDMNotFound(volume_id=volume_id) # 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) @property def is_root(self): return self.boot_index == 0 @property def is_volume(self): return self.destination_type == 'volume' @property def is_image(self): return self.source_type == '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'])