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:
mayurindalkar 2018-01-30 20:22:21 +05:30
parent cf56cdd4da
commit a9fad35a20
8 changed files with 1656 additions and 0 deletions

View File

@ -159,6 +159,8 @@ from cinder.volume.drivers import storpool as cinder_volume_drivers_storpool
from cinder.volume.drivers.synology import synology_common as \
cinder_volume_drivers_synology_synologycommon
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 \
cinder_volume_drivers_vmware_vmdk
from cinder.volume.drivers import vzstorage as cinder_volume_drivers_vzstorage
@ -259,6 +261,7 @@ def list_opts():
cinder_volume_drivers_inspur_instorage_instorageiscsi.
instorage_mcs_iscsi_opts,
cinder_volume_drivers_storpool.storpool_opts,
cinder_volume_drivers_veritas_access_veritasiscsi.VA_VOL_OPTS,
cinder_volume_manager.volume_manager_opts,
cinder_wsgi_eventletserver.socket_opts,
)),

View File

@ -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)

View 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

View File

@ -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>
...

View File

@ -66,6 +66,7 @@ Driver Configuration Reference
drivers/storpool-volume-driver
drivers/synology-dsm-driver
drivers/tintri-volume-driver
drivers/veritas-access-iscsi-driver
drivers/vzstorage-driver
drivers/vmware-vmdk-driver
drivers/windows-iscsi-volume-driver

View File

@ -0,0 +1,3 @@
---
features:
- Added ISCSI based driver for Veritas Access.