Introduce Hitachi VSP iSCSI driver

This patch introduces Hitachi VSP iSCSI driver.

Signed-off-by: "Kazumasa Nomura <kazumasa.nomura.rx@hitachi.com>"

DocImpact
Implements: blueprint hitachi-vsp-fc-driver

Change-Id: I65d38f88201d55315eb3bfbffb2a72aa88a91a0d
Depends-on: Ife99bf138c86a2f9be91298064513073271f1239
This commit is contained in:
Kazumasa Nomura 2016-12-07 21:40:09 +09:00
parent 5c815388e2
commit 3f6a9739b3
9 changed files with 2200 additions and 22 deletions

View File

@ -116,6 +116,8 @@ from cinder.volume.drivers.hitachi import vsp_fc as \
cinder_volume_drivers_hitachi_vspfc
from cinder.volume.drivers.hitachi import vsp_horcm as \
cinder_volume_drivers_hitachi_vsphorcm
from cinder.volume.drivers.hitachi import vsp_iscsi as \
cinder_volume_drivers_hitachi_vspiscsi
from cinder.volume.drivers.hpe import hpe_3par_common as \
cinder_volume_drivers_hpe_hpe3parcommon
from cinder.volume.drivers.hpe import hpe_lefthand_iscsi as \
@ -294,6 +296,7 @@ def list_opts():
cinder_volume_drivers_hitachi_vspcommon.common_opts,
cinder_volume_drivers_hitachi_vspfc.fc_opts,
cinder_volume_drivers_hitachi_vsphorcm.horcm_opts,
cinder_volume_drivers_hitachi_vspiscsi.iscsi_opts,
cinder_volume_drivers_hpe_hpe3parcommon.hpe3par_opts,
cinder_volume_drivers_hpe_hpelefthandiscsi.hpelefthand_opts,
cinder_volume_drivers_hpe_hpexpopts.FC_VOLUME_OPTS,

File diff suppressed because it is too large Load Diff

View File

