Add ISCSI driver for Veritas Access
This driver implements all the minimum required features for cinder. Change-Id: I48b59a2ecab8b856547a4ae17e6e0fd04094475a Implement: blueprint veritas-access-cinder-iscsi-support
This commit is contained in:
parent
cf56cdd4da
commit
a9fad35a20
@ -159,6 +159,8 @@ from cinder.volume.drivers import storpool as cinder_volume_drivers_storpool
|
|||||||
from cinder.volume.drivers.synology import synology_common as \
|
from cinder.volume.drivers.synology import synology_common as \
|
||||||
cinder_volume_drivers_synology_synologycommon
|
cinder_volume_drivers_synology_synologycommon
|
||||||
from cinder.volume.drivers import tintri as cinder_volume_drivers_tintri
|
from cinder.volume.drivers import tintri as cinder_volume_drivers_tintri
|
||||||
|
from cinder.volume.drivers.veritas_access import veritas_iscsi as \
|
||||||
|
cinder_volume_drivers_veritas_access_veritasiscsi
|
||||||
from cinder.volume.drivers.vmware import vmdk as \
|
from cinder.volume.drivers.vmware import vmdk as \
|
||||||
cinder_volume_drivers_vmware_vmdk
|
cinder_volume_drivers_vmware_vmdk
|
||||||
from cinder.volume.drivers import vzstorage as cinder_volume_drivers_vzstorage
|
from cinder.volume.drivers import vzstorage as cinder_volume_drivers_vzstorage
|
||||||
@ -259,6 +261,7 @@ def list_opts():
|
|||||||
cinder_volume_drivers_inspur_instorage_instorageiscsi.
|
cinder_volume_drivers_inspur_instorage_instorageiscsi.
|
||||||
instorage_mcs_iscsi_opts,
|
instorage_mcs_iscsi_opts,
|
||||||
cinder_volume_drivers_storpool.storpool_opts,
|
cinder_volume_drivers_storpool.storpool_opts,
|
||||||
|
cinder_volume_drivers_veritas_access_veritasiscsi.VA_VOL_OPTS,
|
||||||
cinder_volume_manager.volume_manager_opts,
|
cinder_volume_manager.volume_manager_opts,
|
||||||
cinder_wsgi_eventletserver.socket_opts,
|
cinder_wsgi_eventletserver.socket_opts,
|
||||||
)),
|
)),
|
||||||
|
@ -0,0 +1,674 @@
|
|||||||
|
# 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 xml.dom.minidom import Document
|
||||||
|
|
||||||
|
import mock
|
||||||
|
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(2, 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()[:8]
|
||||||
|
crc2 = hashlib.md5(name2.encode('utf-8')).hexdigest()[:8]
|
||||||
|
|
||||||
|
volume_name_to_ret = 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(2, 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(2, 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(2, 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(2, 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(2, 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(3, 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(3, 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_ret_for_update_object(self):
|
||||||
|
self.mock_object(requests, 'session')
|
||||||
|
|
||||||
|
provider = '%s:%s' % (self._driver._va_ip, self._driver._port)
|
||||||
|
path = self._driver._update_object
|
||||||
|
input_data = None
|
||||||
|
mock_response = MockResponse()
|
||||||
|
session = requests.session
|
||||||
|
|
||||||
|
session.request.return_value = mock_response
|
||||||
|
ret = self._driver._access_api(session, provider, path,
|
||||||
|
input_data, 'GET')
|
||||||
|
|
||||||
|
self.assertTrue(ret)
|
||||||
|
|
||||||
|
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
886
cinder/volume/drivers/veritas_access/veritas_iscsi.py
Normal file
886
cinder/volume/drivers/veritas_access/veritas_iscsi.py
Normal file
@ -0,0 +1,886 @@
|
|||||||
|
# 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 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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._update_object = '/objecttags'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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()[:8]
|
||||||
|
crc2 = hashlib.md5(name2.encode('utf-8')).hexdigest()[:8]
|
||||||
|
return 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)
|
||||||
|
|
||||||
|
path = self._target_status
|
||||||
|
provider = '%s:%s' % (self._va_ip, self._port)
|
||||||
|
|
||||||
|
for target in target_list:
|
||||||
|
target_name = target['name']
|
||||||
|
data = {}
|
||||||
|
data["name"] = target_name
|
||||||
|
|
||||||
|
output = self._access_api(self.session, provider, path,
|
||||||
|
json.dumps(data), 'GET')
|
||||||
|
|
||||||
|
target_status = output['output']['header']['messages'][0]
|
||||||
|
|
||||||
|
if 'ONLINE' not in target_status:
|
||||||
|
message = (_('ACCESSIscsiDriver setup error as %s '
|
||||||
|
'target is offline') % target_name)
|
||||||
|
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')
|
||||||
|
|
||||||
|
for lun in 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')
|
||||||
|
|
||||||
|
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')
|
||||||
|
return fs_list
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
data2 = {"type": "LUN", "key": "cinder_iscsi"}
|
||||||
|
data2["id"] = lun_name
|
||||||
|
data2["value"] = 'cinder_lun'
|
||||||
|
path = self._update_object
|
||||||
|
result = self._access_api(self.session, provider, path,
|
||||||
|
json.dumps(data2), 'POST')
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
data2 = {"type": "LUN", "key": "cinder_iscsi"}
|
||||||
|
data2["id"] = lun_name
|
||||||
|
path = self._update_object
|
||||||
|
result = self._access_api(self.session, provider, path,
|
||||||
|
json.dumps(data2), 'DELETE')
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
data2 = {"type": "LUN_SNAP", "key": "cinder_iscsi"}
|
||||||
|
data2["id"] = snap_name
|
||||||
|
data2["value"] = 'cinder_lun_snap'
|
||||||
|
path = self._update_object
|
||||||
|
result = self._access_api(self.session, provider, path,
|
||||||
|
json.dumps(data2), 'POST')
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
data2 = {"type": "LUN_SNAP", "key": "cinder_iscsi"}
|
||||||
|
data2["id"] = snap_name
|
||||||
|
path = self._update_object
|
||||||
|
result = self._access_api(self.session, provider, path,
|
||||||
|
json.dumps(data2), 'DELETE')
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
data2 = {"type": "LUN", "key": "cinder_iscsi"}
|
||||||
|
data2["id"] = cloned_lun_name
|
||||||
|
data2["value"] = 'cinder_lun'
|
||||||
|
path = self._update_object
|
||||||
|
result = self._access_api(self.session, provider, path,
|
||||||
|
json.dumps(data2), 'POST')
|
||||||
|
|
||||||
|
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 = ""
|
||||||
|
for snap in snap_info['output']['output']['snapshots']:
|
||||||
|
if snap['snapshot_name'] == snap_name:
|
||||||
|
target_name = snap['target_name']
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
data2 = {"type": "LUN", "key": "cinder_iscsi"}
|
||||||
|
data2["id"] = lun_name
|
||||||
|
data2["value"] = 'cinder_lun'
|
||||||
|
path = self._update_object
|
||||||
|
result = self._access_api(self.session, provider, path,
|
||||||
|
json.dumps(data2), 'POST')
|
||||||
|
|
||||||
|
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 or tail == self._update_object:
|
||||||
|
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 path == self._update_object:
|
||||||
|
return True
|
||||||
|
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()
|
||||||
|
self.session = self._authenticate_access(self._va_ip,
|
||||||
|
self._user, self._pwd)
|
||||||
|
|
||||||
|
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,89 @@
|
|||||||
|
===========================
|
||||||
|
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>
|
||||||
|
...
|
@ -66,6 +66,7 @@ Driver Configuration Reference
|
|||||||
drivers/storpool-volume-driver
|
drivers/storpool-volume-driver
|
||||||
drivers/synology-dsm-driver
|
drivers/synology-dsm-driver
|
||||||
drivers/tintri-volume-driver
|
drivers/tintri-volume-driver
|
||||||
|
drivers/veritas-access-iscsi-driver
|
||||||
drivers/vzstorage-driver
|
drivers/vzstorage-driver
|
||||||
drivers/vmware-vmdk-driver
|
drivers/vmware-vmdk-driver
|
||||||
drivers/windows-iscsi-volume-driver
|
drivers/windows-iscsi-volume-driver
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added ISCSI based driver for Veritas Access.
|
Loading…
x
Reference in New Issue
Block a user