Skip to content

Commit

Permalink
Adding Read-Only volume attaching support to Cinder
Browse files Browse the repository at this point in the history
1. Adding an API extension to allow clients set volume Read-Only flag on
demand.
2. Require client to provide and be aware of volume attaching mode when
they call 'os-attach' API.
3. Adding a 'access_mode' field to connection info which
'os-initialize_connection' API returned. This field should be used by
client such as Nova to use correct mode accessing attached volume.
Currently access mode can be 'rw' or 'ro'.
4. In future, the driver within Cinder need to ensure the volume be
exposed under the correct access mode which connection info described,
for example backend should set volume to readonly mode when connection
info ask client using 'ro' access mode consume attached volume. That
means Read-Only is not only a attaching mode but also a status for a
volume.

blueprint read-only-volumes

Change-Id: I4c84614d6541d5f7c358abadb957da7b8c3d9c48
Signed-off-by: Zhi Yan Liu <zhiyanl@cn.ibm.com>
  • Loading branch information
Zhi Yan Liu committed Aug 28, 2013
1 parent 4daffc6 commit 900851f
Show file tree
Hide file tree
Showing 27 changed files with 990 additions and 140 deletions.
36 changes: 33 additions & 3 deletions cinder/api/contrib/volume_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ def _attach(self, req, id, body):
if 'host_name' in body['os-attach']:
host_name = body['os-attach']['host_name']
mountpoint = body['os-attach']['mountpoint']
if 'mode' in body['os-attach']:
mode = body['os-attach']['mode']
else:
mode = 'rw'

if instance_uuid and host_name:
msg = _("Invalid request to attach volume to an "
Expand All @@ -98,8 +102,13 @@ def _attach(self, req, id, body):
msg = _("Invalid request to attach volume to an invalid target")
raise webob.exc.HTTPBadRequest(explanation=msg)

if mode not in ('rw', 'ro'):
msg = _("Invalid request to attach volume with an invalid mode. "
"Attaching mode should be 'rw' or 'ro'")
raise webob.exc.HTTPBadRequest(explanation=msg)

self.volume_api.attach(context, volume,
instance_uuid, host_name, mountpoint)
instance_uuid, host_name, mountpoint, mode)
return webob.Response(status_int=202)

@wsgi.action('os-detach')
Expand Down Expand Up @@ -210,15 +219,36 @@ def _extend(self, req, id, body):
context = req.environ['cinder.context']
volume = self.volume_api.get(context, id)
try:
val = int(body['os-extend']['new_size'])
except ValueError:
_val = int(body['os-extend']['new_size'])
except (KeyError, ValueError):
msg = _("New volume size must be specified as an integer.")
raise webob.exc.HTTPBadRequest(explanation=msg)

size = body['os-extend']['new_size']
self.volume_api.extend(context, volume, size)
return webob.Response(status_int=202)

@wsgi.action('os-update_readonly_flag')
def _volume_readonly_update(self, req, id, body):
"""Update volume readonly flag."""
context = req.environ['cinder.context']
volume = self.volume_api.get(context, id)

if not self.is_valid_body(body, 'os-update_readonly_flag'):
msg = _("No 'os-update_readonly_flag' was specified "
"in request.")
raise webob.exc.HTTPBadRequest(explanation=msg)

readonly_flag = body['os-update_readonly_flag'].get('readonly')

if not isinstance(readonly_flag, bool):
msg = _("Volume 'readonly' flag must be specified "
"in request as a boolean.")
raise webob.exc.HTTPBadRequest(explanation=msg)

self.volume_api.update_readonly_flag(context, volume, readonly_flag)
return webob.Response(status_int=202)


