OpenStack Block Storage (Cinder)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

255 lines
10 KiB

# 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
# 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
class VolumeAttachment(base.CinderPersistentObject, base.CinderObject,
# 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)
def project_id(self):
return self.volume.project_id
def volume_host(self):
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 = 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)
def _from_db_object(cls, context, attachment, db_attachment,
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:
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)
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
# 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
# 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,
if connector:
# Update ourselves and delete the attachment_specs.
attachment.connector = connector
# TODO(mriedem): Really need a delete-all method for this.
for spec_key in connector:
context,, spec_key)
return attachment
def obj_load_attr(self, attrname):
if attrname not in self.OPTIONAL_FIELDS:
raise exception.ObjectActionError(
reason=_('attribute %s not lazy-loadable') % attrname)
if not self._context:
raise exception.OrphanedObjectError(method='obj_load_attr',
if attrname == 'volume':
volume = objects.Volume.get_by_id(self._context, self.volume_id)
self.volume = volume
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 _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:
if 'connector' in updates:
if 'volume' in updates:
raise exception.ObjectActionError(action='save',
reason=_('volume changed'))
db.volume_attachment_update(self._context,, updates)
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(
instance_uuid, host_name,
mount_point, attach_mode)
return objects.Volume._from_db_object(self._context,
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:
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,
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'),
def get_all_by_volume_id(cls, context, volume_id):
attachments = db.volume_attachment_get_all_by_volume_id(context,
return base.obj_make_list(context,
def get_all_by_host(cls, context, host, search_opts=None):
attachments = db.volume_attachment_get_all_by_host(context,
return base.obj_make_list(context, cls(context),
objects.VolumeAttachment, attachments)
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)
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,
return base.obj_make_list(context, cls(context),
objects.VolumeAttachment, attachments)
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,
return base.obj_make_list(context, cls(context),
objects.VolumeAttachment, attachments)