45043e9740
1.As mentioned in [1], we should avoid using six.iteritems to achieve iterators. We can use dict.items instead, as it will return iterators in PY3 as well. And dict.items/keys will more readable. 2.In py2, the performance about list should be negligible, see the link [2]. [1] https://wiki.openstack.org/wiki/Python3 [2] http://lists.openstack.org/pipermail/openstack-dev/2015-June/066391.html The patch list: tests. Partial-Implements: blueprint replace-iteritems-with-items Change-Id: I2d09c1d06370fed9c7c116a9eedea929fa5b5dc2
417 lines
16 KiB
Python
417 lines
16 KiB
Python
# Copyright 2013 OpenStack Foundation
|
|
# 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.
|
|
|
|
from eventlet import greenthread
|
|
import mock
|
|
import six
|
|
|
|
from nova import exception
|
|
from nova import test
|
|
from nova.tests.unit.virt.xenapi import stubs
|
|
from nova.virt.xenapi import volume_utils
|
|
|
|
|
|
class SROps(stubs.XenAPITestBaseNoDB):
|
|
def test_find_sr_valid_uuid(self):
|
|
self.session = mock.Mock()
|
|
self.session.call_xenapi.return_value = 'sr_ref'
|
|
self.assertEqual(volume_utils.find_sr_by_uuid(self.session,
|
|
'sr_uuid'),
|
|
'sr_ref')
|
|
|
|
def test_find_sr_invalid_uuid(self):
|
|
class UUIDException(Exception):
|
|
details = ["UUID_INVALID", "", "", ""]
|
|
|
|
self.session = mock.Mock()
|
|
self.session.XenAPI.Failure = UUIDException
|
|
self.session.call_xenapi.side_effect = UUIDException
|
|
self.assertIsNone(
|
|
volume_utils.find_sr_by_uuid(self.session, 'sr_uuid'))
|
|
|
|
def test_find_sr_from_vdi(self):
|
|
vdi_ref = 'fake-ref'
|
|
|
|
def fake_call_xenapi(method, *args):
|
|
self.assertEqual(method, 'VDI.get_SR')
|
|
self.assertEqual(args[0], vdi_ref)
|
|
return args[0]
|
|
|
|
session = mock.Mock()
|
|
session.call_xenapi.side_effect = fake_call_xenapi
|
|
self.assertEqual(volume_utils.find_sr_from_vdi(session, vdi_ref),
|
|
vdi_ref)
|
|
|
|
def test_find_sr_from_vdi_exception(self):
|
|
vdi_ref = 'fake-ref'
|
|
|
|
class FakeException(Exception):
|
|
pass
|
|
|
|
session = mock.Mock()
|
|
session.XenAPI.Failure = FakeException
|
|
session.call_xenapi.side_effect = FakeException
|
|
self.assertRaises(exception.StorageError,
|
|
volume_utils.find_sr_from_vdi, session, vdi_ref)
|
|
|
|
|
|
class ISCSIParametersTestCase(stubs.XenAPITestBaseNoDB):
|
|
def test_target_host(self):
|
|
self.assertEqual(volume_utils._get_target_host('host:port'),
|
|
'host')
|
|
|
|
self.assertEqual(volume_utils._get_target_host('host'),
|
|
'host')
|
|
|
|
# There is no default value
|
|
self.assertIsNone(volume_utils._get_target_host(':port'))
|
|
|
|
self.assertIsNone(volume_utils._get_target_host(None))
|
|
|
|
def test_target_port(self):
|
|
self.assertEqual(volume_utils._get_target_port('host:port'), 'port')
|
|
self.assertEqual(volume_utils._get_target_port('host'), 3260)
|
|
|
|
|
|
class IntroduceTestCase(stubs.XenAPITestBaseNoDB):
|
|
|
|
@mock.patch.object(volume_utils, '_get_vdi_ref')
|
|
@mock.patch.object(greenthread, 'sleep')
|
|
def test_introduce_vdi_retry(self, mock_sleep, mock_get_vdi_ref):
|
|
def fake_get_vdi_ref(session, sr_ref, vdi_uuid, target_lun):
|
|
fake_get_vdi_ref.call_count += 1
|
|
if fake_get_vdi_ref.call_count == 2:
|
|
return 'vdi_ref'
|
|
|
|
def fake_call_xenapi(method, *args):
|
|
if method == 'SR.scan':
|
|
return
|
|
elif method == 'VDI.get_record':
|
|
return {'managed': 'true'}
|
|
|
|
session = mock.Mock()
|
|
session.call_xenapi.side_effect = fake_call_xenapi
|
|
|
|
mock_get_vdi_ref.side_effect = fake_get_vdi_ref
|
|
fake_get_vdi_ref.call_count = 0
|
|
|
|
self.assertEqual(volume_utils.introduce_vdi(session, 'sr_ref'),
|
|
'vdi_ref')
|
|
mock_sleep.assert_called_once_with(20)
|
|
|
|
@mock.patch.object(volume_utils, '_get_vdi_ref')
|
|
@mock.patch.object(greenthread, 'sleep')
|
|
def test_introduce_vdi_exception(self, mock_sleep, mock_get_vdi_ref):
|
|
def fake_call_xenapi(method, *args):
|
|
if method == 'SR.scan':
|
|
return
|
|
elif method == 'VDI.get_record':
|
|
return {'managed': 'true'}
|
|
|
|
session = mock.Mock()
|
|
session.call_xenapi.side_effect = fake_call_xenapi
|
|
mock_get_vdi_ref.return_value = None
|
|
|
|
self.assertRaises(exception.StorageError,
|
|
volume_utils.introduce_vdi, session, 'sr_ref')
|
|
mock_sleep.assert_called_once_with(20)
|
|
|
|
|
|
class ParseVolumeInfoTestCase(stubs.XenAPITestBaseNoDB):
|
|
def test_mountpoint_to_number(self):
|
|
cases = {
|
|
'sda': 0,
|
|
'sdp': 15,
|
|
'hda': 0,
|
|
'hdp': 15,
|
|
'vda': 0,
|
|
'xvda': 0,
|
|
'0': 0,
|
|
'10': 10,
|
|
'vdq': -1,
|
|
'sdq': -1,
|
|
'hdq': -1,
|
|
'xvdq': -1,
|
|
}
|
|
|
|
for (input, expected) in cases.items():
|
|
actual = volume_utils._mountpoint_to_number(input)
|
|
self.assertEqual(actual, expected,
|
|
'%s yielded %s, not %s' % (input, actual, expected))
|
|
|
|
@classmethod
|
|
def _make_connection_info(cls):
|
|
target_iqn = 'iqn.2010-10.org.openstack:volume-00000001'
|
|
return {'driver_volume_type': 'iscsi',
|
|
'data': {'volume_id': 1,
|
|
'target_iqn': target_iqn,
|
|
'target_portal': '127.0.0.1:3260,fake',
|
|
'target_lun': None,
|
|
'auth_method': 'CHAP',
|
|
'auth_username': 'username',
|
|
'auth_password': 'verybadpass'}}
|
|
|
|
def test_parse_volume_info_parsing_auth_details(self):
|
|
conn_info = self._make_connection_info()
|
|
result = volume_utils._parse_volume_info(conn_info['data'])
|
|
|
|
self.assertEqual('username', result['chapuser'])
|
|
self.assertEqual('verybadpass', result['chappassword'])
|
|
|
|
def test_parse_volume_info_missing_details(self):
|
|
# Tests that a StorageError is raised if volume_id, target_host, or
|
|
# target_ign is missing from connection_data. Also ensures that the
|
|
# auth_password value is not present in the StorageError message.
|
|
for data_key_to_null in ('volume_id', 'target_portal', 'target_iqn'):
|
|
conn_info = self._make_connection_info()
|
|
conn_info['data'][data_key_to_null] = None
|
|
ex = self.assertRaises(exception.StorageError,
|
|
volume_utils._parse_volume_info,
|
|
conn_info['data'])
|
|
self.assertNotIn('verybadpass', six.text_type(ex))
|
|
|
|
def test_get_device_number_raise_exception_on_wrong_mountpoint(self):
|
|
self.assertRaises(
|
|
exception.StorageError,
|
|
volume_utils.get_device_number,
|
|
'dev/sd')
|
|
|
|
|
|
class FindVBDTestCase(stubs.XenAPITestBaseNoDB):
|
|
def test_find_vbd_by_number_works(self):
|
|
session = mock.Mock()
|
|
session.VM.get_VBDs.return_value = ["a", "b"]
|
|
session.VBD.get_userdevice.return_value = "1"
|
|
|
|
result = volume_utils.find_vbd_by_number(session, "vm_ref", 1)
|
|
|
|
self.assertEqual("a", result)
|
|
session.VM.get_VBDs.assert_called_once_with("vm_ref")
|
|
session.VBD.get_userdevice.assert_called_once_with("a")
|
|
|
|
def test_find_vbd_by_number_no_matches(self):
|
|
session = mock.Mock()
|
|
session.VM.get_VBDs.return_value = ["a", "b"]
|
|
session.VBD.get_userdevice.return_value = "3"
|
|
|
|
result = volume_utils.find_vbd_by_number(session, "vm_ref", 1)
|
|
|
|
self.assertIsNone(result)
|
|
session.VM.get_VBDs.assert_called_once_with("vm_ref")
|
|
expected = [mock.call("a"), mock.call("b")]
|
|
self.assertEqual(expected,
|
|
session.VBD.get_userdevice.call_args_list)
|
|
|
|
def test_find_vbd_by_number_no_vbds(self):
|
|
session = mock.Mock()
|
|
session.VM.get_VBDs.return_value = []
|
|
|
|
result = volume_utils.find_vbd_by_number(session, "vm_ref", 1)
|
|
|
|
self.assertIsNone(result)
|
|
session.VM.get_VBDs.assert_called_once_with("vm_ref")
|
|
self.assertFalse(session.VBD.get_userdevice.called)
|
|
|
|
def test_find_vbd_by_number_ignores_exception(self):
|
|
session = mock.Mock()
|
|
session.XenAPI.Failure = test.TestingException
|
|
session.VM.get_VBDs.return_value = ["a"]
|
|
session.VBD.get_userdevice.side_effect = test.TestingException
|
|
|
|
result = volume_utils.find_vbd_by_number(session, "vm_ref", 1)
|
|
|
|
self.assertIsNone(result)
|
|
session.VM.get_VBDs.assert_called_once_with("vm_ref")
|
|
session.VBD.get_userdevice.assert_called_once_with("a")
|
|
|
|
|
|
class IntroduceSRTestCase(stubs.XenAPITestBaseNoDB):
|
|
@mock.patch.object(volume_utils, '_create_pbd')
|
|
def test_backend_kind(self, create_pbd):
|
|
session = mock.Mock()
|
|
session.product_version = (6, 5, 0)
|
|
session.call_xenapi.return_value = 'sr_ref'
|
|
params = {'sr_type': 'iscsi'}
|
|
sr_uuid = 'sr_uuid'
|
|
label = 'label'
|
|
expected_params = {'backend-kind': 'vbd'}
|
|
|
|
volume_utils.introduce_sr(session, sr_uuid, label, params)
|
|
session.call_xenapi.assert_any_call('SR.introduce', sr_uuid,
|
|
label, '', 'iscsi',
|
|
'', False, expected_params)
|
|
|
|
@mock.patch.object(volume_utils, '_create_pbd')
|
|
def test_backend_kind_upstream_fix(self, create_pbd):
|
|
session = mock.Mock()
|
|
session.product_version = (7, 0, 0)
|
|
session.call_xenapi.return_value = 'sr_ref'
|
|
params = {'sr_type': 'iscsi'}
|
|
sr_uuid = 'sr_uuid'
|
|
label = 'label'
|
|
expected_params = {}
|
|
|
|
volume_utils.introduce_sr(session, sr_uuid, label, params)
|
|
session.call_xenapi.assert_any_call('SR.introduce', sr_uuid,
|
|
label, '', 'iscsi',
|
|
'', False, expected_params)
|
|
|
|
|
|
class BootedFromVolumeTestCase(stubs.XenAPITestBaseNoDB):
|
|
def test_booted_from_volume(self):
|
|
session = mock.Mock()
|
|
session.VM.get_VBDs.return_value = ['vbd_ref']
|
|
session.VBD.get_userdevice.return_value = '0'
|
|
session.VBD.get_other_config.return_value = {'osvol': True}
|
|
booted_from_volume = volume_utils.is_booted_from_volume(session,
|
|
'vm_ref')
|
|
self.assertTrue(booted_from_volume)
|
|
|
|
def test_not_booted_from_volume(self):
|
|
session = mock.Mock()
|
|
session.VM.get_VBDs.return_value = ['vbd_ref']
|
|
session.VBD.get_userdevice.return_value = '0'
|
|
session.VBD.get_other_config.return_value = {}
|
|
booted_from_volume = volume_utils.is_booted_from_volume(session,
|
|
'vm_ref')
|
|
self.assertFalse(booted_from_volume)
|
|
|
|
|
|
class MultipleVolumesTestCase(stubs.XenAPITestBaseNoDB):
|
|
def test_sr_info_two_luns(self):
|
|
data1 = {'target_portal': 'host:port',
|
|
'target_iqn': 'iqn',
|
|
'volume_id': 'vol_id_1',
|
|
'target_lun': 1}
|
|
data2 = {'target_portal': 'host:port',
|
|
'target_iqn': 'iqn',
|
|
'volume_id': 'vol_id_2',
|
|
'target_lun': 2}
|
|
(sr_uuid1, label1, params1) = volume_utils.parse_sr_info(data1)
|
|
(sr_uuid2, label2, params2) = volume_utils.parse_sr_info(data2)
|
|
|
|
self.assertEqual(sr_uuid1, sr_uuid2)
|
|
self.assertEqual(label1, label2)
|
|
|
|
@mock.patch.object(volume_utils, 'forget_sr')
|
|
def test_purge_sr_no_VBDs(self, mock_forget):
|
|
|
|
def _call_xenapi(func, *args):
|
|
if func == 'SR.get_VDIs':
|
|
return ['VDI1', 'VDI2']
|
|
if func == 'VDI.get_VBDs':
|
|
return []
|
|
|
|
self.session = mock.Mock()
|
|
self.session.call_xenapi = _call_xenapi
|
|
|
|
volume_utils.purge_sr(self.session, 'SR')
|
|
|
|
mock_forget.assert_called_once_with(self.session, 'SR')
|
|
|
|
@mock.patch.object(volume_utils, 'forget_sr')
|
|
def test_purge_sr_in_use(self, mock_forget):
|
|
|
|
def _call_xenapi(func, *args):
|
|
if func == 'SR.get_VDIs':
|
|
return ['VDI1', 'VDI2']
|
|
if func == 'VDI.get_VBDs':
|
|
if args[0] == 'VDI1':
|
|
return ['VBD1']
|
|
if args[0] == 'VDI2':
|
|
return ['VBD2']
|
|
|
|
self.session = mock.Mock()
|
|
self.session.call_xenapi = _call_xenapi
|
|
|
|
volume_utils.purge_sr(self.session, 'SR')
|
|
|
|
self.assertEqual([], mock_forget.mock_calls)
|
|
|
|
|
|
class TestStreamToVDI(stubs.XenAPITestBaseNoDB):
|
|
|
|
@mock.patch.object(volume_utils, '_stream_to_vdi')
|
|
@mock.patch.object(volume_utils, '_get_vdi_import_path',
|
|
return_value='vdi_import_path')
|
|
def test_creates_task_conn(self, mock_import_path, mock_stream):
|
|
session = stubs.get_fake_session()
|
|
session.custom_task = mock.MagicMock()
|
|
session.custom_task.return_value.__enter__.return_value = 'task'
|
|
session.http_connection = mock.MagicMock()
|
|
session.http_connection.return_value.__enter__.return_value = 'conn'
|
|
|
|
instance = {'name': 'instance-name'}
|
|
|
|
volume_utils.stream_to_vdi(session, instance, 'vhd', 'file_obj', 100,
|
|
'vdi_ref')
|
|
|
|
session.custom_task.assert_called_with('VDI_IMPORT_for_instance-name')
|
|
mock_stream.assert_called_with('conn', 'vdi_import_path', 100,
|
|
'file_obj')
|
|
|
|
self.assertTrue(session.http_connection.return_value.__exit__.called)
|
|
self.assertTrue(session.custom_task.return_value.__exit__.called)
|
|
|
|
def test_stream_to_vdi_tiny(self):
|
|
mock_file = mock.Mock()
|
|
mock_file.read.side_effect = ['a']
|
|
mock_conn = mock.Mock()
|
|
resp = mock.Mock()
|
|
resp.status = '200'
|
|
resp.reason = 'OK'
|
|
mock_conn.getresponse.return_value = resp
|
|
|
|
volume_utils._stream_to_vdi(mock_conn, '/path', 1, mock_file)
|
|
args, kwargs = mock_conn.request.call_args
|
|
self.assertEqual(kwargs['headers']['Content-Length'], '1')
|
|
mock_file.read.assert_called_once_with(1)
|
|
mock_conn.send.assert_called_once_with('a')
|
|
|
|
def test_stream_to_vdi_chunk_multiple(self):
|
|
mock_file = mock.Mock()
|
|
mock_file.read.side_effect = ['aaaaa', 'bbbbb']
|
|
mock_conn = mock.Mock()
|
|
resp = mock.Mock()
|
|
resp.status = '200'
|
|
resp.reason = 'OK'
|
|
mock_conn.getresponse.return_value = resp
|
|
|
|
tot_size = 2 * 16 * 1024
|
|
volume_utils._stream_to_vdi(mock_conn, '/path', tot_size, mock_file)
|
|
args, kwargs = mock_conn.request.call_args
|
|
self.assertEqual(kwargs['headers']['Content-Length'], str(tot_size))
|
|
mock_file.read.assert_has_calls([mock.call(16 * 1024),
|
|
mock.call(16 * 1024)])
|
|
mock_conn.send.assert_has_calls([mock.call('aaaaa'),
|
|
mock.call('bbbbb')])
|
|
|
|
def test_stream_to_vdi_chunk_remaining(self):
|
|
mock_file = mock.Mock()
|
|
mock_file.read.side_effect = ['aaaaa', 'bb']
|
|
mock_conn = mock.Mock()
|
|
resp = mock.Mock()
|
|
resp.status = '200'
|
|
resp.reason = 'OK'
|
|
mock_conn.getresponse.return_value = resp
|
|
|
|
tot_size = 16 * 1024 + 1024
|
|
volume_utils._stream_to_vdi(mock_conn, '/path', tot_size, mock_file)
|
|
args, kwargs = mock_conn.request.call_args
|
|
self.assertEqual(kwargs['headers']['Content-Length'], str(tot_size))
|
|
mock_file.read.assert_has_calls([mock.call(16 * 1024),
|
|
mock.call(1024)])
|
|
mock_conn.send.assert_has_calls([mock.call('aaaaa'), mock.call('bb')])
|