From 18f1b456f04153d059e59ae6afc0f261592b41e2 Mon Sep 17 00:00:00 2001 From: Tom Swanson Date: Wed, 11 May 2016 14:02:12 -0500 Subject: [PATCH] Dell SC: Switched REST calls to async where available Some commands can timeout before they are complete. We need to send these commands down async. This patch does that. Change-Id: I4a66b60dc5bb2e0b2ad7c16d8c5e2bb9523a5e54 --- cinder/tests/unit/test_dellscapi.py | 54 +++--- .../drivers/dell/dell_storagecenter_api.py | 170 +++++++++++------- 2 files changed, 131 insertions(+), 93 deletions(-) diff --git a/cinder/tests/unit/test_dellscapi.py b/cinder/tests/unit/test_dellscapi.py index 82126f54ebe..bd7dc4d76d9 100644 --- a/cinder/tests/unit/test_dellscapi.py +++ b/cinder/tests/unit/test_dellscapi.py @@ -4785,10 +4785,10 @@ class DellSCSanAPITestCase(test.TestCase): 'storageProfile': {'name': 'Fake', 'instanceId': 'fakeId'}}) @mock.patch.object(dell_storagecenter_api.HttpClient, - 'post', + 'put', return_value=RESPONSE_200) def test_update_storage_profile(self, - mock_post, + mock_put, mock_prefs, mock_close_connection, mock_open_connection, @@ -4797,7 +4797,7 @@ class DellSCSanAPITestCase(test.TestCase): fake_scvolume = {'name': 'name', 'instanceId': 'id'} res = self.scapi.update_storage_profile(fake_scvolume, None) self.assertTrue(res) - self.assertTrue('fakeId' in repr(mock_post.call_args_list[0])) + self.assertTrue('fakeId' in repr(mock_put.call_args_list[0])) self.assertEqual(1, LOG.info.call_count) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, @@ -5018,8 +5018,7 @@ class DellSCSanAPITestCase(test.TestCase): res = self.scapi._update_volume_profiles(scvolume, newid, None) self.assertTrue(mock_get_id.called) self.assertTrue(mock_get_volume_configuration.called) - mock_put.assert_called_once_with(expected_url, - expected_payload) + mock_put.assert_called_once_with(expected_url, expected_payload, True) self.assertTrue(res) # Now do a remove. (Restarting with the original config so this will @@ -5028,8 +5027,7 @@ class DellSCSanAPITestCase(test.TestCase): res = self.scapi._update_volume_profiles(scvolume, None, existingid) self.assertTrue(mock_get_id.called) self.assertTrue(mock_get_volume_configuration.called) - mock_put.assert_called_with(expected_url, - expected_payload) + mock_put.assert_called_with(expected_url, expected_payload, True) self.assertTrue(res) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, @@ -5066,8 +5064,7 @@ class DellSCSanAPITestCase(test.TestCase): res = self.scapi._update_volume_profiles(scvolume, newid, None) self.assertTrue(mock_get_id.called) self.assertTrue(mock_get_volume_configuration.called) - mock_put.assert_called_once_with(expected_url, - expected_payload) + mock_put.assert_called_once_with(expected_url, expected_payload, True) self.assertFalse(res) # Now do a remove. (Restarting with the original config so this will @@ -5076,8 +5073,7 @@ class DellSCSanAPITestCase(test.TestCase): res = self.scapi._update_volume_profiles(scvolume, None, existingid) self.assertTrue(mock_get_id.called) self.assertTrue(mock_get_volume_configuration.called) - mock_put.assert_called_with(expected_url, - expected_payload) + mock_put.assert_called_with(expected_url, expected_payload, True) self.assertFalse(res) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, @@ -5377,7 +5373,7 @@ class DellSCSanAPITestCase(test.TestCase): expected_url = 'StorageCenter/ScReplayProfile/100/CreateReplay' expected_payload = {'description': replayid, 'expireTime': expire} res = self.scapi.snap_cg_replay(profile, replayid, expire) - mock_post.assert_called_once_with(expected_url, expected_payload) + mock_post.assert_called_once_with(expected_url, expected_payload, True) self.assertTrue(mock_get_id.called) self.assertTrue(mock_init_cg_volumes.called) self.assertTrue(res) @@ -5404,7 +5400,7 @@ class DellSCSanAPITestCase(test.TestCase): expected_url = 'StorageCenter/ScReplayProfile/100/CreateReplay' expected_payload = {'description': replayid, 'expireTime': expire} res = self.scapi.snap_cg_replay(profile, replayid, expire) - mock_post.assert_called_once_with(expected_url, expected_payload) + mock_post.assert_called_once_with(expected_url, expected_payload, True) self.assertTrue(mock_get_id.called) self.assertTrue(mock_init_cg_volumes.called) self.assertFalse(res) @@ -5536,11 +5532,11 @@ class DellSCSanAPITestCase(test.TestCase): expected_url = ('StorageCenter/ScReplay/' + self.RPLAYS[0]['instanceId'] + '/Expire') - mock_post.assert_any_call(expected_url, {}) + mock_post.assert_any_call(expected_url, {}, True) expected_url = ('StorageCenter/ScReplay/' + self.RPLAYS[1]['instanceId'] + '/Expire') - mock_post.assert_any_call(expected_url, {}) + mock_post.assert_any_call(expected_url, {}, True) self.assertTrue(mock_find_cg_replays.called) self.assertTrue(res) @@ -5560,7 +5556,7 @@ class DellSCSanAPITestCase(test.TestCase): self.RPLAYS[0]['instanceId'] + '/Expire') res = self.scapi.delete_cg_replay({}, '') - mock_post.assert_called_once_with(expected_url, {}) + mock_post.assert_called_once_with(expected_url, {}, True) self.assertTrue(mock_find_cg_replays.called) self.assertFalse(res) @@ -5612,7 +5608,7 @@ class DellSCSanAPITestCase(test.TestCase): expected_payload = {'Name': newname, 'VolumeFolder': '1'} self.scapi._import_one({'instanceId': '100'}, newname) - mock_put.assert_called_once_with(expected_url, expected_payload) + mock_put.assert_called_once_with(expected_url, expected_payload, True) self.assertTrue(mock_find_volume_folder.called) expected_payload = {'Name': newname} self.scapi._import_one({'instanceId': '100'}, newname) @@ -5879,7 +5875,7 @@ class DellSCSanAPITestCase(test.TestCase): expected_payload = {'Name': newname} self.scapi.unmanage(scvolume) self.assertTrue(mock_get_id.called) - mock_put.assert_called_once_with(expected_url, expected_payload) + mock_put.assert_called_once_with(expected_url, expected_payload, True) @mock.patch.object(dell_storagecenter_api.HttpClient, 'put', @@ -5902,7 +5898,7 @@ class DellSCSanAPITestCase(test.TestCase): self.scapi.unmanage, scvolume) self.assertTrue(mock_get_id.called) - mock_put.assert_called_once_with(expected_url, expected_payload) + mock_put.assert_called_once_with(expected_url, expected_payload, True) @mock.patch.object(dell_storagecenter_api.HttpClient, 'post', @@ -6025,7 +6021,7 @@ class DellSCSanAPITestCase(test.TestCase): expected = 'StorageCenter/ScReplication/%s' % ( self.SCREPL[0]['instanceId']) ret = self.scapi.delete_replication(self.VOLUME, destssn) - mock_delete.assert_any_call(expected) + mock_delete.assert_any_call(expected, True) self.assertTrue(ret) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, @@ -6058,7 +6054,7 @@ class DellSCSanAPITestCase(test.TestCase): expected = 'StorageCenter/ScReplication/%s' % ( self.SCREPL[0]['instanceId']) ret = self.scapi.delete_replication(self.VOLUME, destssn) - mock_delete.assert_any_call(expected) + mock_delete.assert_any_call(expected, True) self.assertFalse(ret) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, @@ -6106,7 +6102,7 @@ class DellSCSanAPITestCase(test.TestCase): False, None, False) - mock_post.assert_any_call('StorageCenter/ScReplication', payload) + mock_post.assert_any_call('StorageCenter/ScReplication', payload, True) self.assertDictEqual(self.SCREPL[0], ret) payload['Type'] = 'Synchronous' payload['ReplicateActiveReplay'] = True @@ -6116,7 +6112,7 @@ class DellSCSanAPITestCase(test.TestCase): True, None, False) - mock_post.assert_any_call('StorageCenter/ScReplication', payload) + mock_post.assert_any_call('StorageCenter/ScReplication', payload, True) self.assertDictEqual(self.SCREPL[0], ret) ret = self.scapi.create_replication(self.VOLUME, str(destssn), @@ -6124,7 +6120,7 @@ class DellSCSanAPITestCase(test.TestCase): True, None, True) - mock_post.assert_any_call('StorageCenter/ScReplication', payload) + mock_post.assert_any_call('StorageCenter/ScReplication', payload, True) self.assertDictEqual(self.SCREPL[0], ret) @mock.patch.object(dell_storagecenter_api.StorageCenterApi, @@ -6171,7 +6167,7 @@ class DellSCSanAPITestCase(test.TestCase): False, None, False) - mock_post.assert_any_call('StorageCenter/ScReplication', payload) + mock_post.assert_any_call('StorageCenter/ScReplication', payload, True) self.assertIsNone(ret) payload['Type'] = 'Synchronous' @@ -6182,7 +6178,7 @@ class DellSCSanAPITestCase(test.TestCase): True, None, True) - mock_post.assert_any_call('StorageCenter/ScReplication', payload) + mock_post.assert_any_call('StorageCenter/ScReplication', payload, True) self.assertIsNone(ret) @mock.patch.object(dell_storagecenter_api.HttpClient, @@ -6453,7 +6449,8 @@ class DellSCSanAPITestCase(test.TestCase): mock_put.return_value = self.RESPONSE_200 ret = self.scapi.manage_replay(screplay, 'guid') self.assertTrue(ret) - mock_put.assert_called_once_with('StorageCenter/ScReplay/1', payload) + mock_put.assert_called_once_with('StorageCenter/ScReplay/1', payload, + True) mock_put.return_value = self.RESPONSE_400 ret = self.scapi.manage_replay(screplay, 'guid') self.assertFalse(ret) @@ -6471,7 +6468,8 @@ class DellSCSanAPITestCase(test.TestCase): mock_put.return_value = self.RESPONSE_200 ret = self.scapi.unmanage_replay(screplay) self.assertTrue(ret) - mock_put.assert_called_once_with('StorageCenter/ScReplay/1', payload) + mock_put.assert_called_once_with('StorageCenter/ScReplay/1', payload, + True) mock_put.return_value = self.RESPONSE_400 ret = self.scapi.unmanage_replay(screplay) self.assertFalse(ret) diff --git a/cinder/volume/drivers/dell/dell_storagecenter_api.py b/cinder/volume/drivers/dell/dell_storagecenter_api.py index 6d775b48b02..2ea0bc24b7b 100644 --- a/cinder/volume/drivers/dell/dell_storagecenter_api.py +++ b/cinder/volume/drivers/dell/dell_storagecenter_api.py @@ -16,6 +16,7 @@ import json import os.path +import eventlet from oslo_log import log as logging import requests from simplejson import scanner @@ -111,45 +112,97 @@ class HttpClient(object): def __formatUrl(self, url): return '%s%s' % (self.baseUrl, url if url[0] != '/' else url[1:]) - @utils.retry(exceptions=(requests.ConnectionError,)) - def get(self, url): - LOG.debug('get: %(url)s', {'url': url}) - return self.session.get( - self.__formatUrl(url), - headers=self.header, - verify=self.verify) + def _get_header(self, async): + if async: + header = self.header.copy() + header['async'] = True + return header + return self.header + + def _wait_for_async_complete(self, asyncTask): + url = asyncTask.get('returnValue') + while True and url: + try: + r = self.session.get(url, headers=self.header, + verify=self.verify) + # We can leave this loop for a variety of reasons. + # Nothing returned. + # r.content blanks. + # Object returned switches to one without objectType or with + # a different objectType. + if r and r.content: + content = r.json() + if content.get('objectType') == 'AsyncTask': + url = content.get('returnValue') + eventlet.sleep(1) + continue + return r + except exception: + methodname = asyncTask.get('methodName') + objectTypeName = asyncTask.get('objectTypeName') + msg = (_('Async error: Unable to retreive %(obj)s ' + 'method %(method)s result') + % {'obj': objectTypeName, 'method': methodname}) + raise exception.VolumeBackendAPIException(message=msg) + return None + + def _rest_ret(self, rest_response, async): + # If we made an async call and it was accepted + # we wait for our response. + if async: + if rest_response.status_code == 202: + asyncTask = rest_response.json() + return self._wait_for_async_complete(asyncTask) + else: + LOG.debug('REST Async error command not accepted:\n' + '\tUrl: %(url)s\n' + '\tCode: %(code)d\n' + '\tReason: %(reason)s\n', + {'url': rest_response.url, + 'code': rest_response.status_code, + 'reason': rest_response.reason}) + msg = _('REST Async Error: Command not accepted.') + raise exception.VolumeBackendAPIException(message=msg) + return rest_response @utils.retry(exceptions=(requests.ConnectionError,)) - def post(self, url, payload): + def get(self, url, async=False): + LOG.debug('get: %(url)s', {'url': url}) + return self._rest_ret(self.session.get(self.__formatUrl(url), + headers=self._get_header(async), + verify=self.verify), async) + + @utils.retry(exceptions=(requests.ConnectionError,)) + def post(self, url, payload, async=False): LOG.debug('post: %(url)s data: %(payload)s', {'url': url, 'payload': payload}) - return self.session.post( + return self._rest_ret(self.session.post( self.__formatUrl(url), data=json.dumps(payload, ensure_ascii=False).encode('utf-8'), - headers=self.header, - verify=self.verify) + headers=self._get_header(async), + verify=self.verify), async) @utils.retry(exceptions=(requests.ConnectionError,)) - def put(self, url, payload): + def put(self, url, payload, async=False): LOG.debug('put: %(url)s data: %(payload)s', {'url': url, 'payload': payload}) - return self.session.put( + return self._rest_ret(self.session.put( self.__formatUrl(url), data=json.dumps(payload, ensure_ascii=False).encode('utf-8'), - headers=self.header, - verify=self.verify) + headers=self._get_header(async), + verify=self.verify), async) @utils.retry(exceptions=(requests.ConnectionError,)) - def delete(self, url): + def delete(self, url, async=False): LOG.debug('delete: %(url)s', {'url': url}) - return self.session.delete( - self.__formatUrl(url), - headers=self.header, - verify=self.verify) + return self._rest_ret( + self.session.delete(self.__formatUrl(url), + headers=self._get_header(async), + verify=self.verify), async) class StorageCenterApiHelper(object): @@ -543,7 +596,7 @@ class StorageCenterApi(object): payload['Parent'] = parent payload['Notes'] = self.notes - r = self.client.post(url, payload) + r = self.client.post(url, payload, True) if self._check_result(r): scfolder = self._first_result(r) return scfolder @@ -864,7 +917,7 @@ class StorageCenterApi(object): # This is a new volume so there is nothing to remove. if addids: payload['ReplayProfileList'] = addids - r = self.client.post('StorageCenter/ScVolume', payload) + r = self.client.post('StorageCenter/ScVolume', payload, True) if self._check_result(r): # Our volume should be in the return. scvolume = self._get_json(r) @@ -1017,7 +1070,8 @@ class StorageCenterApi(object): # If we have an id then delete the volume. if provider_id: - r = self.client.delete('StorageCenter/ScVolume/%s' % provider_id) + r = self.client.delete('StorageCenter/ScVolume/%s' % provider_id, + True) if not self._check_result(r): msg = _('Error deleting volume %(ssn)s: %(volume)s') % { 'ssn': self.ssn, @@ -1065,7 +1119,7 @@ class StorageCenterApi(object): payload['WwnOrIscsiName'] = wwnoriscsiname payload['AllowManual'] = True r = self.client.post('StorageCenter/ScPhysicalServer/%s/AddHba' - % self._get_id(scserver), payload) + % self._get_id(scserver), payload, True) if not self._check_result(r): LOG.error(_LE('_add_hba error: %(wwn)s to %(srvname)s'), {'wwn': wwnoriscsiname, @@ -1153,7 +1207,7 @@ class StorageCenterApi(object): payload['ServerFolder'] = self._get_id(folder) # create our server - r = self.client.post('StorageCenter/ScPhysicalServer', payload) + r = self.client.post('StorageCenter/ScPhysicalServer', payload, True) if self._check_result(r): # Server was created scserver = self._first_result(r) @@ -1606,8 +1660,8 @@ class StorageCenterApi(object): payload = {} payload['server'] = serverid payload['Advanced'] = {'MapToDownServerHbas': True} - r = self.client.post( - 'StorageCenter/ScVolume/%s/MapToServer' % volumeid, payload) + r = self.client.post('StorageCenter/ScVolume/%s/MapToServer' + % volumeid, payload, True) if self._check_result(r): # We just return our mapping LOG.info(_LI('Volume %(vol)s mapped to %(srv)s'), @@ -1640,7 +1694,7 @@ class StorageCenterApi(object): prosrv = profile.get('server') if prosrv is not None and self._get_id(prosrv) == serverid: r = self.client.delete('StorageCenter/ScMappingProfile/%s' - % self._get_id(profile)) + % self._get_id(profile), True) if self._check_result(r): # Check our result in the json. result = self._get_json(r) @@ -1705,8 +1759,7 @@ class StorageCenterApi(object): payload['description'] = replayid payload['expireTime'] = expire r = self.client.post('StorageCenter/ScVolume/%s/CreateReplay' - % self._get_id(scvolume), - payload) + % self._get_id(scvolume), payload, True) if self._check_result(r): replay = self._first_result(r) @@ -1766,8 +1819,7 @@ class StorageCenterApi(object): payload['description'] = replayid payload['expireTime'] = 0 r = self.client.put('StorageCenter/ScReplay/%s' % - self._get_id(screplay), - payload) + self._get_id(screplay), payload, True) if self._check_result(r): return True LOG.error(_LE('Error managing replay %s'), @@ -1784,8 +1836,7 @@ class StorageCenterApi(object): payload = {} payload['expireTime'] = 1440 r = self.client.put('StorageCenter/ScReplay/%s' % - self._get_id(screplay), - payload) + self._get_id(screplay), payload, True) if self._check_result(r): return True LOG.error(_LE('Error unmanaging replay %s'), @@ -1811,7 +1862,7 @@ class StorageCenterApi(object): if replay is not None: # expire our replay. r = self.client.post('StorageCenter/ScReplay/%s/Expire' % - self._get_id(replay), {}) + self._get_id(replay), {}, True) ret = self._check_result(r) # If we couldn't find it we call that a success. return ret @@ -1838,8 +1889,7 @@ class StorageCenterApi(object): if addids: payload['ReplayProfileList'] = addids r = self.client.post('StorageCenter/ScReplay/%s/CreateView' - % self._get_id(screplay), - payload) + % self._get_id(screplay), payload, True) volume = None if self._check_result(r): volume = self._first_result(r) @@ -1882,8 +1932,7 @@ class StorageCenterApi(object): payload = {} payload['NewSize'] = '%d GB' % newsize r = self.client.post('StorageCenter/ScVolume/%s/ExpandToSize' - % self._get_id(scvolume), - payload) + % self._get_id(scvolume), payload, True) if self._check_result(r): vol = self._get_json(r) # More info might be good. @@ -1955,10 +2004,8 @@ class StorageCenterApi(object): 'prof': profile.get('name')}) payload = {} payload['StorageProfile'] = self._get_id(profile) - r = self.client.post('StorageCenter/ScVolumeConfiguration' - '/%s/Modify' - % self._get_id(scvolume), - payload) + r = self.client.put('StorageCenter/ScVolumeConfiguration/%s' + % self._get_id(scvolume), payload, True) if self._check_result(r): return True @@ -1997,7 +2044,7 @@ class StorageCenterApi(object): LOG.debug('ScServer delete %s', self._get_id(scserver)) if scserver.get('deleteAllowed') is True: r = self.client.delete('StorageCenter/ScServer/%s' - % self._get_id(scserver)) + % self._get_id(scserver), True) if self._check_result(r): LOG.debug('ScServer deleted.') else: @@ -2044,7 +2091,7 @@ class StorageCenterApi(object): payload['Type'] = 'Consistent' payload['Notes'] = self.notes r = self.client.post('StorageCenter/ScReplayProfile', - payload) + payload, True) # 201 expected. if self._check_result(r): profile = self._first_result(r) @@ -2059,7 +2106,7 @@ class StorageCenterApi(object): """ self.cg_except_on_no_support() r = self.client.delete('StorageCenter/ScReplayProfile/%s' % - self._get_id(profile)) + self._get_id(profile), True) if self._check_result(r): LOG.info(_LI('Profile %s has been deleted.'), profile.get('name')) @@ -2110,8 +2157,7 @@ class StorageCenterApi(object): payload = {} payload['ReplayProfileList'] = newprofilelist r = self.client.put('StorageCenter/ScVolumeConfiguration/%s' % - self._get_id(scvolumecfg), - payload) + self._get_id(scvolumecfg), payload, True) # check result LOG.debug('_update_volume_profiles %s : %s : %s', self._get_id(scvolume), @@ -2219,8 +2265,7 @@ class StorageCenterApi(object): payload['expireTime'] = expire r = self.client.post('StorageCenter/ScReplayProfile/%s/' 'CreateReplay' - % self._get_id(profile), - payload) + % self._get_id(profile), payload, True) if self._check_result(r): LOG.info(_LI('CreateReplay success %s'), replayid) return True @@ -2296,8 +2341,7 @@ class StorageCenterApi(object): instanceid = self._get_id(replay) LOG.debug('Expiring replay %s', instanceid) r = self.client.post('StorageCenter/ScReplay/%s/Expire' - % instanceid, - {}) + % instanceid, {}, True) if not self._check_result(r): return False # We either couldn't find it or expired it. @@ -2346,8 +2390,7 @@ class StorageCenterApi(object): payload['VolumeFolder'] = self._get_id(folder) r = self.client.put('StorageCenter/ScVolume/%s' % - self._get_id(scvolume), - payload) + self._get_id(scvolume), payload, True) if self._check_result(r): return self._get_json(r) return None @@ -2438,8 +2481,7 @@ class StorageCenterApi(object): payload = {} payload['Name'] = newname r = self.client.put('StorageCenter/ScVolume/%s' % - self._get_id(scvolume), - payload) + self._get_id(scvolume), payload, True) if self._check_result(r): LOG.info(_LI('Volume %s unmanaged.'), scvolume['name']) else: @@ -2470,7 +2512,7 @@ class StorageCenterApi(object): payload['StorageCenter'] = self.ssn payload['BandwidthLimited'] = False r = self.client.post('StorageCenter/ScReplicationQosNode', - payload) + payload, True) if self._check_result(r): return self._get_json(r) @@ -2494,7 +2536,7 @@ class StorageCenterApi(object): payload = {'ReplicateActiveReplay': replactive} r = self.client.put('StorageCenter/ScReplication/%s' % replication['instanceId'], - payload) + payload, True) if not self._check_result(r): return False return True @@ -2533,7 +2575,7 @@ class StorageCenterApi(object): replication = self.get_screplication(scvolume, destssn) if replication: r = self.client.delete('StorageCenter/ScReplication/%s' % - self._get_id(replication)) + self._get_id(replication), True) if self._check_result(r): # check that we whacked the dest volume LOG.info(_LI('Replication %(vol)s to %(dest)s.'), @@ -2558,7 +2600,7 @@ class StorageCenterApi(object): pf.append('scSerialNumber', ssn) pf.append('name', foldername) r = self.client.post('StorageCenter/ScDiskFolder/GetList', - pf.payload) + pf.payload, True) if self._check_result(r): try: # Go for broke. @@ -2604,7 +2646,7 @@ class StorageCenterApi(object): if df: destinationvolumeattributes['DiskFolder'] = self._get_id(df) payload['DestinationVolumeAttributes'] = destinationvolumeattributes - r = self.client.post('StorageCenter/ScReplication', payload) + r = self.client.post('StorageCenter/ScReplication', payload, True) # 201 expected. if self._check_result(r): LOG.info(_LI('Replication created for %(volname)s to %(destsc)s'), @@ -2638,8 +2680,7 @@ class StorageCenterApi(object): else: # Try the name. pf.append('Name', self._repl_name(guid)) - r = self.client.post('StorageCenter/ScVolume/GetList', - pf.payload) + r = self.client.post('StorageCenter/ScVolume/GetList', pf.payload) if self._check_result(r): volumes = self._get_json(r) if len(volumes) == 1: @@ -2654,8 +2695,7 @@ class StorageCenterApi(object): """ if scvol: r = self.client.post('StorageCenter/ScVolume/%s/Unmap' % - self._get_id(scvol), - {}) + self._get_id(scvol), {}, True) return self._check_result(r) return False