# Copyright 2015 SimpliVity Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils from oslo_utils import versionutils from oslo_versionedobjects import fields from cinder import db from cinder import exception from cinder.i18n import _ from cinder import objects from cinder.objects import base from cinder.objects import fields as c_fields @base.CinderObjectRegistry.register 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: Check against the ORM instance's dictionary instead of using # hasattr or get to avoid the lazy loading of the Volume on # 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.opendev.org/632549 # and its related bug report. if 'volume' in expected_attrs and 'volume' in vars(db_attachment): 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()) @base.CinderObjectRegistry.register class VolumeAttachmentList(base.ObjectListBase, base.CinderObject): # Version 1.0: Initial version # Version 1.1: Remove volume_id in get_by_host|instance VERSION = '1.1' fields = { 'objects': fields.ListOfObjectsField('VolumeAttachment'), } @classmethod def get_all_by_volume_id(cls, context, volume_id): attachments = db.volume_attachment_get_all_by_volume_id(context, volume_id) return base.obj_make_list(context, cls(context), objects.VolumeAttachment, attachments) @classmethod def get_all_by_host(cls, context, host, search_opts=None): attachments = db.volume_attachment_get_all_by_host(context, host, search_opts) return base.obj_make_list(context, cls(context), objects.VolumeAttachment, attachments) @classmethod def get_all_by_instance_uuid(cls, context, instance_uuid, search_opts=None): attachments = db.volume_attachment_get_all_by_instance_uuid( context, instance_uuid, search_opts) return base.obj_make_list(context, cls(context), objects.VolumeAttachment, attachments) @classmethod def get_all(cls, context, search_opts=None, marker=None, limit=None, offset=None, sort_keys=None, sort_direction=None): attachments = db.volume_attachment_get_all( context, search_opts, marker, limit, offset, sort_keys, sort_direction) return base.obj_make_list(context, cls(context), objects.VolumeAttachment, attachments) @classmethod def get_all_by_project(cls, context, project_id, search_opts=None, marker=None, limit=None, offset=None, sort_keys=None, sort_direction=None): attachments = db.volume_attachment_get_all_by_project( context, project_id, search_opts, marker, limit, offset, sort_keys, sort_direction) return base.obj_make_list(context, cls(context), objects.VolumeAttachment, attachments)