diff --git a/cinder/api/microversions.py b/cinder/api/microversions.py index a800d8196cc..6f9958e6a6b 100644 --- a/cinder/api/microversions.py +++ b/cinder/api/microversions.py @@ -137,6 +137,8 @@ VOLUME_SHARED_TARGETS_AND_SERVICE_FIELDS = '3.48' BACKEND_STATE_REPORT = '3.49' +MULTIATTACH_VOLUMES = '3.50' + def get_mv_header(version): """Gets a formatted HTTP microversion header. diff --git a/cinder/api/openstack/api_version_request.py b/cinder/api/openstack/api_version_request.py index cee288ee2d0..a4389cff0ad 100644 --- a/cinder/api/openstack/api_version_request.py +++ b/cinder/api/openstack/api_version_request.py @@ -113,6 +113,7 @@ REST_API_VERSION_HISTORY = """ * 3.47 - Support create volume from backup. * 3.48 - Add ``shared_targets`` and ``service_uuid`` fields to volume. * 3.49 - Support report backend storage state in service list. + * 3.50 - Add multiattach capability """ # The minimum and maximum versions of the API supported @@ -120,7 +121,7 @@ REST_API_VERSION_HISTORY = """ # minimum version of the API supported. # Explicitly using /v2 endpoints will still work _MIN_API_VERSION = "3.0" -_MAX_API_VERSION = "3.49" +_MAX_API_VERSION = "3.50" _LEGACY_API_VERSION2 = "2.0" UPDATED = "2017-09-19T20:18:14Z" diff --git a/cinder/tests/unit/volume/test_volume.py b/cinder/tests/unit/volume/test_volume.py index ce4df54988e..791cfd4cfb5 100644 --- a/cinder/tests/unit/volume/test_volume.py +++ b/cinder/tests/unit/volume/test_volume.py @@ -626,6 +626,7 @@ class VolumeTestCase(base.BaseVolumeTestCase): 'description', volume_type=foo) self.assertEqual(foo['id'], vol['volume_type_id']) + self.assertTrue(vol['multiattach']) @mock.patch.object(key_manager, 'API', fake_keymgr.fake_api) def test_create_volume_with_encrypted_volume_type_aes(self): diff --git a/cinder/tests/unit/volume/test_volume_retype.py b/cinder/tests/unit/volume/test_volume_retype.py new file mode 100644 index 00000000000..3144980a980 --- /dev/null +++ b/cinder/tests/unit/volume/test_volume_retype.py @@ -0,0 +1,108 @@ +# 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. +"""Tests for Volume retype Code.""" + +import mock +from oslo_config import cfg + +from cinder import context +from cinder import exception +from cinder import objects +from cinder import quota +from cinder.tests.unit import fake_constants as fake +from cinder.tests.unit import volume as base +from cinder.volume import volume_types + + +QUOTAS = quota.QUOTAS + +CONF = cfg.CONF + + +class VolumeRetypeTestCase(base.BaseVolumeTestCase): + + def setUp(self): + super(VolumeRetypeTestCase, self).setUp() + self.patch('cinder.volume.utils.clear_volume', autospec=True) + self.expected_status = 'available' + self.service_id = 1 + self.user_context = context.RequestContext(user_id=fake.USER_ID, + project_id=fake.PROJECT_ID) + + volume_types.create(self.context, + "old-type", + {}, + description="test-multiattach") + volume_types.create(self.context, + "fake_vol_type", + {}, + description="fake_type") + volume_types.create(self.context, + "multiattach-type", + {'multiattach': " True"}, + description="test-multiattach") + self.default_vol_type = objects.VolumeType.get_by_name_or_id( + self.context, + 'fake_vol_type') + self.multiattach_type = objects.VolumeType.get_by_name_or_id( + self.context, + 'multiattach-type') + + def fake_get_vtype(self, context, identifier): + if identifier == "multiattach-type": + return self.multiattach_type + else: + return self.default_vol_type + + @mock.patch.object(volume_types, 'get_by_name_or_id') + def test_retype_multiattach(self, _mock_get_types): + """Verify multiattach retype restrictions.""" + + _mock_get_types.side_effect = self.fake_get_vtype + + # Test going from default type to multiattach + vol = self.volume_api.create(self.context, + 1, + 'test-vol', + '') + + vol.update({'status': 'available'}) + vol.save() + self.volume_api.retype(self.user_context, + vol, + 'multiattach-type') + vol = objects.Volume.get_by_id(self.context, vol.id) + self.assertTrue(vol.multiattach) + + # Test going from multiattach to a non-multiattach type + vol = self.volume_api.create( + self.context, + 1, + 'test-multiattachvol', + '', + volume_type=self.multiattach_type) + vol.update({'status': 'available'}) + vol.save() + self.volume_api.retype(self.user_context, + vol, + 'fake_vol-type') + vol = objects.Volume.get_by_id(self.context, vol.id) + self.assertFalse(vol.multiattach) + + # Test trying to retype an in-use volume + vol.update({'status': 'in-use'}) + vol.save() + self.assertRaises(exception.InvalidInput, + self.volume_api.retype, + self.context, + vol, + 'multiattach-type') diff --git a/cinder/volume/api.py b/cinder/volume/api.py index fdf84705263..9d10d5d982c 100644 --- a/cinder/volume/api.py +++ b/cinder/volume/api.py @@ -349,6 +349,8 @@ class API(base.Base): # Refresh the object here, otherwise things ain't right vref = objects.Volume.get_by_id( context, vref['id']) + vref.multiattach = self._is_multiattach(volume_type) + vref.save() LOG.info("Create volume request issued successfully.", resource=vref) return vref @@ -1620,14 +1622,14 @@ class API(base.Base): # Support specifying volume type by ID or name try: - vol_type = ( + new_type = ( volume_types.get_by_name_or_id(context.elevated(), new_type)) except exception.InvalidVolumeType: msg = _('Invalid volume_type passed: %s.') % new_type LOG.error(msg) raise exception.InvalidInput(reason=msg) - vol_type_id = vol_type['id'] + new_type_id = new_type['id'] # NOTE(jdg): We check here if multiattach is involved in either side # of the retype, we can't change multiattach on an in-use volume @@ -1636,12 +1638,11 @@ class API(base.Base): # have to get through scheduling if all the conditions are met, we # should consider an up front capabilities check to give fast feedback # rather than "No hosts found" and error status - src_is_multiattach = volume.multiattach tgt_is_multiattach = False - if (vol_type and - self._is_multiattach(vol_type)): - tgt_is_multiattach = True + + if new_type: + tgt_is_multiattach = self._is_multiattach(new_type) if src_is_multiattach != tgt_is_multiattach: if volume.status != "available": @@ -1657,7 +1658,7 @@ class API(base.Base): # early as possible, but won't commit until we change the type. We # pass the reservations onward in case we need to roll back. reservations = quota_utils.get_volume_type_reservation( - context, volume, vol_type_id, reserve_vol_type_only=True) + context, volume, new_type_id, reserve_vol_type_only=True) # Get old reservations try: @@ -1685,12 +1686,12 @@ class API(base.Base): 'migration_status': self.AVAILABLE_MIGRATION_STATUS, 'consistencygroup_id': (None, ''), 'group_id': (None, ''), - 'volume_type_id': db.Not(vol_type_id)} + 'volume_type_id': db.Not(new_type_id)} # We don't support changing QoS at the front-end yet for in-use volumes # TODO(avishay): Call Nova to change QoS setting (libvirt has support # - virDomainSetBlockIoTune() - Nova does not have support yet). - filters = [db.volume_qos_allows_retype(vol_type_id)] + filters = [db.volume_qos_allows_retype(new_type_id)] updates = {'status': 'retyping', 'previous_status': objects.Volume.model.status} @@ -1708,7 +1709,7 @@ class API(base.Base): request_spec = {'volume_properties': volume, 'volume_id': volume.id, - 'volume_type': vol_type, + 'volume_type': new_type, 'migration_policy': migration_policy, 'quota_reservations': reservations, 'old_reservations': old_reservations} @@ -1716,6 +1717,8 @@ class API(base.Base): self.scheduler_rpcapi.retype(context, volume, request_spec=request_spec, filter_properties={}) + volume.multiattach = tgt_is_multiattach + volume.save() LOG.info("Retype volume request issued successfully.", resource=volume) diff --git a/doc/source/admin/blockstorage-volume-multiattach.rst b/doc/source/admin/blockstorage-volume-multiattach.rst new file mode 100644 index 00000000000..13c4c08bdb2 --- /dev/null +++ b/doc/source/admin/blockstorage-volume-multiattach.rst @@ -0,0 +1,49 @@ +.. _volume_multiattach: + +============================================= +Enable attaching a volume to multiple servers +============================================= + +When configured to allow it and for backends that support it, Cinder +allows a volume to be attached to more than one host/server at a time. + +By default this feature is only enabled for administrators, and is +controlled by policy. If the user is not an admin or the policy file +isn't modified only a single attachment per volume is allowed. + +In addition, the ability to attach a volume to multiple hosts/servers +requires that the volume is of a special type that includes an extra-spec +capability setting of multiattach: True:: + +.. code-block:: console + + $ cinder type-create multiattach + $ cinder type-key multiattach set multiattach=" True" + +Now any volume of this type is capable of having multiple simultaneous +attachments. You'll need to ensure you have a backend device that reports +support of the multiattach capability, otherwise scheduling will fail on +create. + +At this point Cinder will no longer check in-use status when creating/updating +attachments. + +.. note:: + + This feature is only supported when using the new attachment API's, + attachment-create, attachment-update etc. + +In addition, it's possible to retype a volume to be multiattach capable. +Currently however we do NOT allow retyping a volume to multiattach:True or +multiattach:False if it's status is not ``avaialable``. This is because some +consumers/hypervisors need to make special considerations at attach-time for +multiattach volumes (ie disable caching) and there's no mechanism currently to +go back to ``in-use`` volumes and update them. While going from +``multiattach:True`` --> ``multiattach:False`` isn't as problematic, it is +error prone when it comes to special cases like shelve, migrate etc. The bottom +line is it's *safer* to just avoid changing this setting on ``in-use`` volumes. + +Finally, note that Cinder (nor its backends) does not do anything in terms of file +systems or control of the volumes. In other words, it's up to the user to +ensure that a multiattach or clustered file system is used on the volumes. +Otherwise there may be a high probability of data corruption. diff --git a/doc/source/admin/index.rst b/doc/source/admin/index.rst index 6bda9f36b27..51ec0be5282 100644 --- a/doc/source/admin/index.rst +++ b/doc/source/admin/index.rst @@ -44,6 +44,7 @@ Amazon EC2 Elastic Block Storage (EBS) offering. blockstorage-volume-backups-export-import.rst blockstorage-volume-backups.rst blockstorage-volume-migration.rst + blockstorage-volume-multiattach.rst blockstorage-volume-number-weigher.rst blockstorage-report-backend-state.rst