Merge "ScaleIO driver: update_migrated_volume"
This commit is contained in:
commit
ae84ce085f
@ -12,10 +12,14 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import mock
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
|
|
||||||
|
from cinder import context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
|
from cinder.tests.unit import fake_volume
|
||||||
from cinder.tests.unit.volume.drivers.emc import scaleio
|
from cinder.tests.unit.volume.drivers.emc import scaleio
|
||||||
|
from cinder.tests.unit.volume.drivers.emc.scaleio import mocks
|
||||||
|
|
||||||
|
|
||||||
class TestMisc(scaleio.TestScaleIODriver):
|
class TestMisc(scaleio.TestScaleIODriver):
|
||||||
@ -29,9 +33,16 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
Defines the mock HTTPS responses for the REST API calls.
|
Defines the mock HTTPS responses for the REST API calls.
|
||||||
"""
|
"""
|
||||||
super(TestMisc, self).setUp()
|
super(TestMisc, self).setUp()
|
||||||
|
|
||||||
self.domain_name_enc = urllib.parse.quote(self.DOMAIN_NAME)
|
self.domain_name_enc = urllib.parse.quote(self.DOMAIN_NAME)
|
||||||
self.pool_name_enc = urllib.parse.quote(self.POOL_NAME)
|
self.pool_name_enc = urllib.parse.quote(self.POOL_NAME)
|
||||||
|
self.ctx = context.RequestContext('fake', 'fake', auth_token=True)
|
||||||
|
|
||||||
|
self.volume = fake_volume.fake_volume_obj(
|
||||||
|
self.ctx, **{'name': 'vol1', 'provider_id': '0123456789abcdef'}
|
||||||
|
)
|
||||||
|
self.new_volume = fake_volume.fake_volume_obj(
|
||||||
|
self.ctx, **{'name': 'vol2', 'provider_id': 'fedcba9876543210'}
|
||||||
|
)
|
||||||
|
|
||||||
self.HTTPS_MOCK_RESPONSES = {
|
self.HTTPS_MOCK_RESPONSES = {
|
||||||
self.RESPONSE_MODE.Valid: {
|
self.RESPONSE_MODE.Valid: {
|
||||||
@ -50,6 +61,12 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
'capacityLimitInKb': 1024,
|
'capacityLimitInKb': 1024,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'instances/Volume::{}/action/setVolumeName'.format(
|
||||||
|
self.volume['provider_id']):
|
||||||
|
self.new_volume['provider_id'],
|
||||||
|
'instances/Volume::{}/action/setVolumeName'.format(
|
||||||
|
self.new_volume['provider_id']):
|
||||||
|
self.volume['provider_id'],
|
||||||
},
|
},
|
||||||
self.RESPONSE_MODE.BadStatus: {
|
self.RESPONSE_MODE.BadStatus: {
|
||||||
'types/Domain/instances/getByName::' +
|
'types/Domain/instances/getByName::' +
|
||||||
@ -58,6 +75,13 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
self.RESPONSE_MODE.Invalid: {
|
self.RESPONSE_MODE.Invalid: {
|
||||||
'types/Domain/instances/getByName::' +
|
'types/Domain/instances/getByName::' +
|
||||||
self.domain_name_enc: None,
|
self.domain_name_enc: None,
|
||||||
|
'instances/Volume::{}/action/setVolumeName'.format(
|
||||||
|
self.volume['provider_id']): mocks.MockHTTPSResponse(
|
||||||
|
{
|
||||||
|
'message': 'Invalid volume.',
|
||||||
|
'httpStatusCode': 400,
|
||||||
|
'errorCode': 0
|
||||||
|
}, 400),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,3 +138,51 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
def test_get_volume_stats(self):
|
def test_get_volume_stats(self):
|
||||||
self.driver.storage_pools = self.STORAGE_POOLS
|
self.driver.storage_pools = self.STORAGE_POOLS
|
||||||
self.driver.get_volume_stats(True)
|
self.driver.get_volume_stats(True)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'cinder.volume.drivers.emc.scaleio.ScaleIODriver._rename_volume',
|
||||||
|
return_value=None)
|
||||||
|
def test_update_migrated_volume(self, mock_rename):
|
||||||
|
test_vol = self.driver.update_migrated_volume(
|
||||||
|
self.ctx, self.volume, self.new_volume, 'available')
|
||||||
|
mock_rename.assert_called_with(self.new_volume, self.volume['id'])
|
||||||
|
self.assertEqual({'_name_id': None, 'provider_location': None},
|
||||||
|
test_vol)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'cinder.volume.drivers.emc.scaleio.ScaleIODriver._rename_volume',
|
||||||
|
return_value=None)
|
||||||
|
def test_update_unavailable_migrated_volume(self, mock_rename):
|
||||||
|
test_vol = self.driver.update_migrated_volume(
|
||||||
|
self.ctx, self.volume, self.new_volume, 'unavailable')
|
||||||
|
self.assertFalse(mock_rename.called)
|
||||||
|
self.assertEqual({'_name_id': '1', 'provider_location': None},
|
||||||
|
test_vol)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'cinder.volume.drivers.emc.scaleio.ScaleIODriver._rename_volume',
|
||||||
|
side_effect=exception.VolumeBackendAPIException(data='Error!'))
|
||||||
|
def test_fail_update_migrated_volume(self, mock_rename):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.VolumeBackendAPIException,
|
||||||
|
self.driver.update_migrated_volume,
|
||||||
|
self.ctx,
|
||||||
|
self.volume,
|
||||||
|
self.new_volume,
|
||||||
|
'available'
|
||||||
|
)
|
||||||
|
mock_rename.assert_called_with(self.volume, "ff" + self.volume['id'])
|
||||||
|
|
||||||
|
def test_rename_volume(self):
|
||||||
|
rc = self.driver._rename_volume(
|
||||||
|
self.volume, self.new_volume['id'])
|
||||||
|
self.assertIsNone(rc)
|
||||||
|
|
||||||
|
def test_fail_rename_volume(self):
|
||||||
|
self.set_https_response_mode(self.RESPONSE_MODE.Invalid)
|
||||||
|
self.assertRaises(
|
||||||
|
exception.VolumeBackendAPIException,
|
||||||
|
self.driver._rename_volume,
|
||||||
|
self.volume,
|
||||||
|
self.new_volume['id']
|
||||||
|
)
|
||||||
|
@ -147,9 +147,7 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
{'domain_id': self.protection_domain_id})
|
{'domain_id': self.protection_domain_id})
|
||||||
|
|
||||||
self.connector = connector.InitiatorConnector.factory(
|
self.connector = connector.InitiatorConnector.factory(
|
||||||
# TODO(xyang): Change 'SCALEIO' to connector.SCALEIO after
|
connector.SCALEIO, utils.get_root_helper(),
|
||||||
# os-brick 0.4.0 is released.
|
|
||||||
'SCALEIO', utils.get_root_helper(),
|
|
||||||
device_scan_attempts=
|
device_scan_attempts=
|
||||||
self.configuration.num_volume_device_scan_tries
|
self.configuration.num_volume_device_scan_tries
|
||||||
)
|
)
|
||||||
@ -908,6 +906,75 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
finally:
|
finally:
|
||||||
self._sio_detach_volume(volume)
|
self._sio_detach_volume(volume)
|
||||||
|
|
||||||
|
def update_migrated_volume(self, ctxt, volume, new_volume,
|
||||||
|
original_volume_status):
|
||||||
|
"""Return the update from ScaleIO migrated volume.
|
||||||
|
|
||||||
|
This method updates the volume name of the new ScaleIO volume to
|
||||||
|
match the updated volume ID.
|
||||||
|
The original volume is renamed first since ScaleIO does not allow
|
||||||
|
multiple volumes to have the same name.
|
||||||
|
"""
|
||||||
|
name_id = None
|
||||||
|
location = None
|
||||||
|
if original_volume_status == 'available':
|
||||||
|
# During migration, a new volume is created and will replace
|
||||||
|
# the original volume at the end of the migration. We need to
|
||||||
|
# rename the new volume. The current_name of the new volume,
|
||||||
|
# which is the id of the new volume, will be changed to the
|
||||||
|
# new_name, which is the id of the original volume.
|
||||||
|
current_name = new_volume['id']
|
||||||
|
new_name = volume['id']
|
||||||
|
vol_id = new_volume['provider_id']
|
||||||
|
LOG.info(_LI("Renaming %(id)s from %(current_name)s to "
|
||||||
|
"%(new_name)s."),
|
||||||
|
{'id': vol_id, 'current_name': current_name,
|
||||||
|
'new_name': new_name})
|
||||||
|
|
||||||
|
# Original volume needs to be renamed first
|
||||||
|
self._rename_volume(volume, "ff" + new_name)
|
||||||
|
self._rename_volume(new_volume, new_name)
|
||||||
|
else:
|
||||||
|
# The back-end will not be renamed.
|
||||||
|
name_id = new_volume['_name_id'] or new_volume['id']
|
||||||
|
location = new_volume['provider_location']
|
||||||
|
|
||||||
|
return {'_name_id': name_id, 'provider_location': location}
|
||||||
|
|
||||||
|
def _rename_volume(self, volume, new_id):
|
||||||
|
new_name = self._id_to_base64(new_id)
|
||||||
|
vol_id = volume['provider_id']
|
||||||
|
|
||||||
|
req_vars = {'server_ip': self.server_ip,
|
||||||
|
'server_port': self.server_port,
|
||||||
|
'id': vol_id}
|
||||||
|
request = ("https://%(server_ip)s:%(server_port)s"
|
||||||
|
"/api/instances/Volume::%(id)s/action/setVolumeName" %
|
||||||
|
req_vars)
|
||||||
|
LOG.info(_LI("ScaleIO rename volume request: %s."), request)
|
||||||
|
|
||||||
|
params = {'newName': new_name}
|
||||||
|
r = requests.post(
|
||||||
|
request,
|
||||||
|
data=json.dumps(params),
|
||||||
|
headers=self._get_headers(),
|
||||||
|
auth=(self.server_username,
|
||||||
|
self.server_token),
|
||||||
|
verify=self._get_verify_cert()
|
||||||
|
)
|
||||||
|
r = self._check_response(r, request, False, params)
|
||||||
|
|
||||||
|
if r.status_code != OK_STATUS_CODE:
|
||||||
|
response = r.json()
|
||||||
|
msg = (_("Error renaming volume %(vol)s: %(err)s.") %
|
||||||
|
{'vol': vol_id, 'err': response['message']})
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exception.VolumeBackendAPIException(data=msg)
|
||||||
|
else:
|
||||||
|
LOG.info(_LI("ScaleIO volume %(vol)s was renamed to "
|
||||||
|
"%(new_name)s."),
|
||||||
|
{'vol': vol_id, 'new_name': new_name})
|
||||||
|
|
||||||
def ensure_export(self, context, volume):
|
def ensure_export(self, context, volume):
|
||||||
"""Driver entry point to get the export info for an existing volume."""
|
"""Driver entry point to get the export info for an existing volume."""
|
||||||
pass
|
pass
|
||||||
|
Loading…
Reference in New Issue
Block a user