Merge "Add support for VxFlex OS 3.5 to VxFlex OS driver"
This commit is contained in:
commit
a5a3e05491
|
@ -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
|
@ -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})
|
|
@ -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