class VolumeAttachment(base.CinderPersistentObject, base.CinderObject, base.CinderObjectDictCompat, base.CinderComparableObject): # Version 1.0: Initial version # Version 1.1: Added volume relationship # Version 1.2: Added connection_info attribute # Version 1.3: Added the connector attribute. VERSION = '1.3' OPTIONAL_FIELDS = ['volume'] obj_extra_fields = ['project_id', 'volume_host'] fields = { 'id': fields.UUIDField(), 'volume_id': fields.UUIDField(), 'instance_uuid': fields.UUIDField(nullable=True), 'attached_host': fields.StringField(nullable=True), 'mountpoint': fields.StringField(nullable=True), 'attach_time': fields.DateTimeField(nullable=True), 'detach_time': fields.DateTimeField(nullable=True), 'attach_status': c_fields.VolumeAttachStatusField(nullable=True), 'attach_mode': fields.StringField(nullable=True), 'volume': fields.ObjectField('Volume', nullable=False), 'connection_info': c_fields.DictOfNullableField(nullable=True), 'connector': c_fields.DictOfNullableField(nullable=True) } @property def project_id(self): return self.volume.project_id @property def volume_host(self): return self.volume.host @classmethod def _get_expected_attrs(cls, context, *args, **kwargs): return ['volume'] def obj_make_compatible(self, primitive, target_version): """Make an object representation compatible with target version.""" super(VolumeAttachment, self).obj_make_compatible(primitive, target_version) target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 3): primitive.pop('connector', None) if target_version < (1, 2): primitive.pop('connection_info', None) @classmethod def _from_db_object(cls, context, attachment, db_attachment, expected_attrs=None): if expected_attrs is None: expected_attrs = cls._get_expected_attrs(context) for name, field in attachment.fields.items(): if name in cls.OPTIONAL_FIELDS: continue value = db_attachment.get(name) if isinstance(field, fields.IntegerField): value = value or 0 if name in ('connection_info', 'connector'): # Both of these fields are nullable serialized json dicts. setattr(attachment, name, jsonutils.loads(value) if value else None) else: attachment[name] = value # NOTE: hasattr check is necessary to avoid doing a lazy loading when # loading VolumeList.get_all: Getting a Volume loads its # VolumeAttachmentList, which think they have the volume loaded, but # they don't. More detail on https://review.openstack.org/632549 if 'volume' in expected_attrs and hasattr(db_attachment, 'volume'): db_volume = db_attachment.volume if db_volume: attachment.volume = objects.Volume._from_db_object( context, objects.Volume(), db_volume) attachment._context = context attachment.obj_reset_changes() # This is an online data migration which we should remove when enough # time has passed and we have a blocker schema migration to check to # make sure that the attachment_specs table is empty. Operators should # run the "cinder-manage db online_data_migrations" CLI to force the # migration on-demand. connector = db.attachment_specs_get(context, attachment.id) if connector: # Update ourselves and delete the attachment_specs. attachment.connector = connector attachment.save() # TODO(mriedem): Really need a delete-all method for this. for spec_key in connector: db.attachment_specs_delete(context, attachment.id, spec_key) return attachment def obj_load_attr(self, attrname): if attrname not in self.OPTIONAL_FIELDS: 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()) if attrname == 'volume': volume = objects.Volume.get_by_id(self._context, self.volume_id) self.volume = volume self.obj_reset_changes(fields=[attrname]) @staticmethod def _convert_connection_info_to_db_format(updates): properties = updates.pop('connection_info', None) if properties is not None: updates['connection_info'] = jsonutils.dumps(properties) @staticmethod def _convert_connector_to_db_format(updates): connector = updates.pop('connector', None) if connector is not None: updates['connector'] = jsonutils.dumps(connector) def save(self): updates = self.cinder_obj_get_changes() if updates: if 'connection_info' in updates: self._convert_connection_info_to_db_format(updates) if 'connector' in updates: self._convert_connector_to_db_format(updates) if 'volume' in updates: raise exception.ObjectActionError(action='save', reason=_('volume changed')) db.volume_attachment_update(self._context, self.id, updates) self.obj_reset_changes() def finish_attach(self, instance_uuid, host_name, mount_point, attach_mode='rw'): with self.obj_as_admin(): db_volume, updated_values = db.volume_attached( self._context, self.id, instance_uuid, host_name, mount_point, attach_mode) self.update(updated_values) self.obj_reset_changes(updated_values.keys()) return objects.Volume._from_db_object(self._context, objects.Volume(), db_volume) def create(self): if self.obj_attr_is_set('id'): raise exception.ObjectActionError(action='create', reason=_('already created')) updates = self.cinder_obj_get_changes() if 'connector' in updates: self._convert_connector_to_db_format(updates) with self.obj_as_admin(): db_attachment = db.volume_attach(self._context, updates) self._from_db_object(self._context, self, db_attachment) def destroy(self): updated_values = db.attachment_destroy(self._context, self.id) self.update(updated_values) self.obj_reset_changes(updated_values.keys())
class VolumeAttachment(base.CinderPersistentObject, base.CinderObject, base.CinderObjectDictCompat, base.CinderComparableObject): # Version 1.0: Initial version # Version 1.1: Added volume relationship # Version 1.2: Added connection_info attribute VERSION = '1.2' OPTIONAL_FIELDS = ['volume'] obj_extra_fields = ['project_id', 'volume_host'] fields = { 'id': fields.UUIDField(), 'volume_id': fields.UUIDField(), 'instance_uuid': fields.UUIDField(nullable=True), 'attached_host': fields.StringField(nullable=True), 'mountpoint': fields.StringField(nullable=True), 'attach_time': fields.DateTimeField(nullable=True), 'detach_time': fields.DateTimeField(nullable=True), 'attach_status': c_fields.VolumeAttachStatusField(nullable=True), 'attach_mode': fields.StringField(nullable=True), 'volume': fields.ObjectField('Volume', nullable=False), 'connection_info': c_fields.DictOfNullableField(nullable=True) } @property def project_id(self): return self.volume.project_id @property def volume_host(self): return self.volume.host @classmethod def _get_expected_attrs(cls, context, *args, **kwargs): return ['volume'] def obj_make_compatible(self, primitive, target_version): """Make an object representation compatible with target version.""" super(VolumeAttachment, self).obj_make_compatible(primitive, target_version) target_version = versionutils.convert_version_to_tuple(target_version) if target_version < (1, 2): primitive.pop('connection_info', None) @classmethod def _from_db_object(cls, context, attachment, db_attachment, expected_attrs=None): if expected_attrs is None: expected_attrs = cls._get_expected_attrs(context) for name, field in attachment.fields.items(): if name in cls.OPTIONAL_FIELDS: continue value = db_attachment.get(name) if isinstance(field, fields.IntegerField): value = value or 0 if name == 'connection_info': attachment.connection_info = jsonutils.loads( value) if value else None else: attachment[name] = value if 'volume' in expected_attrs: db_volume = db_attachment.get('volume') if db_volume: attachment.volume = objects.Volume._from_db_object( context, objects.Volume(), db_volume) attachment._context = context attachment.obj_reset_changes() return attachment def obj_load_attr(self, attrname): if attrname not in self.OPTIONAL_FIELDS: 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()) if attrname == 'volume': volume = objects.Volume.get_by_id(self._context, self.volume_id) self.volume = volume self.obj_reset_changes(fields=[attrname]) @staticmethod def _convert_connection_info_to_db_format(updates): properties = updates.pop('connection_info', None) if properties is not None: updates['connection_info'] = jsonutils.dumps(properties) def save(self): updates = self.cinder_obj_get_changes() if updates: if 'connection_info' in updates: self._convert_connection_info_to_db_format(updates) if 'volume' in updates: raise exception.ObjectActionError(action='save', reason=_('volume changed')) db.volume_attachment_update(self._context, self.id, updates) self.obj_reset_changes() def finish_attach(self, instance_uuid, host_name, mount_point, attach_mode='rw'): with self.obj_as_admin(): db_volume, updated_values = db.volume_attached( self._context, self.id, instance_uuid, host_name, mount_point, attach_mode) self.update(updated_values) self.obj_reset_changes(updated_values.keys()) return objects.Volume._from_db_object(self._context, objects.Volume(), db_volume) def create(self): if self.obj_attr_is_set('id'): raise exception.ObjectActionError(action='create', reason=_('already created')) updates = self.cinder_obj_get_changes() with self.obj_as_admin(): db_attachment = db.volume_attach(self._context, updates) self._from_db_object(self._context, self, db_attachment) def destroy(self): updated_values = db.attachment_destroy(self._context, self.id) self.update(updated_values) self.obj_reset_changes(updated_values.keys())