class Volume_actions(extensions.ExtensionDescriptor):
"""Enable volume actions
Expand Down
57 changes: 54 additions & 3 deletions cinder/api/v1/volumes.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,52 @@ def default(self, string):
class VolumeController(wsgi.Controller):
"""The Volumes API controller for the OpenStack API."""

_visible_admin_metadata_keys = ['readonly', 'attached_mode']

def __init__(self, ext_mgr):
self.volume_api = volume.API()
self.ext_mgr = ext_mgr
super(VolumeController, self).__init__()

def _add_visible_admin_metadata(self, context, volume):
if context is None:
return

visible_admin_meta = {}

volume_tmp = (volume if context.is_admin else
self.volume_api.get(context.elevated(), volume['id']))

if volume_tmp.get('volume_admin_metadata'):
for item in volume_tmp['volume_admin_metadata']:
if item['key'] in self._visible_admin_metadata_keys:
visible_admin_meta[item['key']] = item['value']
# avoid circular ref when volume is a Volume instance
elif (volume_tmp.get('admin_metadata') and
isinstance(volume_tmp.get('admin_metadata'), dict)):
for key in self._visible_admin_metadata_keys:
if key in volume_tmp['admin_metadata'].keys():
visible_admin_meta[key] = volume_tmp['admin_metadata'][key]

if not visible_admin_meta:
return

# NOTE(zhiyan): update visible administration metadata to
# volume metadata, administration metadata will rewrite existing key.
if volume.get('volume_metadata'):
orig_meta = volume.get('volume_metadata')
for item in orig_meta:
if item['key'] in visible_admin_meta.keys():
item['value'] = visible_admin_meta.pop(item['key'])
for key, value in visible_admin_meta.iteritems():
orig_meta.append({'key': key, 'value': value})
# avoid circular ref when vol is a Volume instance
elif (volume.get('metadata') and
isinstance(volume.get('metadata'), dict)):
volume['metadata'].update(visible_admin_meta)
else:
volume['metadata'] = visible_admin_meta

@wsgi.serializers(xml=VolumeTemplate)
def show(self, req, id):
"""Return data about the given volume."""
Expand All @@ -224,6 +265,8 @@ def show(self, req, id):
except exception.NotFound:
raise exc.HTTPNotFound()

self._add_visible_admin_metadata(context, vol)

return {'volume': _translate_volume_detail_view(context, vol)}

def delete(self, req, id):
Expand Down Expand Up @@ -267,6 +310,10 @@ def _items(self, req, entity_maker):
volumes = self.volume_api.get_all(context, marker=None, limit=None,
sort_key='created_at',
sort_dir='desc', filters=search_opts)

for volume in volumes:
self._add_visible_admin_metadata(context, volume)

limited_list = common.limited(volumes, req)
res = [entity_maker(context, vol) for vol in limited_list]
return {'volumes': res}
Expand Down Expand Up @@ -361,9 +408,11 @@ def create(self, req, body):
# TODO(vish): Instance should be None at db layer instead of
# trying to lazy load, but for now we turn it into
# a dict to avoid an error.
retval = _translate_volume_detail_view(context,
dict(new_volume.iteritems()),
image_uuid)
new_volume = dict(new_volume.iteritems())

self._add_visible_admin_metadata(context, new_volume)

retval = _translate_volume_detail_view(context, new_volume, image_uuid)

return {'volume': retval}

Expand Down Expand Up @@ -403,6 +452,8 @@ def update(self, req, id, body):

volume.update(update_dict)

self._add_visible_admin_metadata(context, volume)

return {'volume': _translate_volume_detail_view(context, volume)}


Expand Down
55 changes: 54 additions & 1 deletion cinder/api/v2/volumes.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,52 @@ class VolumeController(wsgi.Controller):

_view_builder_class = volume_views.ViewBuilder

_visible_admin_metadata_keys = ['readonly', 'attached_mode']

def __init__(self, ext_mgr):
self.volume_api = volume.API()
self.ext_mgr = ext_mgr
super(VolumeController, self).__init__()

def _add_visible_admin_metadata(self, context, volume):
if context is None:
return

visible_admin_meta = {}

volume_tmp = (volume if context.is_admin else
self.volume_api.get(context.elevated(), volume['id']))

if volume_tmp.get('volume_admin_metadata'):
for item in volume_tmp['volume_admin_metadata']:
if item['key'] in self._visible_admin_metadata_keys:
visible_admin_meta[item['key']] = item['value']
# avoid circular ref when volume is a Volume instance
elif (volume_tmp.get('admin_metadata') and
isinstance(volume_tmp.get('admin_metadata'), dict)):
for key in self._visible_admin_metadata_keys:
if key in volume_tmp['admin_metadata'].keys():
visible_admin_meta[key] = volume_tmp['admin_metadata'][key]

if not visible_admin_meta:
return

# NOTE(zhiyan): update visible administration metadata to
# volume metadata, administration metadata will rewrite existing key.
if volume.get('volume_metadata'):
orig_meta = volume.get('volume_metadata')
for item in orig_meta:
if item['key'] in visible_admin_meta.keys():
item['value'] = visible_admin_meta.pop(item['key'])
for key, value in visible_admin_meta.iteritems():
orig_meta.append({'key': key, 'value': value})
# avoid circular ref when vol is a Volume instance
elif (volume.get('metadata') and
isinstance(volume.get('metadata'), dict)):
volume['metadata'].update(visible_admin_meta)
else:
volume['metadata'] = visible_admin_meta

@wsgi.serializers(xml=VolumeTemplate)
def show(self, req, id):
"""Return data about the given volume."""
Expand All @@ -168,6 +209,8 @@ def show(self, req, id):
msg = _("Volume could not be found")
raise exc.HTTPNotFound(explanation=msg)

self._add_visible_admin_metadata(context, vol)

return self._view_builder.detail(req, vol)

def delete(self, req, id):
Expand Down Expand Up @@ -223,6 +266,10 @@ def _get_volumes(self, req, is_detail):

volumes = self.volume_api.get_all(context, marker, limit, sort_key,
sort_dir, filters)

for volume in volumes:
self._add_visible_admin_metadata(context, volume)

limited_list = common.limited(volumes, req)

if is_detail:
Expand Down Expand Up @@ -324,7 +371,11 @@ def create(self, req, body):
# TODO(vish): Instance should be None at db layer instead of
# trying to lazy load, but for now we turn it into
# a dict to avoid an error.
retval = self._view_builder.summary(req, dict(new_volume.iteritems()))
new_volume = dict(new_volume.iteritems())

self._add_visible_admin_metadata(context, new_volume)

retval = self._view_builder.summary(req, new_volume)

return retval

Expand Down Expand Up @@ -377,6 +428,8 @@ def update(self, req, id, body):

volume.update(update_dict)

self._add_visible_admin_metadata(context, volume)

return self._view_builder.detail(req, volume)


Expand Down
18 changes: 18 additions & 0 deletions cinder/db/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,24 @@ def volume_metadata_update(context, volume_id, metadata, delete):
##################


def volume_admin_metadata_get(context, volume_id):
"""Get all administration metadata for a volume."""
return IMPL.volume_admin_metadata_get(context, volume_id)


def volume_admin_metadata_delete(context, volume_id, key):
"""Delete the given metadata item."""
IMPL.volume_admin_metadata_delete(context, volume_id, key)


def volume_admin_metadata_update(context, volume_id, metadata, delete):
"""Update metadata if it exists, otherwise create it."""
IMPL.volume_admin_metadata_update(context, volume_id, metadata, delete)


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


def volume_type_create(context, values):
"""Create a new volume type."""
return IMPL.volume_type_create(context, values)
Expand Down
Loading

0 comments on commit 900851f

Please sign in to comment.