Skip to content

Commit

Permalink
Add volume multi attach support
Browse files Browse the repository at this point in the history
This patch includes the Cinder changes needed
to support volume multiple attaches.  Nova and
python-cinderclient also need patches associated
to provide support for multiple attachments.

This adds the multiattach flag to volumes.  When a
volume is created, a multiattach flag can be set,
which allows a volume to be attached to more than
one Nova instance or host.  If the multiattach flag is
not set on a volume, it cannot be attached to more
than one Nova instance or host

Each volume attachment is tracked in a
new volume_attachment table.  The attachment id is
the unique identifier for each attachment to an
instance or host.

When a volume is to be detached the attachment
uuid must be passed in to the detach call in
order to determine which attachment should be
removed.  Since a volume can be attached to an
instance and a host, the attachment id is used
as the attachment identifier.

Nova:
https://review.openstack.org/#/c/153033/
https://review.openstack.org/#/c/153038/

python-cinderclient:
https://review.openstack.org/#/c/85856/

Change-Id: I950fa00ed5a30e7758245d5b0557f6df42dc58a3
Implements: blueprint multi-attach-volume
APIImpact
  • Loading branch information
hemna committed Mar 10, 2015
1 parent 490f03b commit 10d5421
Show file tree
Hide file tree
Showing 38 changed files with 1,409 additions and 483 deletions.
5 changes: 4 additions & 1 deletion cinder/api/contrib/admin_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,10 @@ def _force_detach(self, req, id, body):
raise exc.HTTPNotFound()
self.volume_api.terminate_connection(context, volume,
{}, force=True)
self.volume_api.detach(context, volume)

attachment_id = body['os-force_detach'].get('attachment_id', None)

self.volume_api.detach(context, volume, attachment_id)
return webob.Response(status_int=202)

@wsgi.action('os-migrate_volume')
Expand Down
6 changes: 5 additions & 1 deletion cinder/api/contrib/volume_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,11 @@ def _detach(self, req, id, body):
except exception.VolumeNotFound as error:
raise webob.exc.HTTPNotFound(explanation=error.msg)

self.volume_api.detach(context, volume)
attachment_id = None
if body['os-detach']:
attachment_id = body['os-detach'].get('attachment_id', None)

self.volume_api.detach(context, volume, attachment_id)
return webob.Response(status_int=202)

@wsgi.action('os-reserve')
Expand Down
35 changes: 21 additions & 14 deletions cinder/api/v1/volumes.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,18 @@ def _translate_attachment_detail_view(_context, vol):

def _translate_attachment_summary_view(_context, vol):
"""Maps keys for attachment summary view."""
d = {}

volume_id = vol['id']

# NOTE(justinsb): We use the volume id as the id of the attachment object
d['id'] = volume_id

d['volume_id'] = volume_id
d['server_id'] = vol['instance_uuid']
d['host_name'] = vol['attached_host']
if vol.get('mountpoint'):
d['device'] = vol['mountpoint']
d = []
attachments = vol.get('volume_attachment', [])
for attachment in attachments:
if attachment.get('attach_status') == 'attached':
a = {'id': attachment.get('volume_id'),
'attachment_id': attachment.get('id'),
'volume_id': attachment.get('volume_id'),
'server_id': attachment.get('instance_uuid'),
'host_name': attachment.get('attached_host'),
'device': attachment.get('mountpoint'),
}
d.append(a)

return d

Expand Down Expand Up @@ -91,10 +91,14 @@ def _translate_volume_summary_view(context, vol, image_id=None):
else:
d['bootable'] = 'false'

if vol['multiattach']:
d['multiattach'] = 'true'
else:
d['multiattach'] = 'false'

d['attachments'] = []
if vol['attach_status'] == 'attached':
attachment = _translate_attachment_detail_view(context, vol)
d['attachments'].append(attachment)
d['attachments'] = _translate_attachment_detail_view(context, vol)

d['display_name'] = vol['display_name']
d['display_description'] = vol['display_description']
Expand Down Expand Up @@ -146,6 +150,7 @@ def make_volume(elem):
elem.set('volume_type')
elem.set('snapshot_id')
elem.set('source_volid')
elem.set('multiattach')

attachments = xmlutil.SubTemplateElement(elem, 'attachments')
attachment = xmlutil.SubTemplateElement(attachments, 'attachment',
Expand Down Expand Up @@ -373,6 +378,8 @@ def create(self, req, body):
size = kwargs['source_volume']['size']

LOG.info(_LI("Create volume of %s GB"), size, context=context)
multiattach = volume.get('multiattach', False)
kwargs['multiattach'] = multiattach

image_href = None
image_uuid = None
Expand Down
27 changes: 13 additions & 14 deletions cinder/api/v2/views/volumes.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ def detail(self, request, volume):
'bootable': str(volume.get('bootable')).lower(),
'encrypted': self._is_volume_encrypted(volume),
'replication_status': volume.get('replication_status'),
'consistencygroup_id': volume.get('consistencygroup_id')
'consistencygroup_id': volume.get('consistencygroup_id'),
'multiattach': volume.get('multiattach')
}
}

Expand All @@ -83,19 +84,17 @@ def _get_attachments(self, volume):
attachments = []

if volume['attach_status'] == 'attached':
d = {}
volume_id = volume['id']

# note(justinsb): we use the volume id as the id of the attachments
# object
d['id'] = volume_id

