
This driver implements all the minimum required features for cinder. Change-Id: I48b59a2ecab8b856547a4ae17e6e0fd04094475a Implement: blueprint veritas-access-cinder-iscsi-support
887 lines
33 KiB
Python
887 lines
33 KiB
Python
# 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
|