Revert "Remove the Veritas Access Driver"
The driver removal policy was relaxed in January 2020 [0] to allow
unsupported drivers to remain in-tree at the discretion of the Cinder
project team. Thus this driver, which was marked unsupported in
Train and removed early in Ussuri, is being restored. It remains
deprecated and subject to removal should its presence affect the
gate adversely.
[0] https://docs.openstack.org/cinder/latest/drivers-all-about.html#driver-removal
This reverts commit 5649da137b
.
Change-Id: I08a58ea6bb6340c5e09f4dbfa242f9b4a65ae05c
Partially-implements: bp restore-unsupported-drivers
This commit is contained in:
parent
1d3fa89752
commit
207bead25f
@ -145,6 +145,8 @@ from cinder.volume.drivers.stx import common as \
|
||||
cinder_volume_drivers_stx_common
|
||||
from cinder.volume.drivers.synology import synology_common as \
|
||||
cinder_volume_drivers_synology_synologycommon
|
||||
from cinder.volume.drivers.veritas_access import veritas_iscsi as \
|
||||
cinder_volume_drivers_veritas_access_veritasiscsi
|
||||
from cinder.volume.drivers.vmware import vmdk as \
|
||||
cinder_volume_drivers_vmware_vmdk
|
||||
from cinder.volume.drivers.windows import iscsi as \
|
||||
@ -253,6 +255,7 @@ def list_opts():
|
||||
instorage_mcs_opts,
|
||||
cinder_volume_drivers_inspur_instorage_instorageiscsi.
|
||||
instorage_mcs_iscsi_opts,
|
||||
cinder_volume_drivers_veritas_access_veritasiscsi.VA_VOL_OPTS,
|
||||
cinder_volume_manager.volume_manager_opts,
|
||||
cinder_wsgi_eventletserver.socket_opts,
|
||||
)),
|
||||
|
@ -0,0 +1,659 @@
|
||||
# Copyright 2017 Veritas Technologies LLC.
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
Unit tests for Veritas Access cinder driver.
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
import tempfile
|
||||
from unittest import mock
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
from oslo_config import cfg
|
||||
import requests
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.veritas_access import veritas_iscsi
|
||||
|
||||
CONF = cfg.CONF
|
||||
FAKE_BACKEND = 'fake_backend'
|
||||
|
||||
|
||||
class MockResponse(object):
|
||||
def __init__(self):
|
||||
self.status_code = 200
|
||||
|
||||
def json(self):
|
||||
data = {'fake_key': 'fake_val'}
|
||||
return json.dumps(data)
|
||||
|
||||
|
||||
class FakeXML(object):
|
||||
|
||||
def __init__(self):
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
|
||||
def create_vrts_fake_config_file(self):
|
||||
|
||||
target = 'iqn.2017-02.com.veritas:faketarget'
|
||||
portal = '1.1.1.1'
|
||||
auth_detail = '0'
|
||||
doc = Document()
|
||||
|
||||
vrts_node = doc.createElement("VRTS")
|
||||
doc.appendChild(vrts_node)
|
||||
|
||||
vrts_target_node = doc.createElement("VrtsTargets")
|
||||
vrts_node.appendChild(vrts_target_node)
|
||||
|
||||
target_node = doc.createElement("Target")
|
||||
|
||||
vrts_target_node.appendChild(target_node)
|
||||
|
||||
name_ele = doc.createElement("Name")
|
||||
portal_ele = doc.createElement("PortalIP")
|
||||
auth_ele = doc.createElement("Authentication")
|
||||
|
||||
name_ele.appendChild(doc.createTextNode(target))
|
||||
portal_ele.appendChild(doc.createTextNode(portal))
|
||||
auth_ele.appendChild(doc.createTextNode(auth_detail))
|
||||
|
||||
target_node.appendChild(name_ele)
|
||||
target_node.appendChild(portal_ele)
|
||||
target_node.appendChild(auth_ele)
|
||||
|
||||
filename = 'vrts_config.xml'
|
||||
config_file_path = self.tempdir + '/' + filename
|
||||
|
||||
f = open(config_file_path, 'w')
|
||||
doc.writexml(f)
|
||||
f.close()
|
||||
return config_file_path
|
||||
|
||||
|
||||
class fake_volume(object):
|
||||
def __init__(self):
|
||||
self.id = 'fakeid'
|
||||
self.name = 'fakename'
|
||||
self.size = 1
|
||||
self.snapshot_id = False
|
||||
self.metadata = {'dense': True}
|
||||
|
||||
|
||||
class fake_volume2(object):
|
||||
def __init__(self):
|
||||
self.id = 'fakeid2'
|
||||
self.name = 'fakename2'
|
||||
self.size = 2
|
||||
self.snapshot_id = False
|
||||
self.metadata = {'dense': True}
|
||||
|
||||
|
||||
class fake_clone_volume(object):
|
||||
def __init__(self):
|
||||
self.id = 'fakecloneid'
|
||||
self.name = 'fakeclonename'
|
||||
self.size = 1
|
||||
self.snapshot_id = False
|
||||
|
||||
|
||||
class fake_clone_volume2(object):
|
||||
def __init__(self):
|
||||
self.id = 'fakecloneid2'
|
||||
self.name = 'fakeclonename'
|
||||
self.size = 2
|
||||
self.snapshot_id = False
|
||||
|
||||
|
||||
class fake_snapshot(object):
|
||||
def __init__(self):
|
||||
self.id = 'fakeid'
|
||||
self.volume_id = 'fakevolumeid'
|
||||
self.volume_size = 1
|
||||
|
||||
|
||||
class ACCESSIscsiDriverTestCase(test.TestCase):
|
||||
"""Tests ACCESSShareDriver."""
|
||||
|
||||
volume = fake_volume()
|
||||
volume2 = fake_volume2()
|
||||
snapshot = fake_snapshot()
|
||||
clone_volume = fake_clone_volume()
|
||||
clone_volume2 = fake_clone_volume2()
|
||||
connector = {
|
||||
'initiator': 'iqn.1994-05.com.fakeinitiator'
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(ACCESSIscsiDriverTestCase, self).setUp()
|
||||
self._create_fake_config()
|
||||
lcfg = self.configuration
|
||||
self._context = context.get_admin_context()
|
||||
self._driver = veritas_iscsi.ACCESSIscsiDriver(configuration=lcfg)
|
||||
self._driver.do_setup(self._context)
|
||||
|
||||
def _create_fake_config(self):
|
||||
self.mock_object(veritas_iscsi.ACCESSIscsiDriver,
|
||||
'_authenticate_access')
|
||||
self.configuration = mock.Mock(spec=conf.Configuration)
|
||||
self.configuration.safe_get = self.fake_safe_get
|
||||
self.configuration.san_ip = '1.1.1.1'
|
||||
self.configuration.san_login = 'user'
|
||||
self.configuration.san_password = 'passwd'
|
||||
self.configuration.san_api_port = 14161
|
||||
self.configuration.vrts_lun_sparse = True
|
||||
self.configuration.vrts_target_config = (
|
||||
FakeXML().create_vrts_fake_config_file())
|
||||
self.configuration.target_port = 3260
|
||||
self.configuration.volume_backend_name = FAKE_BACKEND
|
||||
|
||||
def fake_safe_get(self, value):
|
||||
try:
|
||||
val = getattr(self.configuration, value)
|
||||
except AttributeError:
|
||||
val = None
|
||||
return val
|
||||
|
||||
def test_create_volume(self):
|
||||
self.mock_object(self._driver, '_vrts_get_suitable_target')
|
||||
self.mock_object(self._driver, '_vrts_get_targets_store')
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
|
||||
mylist = []
|
||||
target = {}
|
||||
target['name'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
target['portal_ip'] = '1.1.1.1'
|
||||
target['auth'] = '0'
|
||||
mylist.append(target)
|
||||
|
||||
self._driver._vrts_get_suitable_target.return_value = (
|
||||
'iqn.2017-02.com.veritas:faketarget')
|
||||
self._driver._access_api.return_value = True
|
||||
return_list = self._driver._vrts_parse_xml_file(
|
||||
self.configuration.vrts_target_config)
|
||||
|
||||
self._driver.create_volume(self.volume)
|
||||
|
||||
self.assertEqual(mylist, return_list)
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
|
||||
def test_create_volume_negative(self):
|
||||
self.mock_object(self._driver, '_vrts_get_suitable_target')
|
||||
self.mock_object(self._driver, '_vrts_get_targets_store')
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
|
||||
self._driver._vrts_get_suitable_target.return_value = (
|
||||
'iqn.2017-02.com.veritas:faketarget')
|
||||
self._driver._access_api.return_value = False
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver.create_volume,
|
||||
self.volume)
|
||||
|
||||
def test_create_volume_negative_no_suitable_target_found(self):
|
||||
self.mock_object(self._driver, '_vrts_get_suitable_target')
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
|
||||
self._driver._vrts_get_suitable_target.return_value = False
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver.create_volume,
|
||||
self.volume)
|
||||
self.assertEqual(0, self._driver._access_api.call_count)
|
||||
|
||||
def test_delete_volume(self):
|
||||
self.mock_object(self._driver, '_get_vrts_lun_list')
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
|
||||
va_lun_name = self._driver._get_va_lun_name(self.volume.id)
|
||||
|
||||
length = len(self.volume.id)
|
||||
index = int(length / 2)
|
||||
name1 = self.volume.id[:index]
|
||||
name2 = self.volume.id[index:]
|
||||
crc1 = hashlib.md5(name1.encode('utf-8')).hexdigest()[:5]
|
||||
crc2 = hashlib.md5(name2.encode('utf-8')).hexdigest()[:5]
|
||||
|
||||
volume_name_to_ret = 'cinder' + '-' + crc1 + '-' + crc2
|
||||
|
||||
lun = {}
|
||||
lun['lun_name'] = va_lun_name
|
||||
lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
lun_list = {'output': {'output': {'luns': [lun]}}}
|
||||
self._driver._get_vrts_lun_list.return_value = lun_list
|
||||
|
||||
self._driver._access_api.return_value = True
|
||||
|
||||
self._driver.delete_volume(self.volume)
|
||||
self.assertEqual(volume_name_to_ret, va_lun_name)
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
|
||||
def test_delete_volume_negative(self):
|
||||
self.mock_object(self._driver, '_get_vrts_lun_list')
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
|
||||
self._driver._access_api.return_value = False
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver.delete_volume,
|
||||
self.volume)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
|
||||
self._driver._access_api.return_value = True
|
||||
|
||||
self._driver.create_snapshot(self.snapshot)
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
|
||||
def test_create_snapshot_negative(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
|
||||
self._driver._access_api.return_value = False
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver.create_snapshot,
|
||||
self.snapshot)
|
||||
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
|
||||
self._driver._access_api.return_value = True
|
||||
|
||||
self._driver.delete_snapshot(self.snapshot)
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
|
||||
def test_delete_snapshot_negative(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
|
||||
self._driver._access_api.return_value = False
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver.delete_snapshot,
|
||||
self.snapshot)
|
||||
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
|
||||
def test_create_cloned_volume(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_vrts_extend_lun')
|
||||
self.mock_object(self._driver, '_get_vrts_lun_list')
|
||||
self.mock_object(self._driver, '_vrts_get_fs_list')
|
||||
self.mock_object(self._driver, '_vrts_is_space_available_in_store')
|
||||
|
||||
va_lun_name = self._driver._get_va_lun_name(self.volume.id)
|
||||
|
||||
lun = {}
|
||||
lun['lun_name'] = va_lun_name
|
||||
lun['fs_name'] = 'fake_fs'
|
||||
lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
lun_list = {'output': {'output': {'luns': [lun]}}}
|
||||
|
||||
self._driver._get_vrts_lun_list.return_value = lun_list
|
||||
|
||||
self._driver._vrts_is_space_available_in_store.return_value = True
|
||||
|
||||
self._driver._access_api.return_value = True
|
||||
|
||||
self._driver.create_cloned_volume(self.clone_volume, self.volume)
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
self.assertEqual(0, self._driver._vrts_extend_lun.call_count)
|
||||
|
||||
def test_create_cloned_volume_of_greater_size(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_vrts_extend_lun')
|
||||
self.mock_object(self._driver, '_get_vrts_lun_list')
|
||||
self.mock_object(self._driver, '_vrts_get_fs_list')
|
||||
self.mock_object(self._driver, '_vrts_is_space_available_in_store')
|
||||
|
||||
va_lun_name = self._driver._get_va_lun_name(self.volume.id)
|
||||
|
||||
lun = {}
|
||||
lun['lun_name'] = va_lun_name
|
||||
lun['fs_name'] = 'fake_fs'
|
||||
lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
lun_list = {'output': {'output': {'luns': [lun]}}}
|
||||
|
||||
self._driver._get_vrts_lun_list.return_value = lun_list
|
||||
|
||||
self._driver._vrts_is_space_available_in_store.return_value = True
|
||||
|
||||
self._driver._access_api.return_value = True
|
||||
|
||||
self._driver.create_cloned_volume(self.clone_volume2, self.volume)
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
self.assertEqual(1, self._driver._vrts_extend_lun.call_count)
|
||||
|
||||
def test_create_cloned_volume_negative(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_get_vrts_lun_list')
|
||||
self.mock_object(self._driver, '_vrts_get_fs_list')
|
||||
self.mock_object(self._driver, '_vrts_is_space_available_in_store')
|
||||
|
||||
va_lun_name = self._driver._get_va_lun_name(self.volume.id)
|
||||
|
||||
lun = {}
|
||||
lun['lun_name'] = va_lun_name
|
||||
lun['fs_name'] = 'fake_fs'
|
||||
lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
lun_list = {'output': {'output': {'luns': [lun]}}}
|
||||
|
||||
self._driver._get_vrts_lun_list.return_value = lun_list
|
||||
|
||||
self._driver._vrts_is_space_available_in_store.return_value = True
|
||||
|
||||
self._driver._access_api.return_value = False
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver.create_cloned_volume,
|
||||
self.clone_volume, self.volume)
|
||||
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_vrts_extend_lun')
|
||||
self.mock_object(self._driver, '_vrts_get_targets_store')
|
||||
self.mock_object(self._driver, '_vrts_get_assigned_store')
|
||||
self.mock_object(self._driver, '_vrts_get_fs_list')
|
||||
self.mock_object(self._driver, '_vrts_is_space_available_in_store')
|
||||
|
||||
snap_name = self._driver._get_va_lun_name(self.snapshot.id)
|
||||
|
||||
snap = {}
|
||||
snap['snapshot_name'] = snap_name
|
||||
snap['target_name'] = 'fake_target'
|
||||
|
||||
snapshots = []
|
||||
snapshots.append(snap)
|
||||
|
||||
snap_info = {}
|
||||
snap_info['output'] = {'output': {'snapshots': snapshots}}
|
||||
|
||||
self._driver._access_api.return_value = snap_info
|
||||
|
||||
self._driver._vrts_is_space_available_in_store.return_value = True
|
||||
|
||||
self._driver.create_volume_from_snapshot(self.volume, self.snapshot)
|
||||
self.assertEqual(2, self._driver._access_api.call_count)
|
||||
self.assertEqual(0, self._driver._vrts_extend_lun.call_count)
|
||||
|
||||
def test_create_volume_from_snapshot_of_greater_size(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_vrts_extend_lun')
|
||||
self.mock_object(self._driver, '_vrts_get_targets_store')
|
||||
self.mock_object(self._driver, '_vrts_get_assigned_store')
|
||||
self.mock_object(self._driver, '_vrts_get_fs_list')
|
||||
self.mock_object(self._driver, '_vrts_is_space_available_in_store')
|
||||
|
||||
snap_name = self._driver._get_va_lun_name(self.snapshot.id)
|
||||
|
||||
snap = {}
|
||||
snap['snapshot_name'] = snap_name
|
||||
snap['target_name'] = 'fake_target'
|
||||
|
||||
snapshots = []
|
||||
snapshots.append(snap)
|
||||
|
||||
snap_info = {}
|
||||
snap_info['output'] = {'output': {'snapshots': snapshots}}
|
||||
|
||||
self._driver._access_api.return_value = snap_info
|
||||
|
||||
self._driver._vrts_is_space_available_in_store.return_value = True
|
||||
|
||||
self._driver.create_volume_from_snapshot(self.volume2, self.snapshot)
|
||||
self.assertEqual(2, self._driver._access_api.call_count)
|
||||
self.assertEqual(1, self._driver._vrts_extend_lun.call_count)
|
||||
|
||||
def test_create_volume_from_snapshot_negative(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_vrts_get_targets_store')
|
||||
|
||||
snap = {}
|
||||
snap['snapshot_name'] = 'fake_snap_name'
|
||||
snap['target_name'] = 'fake_target'
|
||||
|
||||
snapshots = []
|
||||
snapshots.append(snap)
|
||||
|
||||
snap_info = {}
|
||||
snap_info['output'] = {'output': {'snapshots': snapshots}}
|
||||
|
||||
self._driver._access_api.return_value = snap_info
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver.create_volume_from_snapshot,
|
||||
self.volume, self.snapshot)
|
||||
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
self.assertEqual(0, self._driver._vrts_get_targets_store.call_count)
|
||||
|
||||
def test_extend_volume(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_get_vrts_lun_list')
|
||||
self.mock_object(self._driver, '_vrts_get_fs_list')
|
||||
self.mock_object(self._driver, '_vrts_is_space_available_in_store')
|
||||
|
||||
va_lun_name = self._driver._get_va_lun_name(self.volume.id)
|
||||
|
||||
lun = {}
|
||||
lun['lun_name'] = va_lun_name
|
||||
lun['fs_name'] = 'fake_fs'
|
||||
lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
lun_list = {'output': {'output': {'luns': [lun]}}}
|
||||
|
||||
self._driver._get_vrts_lun_list.return_value = lun_list
|
||||
self._driver._vrts_is_space_available_in_store.return_value = True
|
||||
|
||||
self._driver._access_api.return_value = True
|
||||
|
||||
self._driver.extend_volume(self.volume, 2)
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
|
||||
def test_extend_volume_negative(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_get_vrts_lun_list')
|
||||
self.mock_object(self._driver, '_vrts_get_fs_list')
|
||||
self.mock_object(self._driver, '_vrts_is_space_available_in_store')
|
||||
|
||||
va_lun_name = self._driver._get_va_lun_name(self.volume.id)
|
||||
|
||||
lun = {}
|
||||
lun['lun_name'] = va_lun_name
|
||||
lun['fs_name'] = 'fake_fs'
|
||||
lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
lun_list = {'output': {'output': {'luns': [lun]}}}
|
||||
|
||||
self._driver._get_vrts_lun_list.return_value = lun_list
|
||||
self._driver._vrts_is_space_available_in_store.return_value = True
|
||||
|
||||
self._driver._access_api.return_value = False
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver.extend_volume, self.volume, 2)
|
||||
self.assertEqual(1, self._driver._vrts_get_fs_list.call_count)
|
||||
self.assertEqual(1, self._driver._access_api.call_count)
|
||||
|
||||
def test_extend_volume_negative_not_volume_found(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_get_vrts_lun_list')
|
||||
self.mock_object(self._driver, '_vrts_get_fs_list')
|
||||
|
||||
lun = {}
|
||||
lun['lun_name'] = 'fake_lun'
|
||||
lun['fs_name'] = 'fake_fs'
|
||||
lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
lun_list = {'output': {'output': {'luns': [lun]}}}
|
||||
|
||||
self._driver._get_vrts_lun_list.return_value = lun_list
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver.extend_volume, self.volume, 2)
|
||||
|
||||
self.assertEqual(0, self._driver._vrts_get_fs_list.call_count)
|
||||
self.assertEqual(0, self._driver._access_api.call_count)
|
||||
|
||||
def test_initialize_connection(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_get_vrts_lun_list')
|
||||
self.mock_object(self._driver, '_vrts_target_initiator_mapping')
|
||||
self.mock_object(self._driver, '_vrts_get_iscsi_properties')
|
||||
|
||||
va_lun_name = self._driver._get_va_lun_name(self.volume.id)
|
||||
|
||||
lun = {}
|
||||
lun['lun_name'] = va_lun_name
|
||||
lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
lun_list = {'output': {'output': {'luns': [lun]}}}
|
||||
|
||||
self._driver._get_vrts_lun_list.return_value = lun_list
|
||||
self._driver._access_api.return_value = True
|
||||
self._driver.initialize_connection(self.volume, self.connector)
|
||||
self.assertEqual(1, self._driver._vrts_get_iscsi_properties.call_count)
|
||||
|
||||
def test_initialize_connection_negative(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
self.mock_object(self._driver, '_get_vrts_lun_list')
|
||||
self.mock_object(self._driver, '_vrts_target_initiator_mapping')
|
||||
self.mock_object(self._driver, '_vrts_get_iscsi_properties')
|
||||
|
||||
lun = {}
|
||||
lun['lun_name'] = 'fakelun'
|
||||
lun['target_name'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
lun_list = {'output': {'output': {'luns': [lun]}}}
|
||||
self._driver.LUN_FOUND_INTERVAL = 5
|
||||
|
||||
self._driver._get_vrts_lun_list.return_value = lun_list
|
||||
self._driver._access_api.return_value = True
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver.initialize_connection, self.volume,
|
||||
self.connector)
|
||||
|
||||
self.assertEqual(
|
||||
0, self._driver._vrts_target_initiator_mapping.call_count)
|
||||
self.assertEqual(0, self._driver._vrts_get_iscsi_properties.call_count)
|
||||
|
||||
def test___vrts_get_iscsi_properties(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
|
||||
va_lun_name = self._driver._get_va_lun_name(self.volume.id)
|
||||
storage_object = "'/fakestores/fakeio/" + va_lun_name + "'"
|
||||
|
||||
lun_id_list = {}
|
||||
lun_id_list['output'] = ("[{'storage_object': " +
|
||||
storage_object + ", 'index': '1'}]")
|
||||
|
||||
target_name = 'iqn.2017-02.com.veritas:faketarget'
|
||||
|
||||
self._driver._access_api.return_value = lun_id_list
|
||||
|
||||
iscsi_properties_ret_value = {}
|
||||
iscsi_properties_ret_value['target_discovered'] = True
|
||||
iscsi_properties_ret_value['target_iqn'] = target_name
|
||||
iscsi_properties_ret_value['target_portal'] = '1.1.1.1:3260'
|
||||
iscsi_properties_ret_value['target_lun'] = 1
|
||||
iscsi_properties_ret_value['volume_id'] = 'fakeid'
|
||||
iscsi_properties = self._driver._vrts_get_iscsi_properties(self.volume,
|
||||
target_name)
|
||||
|
||||
self.assertEqual(iscsi_properties_ret_value, iscsi_properties)
|
||||
|
||||
def test__access_api(self):
|
||||
self.mock_object(requests, 'session')
|
||||
|
||||
provider = '%s:%s' % (self._driver._va_ip, self._driver._port)
|
||||
path = '/fake/path'
|
||||
input_data = {}
|
||||
mock_response = MockResponse()
|
||||
session = requests.session
|
||||
|
||||
data = {'fake_key': 'fake_val'}
|
||||
json_data = json.dumps(data)
|
||||
|
||||
session.request.return_value = mock_response
|
||||
ret_value = self._driver._access_api(session, provider, path,
|
||||
json.dumps(input_data), 'GET')
|
||||
|
||||
self.assertEqual(json_data, ret_value)
|
||||
|
||||
def test__access_api_negative(self):
|
||||
session = self._driver.session
|
||||
provider = '%s:%s' % (self._driver._va_ip, self._driver._port)
|
||||
path = '/fake/path'
|
||||
input_data = {}
|
||||
ret_value = self._driver._access_api(session, provider, path,
|
||||
json.dumps(input_data), 'GET')
|
||||
self.assertEqual(False, ret_value)
|
||||
|
||||
def test__get_api(self):
|
||||
provider = '%s:%s' % (self._driver._va_ip, self._driver._port)
|
||||
tail = '/fake/path'
|
||||
ret = self._driver._get_api(provider, tail)
|
||||
|
||||
api_root = 'https://%s/api/access' % (provider)
|
||||
to_be_ret = api_root + tail
|
||||
self.assertEqual(to_be_ret, ret)
|
||||
|
||||
def test__vrts_target_initiator_mapping_negative(self):
|
||||
self.mock_object(self._driver, '_access_api')
|
||||
target_name = 'fake_target'
|
||||
initiator_name = 'fake_initiator'
|
||||
|
||||
self._driver._access_api.return_value = False
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self._driver._vrts_target_initiator_mapping,
|
||||
target_name, initiator_name)
|
||||
|
||||
def test_get_volume_stats(self):
|
||||
self.mock_object(self._driver, '_authenticate_access')
|
||||
self.mock_object(self._driver, '_vrts_get_targets_store')
|
||||
self.mock_object(self._driver, '_vrts_get_fs_list')
|
||||
|
||||
target_list = []
|
||||
target_details = {}
|
||||
target_details['fs_list'] = ['fs1']
|
||||
target_details['wwn'] = 'iqn.2017-02.com.veritas:faketarget'
|
||||
target_list.append(target_details)
|
||||
|
||||
self._driver._vrts_get_targets_store.return_value = target_list
|
||||
|
||||
fs_list = []
|
||||
fs_dict = {}
|
||||
fs_dict['name'] = 'fs1'
|
||||
fs_dict['file_storage_capacity'] = 10737418240
|
||||
fs_dict['file_storage_used'] = 1073741824
|
||||
fs_list.append(fs_dict)
|
||||
|
||||
self._driver._vrts_get_fs_list.return_value = fs_list
|
||||
|
||||
self._driver.get_volume_stats()
|
||||
data = {
|
||||
'volume_backend_name': FAKE_BACKEND,
|
||||
'vendor_name': 'Veritas',
|
||||
'driver_version': '1.0',
|
||||
'storage_protocol': 'iSCSI',
|
||||
'total_capacity_gb': 10,
|
||||
'free_capacity_gb': 9,
|
||||
'reserved_percentage': 0,
|
||||
'thin_provisioning_support': True
|
||||
}
|
||||
|
||||
self.assertEqual(data, self._driver._stats)
|
0
cinder/volume/drivers/veritas_access/__init__.py
Normal file
0
cinder/volume/drivers/veritas_access/__init__.py
Normal file
898
cinder/volume/drivers/veritas_access/veritas_iscsi.py
Normal file
898
cinder/volume/drivers/veritas_access/veritas_iscsi.py
Normal file
@ -0,0 +1,898 @@
|
||||
# Copyright 2017 Veritas Technologies LLC.
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
Veritas Access Driver for ISCSI.
|
||||
|
||||
"""
|
||||
import ast
|
||||
import hashlib
|
||||
import json
|
||||
from random import randint
|
||||
|
||||
from defusedxml import minidom
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
from oslo_utils import netutils
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import units
|
||||
import requests
|
||||
import requests.auth
|
||||
from six.moves import http_client
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import interface
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.san import san
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
VA_VOL_OPTS = [
|
||||
cfg.BoolOpt('vrts_lun_sparse',
|
||||
default=True,
|
||||
help='Create sparse Lun.'),
|
||||
cfg.StrOpt('vrts_target_config',
|
||||
default='/etc/cinder/vrts_target.xml',
|
||||
help='VA config file.')
|
||||
]
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(VA_VOL_OPTS)
|
||||
|
||||
|
||||
class NoAuth(requests.auth.AuthBase):
|
||||
"""This is a 'authentication' handler.
|
||||
|
||||
It exists for use with custom authentication systems, such as the
|
||||
one for the Access API, it simply passes the Authorization header as-is.
|
||||
|
||||
The default authentication handler for requests will clobber the
|
||||
Authorization header.
|
||||
"""
|
||||
|
||||
def __call__(self, r):
|
||||
return r
|
||||
|
||||
|
||||
@interface.volumedriver
|
||||
class ACCESSIscsiDriver(driver.ISCSIDriver):
|
||||
"""ACCESS Share Driver.
|
||||
|
||||
Executes commands relating to ACCESS ISCSI.
|
||||
Supports creation of volumes on ACCESS.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
API version history:
|
||||
|
||||
1.0 - Initial version.
|
||||
"""
|
||||
|
||||
VERSION = "1.0"
|
||||
# ThirdPartySytems wiki page
|
||||
CI_WIKI_NAME = "Veritas_Access_CI"
|
||||
DRIVER_VOLUME_TYPE = 'iSCSI'
|
||||
LUN_FOUND_INTERVAL = 30 # seconds
|
||||
|
||||
# TODO(jsbryant) Remove driver in the 'U' release if CI is not fixed.
|
||||
SUPPORTED = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Parent sets db, host, _execute and base config
|
||||
super(ACCESSIscsiDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
self._va_ip = None
|
||||
self._port = None
|
||||
self._user = None
|
||||
self._pwd = None
|
||||
self.iscsi_port = None
|
||||
self._fs_list_str = '/fs'
|
||||
self._target_list_str = '/iscsi/target/list'
|
||||
self._target_status = '/iscsi/target/status'
|
||||
self._lun_create_str = '/iscsi/lun/create'
|
||||
self._lun_destroy_str = '/iscsi/lun/destroy'
|
||||
self._lun_list_str = '/iscsi/lun/list'
|
||||
self._lun_create_from_snap_str = '/iscsi/lun_from_snap/create'
|
||||
self._snapshot_create_str = '/iscsi/lun/snapshot/create'
|
||||
self._snapshot_destroy_str = '/iscsi/lun/snapshot/destroy'
|
||||
self._snapshot_list_str = '/iscsi/lun/snapshot/list'
|
||||
self._lun_clone_create_str = '/iscsi/lun/clone/create'
|
||||
self._lun_extend_str = '/iscsi/lun/growto'
|
||||
self._lun_shrink_str = '/iscsi/lun/shrinkto'
|
||||
self._lun_getid_str = '/iscsi/lun/getlunid'
|
||||
self._target_map_str = '/iscsi/target/map/add'
|
||||
self._target_list_status = '/iscsi/target/full_list'
|
||||
|
||||
self.configuration.append_config_values(VA_VOL_OPTS)
|
||||
self.configuration.append_config_values(san.san_opts)
|
||||
self.backend_name = (self.configuration.safe_get('volume_'
|
||||
'backend_name') or
|
||||
'ACCESS_ISCSI')
|
||||
self.verify = (self.configuration.
|
||||
safe_get('driver_ssl_cert_verify') or False)
|
||||
|
||||
if self.verify:
|
||||
verify_path = (self.configuration.
|
||||
safe_get('driver_ssl_cert_path') or None)
|
||||
if verify_path:
|
||||
self.verify = verify_path
|
||||
|
||||
@staticmethod
|
||||
def get_driver_options():
|
||||
return VA_VOL_OPTS
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Any initialization the volume driver does while starting."""
|
||||
super(ACCESSIscsiDriver, self).do_setup(context)
|
||||
|
||||
required_config = ['san_ip',
|
||||
'san_login',
|
||||
'san_password',
|
||||
'san_api_port']
|
||||
|
||||
for attr in required_config:
|
||||
if not getattr(self.configuration, attr, None):
|
||||
message = (_('config option %s is not set.') % attr)
|
||||
raise exception.InvalidInput(message=message)
|
||||
|
||||
self._va_ip = self.configuration.san_ip
|
||||
self._user = self.configuration.san_login
|
||||
self._pwd = self.configuration.san_password
|
||||
self._port = self.configuration.san_api_port
|
||||
self._sparse_lun_support = self.configuration.vrts_lun_sparse
|
||||
self.target_info_file = self.configuration.vrts_target_config
|
||||
self.iscsi_port = self.configuration.target_port
|
||||
self.session = self._authenticate_access(self._va_ip, self._user,
|
||||
self._pwd)
|
||||
|
||||
def _get_va_lun_name(self, name):
|
||||
length = len(name)
|
||||
index = int(length / 2)
|
||||
name1 = name[:index]
|
||||
name2 = name[index:]
|
||||
crc1 = hashlib.md5(name1.encode('utf-8')).hexdigest()[:5]
|
||||
crc2 = hashlib.md5(name2.encode('utf-8')).hexdigest()[:5]
|
||||
return 'cinder' + '-' + crc1 + '-' + crc2
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Check if veritas access target is online."""
|
||||
target_list = self._vrts_parse_xml_file(self.target_info_file)
|
||||
if not self._vrts_get_online_targets(target_list):
|
||||
message = ('ACCESSIscsiDriver setup error as '
|
||||
'no target is online')
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
def create_export(self, context, volume, connector):
|
||||
"""Driver entry point to get the export info for a new volume."""
|
||||
pass
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
"""Driver entry point to remove an export for a volume."""
|
||||
pass
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
"""Driver entry point to get the export info for an existing volume."""
|
||||
pass
|
||||
|
||||
def _vrts_get_iscsi_properties(self, volume, target_name):
|
||||
"""Get target and LUN details."""
|
||||
lun_name = self._get_va_lun_name(volume.id)
|
||||
|
||||
data = {}
|
||||
path = self._lun_getid_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
|
||||
lun_id_list = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'GET')
|
||||
|
||||
if not lun_id_list:
|
||||
message = _('ACCESSIscsiDriver get LUN ID list '
|
||||
'operation failed')
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
for lun in ast.literal_eval(lun_id_list['output']):
|
||||
vrts_lun_name = lun['storage_object'].split('/')[3]
|
||||
if vrts_lun_name == lun_name:
|
||||
lun_id = int(lun['index'])
|
||||
|
||||
target_list = self._vrts_parse_xml_file(self.target_info_file)
|
||||
authentication = False
|
||||
portal_ip = ""
|
||||
|
||||
for target in target_list:
|
||||
if target_name == target['name']:
|
||||
portal_ip = target['portal_ip']
|
||||
if target['auth'] == '1':
|
||||
auth_user = target['auth_user']
|
||||
auth_password = target['auth_password']
|
||||
authentication = True
|
||||
break
|
||||
|
||||
if portal_ip == "":
|
||||
message = (_('ACCESSIscsiDriver initialize_connection '
|
||||
'failed for %s as no portal ip was found')
|
||||
% volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
portal_list = portal_ip.split(',')
|
||||
|
||||
target_portal_list = []
|
||||
for ip in portal_list:
|
||||
if netutils.is_valid_ipv6(ip):
|
||||
target_portal_list.append('[%s]:%s' % (ip,
|
||||
str(self.iscsi_port)))
|
||||
else:
|
||||
target_portal_list.append('%s:%s' % (ip, str(self.iscsi_port)))
|
||||
|
||||
iscsi_properties = {}
|
||||
iscsi_properties['target_discovered'] = True
|
||||
iscsi_properties['target_iqn'] = target_name
|
||||
iscsi_properties['target_portal'] = target_portal_list[0]
|
||||
if len(target_portal_list) > 1:
|
||||
iscsi_properties['target_portals'] = target_portal_list
|
||||
iscsi_properties['target_lun'] = lun_id
|
||||
iscsi_properties['volume_id'] = volume.id
|
||||
if authentication:
|
||||
iscsi_properties['auth_username'] = auth_user
|
||||
iscsi_properties['auth_password'] = auth_password
|
||||
iscsi_properties['auth_method'] = 'CHAP'
|
||||
|
||||
return iscsi_properties
|
||||
|
||||
def _get_vrts_lun_list(self):
|
||||
"""Get Lun list."""
|
||||
data = {}
|
||||
path = self._lun_list_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
|
||||
lun_list = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'GET')
|
||||
|
||||
if not lun_list:
|
||||
message = _('ACCESSIscsiDriver get LUN list '
|
||||
'operation failed')
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
return lun_list
|
||||
|
||||
def _vrts_target_initiator_mapping(self, target_name, initiator_name):
|
||||
"""Map target to initiator."""
|
||||
path = self._target_map_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
|
||||
data = {}
|
||||
data["target_name"] = target_name
|
||||
data["initiator_name"] = initiator_name
|
||||
|
||||
result = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'POST')
|
||||
|
||||
if not result:
|
||||
message = (_('ACCESSIscsiDriver target-initiator mapping '
|
||||
'failed for target %s')
|
||||
% target_name)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
def initialize_connection(self, volume, connector, initiator_data=None):
|
||||
"""Initializes the connection and returns connection info.
|
||||
|
||||
The iscsi driver returns a driver_volume_type of 'iscsi'.
|
||||
the format of the driver data is defined in _vrts_get_iscsi_properties.
|
||||
Example return value::
|
||||
|
||||
{
|
||||
'driver_volume_type': 'iscsi'
|
||||
'data': {
|
||||
'target_discovered': True,
|
||||
'target_iqn': 'iqn.2010-10.org.openstack:volume-00000001',
|
||||
'target_portal': '127.0.0.0.1:3260',
|
||||
'target_lun': 1,
|
||||
'volume_id': '12345678-1234-4321-1234-123456789012',
|
||||
}
|
||||
}
|
||||
"""
|
||||
lun_name = self._get_va_lun_name(volume.id)
|
||||
target = {'target_name': ''}
|
||||
|
||||
def _inner():
|
||||
lun_list = self._get_vrts_lun_list()
|
||||
for lun in lun_list['output']['output']['luns']:
|
||||
if lun['lun_name'] == lun_name:
|
||||
target['target_name'] = lun['target_name']
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
timer = loopingcall.FixedIntervalWithTimeoutLoopingCall(_inner)
|
||||
try:
|
||||
timer.start(interval=5, timeout=self.LUN_FOUND_INTERVAL).wait()
|
||||
except loopingcall.LoopingCallTimeOut:
|
||||
message = (_('ACCESSIscsiDriver initialize_connection '
|
||||
'failed for %s as no target was found')
|
||||
% volume.id)
|
||||
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
self._vrts_target_initiator_mapping(target['target_name'],
|
||||
connector['initiator'])
|
||||
|
||||
iscsi_properties = self._vrts_get_iscsi_properties(
|
||||
volume, target['target_name'])
|
||||
|
||||
return {
|
||||
'driver_volume_type': 'iscsi',
|
||||
'data': iscsi_properties
|
||||
}
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Disallow connection from connector."""
|
||||
pass
|
||||
|
||||
def _vrts_parse_xml_file(self, filename):
|
||||
"""VRTS target info.
|
||||
|
||||
<VRTS>
|
||||
<VrtsTargets>
|
||||
<Target>
|
||||
<Name>iqn.2017-02.com.veritas:target03</Name>
|
||||
<PortalIP>10.182.174.188</PortalIP>
|
||||
</Target>
|
||||
<Target>
|
||||
<Name>iqn.2017-02.com.veritas:target04</Name>
|
||||
<PortalIP>10.182.174.189</PortalIP>
|
||||
</Target>
|
||||
</VrtsTargets>
|
||||
</VRST>
|
||||
|
||||
:param filename: the configuration file
|
||||
:returns: list
|
||||
"""
|
||||
myfile = open(filename, 'r')
|
||||
data = myfile.read()
|
||||
myfile.close()
|
||||
dom = minidom.parseString(data)
|
||||
|
||||
mylist = []
|
||||
target = {}
|
||||
|
||||
try:
|
||||
for trg in dom.getElementsByTagName('Target'):
|
||||
target['name'] = (trg.getElementsByTagName('Name')[0]
|
||||
.childNodes[0].nodeValue)
|
||||
target['portal_ip'] = (trg.getElementsByTagName('PortalIP')[0]
|
||||
.childNodes[0].nodeValue)
|
||||
target['auth'] = (trg.getElementsByTagName('Authentication')[0]
|
||||
.childNodes[0].nodeValue)
|
||||
if target['auth'] == '1':
|
||||
target['auth_user'] = (trg.getElementsByTagName
|
||||
('Auth_username')[0]
|
||||
.childNodes[0].nodeValue)
|
||||
target['auth_password'] = (trg.getElementsByTagName
|
||||
('Auth_password')[0]
|
||||
.childNodes[0].nodeValue)
|
||||
|
||||
mylist.append(target)
|
||||
target = {}
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
return mylist
|
||||
|
||||
def _vrts_get_fs_list(self):
|
||||
"""Get FS list."""
|
||||
path = self._fs_list_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
data = {}
|
||||
fs_list = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'GET')
|
||||
|
||||
if not fs_list:
|
||||
message = _('ACCESSIscsiDriver get FS list '
|
||||
'operation failed')
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
return fs_list
|
||||
|
||||
def _vrts_get_online_targets(self, available_targets):
|
||||
"""Out of available targets get list of targets which are online."""
|
||||
|
||||
online_targets = []
|
||||
path = self._target_list_status
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
data = {}
|
||||
target_status_list = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'GET')
|
||||
|
||||
try:
|
||||
target_status_output = (ast.
|
||||
literal_eval(target_status_list['output']))
|
||||
except KeyError:
|
||||
message = _('ACCESSIscsiDriver get online target list '
|
||||
'operation failed')
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
for target in available_targets:
|
||||
if target['name'] in target_status_output.keys():
|
||||
if target_status_output[target['name']] == 'ONLINE':
|
||||
online_targets.append(target)
|
||||
|
||||
return online_targets
|
||||
|
||||
def _vrts_get_targets_store(self):
|
||||
"""Get target and its store list."""
|
||||
path = self._target_list_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
data = {}
|
||||
target_list = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'GET')
|
||||
|
||||
if not target_list:
|
||||
message = _('ACCESSIscsiDriver get target list '
|
||||
'operation failed')
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
return target_list['output']['output']['targets']
|
||||
|
||||
def _vrts_get_assigned_store(self, target, vrts_target_list):
|
||||
"""Get the store mapped to given target."""
|
||||
for vrts_target in vrts_target_list:
|
||||
if vrts_target['wwn'] == target:
|
||||
return vrts_target['fs_list'][0]
|
||||
|
||||
def _vrts_is_space_available_in_store(self, vol_size, store_name, fs_list):
|
||||
"""Check whether space is available on store."""
|
||||
if self._sparse_lun_support:
|
||||
return True
|
||||
|
||||
for fs in fs_list:
|
||||
if fs['name'] == store_name:
|
||||
fs_avilable_space = (int(fs['file_storage_capacity']) -
|
||||
int(fs['file_storage_used']))
|
||||
free_space = fs_avilable_space / units.Gi
|
||||
|
||||
if free_space > vol_size:
|
||||
return True
|
||||
break
|
||||
return False
|
||||
|
||||
def _vrts_get_suitable_target(self, target_list, vol_size):
|
||||
"""Get a suitable target for lun creation.
|
||||
|
||||
Picking random target at first, if space is not available
|
||||
in first selected target then check each target one by one
|
||||
for suitable one.
|
||||
"""
|
||||
|
||||
target_count = len(target_list)
|
||||
|
||||
incrmnt_pointer = 0
|
||||
target_index = randint(0, (target_count - 1))
|
||||
|
||||
fs_list = self._vrts_get_fs_list()
|
||||
|
||||
vrts_target_list = self._vrts_get_targets_store()
|
||||
|
||||
store_name = self._vrts_get_assigned_store(
|
||||
target_list[target_index]['name'],
|
||||
vrts_target_list
|
||||
)
|
||||
|
||||
if not self._vrts_is_space_available_in_store(
|
||||
vol_size, store_name, fs_list):
|
||||
while (incrmnt_pointer != target_count - 1):
|
||||
target_index = (target_index + 1) % target_count
|
||||
store_name = self._vrts_get_assigned_store(
|
||||
target_list[target_index]['name'],
|
||||
vrts_target_list
|
||||
)
|
||||
if self._vrts_is_space_available_in_store(
|
||||
vol_size, store_name, fs_list):
|
||||
return target_list[target_index]['name']
|
||||
incrmnt_pointer = incrmnt_pointer + 1
|
||||
else:
|
||||
return target_list[target_index]['name']
|
||||
|
||||
return False
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a Veritas Access Iscsi LUN."""
|
||||
create_dense = False
|
||||
if 'dense' in volume.metadata.keys():
|
||||
create_dense = strutils.bool_from_string(
|
||||
volume.metadata['dense'])
|
||||
|
||||
lun_name = self._get_va_lun_name(volume.id)
|
||||
lun_size = '%sg' % volume.size
|
||||
path = self._lun_create_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
|
||||
target_list = self._vrts_parse_xml_file(self.target_info_file)
|
||||
|
||||
target_name = self._vrts_get_suitable_target(target_list, volume.size)
|
||||
|
||||
if not target_name:
|
||||
message = (_('ACCESSIscsiDriver create volume failed %s '
|
||||
'as no space is available') % volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
data = {}
|
||||
data["lun_name"] = lun_name
|
||||
data["target_name"] = target_name
|
||||
data["size"] = lun_size
|
||||
if not self._sparse_lun_support or create_dense:
|
||||
data["option"] = "option=dense"
|
||||
|
||||
result = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'POST')
|
||||
|
||||
if not result:
|
||||
message = (_('ACCESSIscsiDriver create volume failed %s')
|
||||
% volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a Veritas Access Iscsi LUN."""
|
||||
lun_name = self._get_va_lun_name(volume.id)
|
||||
lun_list = self._get_vrts_lun_list()
|
||||
target_name = ""
|
||||
|
||||
for lun in lun_list['output']['output']['luns']:
|
||||
if lun['lun_name'] == lun_name:
|
||||
target_name = lun['target_name']
|
||||
|
||||
path = self._lun_destroy_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
|
||||
data = {}
|
||||
data["lun_name"] = lun_name
|
||||
data["target_name"] = target_name
|
||||
|
||||
result = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'POST')
|
||||
|
||||
if not result:
|
||||
message = (_('ACCESSIscsiDriver delete volume failed %s')
|
||||
% volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a snapshot of LUN."""
|
||||
lun_name = self._get_va_lun_name(snapshot.volume_id)
|
||||
snap_name = self._get_va_lun_name(snapshot.id)
|
||||
path = self._snapshot_create_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
|
||||
data = {}
|
||||
data["lun_name"] = lun_name
|
||||
data["snap_name"] = snap_name
|
||||
|
||||
result = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'POST')
|
||||
|
||||
if not result:
|
||||
message = (_('ACCESSIscsiDriver create snapshot failed for %s')
|
||||
% snapshot.volume_id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot of LUN."""
|
||||
lun_name = self._get_va_lun_name(snapshot.volume_id)
|
||||
snap_name = self._get_va_lun_name(snapshot.id)
|
||||
path = self._snapshot_destroy_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
|
||||
data = {}
|
||||
data["lun_name"] = lun_name
|
||||
data["snap_name"] = snap_name
|
||||
|
||||
result = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'POST')
|
||||
|
||||
if not result:
|
||||
message = (_('ACCESSIscsiDriver delete snapshot failed for %s')
|
||||
% snapshot.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Create a clone of the volume."""
|
||||
lun_name = self._get_va_lun_name(src_vref.id)
|
||||
cloned_lun_name = self._get_va_lun_name(volume.id)
|
||||
|
||||
lun_found = False
|
||||
|
||||
lun_list = self._get_vrts_lun_list()
|
||||
for lun in lun_list['output']['output']['luns']:
|
||||
if lun['lun_name'] == lun_name:
|
||||
store_name = lun['fs_name']
|
||||
lun_found = True
|
||||
break
|
||||
|
||||
if not lun_found:
|
||||
message = (_('ACCESSIscsiDriver create cloned volume '
|
||||
'failed %s as no source volume found') % volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
fs_list = self._vrts_get_fs_list()
|
||||
|
||||
if not self._vrts_is_space_available_in_store(volume.size, store_name,
|
||||
fs_list):
|
||||
message = (_('ACCESSIscsiDriver create cloned volume '
|
||||
'failed %s as no space is available') % volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
path = self._lun_clone_create_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
|
||||
data = {}
|
||||
data["lun_name"] = lun_name
|
||||
data["clone_name"] = cloned_lun_name
|
||||
|
||||
result = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'POST')
|
||||
|
||||
if not result:
|
||||
message = (_('ACCESSIscsiDriver create cloned '
|
||||
'volume failed for %s')
|
||||
% src_vref.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
if volume.size > src_vref.size:
|
||||
self._vrts_extend_lun(volume, volume.size)
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from snapshot."""
|
||||
LOG.debug('ACCESSIscsiDriver create_volume_from_snapshot called')
|
||||
|
||||
lun_name = self._get_va_lun_name(volume.id)
|
||||
snap_name = self._get_va_lun_name(snapshot.id)
|
||||
|
||||
path = self._snapshot_list_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
data = {}
|
||||
data["snap_name"] = snap_name
|
||||
snap_info = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'GET')
|
||||
|
||||
target_name = ""
|
||||
if snap_info:
|
||||
for snap in snap_info['output']['output']['snapshots']:
|
||||
if snap['snapshot_name'] == snap_name:
|
||||
target_name = snap['target_name']
|
||||
break
|
||||
|
||||
if target_name == "":
|
||||
message = (_('ACCESSIscsiDriver create volume from snapshot '
|
||||
'failed for volume %s as failed to gather '
|
||||
'snapshot details')
|
||||
% volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
vrts_target_list = self._vrts_get_targets_store()
|
||||
store_name = self._vrts_get_assigned_store(
|
||||
target_name, vrts_target_list)
|
||||
|
||||
fs_list = self._vrts_get_fs_list()
|
||||
|
||||
if not self._vrts_is_space_available_in_store(volume.size, store_name,
|
||||
fs_list):
|
||||
message = (_('ACCESSIscsiDriver create volume from snapshot '
|
||||
'failed %s as no space is available') % volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
path = self._lun_create_from_snap_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
|
||||
data = {}
|
||||
data["lun_name"] = lun_name
|
||||
data["snap_name"] = snap_name
|
||||
|
||||
result = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'POST')
|
||||
|
||||
if not result:
|
||||
message = (_('ACCESSIscsiDriver create volume from snapshot '
|
||||
'failed for volume %s')
|
||||
% volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
if volume.size > snapshot.volume_size:
|
||||
self._vrts_extend_lun(volume, volume.size)
|
||||
|
||||
def _vrts_extend_lun(self, volume, size):
|
||||
"""Extend vrts LUN to given size."""
|
||||
lun_name = self._get_va_lun_name(volume.id)
|
||||
target = {'target_name': ''}
|
||||
|
||||
def _inner():
|
||||
lun_list = self._get_vrts_lun_list()
|
||||
for lun in lun_list['output']['output']['luns']:
|
||||
if lun['lun_name'] == lun_name:
|
||||
target['target_name'] = lun['target_name']
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
timer = loopingcall.FixedIntervalWithTimeoutLoopingCall(_inner)
|
||||
|
||||
try:
|
||||
timer.start(interval=5, timeout=self.LUN_FOUND_INTERVAL).wait()
|
||||
except loopingcall.LoopingCallTimeOut:
|
||||
return False
|
||||
|
||||
lun_size = '%sg' % size
|
||||
path = self._lun_extend_str
|
||||
provider = '%s:%s' % (self._va_ip, self._port)
|
||||
|
||||
data = {}
|
||||
data["lun_name"] = lun_name
|
||||
data["target_name"] = target['target_name']
|
||||
data["size"] = lun_size
|
||||
|
||||
result = self._access_api(self.session, provider, path,
|
||||
json.dumps(data), 'POST')
|
||||
return result
|
||||
|
||||
def extend_volume(self, volume, size):
|
||||
"""Extend the volume to new size"""
|
||||
lun_name = self._get_va_lun_name(volume.id)
|
||||
lun_found = False
|
||||
|
||||
lun_list = self._get_vrts_lun_list()
|
||||
for lun in lun_list['output']['output']['luns']:
|
||||
if lun['lun_name'] == lun_name:
|
||||
store_name = lun['fs_name']
|
||||
lun_found = True
|
||||
break
|
||||
|
||||
if not lun_found:
|
||||
message = (_('ACCESSIscsiDriver extend volume '
|
||||
'failed %s as no volume found at backend')
|
||||
% volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
fs_list = self._vrts_get_fs_list()
|
||||
|
||||
if not self._vrts_is_space_available_in_store(size, store_name,
|
||||
fs_list):
|
||||
message = (_('ACCESSIscsiDriver extend volume '
|
||||
'failed %s as no space is available') % volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
result = self._vrts_extend_lun(volume, size)
|
||||
|
||||
if not result:
|
||||
message = (_('ACCESSIscsiDriver extend '
|
||||
'volume failed for %s')
|
||||
% volume.id)
|
||||
LOG.error(message)
|
||||
raise exception.VolumeBackendAPIException(message=message)
|
||||
|
||||
def _get_api(self, provider, tail):
|
||||
api_root = 'https://%s/api/access' % (provider)
|
||||
if tail == self._fs_list_str:
|
||||
api_root = 'https://%s/api' % (provider)
|
||||
|
||||
return api_root + tail
|
||||
|
||||
def _access_api(self, session, provider, path, input_data, method):
|
||||
"""Returns False if failure occurs."""
|
||||
kwargs = {'data': input_data}
|
||||
if not isinstance(input_data, dict):
|
||||
kwargs['headers'] = {'Content-Type': 'application/json'}
|
||||
full_url = self._get_api(provider, path)
|
||||
response = session.request(method, full_url, **kwargs)
|
||||
if response.status_code == 401:
|
||||
LOG.debug('Generating new session.')
|
||||
self.session = self._authenticate_access(self._va_ip,
|
||||
self._user, self._pwd)
|
||||
response = self.session.request(method, full_url, **kwargs)
|
||||
|
||||
if response.status_code != http_client.OK:
|
||||
LOG.error('Access API operation failed with HTTP error code %s.',
|
||||
str(response.status_code))
|
||||
return False
|
||||
result = response.json()
|
||||
return result
|
||||
|
||||
def _authenticate_access(self, address, username, password):
|
||||
session = requests.session()
|
||||
session.verify = self.verify
|
||||
session.auth = NoAuth()
|
||||
|
||||
# Here 'address' will be only IPv4.
|
||||
response = session.post('https://%s:%s/api/rest/authenticate'
|
||||
% (address, self._port),
|
||||
data={'username': username,
|
||||
'password': password})
|
||||
if response.status_code != http_client.OK:
|
||||
LOG.error('Failed to authenticate to remote cluster at %s as %s.',
|
||||
address, username)
|
||||
raise exception.NotAuthorized(_('Authentication failure.'))
|
||||
result = response.json()
|
||||
session.headers.update({'Authorization': 'Bearer {}'
|
||||
.format(result['token'])})
|
||||
session.headers.update({'Content-Type': 'application/json'})
|
||||
|
||||
return session
|
||||
|
||||
def _get_va_backend_capacity(self):
|
||||
"""Get VA backend total and free capacity."""
|
||||
target_list = self._vrts_parse_xml_file(self.target_info_file)
|
||||
fs_list = self._vrts_get_fs_list()
|
||||
vrts_target_list = self._vrts_get_targets_store()
|
||||
|
||||
total_space = 0
|
||||
free_space = 0
|
||||
|
||||
target_name = []
|
||||
target_store = []
|
||||
|
||||
for target in target_list:
|
||||
target_name.append(target['name'])
|
||||
|
||||
for target in vrts_target_list:
|
||||
if target['wwn'] in target_name:
|
||||
target_store.append(target['fs_list'][0])
|
||||
|
||||
for store in target_store:
|
||||
for fs in fs_list:
|
||||
if fs['name'] == store:
|
||||
total_space = total_space + fs['file_storage_capacity']
|
||||
fs_free_space = (fs['file_storage_capacity'] -
|
||||
fs['file_storage_used'])
|
||||
|
||||
if fs_free_space > free_space:
|
||||
free_space = fs_free_space
|
||||
|
||||
total_capacity = int(total_space) / units.Gi
|
||||
free_capacity = int(free_space) / units.Gi
|
||||
|
||||
return (total_capacity, free_capacity)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Retrieve status info from share volume group."""
|
||||
|
||||
total_capacity, free_capacity = self._get_va_backend_capacity()
|
||||
backend_name = self.configuration.safe_get('volume_backend_name')
|
||||
res_percentage = self.configuration.safe_get('reserved_percentage')
|
||||
self._stats["volume_backend_name"] = backend_name or 'VeritasISCSI'
|
||||
self._stats["vendor_name"] = 'Veritas'
|
||||
self._stats["reserved_percentage"] = res_percentage or 0
|
||||
self._stats["driver_version"] = self.VERSION
|
||||
self._stats["storage_protocol"] = self.DRIVER_VOLUME_TYPE
|
||||
self._stats['total_capacity_gb'] = total_capacity
|
||||
self._stats['free_capacity_gb'] = free_capacity
|
||||
self._stats['thin_provisioning_support'] = True
|
||||
|
||||
return self._stats
|
@ -0,0 +1,91 @@
|
||||
===========================
|
||||
Veritas ACCESS iSCSI driver
|
||||
===========================
|
||||
|
||||
Veritas Access is a software-defined scale-out network-attached
|
||||
storage (NAS) solution for unstructured data that works on commodity
|
||||
hardware and takes advantage of placing data on premise or in the
|
||||
cloud based on intelligent policies. Through Veritas Access iSCSI
|
||||
Driver, OpenStack Block Storage can use Veritas Access backend as a
|
||||
block storage resource. The driver enables you to create iSCSI volumes
|
||||
that an OpenStack Block Storage server can allocate to any virtual machine
|
||||
running on a compute host.
|
||||
|
||||
Requirements
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The Veritas ACCESS iSCSI Driver, version ``1.0.0`` and later, supports
|
||||
Veritas ACCESS release ``7.4`` and later.
|
||||
|
||||
Supported operations
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Create and delete volumes.
|
||||
- Create and delete snapshots.
|
||||
- Create volume from snapshot.
|
||||
- Extend a volume.
|
||||
- Attach and detach volumes.
|
||||
- Clone volumes.
|
||||
|
||||
Configuration
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
#. Enable RESTful service on the Veritas Access Backend.
|
||||
|
||||
#. Create Veritas Access iSCSI target, add store and portal IP to it.
|
||||
|
||||
You can create target and add portal IP, store to it as follows:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
Target> iscsi target create iqn.2018-02.com.veritas:target02
|
||||
Target> iscsi target store add target_fs iqn.2018-02.com.veritas:target02
|
||||
Target> iscsi target portal add iqn.2018-02.com.veritas:target02 10.10.10.1
|
||||
...
|
||||
|
||||
You can add authentication to target as follows:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
Target> iscsi target auth incominguser add iqn.2018-02.com.veritas:target02 user1
|
||||
...
|
||||
|
||||
#. Ensure that the Veritas Access iSCSI target service is online. If the
|
||||
Veritas Access
|
||||
iSCSI target service is not online, enable the service by using the CLI or
|
||||
REST API.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
Target> iscsi service start
|
||||
Target> iscsi service status
|
||||
...
|
||||
|
||||
Define the following required properties in the ``cinder.conf`` file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
volume_driver = cinder.volume.drivers.veritas_access.veritas_iscsi.ACCESSIscsiDriver
|
||||
san_ip = va_console_ip
|
||||
san_api_port = 14161
|
||||
san_login = master
|
||||
san_password = password
|
||||
target_port = 3260
|
||||
vrts_lun_sparse = True
|
||||
vrts_target_config = /etc/cinder/vrts_target.xml
|
||||
|
||||
#. Define Veritas Access Target details in ``/etc/cinder/vrts_target.xml``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
<?xml version="1.0" ?>
|
||||
<VRTS>
|
||||
<VrtsTargets>
|
||||
<Target>
|
||||
<Name>iqn.2018-02.com.veritas:target02</Name>
|
||||
<PortalIP>10.10.10.1</PortalIP>
|
||||
<Authentication>0</Authentication>
|
||||
</Target>
|
||||
</VrtsTargets>
|
||||
</VRTS>
|
||||
...
|
@ -159,6 +159,9 @@ title=StorPool Storage Driver (storpool)
|
||||
[driver.synology]
|
||||
title=Synology Storage Driver (iSCSI)
|
||||
|
||||
[driver.vrtsaccess]
|
||||
title=Veritas Access iSCSI Driver (iSCSI)
|
||||
|
||||
[driver.vrtscnfs]
|
||||
title=Veritas Cluster NFS Driver (NFS)
|
||||
|
||||
@ -232,6 +235,7 @@ driver.rbd=complete
|
||||
driver.seagate=complete
|
||||
driver.storpool=complete
|
||||
driver.synology=complete
|
||||
driver.vrtsaccess=missing
|
||||
driver.vrtscnfs=missing
|
||||
driver.vmware=complete
|
||||
driver.win_iscsi=complete
|
||||
@ -291,6 +295,7 @@ driver.rbd=complete
|
||||
driver.seagate=complete
|
||||
driver.storpool=complete
|
||||
driver.synology=complete
|
||||
driver.vrtsaccess=complete
|
||||
driver.vrtscnfs=complete
|
||||
driver.vmware=complete
|
||||
driver.win_iscsi=complete
|
||||
@ -350,6 +355,7 @@ driver.rbd=missing
|
||||
driver.seagate=missing
|
||||
driver.storpool=missing
|
||||
driver.synology=missing
|
||||
driver.vrtsaccess=missing
|
||||
driver.vrtscnfs=missing
|
||||
driver.vmware=missing
|
||||
driver.win_iscsi=missing
|
||||
@ -412,6 +418,7 @@ driver.rbd=missing
|
||||
driver.seagate=missing
|
||||
driver.storpool=missing
|
||||
driver.synology=missing
|
||||
driver.vrtsaccess=missing
|
||||
driver.vrtscnfs=missing
|
||||
driver.vmware=missing
|
||||
driver.win_iscsi=missing
|
||||
@ -473,6 +480,7 @@ driver.rbd=complete
|
||||
driver.seagate=missing
|
||||
driver.storpool=complete
|
||||
driver.synology=missing
|
||||
driver.vrtsaccess=missing
|
||||
driver.vrtscnfs=missing
|
||||
driver.vmware=missing
|
||||
driver.win_iscsi=missing
|
||||
@ -535,6 +543,7 @@ driver.rbd=missing
|
||||
driver.seagate=missing
|
||||
driver.storpool=missing
|
||||
driver.synology=missing
|
||||
driver.vrtsaccess=missing
|
||||
driver.vrtscnfs=missing
|
||||
driver.vmware=missing
|
||||
driver.win_iscsi=missing
|
||||
@ -596,6 +605,7 @@ driver.rbd=complete
|
||||
driver.seagate=missing
|
||||
driver.storpool=complete
|
||||
driver.synology=missing
|
||||
driver.vrtsaccess=missing
|
||||
driver.vrtscnfs=missing
|
||||
driver.vmware=missing
|
||||
driver.win_iscsi=missing
|
||||
@ -658,6 +668,7 @@ driver.rbd=missing
|
||||
driver.seagate=missing
|
||||
driver.storpool=complete
|
||||
driver.synology=missing
|
||||
driver.vrtsaccess=missing
|
||||
driver.vrtscnfs=missing
|
||||
driver.vmware=missing
|
||||
driver.win_iscsi=missing
|
||||
@ -720,6 +731,7 @@ driver.rbd=complete
|
||||
driver.seagate=complete
|
||||
driver.storpool=complete
|
||||
driver.synology=missing
|
||||
driver.vrtsaccess=missing
|
||||
driver.vrtscnfs=missing
|
||||
driver.vmware=missing
|
||||
driver.win_iscsi=missing
|
||||
@ -779,6 +791,7 @@ driver.rbd=missing
|
||||
driver.seagate=missing
|
||||
driver.storpool=missing
|
||||
driver.synology=missing
|
||||
driver.vrtsaccess=missing
|
||||
driver.vrtscnfs=missing
|
||||
driver.vmware=complete
|
||||
driver.win_iscsi=missing
|
||||
@ -842,6 +855,7 @@ driver.rbd=complete
|
||||
driver.seagate=missing
|
||||
driver.storpool=missing
|
||||
driver.synology=missing
|
||||
driver.vrtsaccess=missing
|
||||
driver.vrtscnfs=missing
|
||||
driver.vmware=missing
|
||||
driver.win_iscsi=missing
|
||||
|
@ -90,5 +90,4 @@ release.
|
||||
* HPE Lefthand Driver (iSCSI)
|
||||
* ProphetStor Flexvisor Driver
|
||||
* Sheepdog Driver
|
||||
* Veritas Access Storage Driver
|
||||
* Virtuozzo Storage Driver
|
||||
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
The Veritas Access driver was marked unsupported in the
|
||||
Train release and has now been removed. All data on
|
||||
Veritas Access backends should be migrated to a supported
|
||||
storage backend before upgrading your Cinder installation.
|
Loading…
Reference in New Issue
Block a user