Remove unsupported BlockDeviceDriver
BlockDeviceDriver was deprecated in Ocata release and marked as 'unsupported'. There is no CI for it too. Change-Id: I145e4aeb535073b018cb4fa6ca9c939b1403ddb5
This commit is contained in:
parent
4ee1bc921f
commit
711e88a8f9
@ -70,8 +70,6 @@ from cinder import ssh_utils as cinder_sshutils
|
|||||||
from cinder.transfer import api as cinder_transfer_api
|
from cinder.transfer import api as cinder_transfer_api
|
||||||
from cinder.volume import api as cinder_volume_api
|
from cinder.volume import api as cinder_volume_api
|
||||||
from cinder.volume import driver as cinder_volume_driver
|
from cinder.volume import driver as cinder_volume_driver
|
||||||
from cinder.volume.drivers import block_device as \
|
|
||||||
cinder_volume_drivers_blockdevice
|
|
||||||
from cinder.volume.drivers import blockbridge as \
|
from cinder.volume.drivers import blockbridge as \
|
||||||
cinder_volume_drivers_blockbridge
|
cinder_volume_drivers_blockbridge
|
||||||
from cinder.volume.drivers import coho as cinder_volume_drivers_coho
|
from cinder.volume.drivers import coho as cinder_volume_drivers_coho
|
||||||
@ -300,7 +298,6 @@ def list_opts():
|
|||||||
itertools.chain(
|
itertools.chain(
|
||||||
cinder_volume_driver.volume_opts,
|
cinder_volume_driver.volume_opts,
|
||||||
cinder_volume_driver.iser_opts,
|
cinder_volume_driver.iser_opts,
|
||||||
cinder_volume_drivers_blockdevice.volume_opts,
|
|
||||||
cinder_volume_drivers_blockbridge.blockbridge_opts,
|
cinder_volume_drivers_blockbridge.blockbridge_opts,
|
||||||
cinder_volume_drivers_coho.coho_opts,
|
cinder_volume_drivers_coho.coho_opts,
|
||||||
cinder_volume_drivers_coprhd_common.volume_opts,
|
cinder_volume_drivers_coprhd_common.volume_opts,
|
||||||
|
@ -1,421 +0,0 @@
|
|||||||
# Copyright (c) 2013 Mirantis, Inc.
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from cinder import context
|
|
||||||
from cinder import db
|
|
||||||
import cinder.exception
|
|
||||||
from cinder.objects import fields
|
|
||||||
from cinder.objects import snapshot as obj_snap
|
|
||||||
from cinder.objects import volume as obj_volume
|
|
||||||
import cinder.test
|
|
||||||
from cinder.tests.unit import fake_constants as fake
|
|
||||||
from cinder.volume import configuration as conf
|
|
||||||
from cinder.volume.drivers import block_device
|
|
||||||
from cinder.volume import utils as volutils
|
|
||||||
|
|
||||||
|
|
||||||
class TestBlockDeviceDriver(cinder.test.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
fake_opt = [cfg.StrOpt('fake_opt', default='fake', help='fake option')]
|
|
||||||
super(TestBlockDeviceDriver, self).setUp()
|
|
||||||
self.configuration = conf.Configuration(fake_opt, 'fake_group')
|
|
||||||
self.configuration.available_devices = ['/dev/loop1', '/dev/loop2']
|
|
||||||
self.configuration.iscsi_helper = 'tgtadm'
|
|
||||||
self.host = 'localhost'
|
|
||||||
self.configuration.iscsi_port = 3260
|
|
||||||
self.configuration.volume_dd_blocksize = 1234
|
|
||||||
self.drv = block_device.BlockDeviceDriver(
|
|
||||||
configuration=self.configuration,
|
|
||||||
host='localhost', db=db)
|
|
||||||
|
|
||||||
def test_initialize_connection(self):
|
|
||||||
TEST_VOLUME1 = obj_volume.Volume(host='localhost1',
|
|
||||||
provider_location='1 2 3 /dev/loop1',
|
|
||||||
provider_auth='a b c',
|
|
||||||
attached_mode='rw',
|
|
||||||
id=fake.VOLUME_ID)
|
|
||||||
TEST_CONNECTOR = {'host': 'localhost1'}
|
|
||||||
|
|
||||||
data = self.drv.initialize_connection(TEST_VOLUME1, TEST_CONNECTOR)
|
|
||||||
expected_data = {'data': {'device_path': '/dev/loop1'},
|
|
||||||
'driver_volume_type': 'local'}
|
|
||||||
|
|
||||||
self.assertEqual(expected_data, data)
|
|
||||||
|
|
||||||
@mock.patch('cinder.volume.driver.ISCSIDriver.initialize_connection')
|
|
||||||
def test_initialize_connection_different_hosts(self, _init_conn):
|
|
||||||
TEST_CONNECTOR = {'host': 'localhost1'}
|
|
||||||
TEST_VOLUME2 = obj_volume.Volume(host='localhost2',
|
|
||||||
provider_location='1 2 3 /dev/loop2',
|
|
||||||
provider_auth='d e f',
|
|
||||||
attached_mode='rw',
|
|
||||||
id=fake.VOLUME2_ID)
|
|
||||||
_init_conn.return_value = 'data'
|
|
||||||
|
|
||||||
data = self.drv.initialize_connection(TEST_VOLUME2, TEST_CONNECTOR)
|
|
||||||
expected_data = {'data': {'auth_method': 'd',
|
|
||||||
'auth_password': 'f',
|
|
||||||
'auth_username': 'e',
|
|
||||||
'encrypted': False,
|
|
||||||
'target_discovered': False,
|
|
||||||
'target_iqn': '2',
|
|
||||||
'target_lun': 3,
|
|
||||||
'target_portal': '1',
|
|
||||||
'volume_id': fake.VOLUME2_ID}}
|
|
||||||
|
|
||||||
self.assertEqual(expected_data['data'], data['data'])
|
|
||||||
|
|
||||||
@mock.patch('cinder.volume.drivers.block_device.BlockDeviceDriver.'
|
|
||||||
'local_path', return_value=None)
|
|
||||||
@mock.patch('cinder.volume.utils.clear_volume')
|
|
||||||
def test_delete_not_volume_provider_location(self, _clear_volume,
|
|
||||||
_local_path):
|
|
||||||
TEST_VOLUME2 = obj_volume.Volume(provider_location=None)
|
|
||||||
self.drv.delete_volume(TEST_VOLUME2)
|
|
||||||
_local_path.assert_called_once_with(TEST_VOLUME2)
|
|
||||||
|
|
||||||
@mock.patch('os.path.exists', return_value=True)
|
|
||||||
@mock.patch('cinder.volume.utils.clear_volume')
|
|
||||||
def test_delete_volume_path_exist(self, _clear_volume, _exists):
|
|
||||||
TEST_VOLUME = obj_volume.Volume(name_id=fake.VOLUME_NAME_ID,
|
|
||||||
size=1,
|
|
||||||
provider_location='/dev/loop1',
|
|
||||||
display_name='vol1',
|
|
||||||
status='available')
|
|
||||||
|
|
||||||
with mock.patch.object(self.drv, 'local_path',
|
|
||||||
return_value='/dev/loop1') as lp_mocked:
|
|
||||||
with mock.patch.object(self.drv, '_get_devices_sizes',
|
|
||||||
return_value={'/dev/loop1': 1}) as \
|
|
||||||
gds_mocked:
|
|
||||||
volutils.clear_volume(gds_mocked, lp_mocked)
|
|
||||||
|
|
||||||
self.drv.delete_volume(TEST_VOLUME)
|
|
||||||
|
|
||||||
lp_mocked.assert_called_once_with(TEST_VOLUME)
|
|
||||||
gds_mocked.assert_called_once_with(['/dev/loop1'])
|
|
||||||
|
|
||||||
self.assertTrue(_exists.called)
|
|
||||||
self.assertTrue(_clear_volume.called)
|
|
||||||
|
|
||||||
def test_delete_path_is_not_in_list_of_available_devices(self):
|
|
||||||
TEST_VOLUME2 = obj_volume.Volume(provider_location='/dev/loop0')
|
|
||||||
with mock.patch.object(self.drv, 'local_path',
|
|
||||||
return_value='/dev/loop0') as lp_mocked:
|
|
||||||
self.drv.delete_volume(TEST_VOLUME2)
|
|
||||||
lp_mocked.assert_called_once_with(TEST_VOLUME2)
|
|
||||||
|
|
||||||
def test__update_provider_location(self):
|
|
||||||
TEST_VOLUME = obj_volume.Volume(name_id=fake.VOLUME_NAME_ID,
|
|
||||||
size=1,
|
|
||||||
display_name='vol1')
|
|
||||||
with mock.patch.object(obj_volume.Volume, 'update') as update_mocked, \
|
|
||||||
mock.patch.object(obj_volume.Volume, 'save') as save_mocked:
|
|
||||||
self.drv._update_provider_location(TEST_VOLUME, 'dev_path')
|
|
||||||
self.assertEqual(1, update_mocked.call_count)
|
|
||||||
save_mocked.assert_called_once_with()
|
|
||||||
|
|
||||||
def test_create_volume(self):
|
|
||||||
TEST_VOLUME = obj_volume.Volume(name_id=fake.VOLUME_NAME_ID,
|
|
||||||
size=1,
|
|
||||||
display_name='vol1')
|
|
||||||
|
|
||||||
with mock.patch.object(self.drv, 'find_appropriate_size_device',
|
|
||||||
return_value='dev_path') as fasd_mocked:
|
|
||||||
with mock.patch.object(self.drv, '_update_provider_location') as \
|
|
||||||
upl_mocked:
|
|
||||||
self.drv.create_volume(TEST_VOLUME)
|
|
||||||
fasd_mocked.assert_called_once_with(TEST_VOLUME.size)
|
|
||||||
upl_mocked.assert_called_once_with(TEST_VOLUME, 'dev_path')
|
|
||||||
|
|
||||||
def test_update_volume_stats(self):
|
|
||||||
|
|
||||||
with mock.patch.object(self.drv, '_devices_sizes',
|
|
||||||
return_value={'/dev/loop1': 1024,
|
|
||||||
'/dev/loop2': 1024}) as \
|
|
||||||
ds_mocked:
|
|
||||||
with mock.patch.object(self.drv, '_get_used_devices') as \
|
|
||||||
gud_mocked:
|
|
||||||
self.drv._update_volume_stats()
|
|
||||||
|
|
||||||
reserved_percentage = self.configuration.reserved_percentage
|
|
||||||
self.assertEqual({
|
|
||||||
'vendor_name': "Open Source",
|
|
||||||
'driver_version': self.drv.VERSION,
|
|
||||||
'volume_backend_name': 'BlockDev',
|
|
||||||
'storage_protocol': 'unknown',
|
|
||||||
'pools': [{
|
|
||||||
'QoS_support': False,
|
|
||||||
'total_capacity_gb': 2,
|
|
||||||
'free_capacity_gb': 2,
|
|
||||||
'reserved_percentage': reserved_percentage,
|
|
||||||
'pool_name': 'BlockDev'}]}, self.drv._stats)
|
|
||||||
gud_mocked.assert_called_once_with()
|
|
||||||
ds_mocked.assert_called_once_with()
|
|
||||||
|
|
||||||
@mock.patch('cinder.volume.utils.copy_volume')
|
|
||||||
def test_create_cloned_volume(self, _copy_volume):
|
|
||||||
TEST_SRC = obj_volume.Volume(id=fake.VOLUME_ID,
|
|
||||||
name_id=fake.VOLUME_NAME_ID,
|
|
||||||
size=1,
|
|
||||||
provider_location='/dev/loop1')
|
|
||||||
TEST_VOLUME = obj_volume.Volume(name_id=fake.VOLUME2_NAME_ID,
|
|
||||||
size=1,
|
|
||||||
display_name='vol1')
|
|
||||||
|
|
||||||
with mock.patch.object(self.drv, 'find_appropriate_size_device',
|
|
||||||
return_value='/dev/loop2') as fasd_mocked:
|
|
||||||
with mock.patch.object(self.drv, '_get_devices_sizes',
|
|
||||||
return_value={'/dev/loop2': 2}) as \
|
|
||||||
gds_mocked:
|
|
||||||
with mock.patch.object(self.drv, 'local_path',
|
|
||||||
return_value='/dev/loop1') as \
|
|
||||||
lp_mocked:
|
|
||||||
with mock.patch.object(self.drv,
|
|
||||||
'_update_provider_location') as \
|
|
||||||
upl_mocked:
|
|
||||||
volutils.copy_volume('/dev/loop1', fasd_mocked, 2,
|
|
||||||
mock.sentinel,
|
|
||||||
execute=self.drv._execute)
|
|
||||||
self.drv.create_cloned_volume(TEST_VOLUME, TEST_SRC)
|
|
||||||
fasd_mocked.assert_called_once_with(TEST_SRC.size)
|
|
||||||
lp_mocked.assert_called_once_with(TEST_SRC)
|
|
||||||
gds_mocked.assert_called_once_with(['/dev/loop2'])
|
|
||||||
upl_mocked.assert_called_once_with(
|
|
||||||
TEST_VOLUME, '/dev/loop2')
|
|
||||||
|
|
||||||
@mock.patch.object(cinder.image.image_utils, 'fetch_to_raw')
|
|
||||||
def test_copy_image_to_volume(self, _fetch_to_raw):
|
|
||||||
TEST_VOLUME = obj_volume.Volume(name_id=fake.VOLUME_NAME_ID,
|
|
||||||
size=1,
|
|
||||||
provider_location='/dev/loop1')
|
|
||||||
TEST_IMAGE_SERVICE = "image_service"
|
|
||||||
TEST_IMAGE_ID = "image_id"
|
|
||||||
|
|
||||||
with mock.patch.object(self.drv, 'local_path',
|
|
||||||
return_value='/dev/loop1') as lp_mocked:
|
|
||||||
self.drv.copy_image_to_volume(context, TEST_VOLUME,
|
|
||||||
TEST_IMAGE_SERVICE, TEST_IMAGE_ID)
|
|
||||||
lp_mocked.assert_called_once_with(TEST_VOLUME)
|
|
||||||
|
|
||||||
_fetch_to_raw.assert_called_once_with(context, TEST_IMAGE_SERVICE,
|
|
||||||
TEST_IMAGE_ID, '/dev/loop1',
|
|
||||||
1234, size=1)
|
|
||||||
|
|
||||||
def test_copy_volume_to_image(self):
|
|
||||||
TEST_VOLUME = {'provider_location': '/dev/loop1'}
|
|
||||||
TEST_IMAGE_SERVICE = "image_service"
|
|
||||||
TEST_IMAGE_META = "image_meta"
|
|
||||||
|
|
||||||
with mock.patch.object(cinder.image.image_utils, 'upload_volume') as \
|
|
||||||
_upload_volume:
|
|
||||||
with mock.patch.object(self.drv, 'local_path') as _local_path:
|
|
||||||
_local_path.return_value = '/dev/loop1'
|
|
||||||
self.drv.copy_volume_to_image(context, TEST_VOLUME,
|
|
||||||
TEST_IMAGE_SERVICE,
|
|
||||||
TEST_IMAGE_META)
|
|
||||||
|
|
||||||
self.assertTrue(_local_path.called)
|
|
||||||
_upload_volume.assert_called_once_with(context,
|
|
||||||
TEST_IMAGE_SERVICE,
|
|
||||||
TEST_IMAGE_META,
|
|
||||||
'/dev/loop1')
|
|
||||||
|
|
||||||
def test_get_used_devices(self):
|
|
||||||
TEST_VOLUME1 = {'host': 'localhost',
|
|
||||||
'provider_location': '/dev/loop1'}
|
|
||||||
TEST_VOLUME2 = {'host': 'localhost',
|
|
||||||
'provider_location': '/dev/loop2'}
|
|
||||||
|
|
||||||
def fake_local_path(vol):
|
|
||||||
return vol['provider_location'].split()[-1]
|
|
||||||
|
|
||||||
with mock.patch.object(obj_volume.VolumeList, 'get_all_by_host',
|
|
||||||
return_value=[TEST_VOLUME1, TEST_VOLUME2]),\
|
|
||||||
mock.patch.object(obj_snap.SnapshotList, 'get_by_host',
|
|
||||||
return_value=[]):
|
|
||||||
with mock.patch.object(context, 'get_admin_context'):
|
|
||||||
with mock.patch.object(self.drv, 'local_path',
|
|
||||||
return_value=fake_local_path):
|
|
||||||
path1 = self.drv.local_path(TEST_VOLUME1)
|
|
||||||
path2 = self.drv.local_path(TEST_VOLUME2)
|
|
||||||
self.assertEqual(set([path1, path2]),
|
|
||||||
self.drv._get_used_devices())
|
|
||||||
|
|
||||||
def test_get_devices_sizes(self):
|
|
||||||
dev_paths = ['/dev/loop1', '/dev/loop2', '/dev/loop3']
|
|
||||||
out = '4294967296\n2147483648\n3221225472\nn'
|
|
||||||
with mock.patch.object(self.drv,
|
|
||||||
'_execute',
|
|
||||||
return_value=(out, None)) as _execute:
|
|
||||||
actual = self.drv._get_devices_sizes(dev_paths)
|
|
||||||
self.assertEqual(3, len(actual))
|
|
||||||
self.assertEqual({'/dev/loop1': 4096, '/dev/loop2': 2048,
|
|
||||||
'/dev/loop3': 3072}, actual)
|
|
||||||
_execute.assert_called_once_with('blockdev', '--getsize64',
|
|
||||||
*dev_paths, run_as_root=True)
|
|
||||||
|
|
||||||
def test_devices_sizes(self):
|
|
||||||
with mock.patch.object(self.drv, '_get_devices_sizes') as \
|
|
||||||
_get_dvc_size:
|
|
||||||
_get_dvc_size.return_value = {'/dev/loop1': 1, '/dev/loop2': 1}
|
|
||||||
self.assertEqual(2, len(self.drv._devices_sizes()))
|
|
||||||
self.assertEqual({'/dev/loop1': 1, '/dev/loop2': 1},
|
|
||||||
self.drv._devices_sizes())
|
|
||||||
|
|
||||||
def test_find_appropriate_size_device_no_free_disks(self):
|
|
||||||
size = 1
|
|
||||||
with mock.patch.object(self.drv, '_devices_sizes') as _dvc_sizes:
|
|
||||||
with mock.patch.object(self.drv, '_get_used_devices') as \
|
|
||||||
_get_used_dvc:
|
|
||||||
_dvc_sizes.return_value = {'/dev/loop1': 1,
|
|
||||||
'/dev/loop2': 1}
|
|
||||||
_get_used_dvc.return_value = set(['/dev/loop1', '/dev/loop2'])
|
|
||||||
self.assertRaises(cinder.exception.CinderException,
|
|
||||||
self.drv.find_appropriate_size_device, size)
|
|
||||||
|
|
||||||
def test_find_appropriate_size_device_not_big_enough_disk(self):
|
|
||||||
size = 2948
|
|
||||||
with mock.patch.object(self.drv, '_devices_sizes') as _dvc_sizes:
|
|
||||||
with mock.patch.object(self.drv, '_get_used_devices') as \
|
|
||||||
_get_used_dvc:
|
|
||||||
_dvc_sizes.return_value = {'/dev/loop1': 1024,
|
|
||||||
'/dev/loop2': 1924}
|
|
||||||
_get_used_dvc.return_value = set(['/dev/loop1'])
|
|
||||||
self.assertRaises(cinder.exception.CinderException,
|
|
||||||
self.drv.find_appropriate_size_device, size)
|
|
||||||
|
|
||||||
def test_find_appropriate_size_device(self):
|
|
||||||
size = 1
|
|
||||||
with mock.patch.object(self.drv, '_devices_sizes') as _dvc_sizes:
|
|
||||||
with mock.patch.object(self.drv, '_get_used_devices') as \
|
|
||||||
_get_used_dvc:
|
|
||||||
_dvc_sizes.return_value = {'/dev/loop1': 2048,
|
|
||||||
'/dev/loop2': 1024}
|
|
||||||
_get_used_dvc.return_value = set()
|
|
||||||
self.assertEqual('/dev/loop2',
|
|
||||||
self.drv.find_appropriate_size_device(size))
|
|
||||||
|
|
||||||
def test_extend_volume_exists(self):
|
|
||||||
TEST_VOLUME = {'name': 'vol1', 'id': 123}
|
|
||||||
with mock.patch.object(self.drv, '_get_devices_sizes',
|
|
||||||
return_value={'/dev/loop1': 1024}) as \
|
|
||||||
mock_get_size:
|
|
||||||
with mock.patch.object(self.drv, 'local_path',
|
|
||||||
return_value='/dev/loop1') as lp_mocked:
|
|
||||||
self.assertRaises(cinder.exception.CinderException,
|
|
||||||
self.drv.extend_volume, TEST_VOLUME, 2)
|
|
||||||
lp_mocked.assert_called_once_with(TEST_VOLUME)
|
|
||||||
mock_get_size.assert_called_once_with(['/dev/loop1'])
|
|
||||||
|
|
||||||
@mock.patch('cinder.volume.utils.copy_volume')
|
|
||||||
def test_create_snapshot(self, _copy_volume):
|
|
||||||
TEST_VOLUME = obj_volume.Volume(id=fake.VOLUME_ID,
|
|
||||||
name_id=fake.VOLUME_NAME_ID,
|
|
||||||
size=1,
|
|
||||||
display_name='vol1',
|
|
||||||
status='available',
|
|
||||||
provider_location='/dev/loop1')
|
|
||||||
TEST_SNAP = obj_snap.Snapshot(id=fake.SNAPSHOT_ID,
|
|
||||||
volume_id=fake.VOLUME_ID,
|
|
||||||
volume_size=1024,
|
|
||||||
provider_location='/dev/loop2',
|
|
||||||
volume=TEST_VOLUME)
|
|
||||||
|
|
||||||
with mock.patch.object(self.drv, 'find_appropriate_size_device',
|
|
||||||
return_value='/dev/loop2') as fasd_mocked:
|
|
||||||
with mock.patch.object(self.drv, '_get_devices_sizes',
|
|
||||||
return_value={'/dev/loop2': 1024}) as \
|
|
||||||
gds_mocked:
|
|
||||||
with mock.patch.object(self.drv,
|
|
||||||
'_update_provider_location') as \
|
|
||||||
upl_mocked:
|
|
||||||
volutils.copy_volume('/dev/loop1', fasd_mocked, 1024,
|
|
||||||
mock.sentinel,
|
|
||||||
execute=self.drv._execute)
|
|
||||||
self.drv.create_snapshot(TEST_SNAP)
|
|
||||||
fasd_mocked.assert_called_once_with(TEST_SNAP.volume_size)
|
|
||||||
gds_mocked.assert_called_once_with(['/dev/loop2'])
|
|
||||||
upl_mocked.assert_called_once_with(
|
|
||||||
TEST_SNAP, '/dev/loop2')
|
|
||||||
|
|
||||||
def test_create_snapshot_with_not_available_volume(self):
|
|
||||||
TEST_VOLUME = obj_volume.Volume(id=fake.VOLUME_ID,
|
|
||||||
name_id=fake.VOLUME_NAME_ID,
|
|
||||||
size=1,
|
|
||||||
display_name='vol1',
|
|
||||||
status='in use',
|
|
||||||
provider_location='/dev/loop1')
|
|
||||||
TEST_SNAP = obj_snap.Snapshot(id=fake.SNAPSHOT_ID,
|
|
||||||
volume_id=fake.VOLUME_ID,
|
|
||||||
volume_size=1024,
|
|
||||||
provider_location='/dev/loop2',
|
|
||||||
volume=TEST_VOLUME)
|
|
||||||
|
|
||||||
self.assertRaises(cinder.exception.CinderException,
|
|
||||||
self.drv.create_snapshot, TEST_SNAP)
|
|
||||||
|
|
||||||
@mock.patch('cinder.volume.utils.copy_volume')
|
|
||||||
def test_create_volume_from_snapshot(self, _copy_volume):
|
|
||||||
TEST_SNAP = obj_snap.Snapshot(volume_id=fake.VOLUME_ID,
|
|
||||||
volume_size=1024,
|
|
||||||
provider_location='/dev/loop1')
|
|
||||||
TEST_VOLUME = obj_volume.Volume(id=fake.VOLUME_ID,
|
|
||||||
name_id=fake.VOLUME_NAME_ID,
|
|
||||||
size=1,
|
|
||||||
display_name='vol1',
|
|
||||||
provider_location='/dev/loop2')
|
|
||||||
|
|
||||||
with mock.patch.object(self.drv, 'find_appropriate_size_device',
|
|
||||||
return_value='/dev/loop2') as fasd_mocked:
|
|
||||||
with mock.patch.object(self.drv, '_get_devices_sizes',
|
|
||||||
return_value={'/dev/loop2': 1024}) as \
|
|
||||||
gds_mocked:
|
|
||||||
with mock.patch.object(self.drv,
|
|
||||||
'_update_provider_location') as \
|
|
||||||
upl_mocked:
|
|
||||||
volutils.copy_volume('/dev/loop1', fasd_mocked, 1024,
|
|
||||||
mock.sentinel,
|
|
||||||
execute=self.drv._execute)
|
|
||||||
self.drv.create_volume_from_snapshot(
|
|
||||||
TEST_VOLUME, TEST_SNAP)
|
|
||||||
fasd_mocked.assert_called_once_with(
|
|
||||||
TEST_SNAP.volume_size)
|
|
||||||
gds_mocked.assert_called_once_with(['/dev/loop2'])
|
|
||||||
upl_mocked.assert_called_once_with(
|
|
||||||
TEST_VOLUME, '/dev/loop2')
|
|
||||||
|
|
||||||
@mock.patch('os.path.exists', return_value=True)
|
|
||||||
@mock.patch('cinder.volume.utils.clear_volume')
|
|
||||||
def test_delete_snapshot(self, _clear_volume, _exists):
|
|
||||||
TEST_SNAP = obj_snap.Snapshot(volume_id=fake.VOLUME_ID,
|
|
||||||
provider_location='/dev/loop1',
|
|
||||||
status=fields.SnapshotStatus.AVAILABLE)
|
|
||||||
|
|
||||||
with mock.patch.object(self.drv, 'local_path',
|
|
||||||
return_value='/dev/loop1') as lp_mocked:
|
|
||||||
with mock.patch.object(self.drv, '_get_devices_sizes',
|
|
||||||
return_value={'/dev/loop1': 1}) as \
|
|
||||||
gds_mocked:
|
|
||||||
volutils.clear_volume(gds_mocked, lp_mocked)
|
|
||||||
self.drv.delete_snapshot(TEST_SNAP)
|
|
||||||
lp_mocked.assert_called_once_with(TEST_SNAP)
|
|
||||||
gds_mocked.assert_called_once_with(['/dev/loop1'])
|
|
||||||
|
|
||||||
self.assertTrue(_exists.called)
|
|
||||||
self.assertTrue(_clear_volume.called)
|
|
@ -1,326 +0,0 @@
|
|||||||
# Copyright (c) 2013 Mirantis, Inc.
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_log import versionutils
|
|
||||||
from oslo_utils import importutils
|
|
||||||
from oslo_utils import units
|
|
||||||
|
|
||||||
from cinder import context
|
|
||||||
from cinder import exception
|
|
||||||
from cinder.i18n import _
|
|
||||||
from cinder.image import image_utils
|
|
||||||
from cinder import interface
|
|
||||||
from cinder import objects
|
|
||||||
from cinder import utils
|
|
||||||
from cinder.volume import configuration
|
|
||||||
from cinder.volume import driver
|
|
||||||
from cinder.volume import utils as volutils
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
volume_opts = [
|
|
||||||
cfg.ListOpt('available_devices',
|
|
||||||
default=[],
|
|
||||||
help='List of all available devices'),
|
|
||||||
]
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CONF.register_opts(volume_opts, group=configuration.SHARED_CONF_GROUP)
|
|
||||||
|
|
||||||
|
|
||||||
@interface.volumedriver
|
|
||||||
class BlockDeviceDriver(driver.BaseVD,
|
|
||||||
driver.CloneableImageVD):
|
|
||||||
VERSION = '2.3.0'
|
|
||||||
|
|
||||||
# ThirdPartySystems wiki page
|
|
||||||
CI_WIKI_NAME = "Cinder_Jenkins"
|
|
||||||
SUPPORTED = False
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(BlockDeviceDriver, self).__init__(*args, **kwargs)
|
|
||||||
# This driver has been marked as deprecated in the Ocata release, as
|
|
||||||
# per the standard OpenStack deprecation policy it can be removed in
|
|
||||||
# the Queens release.
|
|
||||||
msg = _("The block_device driver is deprecated and will be "
|
|
||||||
"removed in a future release.")
|
|
||||||
versionutils.report_deprecated_feature(LOG, msg)
|
|
||||||
|
|
||||||
self.configuration.append_config_values(volume_opts)
|
|
||||||
self.backend_name = \
|
|
||||||
self.configuration.safe_get('volume_backend_name') or "BlockDev"
|
|
||||||
target_driver =\
|
|
||||||
self.target_mapping[self.configuration.safe_get('iscsi_helper')]
|
|
||||||
self.target_driver = importutils.import_object(
|
|
||||||
target_driver,
|
|
||||||
configuration=self.configuration,
|
|
||||||
db=self.db,
|
|
||||||
executor=self._execute)
|
|
||||||
|
|
||||||
def check_for_setup_error(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _update_provider_location(self, obj, device):
|
|
||||||
# We update provider_location and host to mark device as used to
|
|
||||||
# avoid race with other threads.
|
|
||||||
# TODO(ynesenenko): need to remove DB access from driver
|
|
||||||
host = '{host}#{pool}'.format(host=self.host, pool=self.get_pool(obj))
|
|
||||||
obj.update({'provider_location': device, 'host': host})
|
|
||||||
obj.save()
|
|
||||||
|
|
||||||
@utils.synchronized('block_device', external=True)
|
|
||||||
def create_volume(self, volume):
|
|
||||||
device = self.find_appropriate_size_device(volume.size)
|
|
||||||
LOG.info("Creating %(volume)s on %(device)s",
|
|
||||||
{"volume": volume.name, "device": device})
|
|
||||||
self._update_provider_location(volume, device)
|
|
||||||
|
|
||||||
def delete_volume(self, volume):
|
|
||||||
"""Deletes a logical volume."""
|
|
||||||
self._clear_block_device(volume)
|
|
||||||
|
|
||||||
def _clear_block_device(self, device):
|
|
||||||
"""Deletes a block device."""
|
|
||||||
dev_path = self.local_path(device)
|
|
||||||
if not dev_path or dev_path not in \
|
|
||||||
self.configuration.available_devices:
|
|
||||||
return
|
|
||||||
if os.path.exists(dev_path) and \
|
|
||||||
self.configuration.volume_clear != 'none':
|
|
||||||
dev_size = self._get_devices_sizes([dev_path])
|
|
||||||
volutils.clear_volume(
|
|
||||||
dev_size[dev_path], dev_path,
|
|
||||||
volume_clear=self.configuration.volume_clear,
|
|
||||||
volume_clear_size=self.configuration.volume_clear_size)
|
|
||||||
else:
|
|
||||||
LOG.warning("The device %s won't be cleared.", device)
|
|
||||||
|
|
||||||
if device.status == "error_deleting":
|
|
||||||
msg = _("Failed to delete device.")
|
|
||||||
LOG.error(msg, resource=device)
|
|
||||||
raise exception.VolumeDriverException(msg)
|
|
||||||
|
|
||||||
def local_path(self, device):
|
|
||||||
if device.provider_location:
|
|
||||||
path = device.provider_location.rsplit(" ", 1)
|
|
||||||
return path[-1]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def copy_image_to_volume(self, context, volume, image_service, image_id):
|
|
||||||
"""Fetch the image from image_service and write it to the volume."""
|
|
||||||
image_utils.fetch_to_raw(context,
|
|
||||||
image_service,
|
|
||||||
image_id,
|
|
||||||
self.local_path(volume),
|
|
||||||
self.configuration.volume_dd_blocksize,
|
|
||||||
size=volume.size)
|
|
||||||
|
|
||||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
|
||||||
"""Copy the volume to the specified image."""
|
|
||||||
image_utils.upload_volume(context,
|
|
||||||
image_service,
|
|
||||||
image_meta,
|
|
||||||
self.local_path(volume))
|
|
||||||
|
|
||||||
@utils.synchronized('block_device', external=True)
|
|
||||||
def create_cloned_volume(self, volume, src_vref):
|
|
||||||
LOG.info('Creating clone of volume: %s.', src_vref.id)
|
|
||||||
device = self.find_appropriate_size_device(src_vref.size)
|
|
||||||
dev_size = self._get_devices_sizes([device])
|
|
||||||
volutils.copy_volume(
|
|
||||||
self.local_path(src_vref), device,
|
|
||||||
dev_size[device],
|
|
||||||
self.configuration.volume_dd_blocksize,
|
|
||||||
execute=self._execute)
|
|
||||||
self._update_provider_location(volume, device)
|
|
||||||
|
|
||||||
def get_volume_stats(self, refresh=False):
|
|
||||||
if refresh:
|
|
||||||
self._update_volume_stats()
|
|
||||||
return self._stats
|
|
||||||
|
|
||||||
def _update_volume_stats(self):
|
|
||||||
"""Retrieve stats info from volume group."""
|
|
||||||
dict_of_devices_sizes = self._devices_sizes()
|
|
||||||
used_devices = self._get_used_devices()
|
|
||||||
total_size = 0
|
|
||||||
free_size = 0
|
|
||||||
for device, size in dict_of_devices_sizes.items():
|
|
||||||
if device not in used_devices:
|
|
||||||
free_size += size
|
|
||||||
total_size += size
|
|
||||||
|
|
||||||
LOG.debug("Updating volume stats.")
|
|
||||||
data = {
|
|
||||||
'volume_backend_name': self.backend_name,
|
|
||||||
'vendor_name': "Open Source",
|
|
||||||
'driver_version': self.VERSION,
|
|
||||||
'storage_protocol': 'unknown',
|
|
||||||
'pools': []}
|
|
||||||
|
|
||||||
single_pool = {
|
|
||||||
'pool_name': data['volume_backend_name'],
|
|
||||||
'total_capacity_gb': total_size / units.Ki,
|
|
||||||
'free_capacity_gb': free_size / units.Ki,
|
|
||||||
'reserved_percentage': self.configuration.reserved_percentage,
|
|
||||||
'QoS_support': False}
|
|
||||||
|
|
||||||
data['pools'].append(single_pool)
|
|
||||||
self._stats = data
|
|
||||||
|
|
||||||
def get_pool(self, volume):
|
|
||||||
return self.backend_name
|
|
||||||
|
|
||||||
def _get_used_paths(self, lst):
|
|
||||||
used_dev = set()
|
|
||||||
for item in lst:
|
|
||||||
local_path = self.local_path(item)
|
|
||||||
if local_path:
|
|
||||||
used_dev.add(local_path)
|
|
||||||
return used_dev
|
|
||||||
|
|
||||||
def _get_used_devices(self):
|
|
||||||
lst = objects.VolumeList.get_all_by_host(context.get_admin_context(),
|
|
||||||
self.host)
|
|
||||||
used_devices = self._get_used_paths(lst)
|
|
||||||
snp_lst = objects.SnapshotList.get_by_host(context.get_admin_context(),
|
|
||||||
self.host)
|
|
||||||
return used_devices.union(self._get_used_paths(snp_lst))
|
|
||||||
|
|
||||||
def _get_devices_sizes(self, dev_paths):
|
|
||||||
"""Return devices' sizes in Mb"""
|
|
||||||
out, _err = self._execute('blockdev', '--getsize64', *dev_paths,
|
|
||||||
run_as_root=True)
|
|
||||||
dev_sizes = {}
|
|
||||||
out = out.split('\n')
|
|
||||||
# blockdev returns devices' sizes in order that
|
|
||||||
# they have been passed to it.
|
|
||||||
for n, size in enumerate(out[:-1]):
|
|
||||||
dev_sizes[dev_paths[n]] = int(size) / units.Mi
|
|
||||||
|
|
||||||
return dev_sizes
|
|
||||||
|
|
||||||
def _devices_sizes(self):
|
|
||||||
available_devices = self.configuration.available_devices
|
|
||||||
return self._get_devices_sizes(available_devices)
|
|
||||||
|
|
||||||
def find_appropriate_size_device(self, size):
|
|
||||||
dict_of_devices_sizes = self._devices_sizes()
|
|
||||||
free_devices = (set(self.configuration.available_devices) -
|
|
||||||
self._get_used_devices())
|
|
||||||
if not free_devices:
|
|
||||||
raise exception.CinderException(_("No free disk"))
|
|
||||||
possible_device = None
|
|
||||||
possible_device_size = None
|
|
||||||
for device in free_devices:
|
|
||||||
dev_size = dict_of_devices_sizes[device]
|
|
||||||
if (size * units.Ki <= dev_size and
|
|
||||||
(possible_device is None or
|
|
||||||
dev_size < possible_device_size)):
|
|
||||||
possible_device = device
|
|
||||||
possible_device_size = dev_size
|
|
||||||
|
|
||||||
if possible_device:
|
|
||||||
return possible_device
|
|
||||||
else:
|
|
||||||
raise exception.CinderException(_("No big enough free disk"))
|
|
||||||
|
|
||||||
def extend_volume(self, volume, new_size):
|
|
||||||
dev_path = self.local_path(volume)
|
|
||||||
total_size = self._get_devices_sizes([dev_path])
|
|
||||||
# Convert from Megabytes to Gigabytes
|
|
||||||
size = total_size[dev_path] / units.Ki
|
|
||||||
if size < new_size:
|
|
||||||
msg = _("Insufficient free space available to extend volume.")
|
|
||||||
LOG.error(msg, resource=volume)
|
|
||||||
raise exception.CinderException(msg)
|
|
||||||
|
|
||||||
@utils.synchronized('block_device', external=True)
|
|
||||||
def create_snapshot(self, snapshot):
|
|
||||||
volume = snapshot.volume
|
|
||||||
if volume.status != 'available':
|
|
||||||
msg = _("Volume is not available.")
|
|
||||||
LOG.error(msg, resource=volume)
|
|
||||||
raise exception.CinderException(msg)
|
|
||||||
|
|
||||||
LOG.info('Creating volume snapshot: %s.', snapshot.id)
|
|
||||||
device = self.find_appropriate_size_device(snapshot.volume_size)
|
|
||||||
dev_size = self._get_devices_sizes([device])
|
|
||||||
volutils.copy_volume(
|
|
||||||
self.local_path(volume), device,
|
|
||||||
dev_size[device],
|
|
||||||
self.configuration.volume_dd_blocksize,
|
|
||||||
execute=self._execute)
|
|
||||||
self._update_provider_location(snapshot, device)
|
|
||||||
|
|
||||||
def delete_snapshot(self, snapshot):
|
|
||||||
self._clear_block_device(snapshot)
|
|
||||||
|
|
||||||
@utils.synchronized('block_device', external=True)
|
|
||||||
def create_volume_from_snapshot(self, volume, snapshot):
|
|
||||||
LOG.info('Creating volume %s from snapshot.', volume.id)
|
|
||||||
device = self.find_appropriate_size_device(snapshot.volume_size)
|
|
||||||
dev_size = self._get_devices_sizes([device])
|
|
||||||
volutils.copy_volume(
|
|
||||||
self.local_path(snapshot), device,
|
|
||||||
dev_size[device],
|
|
||||||
self.configuration.volume_dd_blocksize,
|
|
||||||
execute=self._execute)
|
|
||||||
self._update_provider_location(volume, device)
|
|
||||||
|
|
||||||
# ####### Interface methods for DataPath (Target Driver) ########
|
|
||||||
|
|
||||||
def ensure_export(self, context, volume):
|
|
||||||
volume_path = self.local_path(volume)
|
|
||||||
model_update = \
|
|
||||||
self.target_driver.ensure_export(
|
|
||||||
context,
|
|
||||||
volume,
|
|
||||||
volume_path)
|
|
||||||
return model_update
|
|
||||||
|
|
||||||
def create_export(self, context, volume, connector):
|
|
||||||
volume_path = self.local_path(volume)
|
|
||||||
export_info = self.target_driver.create_export(context,
|
|
||||||
volume,
|
|
||||||
volume_path)
|
|
||||||
return {
|
|
||||||
'provider_location': export_info['location'] + ' ' + volume_path,
|
|
||||||
'provider_auth': export_info['auth'],
|
|
||||||
}
|
|
||||||
|
|
||||||
def remove_export(self, context, volume):
|
|
||||||
self.target_driver.remove_export(context, volume)
|
|
||||||
|
|
||||||
def initialize_connection(self, volume, connector):
|
|
||||||
if connector['host'] != volutils.extract_host(volume.host, 'host'):
|
|
||||||
return self.target_driver.initialize_connection(volume, connector)
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
'driver_volume_type': 'local',
|
|
||||||
'data': {'device_path': self.local_path(volume)},
|
|
||||||
}
|
|
||||||
|
|
||||||
def validate_connector(self, connector):
|
|
||||||
return self.target_driver.validate_connector(connector)
|
|
||||||
|
|
||||||
def terminate_connection(self, volume, connector, **kwargs):
|
|
||||||
pass
|
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
BlockDeviceDriver was deprecated in Ocata release and marked as
|
||||||
|
'unsupported'. There is no CI for it too. If you used this driver before
|
||||||
|
you have to migrate your volumes to LVM with LIO target yourself before
|
||||||
|
upgrading to Queens release to get your volumes working.
|
Loading…
x
Reference in New Issue
Block a user