# Copyright (c) 2014 X-IO Technologies.
# All Rights Reserved.
#
# 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.
import mock
from six.moves import http_client
from cinder import context
from cinder import exception
from cinder import test
from cinder.tests.unit import utils
from cinder.volume.drivers import xio
from cinder.volume import qos_specs
from cinder.volume import volume_types
ISE_IP1 = '10.12.12.1'
ISE_IP2 = '10.11.12.2'
ISE_ISCSI_IP1 = '1.2.3.4'
ISE_ISCSI_IP2 = '1.2.3.5'
ISE_GID = 'isegid'
ISE_IQN = ISE_GID
ISE_WWN1 = ISE_GID + '1'
ISE_WWN2 = ISE_GID + '2'
ISE_WWN3 = ISE_GID + '3'
ISE_WWN4 = ISE_GID + '4'
ISE_TARGETS = [ISE_WWN1, ISE_WWN2, ISE_WWN3, ISE_WWN4]
ISE_INIT_TARGET_MAP = {'init_wwn1': ISE_TARGETS,
'init_wwn2': ISE_TARGETS}
VOLUME_SIZE = 10
NEW_VOLUME_SIZE = 20
VOLUME1 = {'id': '1', 'name': 'volume1',
'size': VOLUME_SIZE, 'volume_type_id': 'type1'}
VOLUME2 = {'id': '2', 'name': 'volume2',
'size': VOLUME_SIZE, 'volume_type_id': 'type2',
'provider_auth': 'CHAP abc abc'}
VOLUME3 = {'id': '3', 'name': 'volume3',
'size': VOLUME_SIZE, 'volume_type_id': None}
SNAPSHOT1 = {'name': 'snapshot1',
'volume_name': VOLUME1['name'],
'volume_type_id': 'type3'}
CLONE1 = {'id': '3', 'name': 'clone1',
'size': VOLUME_SIZE, 'volume_type_id': 'type4'}
HOST1 = 'host1'
HOST2 = 'host2'
ISCSI_CONN1 = {'initiator': 'init_iqn1',
'host': HOST1}
ISCSI_CONN2 = {'initiator': 'init_iqn2',
'host': HOST2}
FC_CONN1 = {'wwpns': ['init_wwn1', 'init_wwn2'],
'host': HOST1}
FC_CONN2 = {'wwpns': ['init_wwn3', 'init_wwn4'],
'host': HOST2}
ISE_HTTP_IP = 'http://' + ISE_IP1
ISE_HOST_LOCATION = '/storage/hosts/1'
ISE_HOST_LOCATION_URL = ISE_HTTP_IP + ISE_HOST_LOCATION
ISE_VOLUME1_LOCATION = '/storage/volumes/volume1'
ISE_VOLUME1_LOCATION_URL = ISE_HTTP_IP + ISE_VOLUME1_LOCATION
ISE_VOLUME2_LOCATION = '/storage/volumes/volume2'
ISE_VOLUME2_LOCATION_URL = ISE_HTTP_IP + ISE_VOLUME2_LOCATION
ISE_VOLUME3_LOCATION = '/storage/volumes/volume3'
ISE_VOLUME3_LOCATION_URL = ISE_HTTP_IP + ISE_VOLUME3_LOCATION
ISE_SNAPSHOT_LOCATION = '/storage/volumes/snapshot1'
ISE_SNAPSHOT_LOCATION_URL = ISE_HTTP_IP + ISE_SNAPSHOT_LOCATION
ISE_CLONE_LOCATION = '/storage/volumes/clone1'
ISE_CLONE_LOCATION_URL = ISE_HTTP_IP + ISE_CLONE_LOCATION
ISE_ALLOCATION_LOCATION = '/storage/allocations/a1'
ISE_ALLOCATION_LOCATION_URL = ISE_HTTP_IP + ISE_ALLOCATION_LOCATION
ISE_GET_QUERY_XML =\
"""
ABC12345
%s
%s
""" % (ISE_IP1, ISE_IP2)
ISE_GET_QUERY_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_QUERY_XML.split())}
ISE_GET_QUERY_NO_CAP_XML =\
"""
ABC12345
%s
%s
""" % (ISE_IP1, ISE_IP2)
ISE_GET_QUERY_NO_CAP_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_QUERY_NO_CAP_XML.split())}
ISE_GET_QUERY_NO_CTRL_XML =\
"""
ABC12345
"""
ISE_GET_QUERY_NO_CTRL_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_QUERY_NO_CTRL_XML.split())}
ISE_GET_QUERY_NO_IP_XML =\
"""
ABC12345
"""
ISE_GET_QUERY_NO_IP_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_QUERY_NO_IP_XML.split())}
ISE_GET_QUERY_NO_GID_XML =\
"""
%s
%s
""" % (ISE_IP1, ISE_IP2)
ISE_GET_QUERY_NO_GID_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_QUERY_NO_GID_XML.split())}
ISE_GET_QUERY_NO_CLONE_XML =\
"""
ABC12345
%s
%s
""" % (ISE_IP1, ISE_IP2)
ISE_GET_QUERY_NO_CLONE_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_QUERY_NO_CLONE_XML.split())}
ISE_GET_STORAGE_POOLS_XML =\
"""
Pool 1
1
None
60
30
45
0
40
0
100
volgid
volgid2
"""
ISE_GET_STORAGE_POOLS_RESP =\
{'status': http_client.OK,
'location': 'Pool location',
'content': " ".join(ISE_GET_STORAGE_POOLS_XML.split())}
ISE_GET_VOL_STATUS_NO_VOL_NODE_XML =\
""""""
ISE_GET_VOL_STATUS_NO_VOL_NODE_RESP =\
{'status': http_client.OK,
'location': 'u%s' % ISE_VOLUME1_LOCATION_URL,
'content': " ".join(ISE_GET_VOL_STATUS_NO_VOL_NODE_XML.split())}
ISE_GET_VOL_STATUS_NO_STATUS_XML =\
"""
""" % (ISE_VOLUME1_LOCATION_URL)
ISE_GET_VOL_STATUS_NO_STATUS_RESP =\
{'status': http_client.OK,
'location': 'u%s' % ISE_VOLUME1_LOCATION_URL,
'content': " ".join(ISE_GET_VOL_STATUS_NO_STATUS_XML.split())}
ISE_GET_VOL1_STATUS_XML =\
"""
Prepared
10
""" % (ISE_VOLUME1_LOCATION_URL)
ISE_GET_VOL1_STATUS_RESP =\
{'status': http_client.OK,
'location': 'u%s' % ISE_VOLUME1_LOCATION_URL,
'content': " ".join(ISE_GET_VOL1_STATUS_XML.split())}
ISE_GET_VOL2_STATUS_XML =\
"""
Prepared
""" % (ISE_VOLUME2_LOCATION_URL)
ISE_GET_VOL2_STATUS_RESP =\
{'status': http_client.OK,
'location': 'u%s' % ISE_VOLUME2_LOCATION_URL,
'content': " ".join(ISE_GET_VOL2_STATUS_XML.split())}
ISE_GET_VOL3_STATUS_XML =\
"""
Prepared
""" % (ISE_VOLUME3_LOCATION_URL)
ISE_GET_VOL3_STATUS_RESP =\
{'status': http_client.OK,
'location': 'u%s' % ISE_VOLUME3_LOCATION_URL,
'content': " ".join(ISE_GET_VOL3_STATUS_XML.split())}
ISE_GET_SNAP1_STATUS_XML =\
"""
Prepared
""" % (ISE_SNAPSHOT_LOCATION_URL)
ISE_GET_SNAP1_STATUS_RESP =\
{'status': http_client.OK,
'location': 'u%s' % ISE_SNAPSHOT_LOCATION_URL,
'content': " ".join(ISE_GET_SNAP1_STATUS_XML.split())}
ISE_GET_CLONE1_STATUS_XML =\
"""
Prepared
""" % (ISE_CLONE_LOCATION_URL)
ISE_GET_CLONE1_STATUS_RESP =\
{'status': http_client.OK,
'location': 'u%s' % ISE_CLONE_LOCATION_URL,
'content': " ".join(ISE_GET_CLONE1_STATUS_XML.split())}
ISE_CREATE_VOLUME_XML = """"""
ISE_CREATE_VOLUME_RESP =\
{'status': http_client.CREATED,
'location': ISE_VOLUME1_LOCATION_URL,
'content': " ".join(ISE_CREATE_VOLUME_XML.split())}
ISE_GET_IONETWORKS_XML =\
"""
"""
ISE_GET_IONETWORKS_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_IONETWORKS_XML.split())}
ISE_GET_IONETWORKS_CHAP_XML =\
"""
abc
abc
"""
ISE_GET_IONETWORKS_CHAP_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_IONETWORKS_CHAP_XML.split())}
ISE_DELETE_VOLUME_XML = """"""
ISE_DELETE_VOLUME_RESP =\
{'status': http_client.NO_CONTENT,
'location': '',
'content': " ".join(ISE_DELETE_VOLUME_XML.split())}
ISE_GET_ALLOC_WITH_EP_XML =\
"""
%s
%s
1
""" %\
(ISE_ALLOCATION_LOCATION_URL, VOLUME1['name'], HOST1)
ISE_GET_ALLOC_WITH_EP_RESP =\
{'status': http_client.OK,
'location': ISE_ALLOCATION_LOCATION_URL,
'content': " ".join(ISE_GET_ALLOC_WITH_EP_XML.split())}
ISE_GET_ALLOC_WITH_NO_ALLOC_XML =\
"""""" % ISE_ALLOCATION_LOCATION_URL
ISE_GET_ALLOC_WITH_NO_ALLOC_RESP =\
{'status': http_client.OK,
'location': ISE_ALLOCATION_LOCATION_URL,
'content': " ".join(ISE_GET_ALLOC_WITH_NO_ALLOC_XML.split())}
ISE_DELETE_ALLOC_XML = """"""
ISE_DELETE_ALLOC_RESP =\
{'status': http_client.NO_CONTENT,
'location': '',
'content': " ".join(ISE_DELETE_ALLOC_XML.split())}
ISE_GET_HOSTS_NOHOST_XML =\
""""""
ISE_GET_HOSTS_NOHOST_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_HOSTS_NOHOST_XML.split())}
ISE_GET_HOSTS_HOST1_XML =\
"""
"OPENSTACK"
%s
1
init_wwn1
init_wwn2
init_iqn1
""" % HOST1
ISE_GET_HOSTS_HOST1_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_HOSTS_HOST1_XML.split())}
ISE_GET_HOSTS_HOST1_HOST_TYPE_XML =\
"""
"WINDOWS"
%s
1
init_wwn1
init_wwn2
init_iqn1
""" % HOST1
ISE_GET_HOSTS_HOST1_HOST_TYPE_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_HOSTS_HOST1_HOST_TYPE_XML.split())}
ISE_GET_HOSTS_HOST2_XML =\
"""
%s
2
init_wwn3
init_wwn4
init_iqn2
""" % HOST2
ISE_GET_HOSTS_HOST2_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_HOSTS_HOST2_XML.split())}
ISE_CREATE_HOST_XML =\
""""""
ISE_CREATE_HOST_RESP =\
{'status': http_client.CREATED,
'location': 'http://ip/storage/hosts/host1',
'content': " ".join(ISE_CREATE_HOST_XML.split())}
ISE_CREATE_ALLOC_XML =\
""""""
ISE_CREATE_ALLOC_RESP =\
{'status': http_client.CREATED,
'location': ISE_ALLOCATION_LOCATION_URL,
'content': " ".join(ISE_CREATE_ALLOC_XML.split())}
ISE_GET_ENDPOINTS_XML =\
"""
isegid
iSCSI
ise1
a1
isegid
Fibre Channel
ise1
a1
""" % (ISE_ALLOCATION_LOCATION_URL,
ISE_ALLOCATION_LOCATION_URL)
ISE_GET_ENDPOINTS_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_ENDPOINTS_XML.split())}
ISE_GET_CONTROLLERS_XML =\
"""
%s
isegid
%s
%s
%s
isegid
%s
%s
""" % (ISE_ISCSI_IP1, ISE_WWN1, ISE_WWN2,
ISE_ISCSI_IP2, ISE_WWN3, ISE_WWN4)
ISE_GET_CONTROLLERS_RESP =\
{'status': http_client.OK,
'location': '',
'content': " ".join(ISE_GET_CONTROLLERS_XML.split())}
ISE_CREATE_SNAPSHOT_XML = """"""
ISE_CREATE_SNAPSHOT_RESP =\
{'status': http_client.CREATED,
'location': ISE_SNAPSHOT_LOCATION_URL,
'content': " ".join(ISE_CREATE_SNAPSHOT_XML.split())}
ISE_PREP_SNAPSHOT_XML = """"""
ISE_PREP_SNAPSHOT_RESP =\
{'status': http_client.ACCEPTED,
'location': ISE_SNAPSHOT_LOCATION_URL,
'content': " ".join(ISE_PREP_SNAPSHOT_XML.split())}
ISE_MODIFY_VOLUME_XML = """"""
ISE_MODIFY_VOLUME_RESP =\
{'status': http_client.CREATED,
'location': ISE_VOLUME1_LOCATION_URL,
'content': " ".join(ISE_MODIFY_VOLUME_XML.split())}
ISE_MODIFY_HOST_XML = """"""
ISE_MODIFY_HOST_RESP =\
{'status': http_client.CREATED,
'location': ISE_HOST_LOCATION_URL,
'content': " ".join(ISE_MODIFY_HOST_XML.split())}
ISE_BAD_CONNECTION_RESP =\
{'status': 0,
'location': '',
'content': " "}
ISE_400_RESP =\
{'status': http_client.BAD_REQUEST,
'location': '',
'content': ""}
ISE_GET_VOL_STATUS_404_XML = \
"""VOLUME not found."""
ISE_GET_VOL_STATUS_404_RESP =\
{'status': http_client.NOT_FOUND,
'location': '',
'content': " ".join(ISE_GET_VOL_STATUS_404_XML.split())}
ISE_400_INVALID_STATE_XML = \
"""Not in a valid state."""
ISE_400_INVALID_STATE_RESP =\
{'status': http_client.BAD_REQUEST,
'location': '',
'content': " ".join(ISE_400_INVALID_STATE_XML.split())}
ISE_409_CONFLICT_XML = \
"""Conflict"""
ISE_409_CONFLICT_RESP =\
{'status': http_client.CONFLICT,
'location': '',
'content': " ".join(ISE_409_CONFLICT_XML.split())}
DRIVER = "cinder.volume.drivers.xio.XIOISEDriver"
@mock.patch(DRIVER + "._opener", autospec=True)
class XIOISEDriverTestCase(object):
# Test cases for X-IO volume driver
def setUp(self):
super(XIOISEDriverTestCase, self).setUp()
# set good default values
self.configuration = mock.Mock()
self.configuration.san_ip = ISE_IP1
self.configuration.san_user = 'fakeuser'
self.configuration.san_password = 'fakepass'
self.configuration.iscsi_ip_address = ISE_ISCSI_IP1
self.configuration.driver_use_ssl = False
self.configuration.ise_completion_retries = 30
self.configuration.ise_connection_retries = 5
self.configuration.ise_retry_interval = 1
self.configuration.volume_backend_name = 'ise1'
self.driver = None
self.protocol = ''
self.connector = None
self.connection_failures = 0
self.hostgid = ''
self.use_response_table = 1
def setup_test(self, protocol):
self.protocol = protocol
# set good default values
if self.protocol == 'iscsi':
self.configuration.ise_protocol = protocol
self.connector = ISCSI_CONN1
self.hostgid = self.connector['initiator']
elif self.protocol == 'fibre_channel':
self.configuration.ise_protocol = protocol
self.connector = FC_CONN1
self.hostgid = self.connector['wwpns'][0]
def setup_driver(self):
# this setups up driver object with previously set configuration values
if self.configuration.ise_protocol == 'iscsi':
self.driver =\
xio.XIOISEISCSIDriver(configuration=self.configuration)
elif self.configuration.ise_protocol == 'fibre_channel':
self.driver =\
xio.XIOISEFCDriver(configuration=self.configuration)
elif self.configuration.ise_protocol == 'test_prot':
# if test_prot specified override with correct protocol
# used to bypass protocol specific driver
self.configuration.ise_protocol = self.protocol
self.driver = xio.XIOISEDriver(configuration=self.configuration)
else:
# Invalid protocol type
raise exception.Invalid()
#################################
# UNIT TESTS #
#################################
def test_do_setup(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_RESP])
self.driver.do_setup(None)
def test_negative_do_setup_no_clone_support(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_NO_CLONE_RESP])
self.assertRaises(exception.XIODriverException,
self.driver.do_setup, None)
def test_negative_do_setup_no_capabilities(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_NO_CAP_RESP])
self.assertRaises(exception.XIODriverException,
self.driver.do_setup, None)
def test_negative_do_setup_no_ctrl(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_NO_CTRL_RESP])
self.assertRaises(exception.XIODriverException,
self.driver.do_setup, None)
def test_negative_do_setup_no_ipaddress(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_NO_IP_RESP])
self.driver.do_setup(None)
def test_negative_do_setup_bad_globalid_none(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_NO_GID_RESP])
self.assertRaises(exception.XIODriverException,
self.driver.do_setup, None)
def test_check_for_setup_error(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP])
self.setup_driver()
self.driver.check_for_setup_error()
def test_negative_do_setup_bad_ip(self, mock_req):
# set san_ip to bad value
self.configuration.san_ip = ''
mock_req.side_effect = iter([ISE_GET_QUERY_RESP])
self.setup_driver()
self.assertRaises(exception.XIODriverException,
self.driver.check_for_setup_error)
def test_negative_do_setup_bad_user_blank(self, mock_req):
# set san_user to bad value
self.configuration.san_login = ''
mock_req.side_effect = iter([ISE_GET_QUERY_RESP])
self.setup_driver()
self.assertRaises(exception.XIODriverException,
self.driver.check_for_setup_error)
def test_negative_do_setup_bad_password_blank(self, mock_req):
# set san_password to bad value
self.configuration.san_password = ''
mock_req.side_effect = iter([ISE_GET_QUERY_RESP])
self.setup_driver()
self.assertRaises(exception.XIODriverException,
self.driver.check_for_setup_error)
def test_get_volume_stats(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_STORAGE_POOLS_RESP])
backend_name = self.configuration.volume_backend_name
if self.configuration.ise_protocol == 'iscsi':
protocol = 'iSCSI'
else:
protocol = 'fibre_channel'
exp_result = {'vendor_name': "X-IO",
'driver_version': "1.1.4",
'volume_backend_name': backend_name,
'reserved_percentage': 0,
'total_capacity_gb': 100,
'free_capacity_gb': 60,
'QoS_support': True,
'affinity': True,
'thin': False,
'pools': [{'pool_ise_name': "Pool 1",
'pool_name': "1",
'status': "Operational",
'status_details': "None",
'free_capacity_gb': 60,
'free_capacity_gb_raid_0': 60,
'free_capacity_gb_raid_1': 30,
'free_capacity_gb_raid_5': 45,
'allocated_capacity_gb': 40,
'allocated_capacity_gb_raid_0': 0,
'allocated_capacity_gb_raid_1': 40,
'allocated_capacity_gb_raid_5': 0,
'health': 100,
'media': "Hybrid",
'total_capacity_gb': 100,
'QoS_support': True,
'reserved_percentage': 0}],
'active_volumes': 2,
'storage_protocol': protocol}
act_result = self.driver.get_volume_stats(True)
self.assertDictEqual(exp_result, act_result)
def test_get_volume_stats_ssl(self, mock_req):
self.configuration.driver_use_ssl = True
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_STORAGE_POOLS_RESP])
self.driver.get_volume_stats(True)
def test_negative_get_volume_stats_bad_primary(self, mock_req):
self.configuration.ise_connection_retries = 1
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_BAD_CONNECTION_RESP,
ISE_GET_STORAGE_POOLS_RESP])
self.driver.get_volume_stats(True)
def test_create_volume(self, mock_req):
ctxt = context.get_admin_context()
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "1",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT1', extra_specs)
specs = {'qos:minIOPS': '20',
'qos:maxIOPS': '2000',
'qos:burstIOPS': '5000'}
qos = qos_specs.create(ctxt, 'fake-qos', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
VOLUME1['volume_type_id'] = type_ref['id']
self.setup_driver()
if self.configuration.ise_protocol == 'iscsi':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_CREATE_VOLUME_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_GET_IONETWORKS_RESP])
exp_result = {}
exp_result = {"provider_auth": ""}
act_result = self.driver.create_volume(VOLUME1)
self.assertDictEqual(exp_result, act_result)
elif self.configuration.ise_protocol == 'fibre_channel':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_CREATE_VOLUME_RESP,
ISE_GET_VOL1_STATUS_RESP])
self.driver.create_volume(VOLUME1)
def test_create_volume_chap(self, mock_req):
ctxt = context.get_admin_context()
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "1",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT1', extra_specs)
specs = {'qos:minIOPS': '20',
'qos:maxIOPS': '2000',
'qos:burstIOPS': '5000'}
qos = qos_specs.create(ctxt, 'fake-qos', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
VOLUME1['volume_type_id'] = type_ref['id']
self.setup_driver()
if self.configuration.ise_protocol == 'iscsi':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_CREATE_VOLUME_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_GET_IONETWORKS_CHAP_RESP])
exp_result = {}
exp_result = {"provider_auth": "CHAP abc abc"}
act_result = self.driver.create_volume(VOLUME1)
self.assertDictEqual(exp_result, act_result)
elif self.configuration.ise_protocol == 'fibre_channel':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_CREATE_VOLUME_RESP,
ISE_GET_VOL1_STATUS_RESP])
self.driver.create_volume(VOLUME1)
def test_create_volume_type_none(self, mock_req):
self.setup_driver()
if self.configuration.ise_protocol == 'iscsi':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_CREATE_VOLUME_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_GET_IONETWORKS_RESP])
elif self.configuration.ise_protocol == 'fibre_channel':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_CREATE_VOLUME_RESP,
ISE_GET_VOL1_STATUS_RESP])
self.driver.create_volume(VOLUME3)
def test_delete_volume(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_DELETE_ALLOC_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_DELETE_VOLUME_RESP,
ISE_GET_VOL_STATUS_404_RESP])
self.setup_driver()
self.driver.delete_volume(VOLUME1)
def test_delete_volume_delayed(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_DELETE_ALLOC_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_DELETE_VOLUME_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_GET_VOL_STATUS_404_RESP])
self.setup_driver()
self.driver.delete_volume(VOLUME1)
def test_delete_volume_timeout(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_DELETE_ALLOC_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_DELETE_VOLUME_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_GET_VOL1_STATUS_RESP])
self.configuration.ise_completion_retries = 3
self.setup_driver()
self.driver.delete_volume(VOLUME1)
def test_delete_volume_none_existing(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_DELETE_ALLOC_RESP,
ISE_GET_VOL1_STATUS_RESP])
self.setup_driver()
self.driver.delete_volume(VOLUME2)
def test_initialize_connection_positive(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST2_RESP,
ISE_CREATE_HOST_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_CREATE_ALLOC_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_GET_CONTROLLERS_RESP])
self.setup_driver()
exp_result = {}
if self.configuration.ise_protocol == 'iscsi':
exp_result = {"driver_volume_type": "iscsi",
"data": {"target_lun": 1,
"volume_id": '1',
"target_discovered": False,
"target_iqn": ISE_IQN,
"target_portal": ISE_ISCSI_IP1 + ":3260"}}
elif self.configuration.ise_protocol == 'fibre_channel':
exp_result = {"driver_volume_type": "fibre_channel",
"data": {"target_lun": 1,
"volume_id": '1',
"target_discovered": True,
"initiator_target_map": ISE_INIT_TARGET_MAP,
"target_wwn": ISE_TARGETS}}
act_result =\
self.driver.initialize_connection(VOLUME1, self.connector)
self.assertDictEqual(exp_result, act_result)
def test_initialize_connection_positive_host_type(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST1_HOST_TYPE_RESP,
ISE_MODIFY_HOST_RESP,
ISE_CREATE_ALLOC_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_GET_CONTROLLERS_RESP])
self.setup_driver()
exp_result = {}
if self.configuration.ise_protocol == 'iscsi':
exp_result = {"driver_volume_type": "iscsi",
"data": {"target_lun": 1,
"volume_id": '1',
"target_discovered": False,
"target_iqn": ISE_IQN,
"target_portal": ISE_ISCSI_IP1 + ":3260"}}
elif self.configuration.ise_protocol == 'fibre_channel':
exp_result = {"driver_volume_type": "fibre_channel",
"data": {"target_lun": 1,
"volume_id": '1',
"target_discovered": True,
"initiator_target_map": ISE_INIT_TARGET_MAP,
"target_wwn": ISE_TARGETS}}
act_result =\
self.driver.initialize_connection(VOLUME1, self.connector)
self.assertDictEqual(exp_result, act_result)
def test_initialize_connection_positive_chap(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST2_RESP,
ISE_CREATE_HOST_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_CREATE_ALLOC_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_GET_CONTROLLERS_RESP])
self.setup_driver()
exp_result = {}
if self.configuration.ise_protocol == 'iscsi':
exp_result = {"driver_volume_type": "iscsi",
"data": {"target_lun": 1,
"volume_id": '2',
"target_discovered": False,
"target_iqn": ISE_IQN,
"target_portal": ISE_ISCSI_IP1 + ":3260",
'auth_method': 'CHAP',
'auth_username': 'abc',
'auth_password': 'abc'}}
elif self.configuration.ise_protocol == 'fibre_channel':
exp_result = {"driver_volume_type": "fibre_channel",
"data": {"target_lun": 1,
"volume_id": '2',
"target_discovered": True,
"initiator_target_map": ISE_INIT_TARGET_MAP,
"target_wwn": ISE_TARGETS}}
act_result =\
self.driver.initialize_connection(VOLUME2, self.connector)
self.assertDictEqual(exp_result, act_result)
def test_initialize_connection_negative_no_host(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST2_RESP,
ISE_CREATE_HOST_RESP,
ISE_GET_HOSTS_HOST2_RESP])
self.setup_driver()
self.assertRaises(exception.XIODriverException,
self.driver.initialize_connection,
VOLUME2, self.connector)
def test_initialize_connection_negative_host_type(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST1_HOST_TYPE_RESP,
ISE_400_RESP])
self.setup_driver()
self.assertRaises(exception.XIODriverException,
self.driver.initialize_connection,
VOLUME2, self.connector)
def test_terminate_connection_positive(self, mock_req):
self.setup_driver()
if self.configuration.ise_protocol == 'iscsi':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_DELETE_ALLOC_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_DELETE_ALLOC_RESP])
elif self.configuration.ise_protocol == 'fibre_channel':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_DELETE_ALLOC_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_GET_CONTROLLERS_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_DELETE_ALLOC_RESP])
self.driver.terminate_connection(VOLUME1, self.connector)
def test_terminate_connection_positive_noalloc(self, mock_req):
self.setup_driver()
if self.configuration.ise_protocol == 'iscsi':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_GET_ALLOC_WITH_NO_ALLOC_RESP,
ISE_GET_ALLOC_WITH_NO_ALLOC_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_DELETE_ALLOC_RESP])
elif self.configuration.ise_protocol == 'fibre_channel':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_GET_ALLOC_WITH_NO_ALLOC_RESP,
ISE_GET_ALLOC_WITH_NO_ALLOC_RESP,
ISE_GET_CONTROLLERS_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_DELETE_ALLOC_RESP])
self.driver.terminate_connection(VOLUME1, self.connector)
def test_negative_terminate_connection_bad_host(self, mock_req):
self.setup_driver()
test_connector = {}
if self.configuration.ise_protocol == 'iscsi':
test_connector['initiator'] = 'bad_iqn'
test_connector['host'] = ''
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST1_RESP])
elif self.configuration.ise_protocol == 'fibre_channel':
test_connector['wwpns'] = 'bad_wwn'
test_connector['host'] = ''
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_HOSTS_HOST1_RESP,
ISE_GET_CONTROLLERS_RESP])
self.driver.terminate_connection(VOLUME1, test_connector)
def test_create_snapshot(self, mock_req):
ctxt = context.get_admin_context()
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "1",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT1', extra_specs)
specs = {'qos:minIOPS': '20',
'qos:maxIOPS': '2000',
'qos:burstIOPS': '5000'}
qos = qos_specs.create(ctxt, 'fake-qos', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
SNAPSHOT1['volume_type_id'] = type_ref['id']
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_PREP_SNAPSHOT_RESP,
ISE_GET_SNAP1_STATUS_RESP,
ISE_CREATE_SNAPSHOT_RESP,
ISE_GET_SNAP1_STATUS_RESP])
self.setup_driver()
self.driver.create_snapshot(SNAPSHOT1)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=utils.ZeroIntervalLoopingCall)
def test_negative_create_snapshot_invalid_state_recover(self, mock_req):
ctxt = context.get_admin_context()
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "1",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT1', extra_specs)
specs = {'qos:minIOPS': '20',
'qos:maxIOPS': '2000',
'qos:burstIOPS': '5000'}
qos = qos_specs.create(ctxt, 'fake-qos', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
SNAPSHOT1['volume_type_id'] = type_ref['id']
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_400_INVALID_STATE_RESP,
ISE_PREP_SNAPSHOT_RESP,
ISE_GET_SNAP1_STATUS_RESP,
ISE_CREATE_SNAPSHOT_RESP,
ISE_GET_SNAP1_STATUS_RESP])
self.setup_driver()
self.driver.create_snapshot(SNAPSHOT1)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=utils.ZeroIntervalLoopingCall)
def test_negative_create_snapshot_invalid_state_norecover(self, mock_req):
ctxt = context.get_admin_context()
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "1",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT1', extra_specs)
specs = {'qos:minIOPS': '20',
'qos:maxIOPS': '2000',
'qos:burstIOPS': '5000'}
qos = qos_specs.create(ctxt, 'fake-qos', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
SNAPSHOT1['volume_type_id'] = type_ref['id']
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_400_INVALID_STATE_RESP,
ISE_400_INVALID_STATE_RESP,
ISE_400_INVALID_STATE_RESP,
ISE_400_INVALID_STATE_RESP,
ISE_400_INVALID_STATE_RESP])
self.configuration.ise_completion_retries = 5
self.setup_driver()
self.assertRaises(exception.XIODriverException,
self.driver.create_snapshot, SNAPSHOT1)
def test_negative_create_snapshot_conflict(self, mock_req):
ctxt = context.get_admin_context()
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "1",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT1', extra_specs)
specs = {'qos:minIOPS': '20',
'qos:maxIOPS': '2000',
'qos:burstIOPS': '5000'}
qos = qos_specs.create(ctxt, 'fake-qos', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
SNAPSHOT1['volume_type_id'] = type_ref['id']
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_409_CONFLICT_RESP])
self.configuration.ise_completion_retries = 1
self.setup_driver()
self.assertRaises(exception.XIODriverException,
self.driver.create_snapshot, SNAPSHOT1)
def test_delete_snapshot(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_ALLOC_WITH_EP_RESP,
ISE_DELETE_ALLOC_RESP,
ISE_GET_SNAP1_STATUS_RESP,
ISE_DELETE_VOLUME_RESP])
self.setup_driver()
self.driver.delete_snapshot(SNAPSHOT1)
def test_clone_volume(self, mock_req):
ctxt = context.get_admin_context()
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "1",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT1', extra_specs)
specs = {'qos:minIOPS': '20',
'qos:maxIOPS': '2000',
'qos:burstIOPS': '5000'}
qos = qos_specs.create(ctxt, 'fake-qos', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
VOLUME1['volume_type_id'] = type_ref['id']
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_PREP_SNAPSHOT_RESP,
ISE_GET_SNAP1_STATUS_RESP,
ISE_CREATE_SNAPSHOT_RESP,
ISE_GET_SNAP1_STATUS_RESP])
self.setup_driver()
self.driver.create_cloned_volume(CLONE1, VOLUME1)
def test_extend_volume(self, mock_req):
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_MODIFY_VOLUME_RESP])
self.setup_driver()
self.driver.extend_volume(VOLUME1, NEW_VOLUME_SIZE)
def test_retype_volume(self, mock_req):
ctxt = context.get_admin_context()
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "1",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT1', extra_specs)
specs = {'qos:minIOPS': '20',
'qos:maxIOPS': '2000',
'qos:burstIOPS': '5000'}
qos = qos_specs.create(ctxt, 'fake-qos', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
VOLUME1['volume_type_id'] = type_ref['id']
# New volume type
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "5",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT2', extra_specs)
specs = {'qos:minIOPS': '30',
'qos:maxIOPS': '3000',
'qos:burstIOPS': '10000'}
qos = qos_specs.create(ctxt, 'fake-qos2', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_MODIFY_VOLUME_RESP])
self.setup_driver()
self.driver.retype(ctxt, VOLUME1, type_ref, 0, 0)
def test_create_volume_from_snapshot(self, mock_req):
ctxt = context.get_admin_context()
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "1",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT1', extra_specs)
specs = {'qos:minIOPS': '20',
'qos:maxIOPS': '2000',
'qos:burstIOPS': '5000'}
qos = qos_specs.create(ctxt, 'fake-qos', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
SNAPSHOT1['volume_type_id'] = type_ref['id']
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_SNAP1_STATUS_RESP,
ISE_PREP_SNAPSHOT_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_CREATE_SNAPSHOT_RESP,
ISE_GET_VOL1_STATUS_RESP])
self.setup_driver()
self.driver.create_volume_from_snapshot(VOLUME1, SNAPSHOT1)
def test_manage_existing(self, mock_req):
ctxt = context.get_admin_context()
extra_specs = {"Feature:Pool": "1",
"Feature:Raid": "1",
"Affinity:Type": "flash",
"Alloc:Type": "thick"}
type_ref = volume_types.create(ctxt, 'VT1', extra_specs)
specs = {'qos:minIOPS': '20',
'qos:maxIOPS': '2000',
'qos:burstIOPS': '5000'}
qos = qos_specs.create(ctxt, 'fake-qos', specs)
qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id'])
VOLUME1['volume_type_id'] = type_ref['id']
self.setup_driver()
if self.configuration.ise_protocol == 'iscsi':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_MODIFY_VOLUME_RESP,
ISE_GET_IONETWORKS_RESP])
elif self.configuration.ise_protocol == 'fibre_channel':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_MODIFY_VOLUME_RESP])
self.driver.manage_existing(VOLUME1, {'source-name': 'testvol'})
def test_manage_existing_no_source_name(self, mock_req):
self.setup_driver()
if self.configuration.ise_protocol == 'iscsi':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_MODIFY_VOLUME_RESP,
ISE_GET_IONETWORKS_RESP])
elif self.configuration.ise_protocol == 'fibre_channel':
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP,
ISE_MODIFY_VOLUME_RESP])
self.assertRaises(exception.XIODriverException,
self.driver.manage_existing, VOLUME1, {})
def test_manage_existing_get_size(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP])
exp_result = 10
act_result = \
self.driver.manage_existing_get_size(VOLUME1,
{'source-name': 'a'})
self.assertEqual(exp_result, act_result)
def test_manage_existing_get_size_no_source_name(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP])
self.assertRaises(exception.XIODriverException,
self.driver.manage_existing_get_size, VOLUME1, {})
def test_unmanage(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL1_STATUS_RESP])
self.driver.unmanage(VOLUME1)
def test_negative_unmanage_no_volume_status_xml(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL_STATUS_NO_STATUS_RESP])
self.driver.unmanage(VOLUME1)
def test_negative_unmanage_no_volume_xml(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL_STATUS_NO_VOL_NODE_RESP])
self.assertRaises(exception.XIODriverException,
self.driver.unmanage, VOLUME1)
def test_negative_unmanage_non_existing_volume(self, mock_req):
self.setup_driver()
mock_req.side_effect = iter([ISE_GET_QUERY_RESP,
ISE_GET_VOL_STATUS_404_RESP])
self.assertRaises(exception.XIODriverException,
self.driver.unmanage, VOLUME1)
class XIOISEISCSIDriverTestCase(XIOISEDriverTestCase, test.TestCase):
def setUp(self):
super(XIOISEISCSIDriverTestCase, self).setUp()
self.setup_test('iscsi')
class XIOISEFCDriverTestCase(XIOISEDriverTestCase, test.TestCase):
def setUp(self):
super(XIOISEFCDriverTestCase, self).setUp()
self.setup_test('fibre_channel')