Add support for VxFlex OS 3.5 to VxFlex OS driver
Driver code is prepared for future VxFlex OS 3.5 release. Unit tests are fixed to work properly with updated driver. Implements: blueprint vxflexos-replication-support Change-Id: I693980384df22b2fa581d8715f73c69b0598dd59
This commit is contained in:
parent
819b4a0fc0
commit
f75b2865fe
@ -91,6 +91,7 @@ class TestVxFlexOSDriver(test.TestCase):
|
||||
__COMMON_HTTPS_MOCK_RESPONSES = {
|
||||
RESPONSE_MODE.Valid: {
|
||||
'login': 'login_token',
|
||||
'version': '3.5'
|
||||
},
|
||||
RESPONSE_MODE.BadStatus: {
|
||||
'login': mocks.MockHTTPSResponse(
|
||||
@ -99,6 +100,7 @@ class TestVxFlexOSDriver(test.TestCase):
|
||||
'message': 'Bad Login Response Test',
|
||||
}, 403
|
||||
),
|
||||
'version': '3.5'
|
||||
},
|
||||
}
|
||||
__https_response_mode = RESPONSE_MODE.Valid
|
||||
@ -124,10 +126,14 @@ class TestVxFlexOSDriver(test.TestCase):
|
||||
conf.SHARED_CONF_GROUP)
|
||||
self._set_overrides()
|
||||
self.driver = mocks.VxFlexOSDriver(configuration=self.configuration)
|
||||
self.driver.primary_client = mocks.VxFlexOSClient(self.configuration)
|
||||
self.driver.do_setup({})
|
||||
|
||||
self.mock_object(requests, 'get', self.do_request)
|
||||
self.mock_object(requests, 'post', self.do_request)
|
||||
|
||||
self.driver.primary_client.do_setup()
|
||||
|
||||
def _set_overrides(self):
|
||||
# Override the defaults to fake values
|
||||
self.override_config('san_ip', override='127.0.0.1',
|
||||
|
@ -19,6 +19,7 @@ import requests
|
||||
import six
|
||||
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import driver
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import rest_client
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@ -28,6 +29,14 @@ class VxFlexOSDriver(driver.VxFlexOSDriver):
|
||||
|
||||
Provides some fake configuration options
|
||||
"""
|
||||
def do_setup(self, context):
|
||||
self.provisioning_type = (
|
||||
"thin" if self.configuration.san_thin_provision else "thick"
|
||||
)
|
||||
self.configuration.max_over_subscription_ratio = (
|
||||
self.configuration.vxflexos_max_over_subscription_ratio
|
||||
)
|
||||
|
||||
def local_path(self, volume):
|
||||
pass
|
||||
|
||||
@ -40,7 +49,14 @@ class VxFlexOSDriver(driver.VxFlexOSDriver):
|
||||
def unmanage(self, volume):
|
||||
pass
|
||||
|
||||
def _is_volume_creation_safe(self, _pd, _sp):
|
||||
|
||||
class VxFlexOSClient(rest_client.RestClient):
|
||||
"""Mock VxFlex OS Rest Client class.
|
||||
|
||||
Provides some fake configuration options
|
||||
"""
|
||||
|
||||
def is_volume_creation_safe(self, _pd, _sp):
|
||||
return True
|
||||
|
||||
|
||||
|
@ -23,6 +23,7 @@ from cinder.tests.unit import fake_constants as fake
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.tests.unit.volume.drivers.dell_emc import vxflexos
|
||||
from cinder.tests.unit.volume.drivers.dell_emc.vxflexos import mocks
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import utils as flex_utils
|
||||
|
||||
|
||||
class TestCreateClonedVolume(vxflexos.TestVxFlexOSDriver):
|
||||
@ -40,7 +41,7 @@ class TestCreateClonedVolume(vxflexos.TestVxFlexOSDriver):
|
||||
|
||||
self.src_volume_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(
|
||||
self.driver._id_to_base64(self.src_volume.id)
|
||||
flex_utils.id_to_base64(self.src_volume.id)
|
||||
)
|
||||
)
|
||||
|
||||
@ -55,7 +56,7 @@ class TestCreateClonedVolume(vxflexos.TestVxFlexOSDriver):
|
||||
|
||||
self.new_volume_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(
|
||||
self.driver._id_to_base64(self.new_volume.id)
|
||||
flex_utils.id_to_base64(self.new_volume.id)
|
||||
)
|
||||
)
|
||||
self.HTTPS_MOCK_RESPONSES = {
|
||||
@ -64,6 +65,7 @@ class TestCreateClonedVolume(vxflexos.TestVxFlexOSDriver):
|
||||
self.src_volume_name_2x_enc: self.src_volume.id,
|
||||
'instances/System/action/snapshotVolumes': '{}'.format(
|
||||
json.dumps(self.new_volume_extras)),
|
||||
'instances/Volume::cloned/action/setVolumeSize': None
|
||||
},
|
||||
self.RESPONSE_MODE.BadStatus: {
|
||||
'instances/System/action/snapshotVolumes':
|
||||
|
@ -24,6 +24,7 @@ from cinder.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.tests.unit.volume.drivers.dell_emc import vxflexos
|
||||
from cinder.tests.unit.volume.drivers.dell_emc.vxflexos import mocks
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import utils as flex_utils
|
||||
|
||||
|
||||
class TestCreateSnapShot(vxflexos.TestVxFlexOSDriver):
|
||||
@ -51,10 +52,10 @@ class TestCreateSnapShot(vxflexos.TestVxFlexOSDriver):
|
||||
|
||||
snap_vol_id = self.snapshot.volume_id
|
||||
self.volume_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(self.driver._id_to_base64(snap_vol_id))
|
||||
urllib.parse.quote(flex_utils.id_to_base64(snap_vol_id))
|
||||
)
|
||||
self.snapshot_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(self.driver._id_to_base64(self.snapshot.id))
|
||||
urllib.parse.quote(flex_utils.id_to_base64(self.snapshot.id))
|
||||
)
|
||||
|
||||
self.snapshot_reply = json.dumps(
|
||||
|
@ -22,6 +22,7 @@ from cinder.tests.unit import fake_snapshot
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.tests.unit.volume.drivers.dell_emc import vxflexos
|
||||
from cinder.tests.unit.volume.drivers.dell_emc.vxflexos import mocks
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import utils as flex_utils
|
||||
|
||||
|
||||
class TestCreateVolumeFromSnapShot(vxflexos.TestVxFlexOSDriver):
|
||||
@ -37,11 +38,11 @@ class TestCreateVolumeFromSnapShot(vxflexos.TestVxFlexOSDriver):
|
||||
|
||||
self.snapshot = fake_snapshot.fake_snapshot_obj(ctx)
|
||||
self.snapshot_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(self.driver._id_to_base64(self.snapshot.id))
|
||||
urllib.parse.quote(flex_utils.id_to_base64(self.snapshot.id))
|
||||
)
|
||||
self.volume = fake_volume.fake_volume_obj(ctx)
|
||||
self.volume_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(self.driver._id_to_base64(self.volume.id))
|
||||
urllib.parse.quote(flex_utils.id_to_base64(self.volume.id))
|
||||
)
|
||||
|
||||
self.snapshot_reply = json.dumps(
|
||||
@ -57,6 +58,8 @@ class TestCreateVolumeFromSnapShot(vxflexos.TestVxFlexOSDriver):
|
||||
self.snapshot_name_2x_enc: self.snapshot.id,
|
||||
'instances/System/action/snapshotVolumes':
|
||||
self.snapshot_reply,
|
||||
'instances/Volume::{}/action/setVolumeSize'.format(
|
||||
self.volume.id): None,
|
||||
},
|
||||
self.RESPONSE_MODE.BadStatus: {
|
||||
'instances/System/action/snapshotVolumes':
|
||||
|
@ -18,9 +18,11 @@ from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.tests.unit import fake_constants as fake
|
||||
from cinder.tests.unit.fake_snapshot import fake_snapshot_obj
|
||||
from cinder.tests.unit.fake_volume import fake_volume_obj
|
||||
from cinder.tests.unit.volume.drivers.dell_emc import vxflexos
|
||||
from cinder.tests.unit.volume.drivers.dell_emc.vxflexos import mocks
|
||||
from cinder.volume import configuration
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import utils as flex_utils
|
||||
|
||||
|
||||
class TestDeleteSnapShot(vxflexos.TestVxFlexOSDriver):
|
||||
@ -34,11 +36,16 @@ class TestDeleteSnapShot(vxflexos.TestVxFlexOSDriver):
|
||||
super(TestDeleteSnapShot, self).setUp()
|
||||
ctx = context.RequestContext('fake', 'fake', auth_token=True)
|
||||
|
||||
self.fake_volume = fake_volume_obj(
|
||||
ctx, **{'provider_id': fake.PROVIDER_ID})
|
||||
|
||||
self.snapshot = fake_snapshot_obj(
|
||||
ctx, **{'provider_id': fake.SNAPSHOT_ID})
|
||||
ctx, **{'volume': self.fake_volume,
|
||||
'provider_id': fake.SNAPSHOT_ID})
|
||||
|
||||
self.snapshot_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(
|
||||
self.driver._id_to_base64(self.snapshot.id)
|
||||
flex_utils.id_to_base64(self.snapshot.id)
|
||||
)
|
||||
)
|
||||
|
||||
@ -46,6 +53,7 @@ class TestDeleteSnapShot(vxflexos.TestVxFlexOSDriver):
|
||||
self.RESPONSE_MODE.Valid: {
|
||||
'types/Volume/instances/getByName::' +
|
||||
self.snapshot_name_2x_enc: self.snapshot.id,
|
||||
'instances/Volume::' + self.snapshot.provider_id: {},
|
||||
'instances/Volume::{}/action/removeMappedSdc'.format(
|
||||
self.snapshot.provider_id
|
||||
): self.snapshot.id,
|
||||
@ -54,6 +62,8 @@ class TestDeleteSnapShot(vxflexos.TestVxFlexOSDriver):
|
||||
): self.snapshot.id,
|
||||
},
|
||||
self.RESPONSE_MODE.BadStatus: {
|
||||
'instances/Volume::' + self.snapshot.provider_id:
|
||||
self.BAD_STATUS_RESPONSE,
|
||||
'types/Volume/instances/getByName::' +
|
||||
self.snapshot_name_2x_enc: self.BAD_STATUS_RESPONSE,
|
||||
'instances/Volume::{}/action/removeVolume'.format(
|
||||
|
@ -21,6 +21,7 @@ from cinder.tests.unit import fake_volume
|
||||
from cinder.tests.unit.volume.drivers.dell_emc import vxflexos
|
||||
from cinder.tests.unit.volume.drivers.dell_emc.vxflexos import mocks
|
||||
from cinder.volume import configuration
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import utils as flex_utils
|
||||
|
||||
|
||||
class TestDeleteVolume(vxflexos.TestVxFlexOSDriver):
|
||||
@ -37,11 +38,12 @@ class TestDeleteVolume(vxflexos.TestVxFlexOSDriver):
|
||||
ctx, **{'provider_id': fake.PROVIDER_ID})
|
||||
|
||||
self.volume_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(self.driver._id_to_base64(self.volume.id))
|
||||
urllib.parse.quote(flex_utils.id_to_base64(self.volume.id))
|
||||
)
|
||||
|
||||
self.HTTPS_MOCK_RESPONSES = {
|
||||
self.RESPONSE_MODE.Valid: {
|
||||
'instances/Volume::' + self.volume.provider_id: {},
|
||||
'types/Volume/instances/getByName::' +
|
||||
self.volume_name_2x_enc: self.volume.id,
|
||||
'instances/Volume::{}/action/removeMappedSdc'.format(
|
||||
@ -51,6 +53,8 @@ class TestDeleteVolume(vxflexos.TestVxFlexOSDriver):
|
||||
): self.volume.provider_id,
|
||||
},
|
||||
self.RESPONSE_MODE.BadStatus: {
|
||||
'instances/Volume::' + self.volume.provider_id:
|
||||
self.BAD_STATUS_RESPONSE,
|
||||
'types/Volume/instances/getByName::' +
|
||||
self.volume_name_2x_enc: mocks.MockHTTPSResponse(
|
||||
{
|
||||
|
@ -21,6 +21,7 @@ from cinder.tests.unit.fake_volume import fake_volume_obj
|
||||
from cinder.tests.unit.volume.drivers.dell_emc import vxflexos
|
||||
from cinder.tests.unit.volume.drivers.dell_emc.vxflexos import mocks
|
||||
from cinder.volume import configuration
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import utils as flex_utils
|
||||
|
||||
|
||||
class TestExtendVolume(vxflexos.TestVxFlexOSDriver):
|
||||
@ -45,7 +46,7 @@ class TestExtendVolume(vxflexos.TestVxFlexOSDriver):
|
||||
self.volume = fake_volume_obj(ctx, **{'id': fake.VOLUME_ID,
|
||||
'provider_id': fake.PROVIDER_ID})
|
||||
self.volume_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(self.driver._id_to_base64(self.volume.id))
|
||||
urllib.parse.quote(flex_utils.id_to_base64(self.volume.id))
|
||||
)
|
||||
|
||||
self.HTTPS_MOCK_RESPONSES = {
|
||||
|
@ -104,6 +104,7 @@ class VxFlexOSManageableCase(vxflexos.TestVxFlexOSDriver):
|
||||
def setUp(self):
|
||||
"""Setup a test case environment."""
|
||||
super(VxFlexOSManageableCase, self).setUp()
|
||||
self.driver.storage_pools = super().STORAGE_POOLS
|
||||
|
||||
def _test_get_manageable_things(self,
|
||||
vxflexos_objects=MANAGEABLE_VXFLEXOS_VOLS,
|
||||
|
@ -72,6 +72,8 @@ class TestGroups(vxflexos.TestVxFlexOSDriver):
|
||||
'snapshotGroupId': 'sgid1'})
|
||||
self.HTTPS_MOCK_RESPONSES = {
|
||||
self.RESPONSE_MODE.Valid: {
|
||||
'instances/Volume::' + fake_volume1['provider_id']: {},
|
||||
'instances/Volume::' + fake_volume2['provider_id']: {},
|
||||
'instances/Volume::{}/action/removeVolume'.format(
|
||||
fake_volume1['provider_id']
|
||||
): fake_volume1['provider_id'],
|
||||
@ -185,10 +187,7 @@ class TestGroups(vxflexos.TestVxFlexOSDriver):
|
||||
self.assertEqual(fields.GroupStatus.AVAILABLE,
|
||||
result_model_update['status'])
|
||||
|
||||
def get_pid(snapshot):
|
||||
return snapshot['provider_id']
|
||||
volume_provider_list = list(map(get_pid, result_volumes_model_update))
|
||||
self.assertListEqual(volume_provider_list, ['sid1', 'sid2'])
|
||||
self.assertEqual(len(result_volumes_model_update), len(self.volumes))
|
||||
|
||||
@mock.patch('cinder.volume.volume_utils.is_group_a_cg_snapshot_type')
|
||||
def test_create_group_from_src_snapshot(self, is_group_a_cg_snapshot_type):
|
||||
@ -212,10 +211,7 @@ class TestGroups(vxflexos.TestVxFlexOSDriver):
|
||||
self.assertEqual(fields.GroupStatus.AVAILABLE,
|
||||
result_model_update['status'])
|
||||
|
||||
def get_pid(snapshot):
|
||||
return snapshot['provider_id']
|
||||
volume_provider_list = list(map(get_pid, result_volumes_model_update))
|
||||
self.assertListEqual(volume_provider_list, ['sid1', 'sid2'])
|
||||
self.assertEqual(len(result_volumes_model_update), len(self.volumes))
|
||||
|
||||
@mock.patch('cinder.volume.volume_utils.is_group_a_cg_snapshot_type')
|
||||
def test_delete_group_snapshot(self, is_group_a_cg_snapshot_type):
|
||||
@ -275,10 +271,5 @@ class TestGroups(vxflexos.TestVxFlexOSDriver):
|
||||
result_model_update['status'])
|
||||
self.assertTrue(all(snapshot['status'] == 'available' for snapshot in
|
||||
result_snapshot_model_update))
|
||||
|
||||
def get_pid(snapshot):
|
||||
return snapshot['provider_id']
|
||||
snapshot_provider_list = list(map(get_pid,
|
||||
result_snapshot_model_update))
|
||||
|
||||
self.assertListEqual(['sid1', 'sid2'], snapshot_provider_list)
|
||||
self.assertEqual(len(result_snapshot_model_update),
|
||||
len(self.snapshots))
|
||||
|
@ -23,6 +23,7 @@ from cinder.tests.unit import fake_constants as fake
|
||||
from cinder.tests.unit import fake_volume
|
||||
from cinder.tests.unit.volume.drivers.dell_emc import vxflexos
|
||||
from cinder.tests.unit.volume.drivers.dell_emc.vxflexos import mocks
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import utils as flex_utils
|
||||
from cinder.volume import volume_types
|
||||
|
||||
|
||||
@ -42,7 +43,7 @@ class TestManageExisting(vxflexos.TestVxFlexOSDriver):
|
||||
ctx, **{'provider_id': fake.PROVIDER2_ID})
|
||||
self.volume_no_provider_id = fake_volume.fake_volume_obj(ctx)
|
||||
self.volume_name_2x_enc = urllib.parse.quote(
|
||||
urllib.parse.quote(self.driver._id_to_base64(self.volume.id))
|
||||
urllib.parse.quote(flex_utils.id_to_base64(self.volume.id))
|
||||
)
|
||||
|
||||
self.HTTPS_MOCK_RESPONSES = {
|
||||
@ -90,7 +91,7 @@ class TestManageExisting(vxflexos.TestVxFlexOSDriver):
|
||||
self.volume['volume_type_id'] = fake.VOLUME_TYPE_ID
|
||||
existing_ref = {'source-id': fake.PROVIDER_ID}
|
||||
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.manage_existing, self.volume,
|
||||
existing_ref)
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
# 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 mock import patch
|
||||
from unittest.mock import patch
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
@ -44,7 +44,7 @@ class TestManageExistingSnapshot(vxflexos.TestVxFlexOSDriver):
|
||||
self.snapshot['volume_type_id'] = fake.VOLUME_TYPE_ID
|
||||
self.snapshot2['volume_type_id'] = fake.VOLUME_TYPE_ID
|
||||
self.snapshot_attached = fake_snapshot.fake_snapshot_obj(
|
||||
ctx, **{'provider_id': fake.PROVIDER3_ID})
|
||||
ctx, **{'provider_id': fake.PROVIDER4_ID})
|
||||
|
||||
self.HTTPS_MOCK_RESPONSES = {
|
||||
self.RESPONSE_MODE.Valid: {
|
||||
@ -84,7 +84,7 @@ class TestManageExistingSnapshot(vxflexos.TestVxFlexOSDriver):
|
||||
}, 200),
|
||||
'instances/Volume::' + self.snapshot_attached['provider_id']:
|
||||
mocks.MockHTTPSResponse({
|
||||
'id': fake.PROVIDER3_ID,
|
||||
'id': fake.PROVIDER4_ID,
|
||||
'sizeInKb': 8388608,
|
||||
'mappedSdcInfo': 'Mapped',
|
||||
'ancestorVolumeId': fake.PROVIDER_ID
|
||||
@ -105,7 +105,7 @@ class TestManageExistingSnapshot(vxflexos.TestVxFlexOSDriver):
|
||||
def test_snapshot_not_found(self, _mock_volume_type):
|
||||
existing_ref = {'source-id': fake.PROVIDER2_ID}
|
||||
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.manage_existing_snapshot, self.snapshot,
|
||||
existing_ref)
|
||||
|
||||
@ -115,7 +115,7 @@ class TestManageExistingSnapshot(vxflexos.TestVxFlexOSDriver):
|
||||
return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}})
|
||||
def test_snapshot_attached(self, _mock_volume_type):
|
||||
self.snapshot_attached['volume_type_id'] = fake.VOLUME_TYPE_ID
|
||||
existing_ref = {'source-id': fake.PROVIDER2_ID}
|
||||
existing_ref = {'source-id': fake.PROVIDER4_ID}
|
||||
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
self.driver.manage_existing_snapshot,
|
||||
|
@ -114,6 +114,7 @@ class TestMisc(vxflexos.TestVxFlexOSDriver):
|
||||
}
|
||||
|
||||
def test_valid_configuration(self):
|
||||
self.driver.storage_pools = self.STORAGE_POOLS
|
||||
self.driver.check_for_setup_error()
|
||||
|
||||
def test_no_storage_pools(self):
|
||||
@ -219,8 +220,8 @@ class TestMisc(vxflexos.TestVxFlexOSDriver):
|
||||
self.driver.get_volume_stats(True)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.volume.drivers.dell_emc.vxflexos.driver.VxFlexOSDriver.'
|
||||
'_rename_volume',
|
||||
'cinder.volume.drivers.dell_emc.vxflexos.rest_client.RestClient.'
|
||||
'rename_volume',
|
||||
return_value=None)
|
||||
def test_update_migrated_volume(self, mock_rename):
|
||||
test_vol = self.driver.update_migrated_volume(
|
||||
@ -230,8 +231,8 @@ class TestMisc(vxflexos.TestVxFlexOSDriver):
|
||||
test_vol)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.volume.drivers.dell_emc.vxflexos.driver.VxFlexOSDriver.'
|
||||
'_rename_volume',
|
||||
'cinder.volume.drivers.dell_emc.vxflexos.rest_client.RestClient.'
|
||||
'rename_volume',
|
||||
return_value=None)
|
||||
def test_update_unavailable_migrated_volume(self, mock_rename):
|
||||
test_vol = self.driver.update_migrated_volume(
|
||||
@ -242,8 +243,8 @@ class TestMisc(vxflexos.TestVxFlexOSDriver):
|
||||
test_vol)
|
||||
|
||||
@mock.patch(
|
||||
'cinder.volume.drivers.dell_emc.vxflexos.driver.VxFlexOSDriver.'
|
||||
'_rename_volume',
|
||||
'cinder.volume.drivers.dell_emc.vxflexos.rest_client.RestClient.'
|
||||
'rename_volume',
|
||||
side_effect=exception.VolumeBackendAPIException(data='Error!'))
|
||||
def test_fail_update_migrated_volume(self, mock_rename):
|
||||
self.assertRaises(
|
||||
@ -257,42 +258,56 @@ class TestMisc(vxflexos.TestVxFlexOSDriver):
|
||||
mock_rename.assert_called_with(self.volume, "ff" + self.volume['id'])
|
||||
|
||||
def test_rename_volume(self):
|
||||
rc = self.driver._rename_volume(
|
||||
rc = self.driver.primary_client.rename_volume(
|
||||
self.volume, self.new_volume['id'])
|
||||
self.assertIsNone(rc)
|
||||
|
||||
def test_rename_volume_illegal_syntax(self):
|
||||
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
|
||||
rc = self.driver._rename_volume(
|
||||
rc = self.driver.primary_client.rename_volume(
|
||||
self.volume, self.new_volume['id'])
|
||||
self.assertIsNone(rc)
|
||||
|
||||
def test_rename_volume_non_sio(self):
|
||||
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
|
||||
rc = self.driver._rename_volume(
|
||||
rc = self.driver.primary_client.rename_volume(
|
||||
self.volume, self.new_volume['id'])
|
||||
self.assertIsNone(rc)
|
||||
|
||||
def test_default_provisioning_type_unspecified(self):
|
||||
empty_storage_type = {}
|
||||
self.assertEqual(
|
||||
'thin',
|
||||
self.driver._find_provisioning_type(empty_storage_type))
|
||||
provisioning, compression = (
|
||||
self.driver._get_provisioning_and_compression(
|
||||
empty_storage_type,
|
||||
self.PROT_DOMAIN_NAME,
|
||||
self.STORAGE_POOL_NAME)
|
||||
)
|
||||
self.assertEqual('ThinProvisioned', provisioning)
|
||||
|
||||
@ddt.data((True, 'thin'), (False, 'thick'))
|
||||
@ddt.data((True, 'ThinProvisioned'), (False, 'ThickProvisioned'))
|
||||
@ddt.unpack
|
||||
def test_default_provisioning_type_thin(self, config_provisioning_type,
|
||||
expected_provisioning_type):
|
||||
self.override_config('san_thin_provision', config_provisioning_type,
|
||||
configuration.SHARED_CONF_GROUP)
|
||||
self.driver = mocks.VxFlexOSDriver(configuration=self.configuration)
|
||||
self.driver.do_setup({})
|
||||
self.driver.primary_client = mocks.VxFlexOSClient(self.configuration)
|
||||
self.driver.primary_client.do_setup()
|
||||
empty_storage_type = {}
|
||||
self.assertEqual(
|
||||
expected_provisioning_type,
|
||||
self.driver._find_provisioning_type(empty_storage_type))
|
||||
provisioning, compression = (
|
||||
self.driver._get_provisioning_and_compression(
|
||||
empty_storage_type,
|
||||
self.PROT_DOMAIN_NAME,
|
||||
self.STORAGE_POOL_NAME)
|
||||
)
|
||||
self.assertEqual(expected_provisioning_type, provisioning)
|
||||
|
||||
def test_get_volume_stats_v3(self):
|
||||
self.driver.server_api_version = "3.0"
|
||||
@mock.patch('cinder.volume.drivers.dell_emc.vxflexos.rest_client.'
|
||||
'RestClient.query_rest_api_version',
|
||||
return_value="3.0")
|
||||
def test_get_volume_stats_v3(self, mock_version):
|
||||
self.driver.storage_pools = self.STORAGE_POOLS
|
||||
zero_data = {
|
||||
'types/StoragePool/instances/action/querySelectedStatistics':
|
||||
mocks.MockHTTPSResponse(content=json.dumps(
|
||||
|
@ -58,7 +58,7 @@ class TestMultipleVersions(vxflexos.TestVxFlexOSDriver):
|
||||
|
||||
def test_version(self):
|
||||
"""Valid version request."""
|
||||
self.driver._get_server_api_version(False)
|
||||
self.driver.primary_client.query_rest_api_version(False)
|
||||
|
||||
def test_version_badstatus_response(self):
|
||||
"""Version api returns a bad response."""
|
||||
@ -86,8 +86,8 @@ class TestMultipleVersions(vxflexos.TestVxFlexOSDriver):
|
||||
for vers in self.good_versions:
|
||||
self.version = vers
|
||||
self.setup_response()
|
||||
self.driver._get_server_api_version(False)
|
||||
self.driver.primary_client.query_rest_api_version(False)
|
||||
self.assertEqual(
|
||||
self.driver._get_server_api_version(False),
|
||||
self.driver.primary_client.query_rest_api_version(False),
|
||||
vers
|
||||
)
|
||||
|
File diff suppressed because it is too large
Load Diff
500
cinder/volume/drivers/dell_emc/vxflexos/rest_client.py
Normal file
500
cinder/volume/drivers/dell_emc/vxflexos/rest_client.py
Normal file
@ -0,0 +1,500 @@
|
||||
# Copyright (c) 2020 Dell Inc. or its subsidiaries.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
import json
|
||||
import re
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import units
|
||||
import requests
|
||||
import six
|
||||
from six.moves import http_client
|
||||
from six.moves import urllib
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.utils import retry
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import simplecache
|
||||
from cinder.volume.drivers.dell_emc.vxflexos import utils as flex_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
VOLUME_NOT_FOUND_ERROR = 79
|
||||
OLD_VOLUME_NOT_FOUND_ERROR = 78
|
||||
ILLEGAL_SYNTAX = 0
|
||||
|
||||
|
||||
class RestClient(object):
|
||||
def __init__(self, configuration):
|
||||
self.configuration = configuration
|
||||
self.spCache = simplecache.SimpleCache("Storage Pool", age_minutes=5)
|
||||
self.pdCache = simplecache.SimpleCache("Protection Domain",
|
||||
age_minutes=5)
|
||||
self.rest_ip = None
|
||||
self.rest_port = None
|
||||
self.rest_username = None
|
||||
self.rest_password = None
|
||||
self.rest_token = None
|
||||
self.rest_api_version = None
|
||||
self.verify_certificate = None
|
||||
self.certificate_path = None
|
||||
self.base_url = None
|
||||
self.is_configured = False
|
||||
|
||||
@staticmethod
|
||||
def _get_headers():
|
||||
return {"content-type": "application/json"}
|
||||
|
||||
@property
|
||||
def connection_properties(self):
|
||||
return {
|
||||
"scaleIO_volname": None,
|
||||
"hostIP": None,
|
||||
"serverIP": self.rest_ip,
|
||||
"serverPort": self.rest_port,
|
||||
"serverUsername": self.rest_username,
|
||||
"serverPassword": self.rest_password,
|
||||
"serverToken": self.rest_token,
|
||||
"iopsLimit": None,
|
||||
"bandwidthLimit": None,
|
||||
}
|
||||
|
||||
def do_setup(self):
|
||||
self.rest_port = self.configuration.vxflexos_rest_server_port
|
||||
self.verify_certificate = (
|
||||
self.configuration.safe_get("sio_verify_server_certificate") or
|
||||
self.configuration.safe_get("driver_ssl_cert_verify")
|
||||
)
|
||||
self.rest_ip = self.configuration.safe_get("san_ip")
|
||||
self.rest_username = self.configuration.safe_get("san_login")
|
||||
self.rest_password = self.configuration.safe_get("san_password")
|
||||
if self.verify_certificate:
|
||||
self.certificate_path = (
|
||||
self.configuration.safe_get("sio_server_certificate_path") or
|
||||
self.configuration.safe_get("driver_ssl_cert_path")
|
||||
)
|
||||
if not all([self.rest_ip, self.rest_username, self.rest_password]):
|
||||
msg = _("REST server IP, username and password must be specified.")
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
# validate certificate settings
|
||||
if self.verify_certificate and not self.certificate_path:
|
||||
msg = _("Path to REST server's certificate must be specified.")
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
# log warning if not using certificates
|
||||
if not self.verify_certificate:
|
||||
LOG.warning("Verify certificate is not set, using default of "
|
||||
"False.")
|
||||
self.base_url = ("https://%(server_ip)s:%(server_port)s/api" %
|
||||
{
|
||||
"server_ip": self.rest_ip,
|
||||
"server_port": self.rest_port
|
||||
})
|
||||
LOG.info("REST server IP: %(ip)s, port: %(port)s, "
|
||||
"username: %(user)s. Verify server's certificate: "
|
||||
"%(verify_cert)s.",
|
||||
{
|
||||
"ip": self.rest_ip,
|
||||
"port": self.rest_port,
|
||||
"user": self.rest_username,
|
||||
"verify_cert": self.verify_certificate,
|
||||
})
|
||||
self.is_configured = True
|
||||
|
||||
def query_rest_api_version(self, fromcache=True):
|
||||
url = "/version"
|
||||
|
||||
if self.rest_api_version is None or fromcache is False:
|
||||
r, unused = self.execute_vxflexos_get_request(url)
|
||||
if r.status_code == http_client.OK:
|
||||
self.rest_api_version = r.text.replace('\"', "")
|
||||
LOG.info("REST API Version: %(api_version)s.",
|
||||
{"api_version": self.rest_api_version})
|
||||
else:
|
||||
msg = (_("Failed to query REST API version. "
|
||||
"Status code: %d.") % r.status_code)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
# make sure the response was valid
|
||||
pattern = re.compile(r"^\d+(\.\d+)*$")
|
||||
if not pattern.match(self.rest_api_version):
|
||||
msg = (_("Failed to query REST API version. Response: %s.") %
|
||||
r.text)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return self.rest_api_version
|
||||
|
||||
def query_volume(self, vol_id):
|
||||
url = "/instances/Volume::%(vol_id)s"
|
||||
|
||||
r, response = self.execute_vxflexos_get_request(url, vol_id=vol_id)
|
||||
if r.status_code != http_client.OK and "errorCode" in response:
|
||||
msg = (_("Failed to query volume: %s.") % response["message"])
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return response
|
||||
|
||||
def create_volume(self,
|
||||
protection_domain_name,
|
||||
storage_pool_name,
|
||||
volume,
|
||||
provisioning,
|
||||
compression):
|
||||
url = "/types/Volume/instances"
|
||||
|
||||
domain_id = self._get_protection_domain_id(protection_domain_name)
|
||||
LOG.info("Protection Domain id: %s.", domain_id)
|
||||
pool_id = self.get_storage_pool_id(protection_domain_name,
|
||||
storage_pool_name)
|
||||
LOG.info("Storage Pool id: %s.", pool_id)
|
||||
volume_name = flex_utils.id_to_base64(volume.id)
|
||||
# units.Mi = 1024 ** 2
|
||||
volume_size_kb = volume.size * units.Mi
|
||||
params = {
|
||||
"protectionDomainId": domain_id,
|
||||
"storagePoolId": pool_id,
|
||||
"name": volume_name,
|
||||
"volumeType": provisioning,
|
||||
"volumeSizeInKb": six.text_type(volume_size_kb),
|
||||
"compressionMethod": compression,
|
||||
}
|
||||
r, response = self.execute_vxflexos_post_request(url, params)
|
||||
if r.status_code != http_client.OK and "errorCode" in response:
|
||||
msg = (_("Failed to create volume: %s.") % response["message"])
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return response["id"]
|
||||
|
||||
def snapshot_volume(self, volume_provider_id, snapshot_id):
|
||||
url = "/instances/System/action/snapshotVolumes"
|
||||
|
||||
snap_name = flex_utils.id_to_base64(snapshot_id)
|
||||
params = {
|
||||
"snapshotDefs": [
|
||||
{
|
||||
"volumeId": volume_provider_id,
|
||||
"snapshotName": snap_name,
|
||||
},
|
||||
],
|
||||
}
|
||||
r, response = self.execute_vxflexos_post_request(url, params)
|
||||
if r.status_code != http_client.OK and "errorCode" in response:
|
||||
msg = (_("Failed to create snapshot for volume %(vol_name)s: "
|
||||
"%(response)s.") %
|
||||
{"vol_name": volume_provider_id,
|
||||
"response": response["message"]})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return response["volumeIdList"][0]
|
||||
|
||||
def _get_protection_domain_id_by_name(self, domain_name):
|
||||
url = "/types/Domain/instances/getByName::%(encoded_domain_name)s"
|
||||
|
||||
if not domain_name:
|
||||
msg = _("Unable to query Protection Domain id with None name.")
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
encoded_domain_name = urllib.parse.quote(domain_name, "")
|
||||
r, domain_id = self.execute_vxflexos_get_request(
|
||||
url, encoded_domain_name=encoded_domain_name
|
||||
)
|
||||
if not domain_id:
|
||||
msg = (_("Prorection Domain with name %s wasn't found.")
|
||||
% domain_name)
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
if r.status_code != http_client.OK and "errorCode" in domain_id:
|
||||
msg = (_("Failed to get Protection Domain id with name "
|
||||
"%(name)s: %(err_msg)s.") %
|
||||
{"name": domain_name, "err_msg": domain_id["message"]})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
LOG.info("Protection Domain id: %s.", domain_id)
|
||||
return domain_id
|
||||
|
||||
def _get_protection_domain_id(self, domain_name):
|
||||
response = self._get_protection_domain_properties(domain_name)
|
||||
if response is None:
|
||||
return None
|
||||
return response["id"]
|
||||
|
||||
def _get_protection_domain_properties(self, domain_name):
|
||||
url = "/instances/ProtectionDomain::%(domain_id)s"
|
||||
|
||||
cached_val = self.pdCache.get_value(domain_name)
|
||||
if cached_val is not None:
|
||||
return cached_val
|
||||
domain_id = self._get_protection_domain_id_by_name(domain_name)
|
||||
r, response = self.execute_vxflexos_get_request(
|
||||
url, domain_id=domain_id
|
||||
)
|
||||
if r.status_code != http_client.OK:
|
||||
msg = (_("Failed to get domain properties from id %(domain_id)s: "
|
||||
"%(err_msg)s.") %
|
||||
{"domain_id": domain_id, "err_msg": response})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
self.pdCache.update(domain_name, response)
|
||||
return response
|
||||
|
||||
def _get_storage_pool_id_by_name(self, domain_name, pool_name):
|
||||
url = ("/types/Pool/instances/getByName::"
|
||||
"%(domain_id)s,%(encoded_pool_name)s")
|
||||
|
||||
if not domain_name or not pool_name:
|
||||
msg = (_("Unable to query storage pool id for "
|
||||
"Pool %(pool_name)s and Domain %(domain_name)s.") %
|
||||
{"pool_name": pool_name, "domain_name": domain_name})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
domain_id = self._get_protection_domain_id(domain_name)
|
||||
encoded_pool_name = urllib.parse.quote(pool_name, "")
|
||||
r, pool_id = self.execute_vxflexos_get_request(
|
||||
url, domain_id=domain_id, encoded_pool_name=encoded_pool_name
|
||||
)
|
||||
if not pool_id:
|
||||
msg = (_("Pool with name %(pool_name)s wasn't found in "
|
||||
"domain %(domain_id)s.") %
|
||||
{"pool_name": pool_name, "domain_id": domain_id})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
if r.status_code != http_client.OK and "errorCode" in pool_id:
|
||||
msg = (_("Failed to get pool id from name %(pool_name)s: "
|
||||
"%(err_msg)s.") %
|
||||
{"pool_name": pool_name, "err_msg": pool_id["message"]})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
LOG.info("Pool id: %s.", pool_id)
|
||||
return pool_id
|
||||
|
||||
def get_storage_pool_properties(self, domain_name, pool_name):
|
||||
url = "/instances/StoragePool::%(pool_id)s"
|
||||
|
||||
fullname = "{}:{}".format(domain_name, pool_name)
|
||||
cached_val = self.spCache.get_value(fullname)
|
||||
if cached_val is not None:
|
||||
return cached_val
|
||||
pool_id = self._get_storage_pool_id_by_name(domain_name, pool_name)
|
||||
r, response = self.execute_vxflexos_get_request(url, pool_id=pool_id)
|
||||
if r.status_code != http_client.OK:
|
||||
msg = (_("Failed to get pool properties from id %(pool_id)s: "
|
||||
"%(err_msg)s.") %
|
||||
{"pool_id": pool_id, "err_msg": response})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
self.spCache.update(fullname, response)
|
||||
return response
|
||||
|
||||
def get_storage_pool_id(self, domain_name, pool_name):
|
||||
response = self.get_storage_pool_properties(domain_name, pool_name)
|
||||
if response is None:
|
||||
return None
|
||||
return response["id"]
|
||||
|
||||
def _get_verify_cert(self):
|
||||
verify_cert = False
|
||||
if self.verify_certificate:
|
||||
verify_cert = self.certificate_path
|
||||
return verify_cert
|
||||
|
||||
def execute_vxflexos_get_request(self, url, **url_params):
|
||||
request = self.base_url + url % url_params
|
||||
r = requests.get(request,
|
||||
auth=(self.rest_username, self.rest_token),
|
||||
verify=self._get_verify_cert())
|
||||
r = self._check_response(r, request)
|
||||
response = r.json()
|
||||
return r, response
|
||||
|
||||
def execute_vxflexos_post_request(self, url, params=None, **url_params):
|
||||
if not params:
|
||||
params = {}
|
||||
request = self.base_url + url % url_params
|
||||
r = requests.post(request,
|
||||
data=json.dumps(params),
|
||||
headers=self._get_headers(),
|
||||
auth=(self.rest_username, self.rest_token),
|
||||
verify=self._get_verify_cert())
|
||||
r = self._check_response(r, request, False, params)
|
||||
response = None
|
||||
try:
|
||||
response = r.json()
|
||||
except ValueError:
|
||||
response = None
|
||||
return r, response
|
||||
|
||||
def _check_response(self,
|
||||
response,
|
||||
request,
|
||||
is_get_request=True,
|
||||
params=None):
|
||||
login_url = "/login"
|
||||
|
||||
if (response.status_code == http_client.UNAUTHORIZED or
|
||||
response.status_code == http_client.FORBIDDEN):
|
||||
LOG.info("Token is invalid, going to re-login and get "
|
||||
"a new one.")
|
||||
login_request = self.base_url + login_url
|
||||
verify_cert = self._get_verify_cert()
|
||||
r = requests.get(login_request,
|
||||
auth=(self.rest_username, self.rest_password),
|
||||
verify=verify_cert)
|
||||
token = r.json()
|
||||
self.rest_token = token
|
||||
# Repeat request with valid token.
|
||||
LOG.info("Going to perform request again %s with valid token.",
|
||||
request)
|
||||
if is_get_request:
|
||||
response = requests.get(request,
|
||||
auth=(
|
||||
self.rest_username,
|
||||
self.rest_token
|
||||
),
|
||||
verify=verify_cert)
|
||||
else:
|
||||
response = requests.post(request,
|
||||
data=json.dumps(params),
|
||||
headers=self._get_headers(),
|
||||
auth=(
|
||||
self.rest_username,
|
||||
self.rest_token
|
||||
),
|
||||
verify=verify_cert)
|
||||
level = logging.DEBUG
|
||||
# for anything other than an OK from the REST API, log an error
|
||||
if response.status_code != http_client.OK:
|
||||
level = logging.ERROR
|
||||
LOG.log(level,
|
||||
"REST Request: %s with params %s",
|
||||
request,
|
||||
json.dumps(params))
|
||||
LOG.log(level,
|
||||
"REST Response: %s with data %s",
|
||||
response.status_code,
|
||||
response.text)
|
||||
return response
|
||||
|
||||
@retry(exception.VolumeBackendAPIException)
|
||||
def extend_volume(self, vol_id, new_size):
|
||||
url = "/instances/Volume::%(vol_id)s/action/setVolumeSize"
|
||||
|
||||
round_volume_capacity = (
|
||||
self.configuration.vxflexos_round_volume_capacity
|
||||
)
|
||||
if not round_volume_capacity and not new_size % 8 == 0:
|
||||
LOG.warning("VxFlex OS only supports volumes with a granularity "
|
||||
"of 8 GBs. The new volume size is: %d.",
|
||||
new_size)
|
||||
params = {"sizeInGB": six.text_type(new_size)}
|
||||
r, response = self.execute_vxflexos_post_request(url,
|
||||
params,
|
||||
vol_id=vol_id)
|
||||
if r.status_code != http_client.OK:
|
||||
response = r.json()
|
||||
msg = (_("Failed to extend volume %(vol_id)s: %(err)s.") %
|
||||
{"vol_id": vol_id, "err": response["message"]})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def _unmap_volume_before_delete(self, vol_id):
|
||||
url = "/instances/Volume::%(vol_id)s/action/removeMappedSdc"
|
||||
|
||||
volume_is_mapped = False
|
||||
try:
|
||||
volume = self.query_volume(vol_id)
|
||||
if volume.get("mappedSdcInfo") is not None:
|
||||
volume_is_mapped = True
|
||||
except exception.VolumeBackendAPIException:
|
||||
LOG.info("Volume %s is not found thus is not mapped to any SDC.",
|
||||
vol_id)
|
||||
if volume_is_mapped:
|
||||
params = {"allSdcs": ""}
|
||||
LOG.info("Unmap volume from all sdcs before deletion.")
|
||||
r, unused = self.execute_vxflexos_post_request(url,
|
||||
params,
|
||||
vol_id=vol_id)
|
||||
|
||||
@retry(exception.VolumeBackendAPIException)
|
||||
def remove_volume(self, vol_id):
|
||||
url = "/instances/Volume::%(vol_id)s/action/removeVolume"
|
||||
|
||||
self._unmap_volume_before_delete(vol_id)
|
||||
params = {"removeMode": "ONLY_ME"}
|
||||
r, response = self.execute_vxflexos_post_request(url,
|
||||
params,
|
||||
vol_id=vol_id)
|
||||
if r.status_code != http_client.OK:
|
||||
error_code = response["errorCode"]
|
||||
if error_code == VOLUME_NOT_FOUND_ERROR:
|
||||
LOG.warning("Ignoring error in delete volume %s: "
|
||||
"Volume not found.", vol_id)
|
||||
elif vol_id is None:
|
||||
LOG.warning("Volume does not have provider_id thus does not "
|
||||
"map to a VxFlex OS volume. "
|
||||
"Allowing deletion to proceed.")
|
||||
else:
|
||||
msg = (_("Failed to delete volume %(vol_id)s: %(err)s.") %
|
||||
{"vol_id": vol_id, "err": response["message"]})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def is_volume_creation_safe(self, protection_domain, storage_pool):
|
||||
"""Checks if volume creation is safe or not.
|
||||
|
||||
Using volumes with zero padding disabled can lead to existing data
|
||||
being read off of a newly created volume.
|
||||
"""
|
||||
|
||||
# if we have been told to allow unsafe volumes
|
||||
if self.configuration.vxflexos_allow_non_padded_volumes:
|
||||
# Enabled regardless of type, so safe to proceed
|
||||
return True
|
||||
try:
|
||||
properties = self.get_storage_pool_properties(
|
||||
protection_domain, storage_pool
|
||||
)
|
||||
padded = properties["zeroPaddingEnabled"]
|
||||
except Exception:
|
||||
msg = (_("Unable to retrieve properties for pool %s.") %
|
||||
storage_pool)
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
# zero padded storage pools are safe
|
||||
if padded:
|
||||
return True
|
||||
# if we got here, it's unsafe
|
||||
return False
|
||||
|
||||
def rename_volume(self, volume, name):
|
||||
url = "/instances/Volume::%(id)s/action/setVolumeName"
|
||||
|
||||
new_name = flex_utils.id_to_base64(name)
|
||||
vol_id = volume["provider_id"]
|
||||
params = {"newName": new_name}
|
||||
r, response = self.execute_vxflexos_post_request(url,
|
||||
params,
|
||||
id=vol_id)
|
||||
if r.status_code != http_client.OK:
|
||||
error_code = response["errorCode"]
|
||||
if ((error_code == VOLUME_NOT_FOUND_ERROR or
|
||||
error_code == OLD_VOLUME_NOT_FOUND_ERROR or
|
||||
error_code == ILLEGAL_SYNTAX)):
|
||||
LOG.info("Ignore renaming action because the volume "
|
||||
"%(vol_id)s is not a VxFlex OS volume.",
|
||||
{"vol_id": vol_id})
|
||||
else:
|
||||
msg = (_("Failed to rename volume %(vol_id)s: %(err)s.") %
|
||||
{"vol_id": vol_id, "err": response["message"]})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
else:
|
||||
LOG.info("VxFlex OS volume %(vol_id)s was renamed to "
|
||||
"%(new_name)s.", {"vol_id": vol_id, "new_name": new_name})
|
61
cinder/volume/drivers/dell_emc/vxflexos/utils.py
Normal file
61
cinder/volume/drivers/dell_emc/vxflexos/utils.py
Normal file
@ -0,0 +1,61 @@
|
||||
# Copyright (c) 2020 Dell Inc. or its subsidiaries.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
import base64
|
||||
import binascii
|
||||
from distutils import version
|
||||
import math
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import units
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def version_gte(ver1, ver2):
|
||||
return version.LooseVersion(ver1) >= version.LooseVersion(ver2)
|
||||
|
||||
|
||||
def convert_kb_to_gib(size):
|
||||
return int(math.floor(float(size) / units.Mi))
|
||||
|
||||
|
||||
def id_to_base64(_id):
|
||||
# Base64 encode the id to get a volume name less than 32 characters due
|
||||
# to VxFlex OS limitation.
|
||||
name = str(_id).replace("-", "")
|
||||
try:
|
||||
name = base64.b16decode(name.upper())
|
||||
except (TypeError, binascii.Error):
|
||||
pass
|
||||
if isinstance(name, str):
|
||||
name = name.encode()
|
||||
encoded_name = base64.b64encode(name).decode()
|
||||
LOG.debug("Converted id %(id)s to VxFlex OS name %(name)s.",
|
||||
{"id": _id, "name": encoded_name})
|
||||
return encoded_name
|
||||
|
||||
|
||||
def round_to_num_gran(size, num=8):
|
||||
"""Round size to nearest value that is multiple of `num`."""
|
||||
|
||||
if size % num == 0:
|
||||
return size
|
||||
return size + num - (size % num)
|
||||
|
||||
|
||||
def round_down_to_num_gran(size, num=8):
|
||||
"""Round size down to nearest value that is multiple of `num`."""
|
||||
|
||||
return size - (size % num)
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
VxFlex OS driver now supports VxFlex OS 3.5.x.
|
Loading…
Reference in New Issue
Block a user