Merge "ScaleIO driver: update_migrated_volume"

This commit is contained in:
Jenkins 2015-11-19 02:02:00 +00:00 committed by Gerrit Code Review
commit ae84ce085f
2 changed files with 143 additions and 4 deletions

View File

@ -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']
)

View File

@ -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