d['volume_id'] = volume_id
d['server_id'] = volume['instance_uuid']
d['host_name'] = volume['attached_host']
if volume.get('mountpoint'):
d['device'] = volume['mountpoint']
attachments.append(d)
attaches = volume.get('volume_attachment', [])
for attachment in attaches:
if attachment.get('attach_status') == 'attached':
a = {'id': attachment.get('volume_id'),
'attachment_id': attachment.get('id'),
'volume_id': attachment.get('volume_id'),
'server_id': attachment.get('instance_uuid'),
'host_name': attachment.get('attached_host'),
'device': attachment.get('mountpoint'),
}
attachments.append(a)

return attachments

Expand Down
4 changes: 4 additions & 0 deletions cinder/api/v2/volumes.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

def make_attachment(elem):
elem.set('id')
elem.set('attachment_id')
elem.set('server_id')
elem.set('host_name')
elem.set('volume_id')
Expand All @@ -63,6 +64,7 @@ def make_volume(elem):
elem.set('snapshot_id')
elem.set('source_volid')
elem.set('consistencygroup_id')
elem.set('multiattach')

attachments = xmlutil.SubTemplateElement(elem, 'attachments')
attachment = xmlutil.SubTemplateElement(attachments, 'attachment',
Expand Down Expand Up @@ -412,6 +414,8 @@ def create(self, req, body):

kwargs['availability_zone'] = volume.get('availability_zone', None)
kwargs['scheduler_hints'] = volume.get('scheduler_hints', None)
multiattach = volume.get('multiattach', False)
kwargs['multiattach'] = multiattach

new_volume = self.volume_api.create(context,
size,
Expand Down
34 changes: 22 additions & 12 deletions cinder/backup/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,18 +197,28 @@ def init_host(self):
for volume in volumes:
volume_host = volume_utils.extract_host(volume['host'], 'backend')
backend = self._get_volume_backend(host=volume_host)
if volume['status'] == 'backing-up':
LOG.info(_LI('Resetting volume %s to available '
'(was backing-up).') % volume['id'])
mgr = self._get_manager(backend)
mgr.detach_volume(ctxt, volume['id'])
if volume['status'] == 'restoring-backup':
LOG.info(_LI('Resetting volume %s to error_restoring '
'(was restoring-backup).') % volume['id'])
mgr = self._get_manager(backend)
mgr.detach_volume(ctxt, volume['id'])
self.db.volume_update(ctxt, volume['id'],
{'status': 'error_restoring'})
attachments = volume['volume_attachment']
if attachments:
if volume['status'] == 'backing-up':
LOG.info(_LI('Resetting volume %s to available '
'(was backing-up).'), volume['id'])
mgr = self._get_manager(backend)
for attachment in attachments:
if (attachment['attached_host'] == self.host and
attachment['instance_uuid'] is None):
mgr.detach_volume(ctxt, volume['id'],
attachment['id'])
if volume['status'] == 'restoring-backup':
LOG.info(_LI('setting volume %s to error_restoring '
'(was restoring-backup).'), volume['id'])
mgr = self._get_manager(backend)
for attachment in attachments:
if (attachment['attached_host'] == self.host and
attachment['instance_uuid'] is None):
mgr.detach_volume(ctxt, volume['id'],
attachment['id'])
self.db.volume_update(ctxt, volume['id'],
{'status': 'error_restoring'})

# TODO(smulcahy) implement full resume of backup and restore
# operations on restart (rather than simply resetting)
Expand Down
35 changes: 31 additions & 4 deletions cinder/db/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,16 @@ def iscsi_target_create_safe(context, values):
###############


def volume_attached(context, volume_id, instance_id, host_name, mountpoint):
def volume_attach(context, values):
"""Attach a volume."""
return IMPL.volume_attach(context, values)


def volume_attached(context, volume_id, instance_id, host_name, mountpoint,
attach_mode='rw'):
"""Ensure that a volume is set as attached."""
return IMPL.volume_attached(context, volume_id, instance_id, host_name,
mountpoint)
mountpoint, attach_mode)


def volume_create(context, values):
Expand Down Expand Up @@ -169,9 +175,9 @@ def volume_destroy(context, volume_id):
return IMPL.volume_destroy(context, volume_id)


def volume_detached(context, volume_id):
def volume_detached(context, volume_id, attachment_id):
"""Ensure that a volume is set as detached."""
return IMPL.volume_detached(context, volume_id)
return IMPL.volume_detached(context, volume_id, attachment_id)


def volume_get(context, volume_id):
Expand Down Expand Up @@ -219,6 +225,27 @@ def volume_update(context, volume_id, values):
return IMPL.volume_update(context, volume_id, values)


def volume_attachment_update(context, attachment_id, values):
return IMPL.volume_attachment_update(context, attachment_id, values)


def volume_attachment_get(context, attachment_id, session=None):
return IMPL.volume_attachment_get(context, attachment_id, session)


def volume_attachment_get_used_by_volume_id(context, volume_id):
return IMPL.volume_attachment_get_used_by_volume_id(context, volume_id)


def volume_attachment_get_by_host(context, volume_id, host):
return IMPL.volume_attachment_get_by_host(context, volume_id, host)


def volume_attachment_get_by_instance_uuid(context, volume_id, instance_uuid):
return IMPL.volume_attachment_get_by_instance_uuid(context, volume_id,
instance_uuid)


####################


Expand Down
Loading

0 comments on commit 10d5421

Please sign in to comment.