# 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 mock
from six.moves import urllib
+from cinder import context
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.scaleio import mocks
class TestMisc(scaleio.TestScaleIODriver):
Defines the mock HTTPS responses for the REST API calls.
"""
super(TestMisc, self).setUp()
-
self.domain_name_enc = urllib.parse.quote(self.DOMAIN_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.RESPONSE_MODE.Valid: {
'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: {
'types/Domain/instances/getByName::' +
self.RESPONSE_MODE.Invalid: {
'types/Domain/instances/getByName::' +
self.domain_name_enc: None,
+ 'instances/Volume::{}/action/setVolumeName'.format(
+ self.volume['provider_id']): mocks.MockHTTPSResponse(
+ {
+ 'message': 'Invalid volume.',
+ 'httpStatusCode': 400,
+ 'errorCode': 0
+ }, 400),
},
}
def test_get_volume_stats(self):
self.driver.storage_pools = self.STORAGE_POOLS
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']
+ )
{'domain_id': self.protection_domain_id})
self.connector = connector.InitiatorConnector.factory(
- # TODO(xyang): Change 'SCALEIO' to connector.SCALEIO after
- # os-brick 0.4.0 is released.
- 'SCALEIO', utils.get_root_helper(),
+ connector.SCALEIO, utils.get_root_helper(),
device_scan_attempts=
self.configuration.num_volume_device_scan_tries
)
finally:
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):
"""Driver entry point to get the export info for an existing volume."""
pass