@ -95,8 +95,8 @@ common_opts = [
cfg.BoolOpt(
'vsp_group_request',
default=False,
help='If True, the driver will create host groups on storage ports '
'as needed.'),
help='If True, the driver will create host groups or iSCSI targets on '
'storage ports as needed.'),
]
_REQUIRED_COMMON_OPTS = [
@ -151,6 +151,7 @@ class VSPCommon(object):
'ldev_range': [],
'ports': [],
'wwns': {},
'portals': {},
'output_first': True,
}
@ -627,6 +628,20 @@ class VSPCommon(object):
if not self.conf.safe_get(opt):
msg = utils.output_log(MSG.INVALID_PARAMETER, param=opt)
raise exception.VSPError(msg)
if self.storage_info['protocol'] == 'iSCSI':
self.check_param_iscsi()
def check_param_iscsi(self):
"""Check iSCSI-related parameter values and consistency among them."""
if self.conf.vsp_use_chap_auth:
if not self.conf.vsp_auth_user:
msg = utils.output_log(MSG.INVALID_PARAMETER,
param='vsp_auth_user')
raise exception.VSPError(msg)
if not self.conf.vsp_auth_password:
msg = utils.output_log(MSG.INVALID_PARAMETER,
param='vsp_auth_password')
raise exception.VSPError(msg)
def _range2list(self, param):
"""Analyze a 'xxx-xxx' string and return a list of two integers."""
@ -674,7 +689,7 @@ class VSPCommon(object):
def init_cinder_hosts(self, **kwargs):
"""Initialize server-storage connection."""
targets = kwargs.pop('targets', {'info': {}, 'list': []})
targets = kwargs.pop('targets', {'info': {}, 'list': [], 'iqns': {}})
connector = cinder_utils.brick_get_connector_properties(
multipath=self.conf.use_multipath_for_image_xfer,
enforce_multipath=self.conf.enforce_multipath_for_image_xfer)
@ -719,8 +734,9 @@ class VSPCommon(object):
raise exception.VSPError(msg)
def _create_target(self, targets, port, connector, hba_ids):
"""Create a host group for the specified storage port."""
target_name, gid = self.create_target_to_storage(port, connector)
"""Create a host group or an iSCSI target on the storage port."""
target_name, gid = self.create_target_to_storage(port, connector,
hba_ids)
utils.output_log(MSG.OBJECT_CREATED, object='a target',
details='port: %(port)s, gid: %(gid)s, target_name: '
'%(target)s' %
@ -735,13 +751,13 @@ class VSPCommon(object):
targets['list'].append((port, gid))
@abc.abstractmethod
def create_target_to_storage(self, port, connector):
"""Create a host group on the specified port."""
def create_target_to_storage(self, port, connector, hba_ids):
"""Create a host group or an iSCSI target on the specified port."""
raise NotImplementedError()
@abc.abstractmethod
def set_target_mode(self, port, gid):
"""Configure the host group to meet the environment."""
"""Configure the target to meet the environment."""
raise NotImplementedError()
@abc.abstractmethod
@ -751,7 +767,7 @@ class VSPCommon(object):
@abc.abstractmethod
def delete_target_from_storage(self, port, gid):
"""Delete the host group from the port."""
"""Delete the host group or the iSCSI target from the port."""
raise NotImplementedError()
def output_param_to_log(self):
@ -777,6 +793,7 @@ class VSPCommon(object):
'info': {},
'list': [],
'lun': {},
'iqns': {},
}
ldev = utils.get_ldev(volume)
# When 'ldev' is 0, it should be true.
@ -813,10 +830,18 @@ class VSPCommon(object):
multipath = connector.get('multipath', False)
if self.storage_info['protocol'] == 'FC':
data = self.get_properties_fc(targets)
elif self.storage_info['protocol'] == 'iSCSI':
data = self.get_properties_iscsi(targets, multipath)
if target_lun is not None:
data['target_discovered'] = False
if not multipath or self.storage_info['protocol'] == 'FC':
data['target_lun'] = target_lun
else:
target_luns = []
for target in targets['list']:
if targets['lun'][target[0]]:
target_luns.append(target_lun)
data['target_luns'] = target_luns
return data
def get_properties_fc(self, targets):
@ -827,6 +852,27 @@ class VSPCommon(object):
if targets['lun'][target[0]]]
return data
def get_properties_iscsi(self, targets, multipath):
"""Return iSCSI-specific server-LDEV connection info."""
data = {}
primary_target = targets['list'][0]
if not multipath:
data['target_portal'] = self.storage_info[
'portals'][primary_target[0]]
data['target_iqn'] = targets['iqns'][primary_target]
else:
data['target_portals'] = [
self.storage_info['portals'][target[0]] for target in
targets['list'] if targets['lun'][target[0]]]
data['target_iqns'] = [
targets['iqns'][target] for target in targets['list']
if targets['lun'][target[0]]]
if self.conf.vsp_use_chap_auth:
data['auth_method'] = 'CHAP'
data['auth_username'] = self.conf.vsp_auth_user
data['auth_password'] = self.conf.vsp_auth_password
return data
@coordination.synchronized('vsp-host-{self.conf.vsp_storage_id}-'
'{connector[host]}')
def terminate_connection(self, volume, connector):
@ -834,6 +880,7 @@ class VSPCommon(object):
targets = {
'info': {},
'list': [],
'iqns': {},
}
mapped_targets = {
'list': [],
@ -857,11 +904,12 @@ class VSPCommon(object):
unmap_targets['list'].sort(reverse=True)
self.unmap_ldev(unmap_targets, ldev)
target_wwn = [
self.storage_info['wwns'][port_gid[:utils.PORT_ID_LENGTH]]
for port_gid in unmap_targets['list']]
return {'driver_volume_type': self.driver_info['volume_type'],
'data': {'target_wwn': target_wwn}}
if self.storage_info['protocol'] == 'FC':
target_wwn = [
self.storage_info['wwns'][port_gid[:utils.PORT_ID_LENGTH]]
for port_gid in unmap_targets['list']]
return {'driver_volume_type': self.driver_info['volume_type'],
'data': {'target_wwn': target_wwn}}
@abc.abstractmethod
def find_mapped_targets_from_storage(self, targets, ldev, target_ports):

View File

@ -238,7 +238,7 @@ def horcmgr_synchronized(func):
def _is_valid_target(target, target_name, target_ports, is_pair):
"""Return True if the specified host group is valid, False otherwise."""
"""Return True if the specified target is valid, False otherwise."""
if is_pair:
return (target[:utils.PORT_ID_LENGTH] in target_ports and
target_name == _PAIR_TARGET_NAME)
@ -957,7 +957,7 @@ class VSPHORCM(common.VSPCommon):
interval=interval, success_code=success_code, timeout=timeout)
LOG.debug(
'Deleted logical unit path of the specified logical '
'device. (LDEV: %(ldev)s, host group: %(target)s)',
'device. (LDEV: %(ldev)s, target: %(target)s)',
{'ldev': ldev, 'target': target})
def find_all_mapped_targets_from_storage(self, targets, ldev):
@ -968,7 +968,7 @@ class VSPHORCM(common.VSPCommon):
targets['list'].append(port.split()[0])
def delete_target_from_storage(self, port, gid):
"""Delete the host group from the port."""
"""Delete the host group or the iSCSI target from the port."""
result = self.run_raidcom(
'delete', 'host_grp', '-port',
'-'.join([port, gid]), do_raise=False)
@ -1123,6 +1123,7 @@ HORCM_CMD
targets = {
'info': {},
'list': [],
'iqns': {},
}
super(VSPHORCM, self).init_cinder_hosts(targets=targets)
self._init_pair_targets(targets['info'])
@ -1141,7 +1142,7 @@ HORCM_CMD
'wwpns': [_PAIR_TARGET_NAME_BODY],
}
target_name, gid = self.create_target_to_storage(
port, connector)
port, connector, None)
utils.output_log(MSG.OBJECT_CREATED,
object='a target for pair operation',
details='port: %(port)s, gid: %(gid)s, '

View File

@ -66,7 +66,7 @@ class VSPHORCMFC(horcm.VSPHORCM):
utils.output_log(MSG.SET_CONFIG_VALUE, object='port-wwn list',
value=self.storage_info['wwns'])
def create_target_to_storage(self, port, connector):
def create_target_to_storage(self, port, connector, hba_ids):
"""Create a host group on the specified port."""
wwpns = self.get_hba_ids_from_connector(connector)
target_name = utils.TARGET_PREFIX + min(wwpns)

View File

@ -0,0 +1,184 @@
# Copyright (C) 2016, Hitachi, Ltd.
#
# 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.
#
"""HORCM interface iSCSI module for Hitachi VSP Driver."""
import re
from oslo_log import log as logging
from cinder import exception
from cinder.volume.drivers.hitachi import vsp_horcm as horcm
from cinder.volume.drivers.hitachi import vsp_utils as utils
_ISCSI_LINUX_MODE_OPTS = ['-host_mode', 'LINUX']
_ISCSI_HOST_MODE_OPT = '-host_mode_opt'
_ISCSI_HMO_REPORT_FULL_PORTAL = 83
_ISCSI_TARGETS_PATTERN = re.compile(
(r"^CL\w-\w+ +(?P<gid>\d+) +%s(?!pair00 )\S* +(?P<iqn>\S+) +"
r"\w+ +\w +\d+ ") % utils.TARGET_PREFIX, re.M)
_ISCSI_PORT_PATTERN = re.compile(
r"^(CL\w-\w)\w* +ISCSI +TAR +\w+ +\w+ +\w +\w+ +Y ", re.M)
_ISCSI_IPV4_ADDR_PATTERN = re.compile(
r"^IPV4_ADDR +: +(?P<ipv4_addr>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$", re.M)
_ISCSI_TCP_PORT_PATTERN = re.compile(
r'^TCP_PORT\ +:\ +(?P<tcp_port>\d+)$', re.M)
LOG = logging.getLogger(__name__)
MSG = utils.VSPMsg
class VSPHORCMISCSI(horcm.VSPHORCM):
"""HORCM interface iscsi class for Hitachi VSP Driver.
Version history:
.. code-block:: none
1.0.0 - Initial driver.
"""
def connect_storage(self):
"""Prepare for using the storage."""
target_ports = self.conf.vsp_target_ports
super(VSPHORCMISCSI, self).connect_storage()
result = self.run_raidcom('get', 'port')
for port in _ISCSI_PORT_PATTERN.findall(result[1]):
if (target_ports and port in target_ports and
self._set_target_portal(port)):
self.storage_info['ports'].append(port)
self.check_ports_info()
utils.output_log(MSG.SET_CONFIG_VALUE,
object='port-<IP address:port> list',
value=self.storage_info['portals'])
def _set_target_portal(self, port):
"""Get port info and store it in an instance variable."""
ipv4_addr = None
tcp_port = None
result = self.run_raidcom(
'get', 'port', '-port', port, '-key', 'opt')
match = _ISCSI_IPV4_ADDR_PATTERN.search(result[1])
if match:
ipv4_addr = match.group('ipv4_addr')
match = _ISCSI_TCP_PORT_PATTERN.search(result[1])
if match:
tcp_port = match.group('tcp_port')
if not ipv4_addr or not tcp_port:
return False
self.storage_info['portals'][port] = ':'.join(
[ipv4_addr, tcp_port])
return True
def create_target_to_storage(self, port, connector, hba_ids):
"""Create an iSCSI target on the specified port."""
target_name = utils.TARGET_PREFIX + connector['ip']
args = [
'add', 'host_grp', '-port', port, '-host_grp_name', target_name]
if hba_ids:
args.extend(['-iscsi_name', hba_ids + utils.TARGET_IQN_SUFFIX])
try:
result = self.run_raidcom(*args)
except exception.VSPError:
result = self.run_raidcom('get', 'host_grp', '-port', port)
hostgroup_pt = re.compile(
r"^CL\w-\w+ +(?P<gid>\d+) +%s +\S+ " %
target_name.replace('.', r'\.'), re.M)
gid = hostgroup_pt.findall(result[1])
if gid:
return target_name, gid[0]
else:
raise
return target_name, horcm.find_value(result[1], 'gid')
def set_hba_ids(self, port, gid, hba_ids):
"""Connect the specified HBA with the specified port."""
self.run_raidcom(
'add', 'hba_iscsi', '-port', '-'.join([port, gid]),
'-hba_iscsi_name', hba_ids)
def set_target_mode(self, port, gid):
"""Configure the iSCSI target to meet the environment."""
hostmode_setting = []
hostmode_setting[:] = _ISCSI_LINUX_MODE_OPTS
hostmode_setting.append(_ISCSI_HOST_MODE_OPT)
hostmode_setting.append(_ISCSI_HMO_REPORT_FULL_PORTAL)
self.run_raidcom(
'modify', 'host_grp', '-port',
'-'.join([port, gid]), *hostmode_setting)
def find_targets_from_storage(self, targets, connector, target_ports):
"""Find mapped ports, memorize them and return unmapped port count."""
nr_not_found = 0
target_name = utils.TARGET_PREFIX + connector['ip']
success_code = horcm.HORCM_EXIT_CODE.union([horcm.EX_ENOOBJ])
iqn = self.get_hba_ids_from_connector(connector)
iqn_pattern = re.compile(
r'^CL\w-\w+ +\d+ +\S+ +%s ' % iqn, re.M)
for port in target_ports:
targets['info'][port] = False
result = self.run_raidcom(
'get', 'hba_iscsi', '-port', port, target_name,
success_code=success_code)
if iqn_pattern.search(result[1]):
gid = result[1].splitlines()[1].split()[1]
targets['info'][port] = True
targets['list'].append((port, gid))
continue
result = self.run_raidcom(
'get', 'host_grp', '-port', port)
for gid, iqn in _ISCSI_TARGETS_PATTERN.findall(result[1]):
result = self.run_raidcom(
'get', 'hba_iscsi', '-port', '-'.join([port, gid]))
if iqn_pattern.search(result[1]):
targets['info'][port] = True
targets['list'].append((port, gid))
targets['iqns'][(port, gid)] = iqn
break
else:
nr_not_found += 1
return nr_not_found
def get_properties_iscsi(self, targets, multipath):
"""Check if specified iSCSI targets exist and store their IQNs."""
if not multipath:
target_list = targets['list'][:1]
else:
target_list = targets['list'][:]
for target in target_list:
if target not in targets['iqns']:
port, gid = target
result = self.run_raidcom('get', 'host_grp', '-port', port)
match = re.search(
r"^CL\w-\w+ +%s +\S+ +(?P<iqn>\S+) +\w+ +\w +\d+ " % gid,
result[1], re.M)
if not match:
msg = utils.output_log(MSG.RESOURCE_NOT_FOUND,
resource='Target IQN')
raise exception.VSPError(msg)
targets['iqns'][target] = match.group('iqn')
LOG.debug('Found iqn of the iSCSI target. (port: %(port)s, '
'gid: %(gid)s, target iqn: %(iqn)s)',
{'port': port, 'gid': gid,
'iqn': match.group('iqn')})
return super(VSPHORCMISCSI, self).get_properties_iscsi(
targets, multipath)

View File

@ -0,0 +1,185 @@
# Copyright (C) 2016, Hitachi, Ltd.
#
# 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.
#
"""iSCSI module for Hitachi VSP Driver."""
from oslo_config import cfg
from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.hitachi import vsp_common as common
from cinder.volume.drivers.hitachi import vsp_utils as utils
iscsi_opts = [
cfg.BoolOpt(
'vsp_use_chap_auth',
default=False,
help='If True, CHAP authentication will be applied to communication '
'between hosts and any of the iSCSI targets on the storage ports.'),
cfg.StrOpt(
'vsp_auth_user',
help='Name of the user used for CHAP authentication performed in '
'communication between hosts and iSCSI targets on the storage ports.'),
cfg.StrOpt(
'vsp_auth_password',
secret=True,
help='Password corresponding to vsp_auth_user.'),
]
MSG = utils.VSPMsg
_DRIVER_INFO = {
'proto': 'iSCSI',
'hba_id': 'initiator',
'hba_id_type': 'iSCSI initiator IQN',
'msg_id': {
'target': MSG.CREATE_ISCSI_TARGET_FAILED,
},
'volume_backend_name': utils.DRIVER_PREFIX + 'iSCSI',
'volume_opts': iscsi_opts,
'volume_type': 'iscsi',
}
CONF = cfg.CONF
CONF.register_opts(iscsi_opts)
@interface.volumedriver
class VSPISCSIDriver(driver.ISCSIDriver):
"""iSCSI class for Hitachi VSP Driver.
Version history:
.. code-block:: none
1.0.0 - Initial driver.
"""
VERSION = common.VERSION
# ThirdPartySystems wiki page
CI_WIKI_NAME = "Hitachi_VSP_CI"
def __init__(self, *args, **kwargs):
"""Initialize instance variables."""
utils.output_log(MSG.DRIVER_INITIALIZATION_START,
driver=self.__class__.__name__,
version=self.get_version())
super(VSPISCSIDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(common.common_opts)
self.configuration.append_config_values(iscsi_opts)
self.common = utils.import_object(
self.configuration, _DRIVER_INFO, kwargs.get('db'))
def check_for_setup_error(self):
"""Error are checked in do_setup() instead of this method."""
pass
@utils.output_start_end_log
def create_volume(self, volume):
"""Create a volume and return its properties."""
return self.common.create_volume(volume)
@utils.output_start_end_log
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot and return its properties."""
return self.common.create_volume_from_snapshot(volume, snapshot)
@utils.output_start_end_log
def create_cloned_volume(self, volume, src_vref):
"""Create a clone of the specified volume and return its properties."""
return self.common.create_cloned_volume(volume, src_vref)
@utils.output_start_end_log
def delete_volume(self, volume):
"""Delete the specified volume."""
self.common.delete_volume(volume)
@utils.output_start_end_log
def create_snapshot(self, snapshot):
"""Create a snapshot from a volume and return its properties."""
return self.common.create_snapshot(snapshot)
@utils.output_start_end_log
def delete_snapshot(self, snapshot):
"""Delete the specified snapshot."""
self.common.delete_snapshot(snapshot)
def get_volume_stats(self, refresh=False):
"""Return properties, capabilities and current states of the driver."""
return self.common.get_volume_stats(refresh)
@utils.output_start_end_log
def update_migrated_volume(
self, ctxt, volume, new_volume, original_volume_status):
"""Do any remaining jobs after migration."""
self.common.discard_zero_page(new_volume)
super(VSPISCSIDriver, self).update_migrated_volume(
ctxt, volume, new_volume, original_volume_status)
@utils.output_start_end_log
def copy_image_to_volume(self, context, volume, image_service, image_id):
"""Fetch the image from image_service and write it to the volume."""
super(VSPISCSIDriver, self).copy_image_to_volume(
context, volume, image_service, image_id)
self.common.discard_zero_page(volume)
@utils.output_start_end_log
def extend_volume(self, volume, new_size):
"""Extend the specified volume to the specified size."""
self.common.extend_volume(volume, new_size)
@utils.output_start_end_log
def manage_existing(self, volume, existing_ref):
"""Return volume properties which Cinder needs to manage the volume."""
return self.common.manage_existing(existing_ref)
@utils.output_start_end_log
def manage_existing_get_size(self, volume, existing_ref):
"""Return the size[GB] of the specified volume."""
return self.common.manage_existing_get_size(existing_ref)
@utils.output_start_end_log
def unmanage(self, volume):
"""Prepare the volume for removing it from Cinder management."""
self.common.unmanage(volume)
@utils.output_start_end_log
def do_setup(self, context):
"""Prepare for the startup of the driver."""
self.common.do_setup(context)
def ensure_export(self, context, volume):
"""Synchronously recreate an export for a volume."""
pass
def create_export(self, context, volume, connector):
"""Export the volume."""
pass
def remove_export(self, context, volume):
"""Remove an export for a volume."""
pass
@utils.output_start_end_log
def initialize_connection(self, volume, connector):
"""Initialize connection between the server and the volume."""
return self.common.initialize_connection(volume, connector)
@utils.output_start_end_log
def terminate_connection(self, volume, connector, **kwargs):
"""Terminate connection between the server and the volume."""
self.common.terminate_connection(volume, connector)

View File

@ -43,11 +43,13 @@ _DRIVER_DIR = 'cinder.volume.drivers.hitachi'
_DRIVERS = {
'HORCM': {
'FC': 'vsp_horcm_fc.VSPHORCMFC',
'iSCSI': 'vsp_horcm_iscsi.VSPHORCMISCSI',
},
}
DRIVER_PREFIX = 'VSP'
TARGET_PREFIX = 'HBSD-'
TARGET_IQN_SUFFIX = '.hbsd-target'
GIGABYTE_PER_BLOCK_SIZE = units.Gi / 512
MAX_PROCESS_WAITTIME = 24 * 60 * 60
@ -131,8 +133,8 @@ class VSPMsg(enum.Enum):
DELETE_TARGET_FAILED = {
'msg_id': 306,
'loglevel': base_logging.WARNING,
'msg': _LW('A host group could not be deleted. (port: %(port)s, '
'gid: %(id)s)'),
'msg': _LW('A host group or an iSCSI target could not be deleted. '
'(port: %(port)s, gid: %(id)s)'),
'suffix': WARNING_SUFFIX
}
CREATE_HOST_GROUP_FAILED = {
@ -141,6 +143,12 @@ class VSPMsg(enum.Enum):
'msg': _LW('A host group could not be added. (port: %(port)s)'),
'suffix': WARNING_SUFFIX
}
CREATE_ISCSI_TARGET_FAILED = {
'msg_id': 309,
'loglevel': base_logging.WARNING,
'msg': _LW('An iSCSI target could not be added. (port: %(port)s)'),
'suffix': WARNING_SUFFIX
}
UNMAP_LDEV_FAILED = {
'msg_id': 310,
'loglevel': base_logging.WARNING,
@ -408,7 +416,7 @@ class VSPMsg(enum.Enum):
NO_CONNECTED_TARGET = {
'msg_id': 649,
'loglevel': base_logging.ERROR,
'msg': _LE('The host group was not found.'),
'msg': _LE('The host group or iSCSI target was not found.'),
'suffix': ERROR_SUFFIX
}
RESOURCE_NOT_FOUND = {

View File

@ -0,0 +1,3 @@
---
features:
- Adds new Hitachi VSP iSCSI Driver.