Add MacroSAN cinder driver

This driver supports below features:
* Volume Create/Delete
* Volume Attach/Detach
* Snapshot Create/Delete
* Create Volume from Snapshot
* Copy Image to Volume
* Copy Volume to Image
* Clone Volume
* Extend volume
* Volume Migration

This patchset includes the unit tests.

Implements: blueprint macrosan-cinder-driver

Change-Id: I4c41f4e029103db4514762c50b21c50619edf5a6
This commit is contained in:
wuchongyao 2018-10-22 18:35:18 +08:00 committed by Jay S. Bryant
parent 0e6d77e497
commit 22a0477799
9 changed files with 3580 additions and 0 deletions

View File

@ -126,6 +126,8 @@ from cinder.volume.drivers.lenovo import lenovo_common as \
from cinder.volume.drivers import linstordrv as \
cinder_volume_drivers_linstordrv
from cinder.volume.drivers import lvm as cinder_volume_drivers_lvm
from cinder.volume.drivers.macrosan import driver as \
cinder_volume_drivers_macrosan_driver
from cinder.volume.drivers.netapp import options as \
cinder_volume_drivers_netapp_options
from cinder.volume.drivers.nexenta import options as \
@ -319,6 +321,7 @@ def list_opts():
cinder_volume_drivers_lenovo_lenovocommon.iscsi_opts,
cinder_volume_drivers_linstordrv.linstor_opts,
cinder_volume_drivers_lvm.volume_opts,
cinder_volume_drivers_macrosan_driver.config.macrosan_opts,
cinder_volume_drivers_netapp_options.netapp_proxy_opts,
cinder_volume_drivers_netapp_options.netapp_connection_opts,
cinder_volume_drivers_netapp_options.netapp_transport_opts,

View File

@ -0,0 +1,722 @@
# Copyright (c) 2019 MacroSAN Technologies Co., Ltd.
# 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.
"""Tests for macrosan drivers."""
import mock
import os
import socket
from six.moves import UserDict
from cinder import exception
from cinder import test
from cinder import utils
from cinder.volume import configuration as conf
from cinder.volume.drivers.macrosan import devop_client
from cinder.volume.drivers.macrosan import driver
from cinder.volume import qos_specs
from cinder.volume import utils as volutils
from cinder.volume import volume_types
test_volume = (
UserDict({'name': 'volume-728ec287-bf30-4d2d-98a8-7f1bed3f59ce',
'volume_name': 'test',
'id': '728ec287-bf30-4d2d-98a8-7f1bed3f59ce',
'volume_id': '728ec287-bf30-4d2d-98a8-7f1bed3f59ce',
'provider_auth': None,
'project_id': 'project',
'display_name': 'test',
'display_description': 'test',
'host': 'controller@macrosan#MacroSAN',
'size': 10,
'provider_location':
'macrosan uuid:0x00b34201-025b0000-46b35ae7-b7deec47'}))
test_volume.size = 10
test_volume.volume_type_id = None
test_volume.volume_attachment = []
test_migrate_volume = {
'name': 'volume-d42b436a-54cc-480a-916c-275b0258ef59',
'size': 10,
'volume_name': 'test',
'id': 'd42b436a-54cc-480a-916c-275b0258ef59',
'volume_id': 'd42b436a-54cc-480a-916c-275b0258ef59',
'provider_auth': None,
'project_id': 'project',
'display_name': 'test',
'display_description': 'test',
'volume_type_id': None,
'_name_id': None,
'host': 'controller@macrosan#MacroSAN',
'provider_location':
'macrosan uuid:0x00b34201-00180000-9ac35425-9e288d9a'}
test_snap = {'name': 'volume-728ec287-bf30-4d2d-98a8-7f1bed3f59ce',
'size': 10,
'volume_name': 'test',
'id': 'aa2419a3-c144-46af-831b-e0d914d3957b',
'volume_id': '728ec287-bf30-4d2d-98a8-7f1bed3f59ce',
'provider_auth': None,
'project_id': 'project',
'display_name': 'test',
'display_description': 'test volume',
'volume_type_id': None,
'provider_location': 'pointid: 1',
'volume_size': 10,
'volume': test_volume}
test_connector = {'initiator': 'iqn.1993-08.org.debian:01:62027e12fbc',
'wwpns': ['500b342001001805', '500b342001004605'],
'wwnns': ['21000024ff2003ec', '21000024ff2003ed'],
'host': 'controller'
}
fake_fabric_mapping = {
'switch1': {
'target_port_wwn_list': ['500b342001001805', '500b342001004605'],
'initiator_port_wwn_list': ['21000024ff2003ec', '21000024ff2003ed']
}
}
expected_iscsi_properties = {'target_discovered': False,
'target_portal': '192.168.251.1:3260',
'target_iqn':
'iqn.2010-05.com.macrosan.target:controller',
'target_lun': 0,
'target_iqns':
['iqn.2010-05.com.macrosan.target:controller',
'iqn.2010-05.com.macrosan.target:controller'],
'target_portals':
['192.168.251.1:3260', '192.168.251.2:3260'],
'target_luns': [0, 0],
'volume_id':
'728ec287-bf30-4d2d-98a8-7f1bed3f59ce'
}
expected_initr_port_map_tgtexist = {
'21:00:00:24:ff:20:03:ec': [{'port_name': 'FC-Target-1:1:1',
'wwn': '50:0b:34:20:01:00:18:05'},
{'port_name': 'FC-Target-2:1:1',
'wwn': '50:0b:34:20:01:00:46:05'}],
'21:00:00:24:ff:20:03:ed': [{'port_name': 'FC-Target-1:1:1',
'wwn': '50:0b:34:20:01:00:18:05'},
{'port_name': 'FC-Target-2:1:1',
'wwn': '50:0b:34:20:01:00:46:05'}]}
expected_initr_port_map_tgtnotexist = {'21:00:00:24:ff:20:03:ec': [],
'21:00:00:24:ff:20:03:ed': []}
expected_fctgtexist_properties = {'target_lun': 0,
'target_discovered': True,
'target_wwn':
['500b342001001805', '500b342001004605'],
'volume_id':
'728ec287-bf30-4d2d-98a8-7f1bed3f59ce'
}
class FakeMacroSANFCDriver(driver.MacroSANFCDriver):
"""Fake MacroSAN Storage, Rewrite some methods of MacroSANFCDriver."""
def do_setup(self):
self.client = FakeClient(self.sp1_ipaddr, self.sp2_ipaddr,
self.username + self.passwd)
self.fcsan_lookup_service = FCSanLookupService()
@property
def _self_node_wwns(self):
return ['21000024ff2003ec', '21000024ff2003ed']
def _snapshot_name(self, snapshotid):
return "aa2419a3c14446af831be0d914d3957"
def _get_client_name(self, host):
return 'devstack'
class FCSanLookupService(object):
def get_device_mapping_from_network(self, initiator_list,
target_list):
return fake_fabric_mapping
class DummyBrickGetConnector(object):
def connect_volume(self, fake_con_data):
return {'path': '/dev/mapper/3600b3429d72e349d93bad6597d0000df'}
def disconnect_volume(self, fake_con_data, fake_device):
return None
class FakeMacroSANISCSIDriver(driver.MacroSANISCSIDriver):
"""Fake MacroSAN Storage, Rewrite some methods of MacroSANISCSIDriver."""
def do_setup(self):
self.client = FakeClient(self.sp1_ipaddr, self.sp2_ipaddr,
self.username + self.passwd)
self.device_uuid = '0x00b34201-028100eb-4922a092-1d54b755'
@property
def _self_node_wwns(self):
return ["iqn.1993-08.org.debian:01:62027e12fbc"]
def _snapshot_name(self, snapshotid):
return "aa2419a3c14446af831be0d914d3957"
def _get_iscsi_ports(self, dev_client, host):
if self.client.cmd_fail:
raise exception.VolumeBackendAPIException(data='Command failed.')
else:
return [{'ip': '192.168.251.1', 'port_name': 'iSCSI-Target-1:0:0',
'port': 'eth-1:0:0',
'target': 'iqn.2010-05.com.macrosan.target:controller'},
{'ip': '192.168.251.2', 'port_name': 'iSCSI-Target-2:0:0',
'port': 'eth-2:0:0',
'target': 'iqn.2010-05.com.macrosan.target:controller'}]
def _get_client_name(self, host):
return 'devstack'
@utils.synchronized('MacroSAN-Attach', external=True)
def _attach_volume(self, context, volume, properties, remote=False):
return super(FakeMacroSANISCSIDriver, self)._attach_volume(
context, volume, properties, remote)
@utils.synchronized('MacroSAN-Attach', external=True)
def _detach_volume(self, context, attach_info, volume,
properties, force=False, remote=False,
ignore_errors=True):
return super(FakeMacroSANISCSIDriver, self)._detach_volume(
context, attach_info, volume, properties, force, remote,
ignore_errors)
class FakeClient(devop_client.Client):
def __init__(self, sp1_ip, sp2_ip, secret_key):
self.cmd_fail = False
self.tgt_notexist = False
def get_raid_list(self, pool):
return [{'name': 'RAID-1', 'free_cap': 1749}]
def get_client(self, name):
return True
def create_lun(self, name, owner, pool, raids, lun_mode, size, lun_params):
return True
def get_pool_cap(self, pool):
return 1862, 1749, 0
def delete_lun(self, name):
return True
def setup_snapshot_resource(self, name, res_size, raids):
pass
def snapshot_resource_exists(self, name):
return True
def create_snapshot_point(self, lun_name, snapshot_name):
if self.cmd_fail:
raise exception.VolumeBackendAPIException(data='Command failed')
else:
return True
def disable_snapshot(self, volume_name):
if self.cmd_fail:
raise exception.VolumeBackendAPIException(data='Command failed')
else:
return True
def delete_snapshot_resource(self, volume_name):
if self.cmd_fail:
raise exception.VolumeBackendAPIException(data='Command failed')
else:
return True
def snapshot_point_exists(self, lun_name, pointid):
return True
def lun_exists(self, name):
return True
def snapshot_enabled(self, lun_name):
return True
def create_snapshot_view(self, view_name, lun_name, pointid):
if self.cmd_fail:
raise exception.VolumeBackendAPIException(data='Command failed')
else:
return True
def get_snapshot_pointid(self, lun_name, snapshot_name):
if self.cmd_fail:
raise exception.VolumeBackendAPIException(data='Command failed')
else:
return 1
def delete_snapshot_view(self, view_name):
return True
def delete_snapshot_point(self, lun_name, pointid):
return True
def copy_volume_from_view(self, lun_name, view_name):
return True
def snapshot_copy_task_completed(self, lun_name):
return True
def extend_lun(self, name, raids, size):
return True
def initiator_exists(self, initr_wwn):
return True
def get_device_uuid(self):
return '0x00b34201-025b0000-46b35ae7-b7deec47'
def is_initiator_mapped_to_client(self, initr_wwn, client_name):
return True
def unmap_lun_to_it(self, lun_name, initr_wwn, tgt_port_name):
if self.cmd_fail:
raise exception.VolumeBackendAPIException('Command failed.')
else:
return None
def map_lun_to_it(self, lun_name, initr_wwn, tgt_port_name, lun_id=-1):
if self.cmd_fail:
raise exception.VolumeBackendAPIException('Command failed.')
else:
return None
def map_target_to_initiator(self, tgt_port_name, initr_wwn):
return True
def get_it_unused_id_list(self, it_type, initr_wwn, tgt_port_name):
if self.cmd_fail:
raise exception.VolumeBackendAPIException('Command failed.')
else:
return [i for i in range(511)]
def enable_lun_qos(self, name, strategy):
if self.cmd_fail:
raise Exception()
else:
return None
def get_fc_initr_mapped_ports(self, initr_wwns):
return {'21:00:00:24:ff:20:03:ec':
[{'wwn': '50:0b:34:20:01:00:18:05',
'port_name': 'FC-Target-1:1:1'},
{'wwn': '50:0b:34:20:01:00:46:05',
'port_name': 'FC-Target-2:1:1'}],
'21:00:00:24:ff:20:03:ed':
[{'wwn': '50:0b:34:20:01:00:18:05',
'port_name': 'FC-Target-1:1:1'},
{'wwn': '50:0b:34:20:01:00:46:05',
'port_name': 'FC-Target-2:1:1'}]
}
def get_fc_ports(self):
if self.tgt_notexist:
return [{'sp': 1, 'refcnt': 0,
'port_name': 'FC-Target-1:1:1',
'initr': '', 'online': 0,
'wwn': '50:0b:34:20:01:00:18:05',
'port': 'FC-1:1:1'},
{'sp': 2, 'refcnt': 0,
'port_name': 'FC-Target-2:1:1',
'initr': '', 'online': 0,
'wwn': '50:0b:34:20:01:00:46:05',
'port': 'FC-2:1:1'},
]
else:
return [{'sp': 1, 'refcnt': 0,
'port_name': 'FC-Target-1:1:1',
'initr': '', 'online': 1,
'wwn': '50:0b:34:20:01:00:18:05',
'port': 'FC-1:1:1'},
{'sp': 2, 'refcnt': 0,
'port_name': 'FC-Target-2:1:1',
'initr': '', 'online': 1,
'wwn': '50:0b:34:20:01:00:46:05',
'port': 'FC-2:1:1'},
]
def get_lun_uuid(self, lun_name):
return '0x00b34201-025b0000-46b35ae7-b7deec47'
def get_lun_name(self, lun_uuid):
if lun_uuid == "0x00b34201-025b0000-46b35ae7-b7deec47":
return '728ec287-bf30-4d2d-98a8-7f1bed3f59ce'
if lun_uuid == "0x00b34201-00180000-9ac35425-9e288d9a":
return 'd42b436a-54cc-480a-916c-275b0258ef59'
def get_lun_name_from_rename_file(self, name):
return None
def backup_lun_name_to_rename_file(self, cur_name, original_name):
return None
def get_lun_id(self, tgt_name, lun_name, type='FC'):
return 0
def get_view_lun_id(self, tgt_name, view_name, type='FC'):
return 0
class MacroSANISCSIDriverTestCase(test.TestCase):
def setUp(self):
super(MacroSANISCSIDriverTestCase, self).setUp()
self.configuration = mock.Mock(spec=conf.Configuration)
self.configuration.san_ip = \
"172.192.251.1, 172.192.251.2"
self.configuration.san_login = "openstack"
self.configuration.san_password = "passwd"
self.configuration.macrosan_sdas_ipaddrs = None
self.configuration.macrosan_replication_ipaddrs = None
self.configuration.san_thin_provision = False
self.configuration.macrosan_pool = 'Pool-1'
self.configuration.macrosan_thin_lun_extent_size = 8
self.configuration.macrosan_thin_lun_low_watermark = 8
self.configuration.macrosan_thin_lun_high_watermark = 40
self.configuration.macrosan_force_unmap_itl = False
self.configuration.macrosan_snapshot_resource_ratio = 0.3
self.configuration.macrosan_log_timing = True
self.configuration.macrosan_client = \
['devstack; decive1; "eth-1:0:0"; "eth-2:0:0"']
self.configuration.macrosan_client_default = \
"eth-1:0:0;eth-2:0:0"
self.driver = FakeMacroSANISCSIDriver(configuration=self.configuration)
self.driver.do_setup()
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
def test_create_volume(self, mock_volume_type, mock_qos):
ret = self.driver.create_volume(test_volume)
actual = ret['provider_location']
self.assertEqual(test_volume['provider_location'], actual)
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
def test_create_qos_volume(self, mock_volume_type, mock_qos):
test_volume.volume_type_id = 'a2ed23e0-76c4-426f-a574-a1327275e725'
ret = self.driver.create_volume(test_volume)
actual = ret['provider_location']
self.assertEqual(test_volume['provider_location'], actual)
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
def test_delete_volume(self, mock_volume_type, mock_qos):
self.driver.delete_volume(test_volume)
def test_create_snapshot(self):
self.driver.client.snappoid = True
ret = self.driver.create_snapshot(test_snap)
actual = ret['provider_location']
self.assertEqual(test_snap['provider_location'], actual)
def test_delete_snapshot(self):
self.driver.delete_snapshot(test_snap)
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
@mock.patch.object(socket, 'gethostname', return_value='controller')
@mock.patch.object(utils, 'brick_get_connector',
return_value=DummyBrickGetConnector())
@mock.patch.object(volutils, 'copy_volume', return_value=None)
@mock.patch.object(os.path, 'realpath', return_value=None)
def test_create_volume_from_snapshot(self, mock_volume_type, mock_qos,
mock_hostname,
mock_brick_get_connector,
mock_copy_volume,
mock_os_path):
ret = self.driver.create_volume_from_snapshot(test_volume, test_snap)
actual = ret['provider_location']
self.assertEqual(test_volume['provider_location'], actual)
@mock.patch.object(socket, 'gethostname', return_value='controller')
@mock.patch.object(utils, 'brick_get_connector',
return_value=DummyBrickGetConnector())
@mock.patch.object(volutils, 'copy_volume', return_value=None)
@mock.patch.object(os.path, 'realpath', return_value=None)
def test_create_cloned_volume(self, mock_hostname,
mock_brick_get_connector,
mock_copy_volume,
mock_os_path):
self.driver.client.snappoid = True
ret = self.driver.create_cloned_volume(test_volume, test_volume)
actual = ret['provider_location']
self.assertEqual(test_volume['provider_location'], actual)
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
def test_extend_volume(self, mock_volume_type, mock_qos):
self.driver.extend_volume(test_volume, 15)
def test_update_migrated_volume(self):
expected = {'_name_id':
test_migrate_volume['id'],
'provider_location':
test_migrate_volume['provider_location']}
ret = self.driver.update_migrated_volume("", test_volume,
test_migrate_volume)
self.assertEqual(expected, ret)
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
def test_initialize_connection(self, mock_volume_type, mock_qos):
ret = self.driver.initialize_connection(test_volume, test_connector)
self.assertEqual(expected_iscsi_properties, ret['data'])
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
def test_terminate_connection(self, mock_volume_type, mock_qos):
self.driver.terminate_connection(test_volume, test_connector)
def test_get_raid_list(self):
expected = ["RAID-1"]
ret = self.driver.get_raid_list(20)
self.assertEqual(expected, ret)
def test_get_volume_stats(self):
ret = self.driver.get_volume_stats(True)
expected = "iSCSI"
self.assertEqual(expected, ret['storage_protocol'])
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
def test_create_qos_volume_fail(self, mock_volume_type, mock_qos):
test_volume.volume_type_id = 'a2ed23e0-76c4-426f-a574-a1327275e725'
self.driver.client.cmd_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume, test_volume)
def test_create_snapshot_fail(self):
self.driver.client.cmd_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot, test_snap)
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
@mock.patch.object(socket, 'gethostname', return_value='controller')
@mock.patch.object(utils, 'brick_get_connector',
return_value=DummyBrickGetConnector())
@mock.patch.object(volutils, 'copy_volume', return_value=None)
@mock.patch.object(os.path, 'realpath', return_value=None)
def test_create_volume_from_snapshot_fail(self, mock_volume_type,
mock_qos, mock_hostname,
mock_brick_get_connector,
mock_copy_volume,
mock_os_path):
self.driver.client.cmd_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
test_volume, test_snap)
@mock.patch.object(socket, 'gethostname', return_value='controller')
@mock.patch.object(utils, 'brick_get_connector',
return_value=DummyBrickGetConnector())
@mock.patch.object(volutils, 'copy_volume', return_value=None)
@mock.patch.object(os.path, 'realpath', return_value=None)
def test_create_cloned_volume_fail(self, mock_hostname,
mock_brick_get_connector,
mock_copy_volume,
mock_os_path):
self.driver.client.cmd_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_cloned_volume,
test_volume, test_volume)
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
def test_initialize_connection_fail(self, mock_volume_type, mock_qos):
self.driver.client.cmd_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.initialize_connection,
test_volume, test_connector)
@mock.patch.object(volume_types, 'get_volume_type',
return_value={'qos_specs_id':
'99f3d240-1b20-4b7b-9321-c6b8b86243ff',
'extra_specs': {}})
@mock.patch.object(qos_specs, 'get_qos_specs',
return_value={'specs': {'qos-strategy': 'QoS-1'}})
def test_terminate_connection_fail(self, mock_volume_type, mock_qos):
self.driver.client.cmd_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.terminate_connection,
test_volume, test_connector)
def test_get_raid_list_fail(self):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.get_raid_list, 2000)
class MacroSANFCDriverTestCase(test.TestCase):
def setUp(self):
super(MacroSANFCDriverTestCase, self).setUp()
self.configuration = mock.Mock(spec=conf.Configuration)
self.configuration.san_ip = \
"172.192.251.1, 172.192.251.2"
self.configuration.san_login = "openstack"
self.configuration.san_password = "passwd"
self.configuration.macrosan_sdas_ipaddrs = None
self.configuration.macrosan_replication_ipaddrs = None
self.configuration.san_thin_provision = False
self.configuration.macrosan_pool = 'Pool-1'
self.configuration.macrosan_thin_lun_extent_size = 8
self.configuration.macrosan_thin_lun_low_watermark = 8
self.configuration.macrosan_thin_lun_high_watermark = 40
self.configuration.macrosan_force_unmap_itl = False
self.configuration.macrosan_snapshot_resource_ratio = 0.3
self.configuration.macrosan_log_timing = True
self.configuration.macrosan_host_name = 'devstack'
self.configuration.macrosan_fc_use_sp_port_nr = 1
self.configuration.macrosan_fc_keep_mapped_ports = True
self.configuration.macrosan_host_name = 'devstack'
self.configuration.macrosan_client = \
['devstack; decive1; "eth-1:0:0"; "eth-2:0:0"']
self.configuration.macrosan_client_default = \
"eth-1:0:0;eth-2:0:0"
self.driver = FakeMacroSANFCDriver(configuration=self.configuration)
self.driver.do_setup()
def test_get_initr_port_map_tgtnotexist(self):
self.driver.client.tgt_notexist = True
ret = self.driver._get_initr_port_map(self.driver.client,
test_connector['wwpns'])
self.assertEqual(expected_initr_port_map_tgtnotexist, ret)
def test_get_initr_port_map_tgtexist(self):
ret = self.driver._get_initr_port_map(self.driver.client,
test_connector['wwpns'])
self.assertEqual(expected_initr_port_map_tgtexist, ret)
def test_initialize_connection(self):
ret = self.driver.initialize_connection(test_volume, test_connector)
self.assertEqual(expected_fctgtexist_properties, ret['data'])
def test_terminate_connection(self):
self.driver.terminate_connection(test_volume, test_connector)
@mock.patch.object(socket, 'gethostname', return_value='controller')
@mock.patch.object(utils, 'brick_get_connector',
return_value=DummyBrickGetConnector())
@mock.patch.object(volutils, 'copy_volume', return_value=None)
@mock.patch.object(os.path, 'realpath', return_value=None)
def test_create_volume_from_snapshot(self, mock_hostname,
mock_brick_get_connector,
mock_copy_volume,
mock_os_path):
ret = self.driver.create_volume_from_snapshot(test_volume, test_snap)
actual = ret['provider_location']
self.assertEqual(test_volume['provider_location'], actual)
@mock.patch.object(socket, 'gethostname', return_value='controller')
@mock.patch.object(utils, 'brick_get_connector',
return_value=DummyBrickGetConnector())
@mock.patch.object(volutils, 'copy_volume', return_value=None)
@mock.patch.object(os.path, 'realpath', return_value=None)
def test_create_cloned_volume(self, mock_hostname,
mock_brick_get_connector,
mock_copy_volume,
mock_os_path):
self.driver.client.snappoid = True
ret = self.driver.create_cloned_volume(test_volume, test_volume)
actual = ret['provider_location']
self.assertEqual(test_volume['provider_location'], actual)
@mock.patch.object(socket, 'gethostname', return_value='controller')
@mock.patch.object(utils, 'brick_get_connector',
return_value=DummyBrickGetConnector())
@mock.patch.object(volutils, 'copy_volume', return_value=None)
@mock.patch.object(os.path, 'realpath', return_value=None)
def test_create_volume_from_snapshot_fail(self, mock_hostname,
mock_brick_get_connector,
mock_copy_volume,
mock_os_path):
self.driver.client.cmd_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
test_volume, test_snap)
@mock.patch.object(socket, 'gethostname', return_value='controller')
@mock.patch.object(utils, 'brick_get_connector',
return_value=DummyBrickGetConnector())
@mock.patch.object(volutils, 'copy_volume', return_value=None)
@mock.patch.object(os.path, 'realpath', return_value=None)
def test_create_cloned_volume_fail(self, mock_hostname,
mock_brick_get_connector,
mock_copy_volume,
mock_os_path):
self.driver.client.cmd_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_cloned_volume,
test_volume, test_volume)
def test_initialize_connection_fail(self):
self.driver.client.cmd_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.initialize_connection,
test_volume, test_connector)
def test_terminate_connection_fail(self):
self.driver.client.cmd_fail = True
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.terminate_connection,
test_volume, test_connector)

View File

@ -0,0 +1,100 @@
# Copyright (c) 2019 MacroSAN Technologies Co., Ltd.
# 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.
"""Volume Drivers Config Registration documents for MacroSAN SAN."""
from oslo_config import cfg
macrosan_opts = [
# sdas login_info
cfg.ListOpt('macrosan_sdas_ipaddrs',
default=None,
help="MacroSAN sdas devices' ip addresses"),
cfg.StrOpt('macrosan_sdas_username',
default=None,
help=""),
cfg.StrOpt('macrosan_sdas_password',
default=None,
help="",
secret=True),
# replication login_info
cfg.ListOpt('macrosan_replication_ipaddrs',
default=None,
help="MacroSAN replication devices' ip addresses"),
cfg.StrOpt('macrosan_replication_username',
default=None,
help=""),
cfg.StrOpt('macrosan_replication_password',
default=None,
help="",
secret=True),
cfg.ListOpt('macrosan_replication_destination_ports',
default=None,
sample_default="eth-1:0/eth-1:1, eth-2:0/eth-2:1",
help="Slave device"),
# device_features
cfg.StrOpt('macrosan_pool', quotes=True,
default=None,
help='Pool to use for volume creation'),
cfg.IntOpt('macrosan_thin_lun_extent_size',
default=8,
help="Set the thin lun's extent size"),
cfg.IntOpt('macrosan_thin_lun_low_watermark',
default=5,
help="Set the thin lun's low watermark"),
cfg.IntOpt('macrosan_thin_lun_high_watermark',
default=20,
help="Set the thin lun's high watermark"),
cfg.BoolOpt('macrosan_force_unmap_itl',
default=True,
help="Force disconnect while deleting volume"),
cfg.FloatOpt('macrosan_snapshot_resource_ratio',
default=1.0,
help="Set snapshot's resource ratio"),
cfg.BoolOpt('macrosan_log_timing',
default=True,
help="Whether enable log timing"),
# fc connection
cfg.IntOpt('macrosan_fc_use_sp_port_nr',
default=1,
max=4,
help="The use_sp_port_nr parameter is the number of "
"online FC ports used by the single-ended memory "
"when the FC connection is established in the switch "
"non-all-pass mode. The maximum is 4"),
cfg.BoolOpt('macrosan_fc_keep_mapped_ports',
default=True,
help="In the case of an FC connection, the configuration "
"item associated with the port is maintained."),
# iscsi connection
cfg.ListOpt('macrosan_client',
default=None,
help="""Macrosan iscsi_clients list.
You can configure multiple clients.
You can configure it in this format:
(host; client_name; sp1_iscsi_port; sp2_iscsi_port),
(host; client_name; sp1_iscsi_port; sp2_iscsi_port)
Important warning, Client_name has the following requirements:
[a-zA-Z0-9.-_:], the maximum number of characters is 31
E.g:
(controller1; decive1; eth-1:0; eth-2:0),
(controller2; decive2; eth-1:0/eth-1:1; eth-2:0/eth-2:1),
"""),
cfg.StrOpt('macrosan_client_default',
default=None,
help="This is the default connection information for iscsi. "
"This default configuration is used "
"when no host related information is obtained.")
]

View File

@ -0,0 +1,679 @@
# Copyright (c) 2019 MacroSAN Technologies Co., Ltd.
# 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.
"""Base device operation on MacroSAN SAN."""
import logging
from random import shuffle
import requests
from cinder import exception
from cinder.i18n import _
LOG = logging.getLogger(__name__)
context_request_id = None
class Client(object):
"""Device Client to do operation."""
def __init__(self, sp1_ip, sp2_ip, secret_key):
"""Initialize the client."""
self.sp1_ip = sp1_ip
self.sp2_ip = sp2_ip
self.port = 12138
self.choosed_ip = None
self.last_request_id = None
self.last_ip = None
self.timeout = 30
self.SECRET_KEY = secret_key
self.url_prefix = '/api/v1'
def conn_test(self):
iplist = [('sp1', self.sp1_ip), ('sp2', self.sp2_ip)]
shuffle(iplist)
ha = {}
for sp, ip in iplist:
try:
url = ('http://%s:%s%s/ha_status' %
(ip, str(self.port), self.url_prefix))
header = {'Authorization': 'Bearer %s' % self.SECRET_KEY}
response = requests.get(url=url,
timeout=self.timeout, headers=header)
ha = self.response_processing(response)
if ha[sp] in ['single', 'double']:
LOG.debug('Heart Beating......%(ha)s ', {'ha': ha})
return ip
except Exception:
pass
raise exception.VolumeBackendAPIException(
data=_('Connect to MacroSAN IPSAN Error, HA Status:%s') % str(ha))
def send_request(self, method='get', url='/', data=None):
header = {'Authorization': 'Bearer %s' % self.SECRET_KEY}
try:
ip = self.conn_test()
url = ('http://%s:%s%s%s' %
(ip, str(self.port), self.url_prefix, url))
response = None
if method == 'get':
response = requests.get(url=url, params=data,
timeout=self.timeout, headers=header)
elif method == 'post':
response = requests.post(url=url, json=data,
timeout=self.timeout, headers=header)
elif method == 'put':
response = requests.put(url=url, json=data,
timeout=self.timeout, headers=header)
elif method == 'delete':
response = requests.delete(url=url, json=data,
timeout=self.timeout,
headers=header)
return self.response_processing(response)
except requests.exceptions.ConnectionError:
LOG.error('========== Unable to establish connection '
'with VolumeBackend %(url)s', {'url': url})
def response_processing(self, response):
if response.status_code != 200:
LOG.error('========== Command %(url)s execution error,'
'response_conde: %(status)s',
{'url': response.url, 'status': response.status_code})
raise exception.VolumeBackendAPIException(data=response.json())
LOG.debug('The response is: %(response)s, %(text)s',
{'response': response, 'text': response.json()})
return response.json()
def get_ha_state(self):
"""Get HA state."""
return self.send_request(method='get', url='/ha_status')
def lun_exists(self, name):
"""Whether the lun exists."""
data = {
'attr': 'existence',
'name': name
}
return self.send_request('get', '/lun', data=data)
def snapshot_point_exists(self, lun_name, pointid):
"""Whether the snapshot point exists."""
data = {
'attr': 'existence',
'lun_name': lun_name,
'pointid': pointid
}
return self.send_request(method='get',
url='/snapshot_point', data=data)
def it_exists(self, initr_wwn, tgt_port_name):
"""Whether the it exists."""
data = {
'attr': 'it',
'initr_wwn': initr_wwn,
'tgt_port_name': tgt_port_name
}
return self.send_request(method='get', url='/itl', data=data)
def is_initiator_mapped_to_client(self, initr_wwn, client_name):
"""Whether initiator is mapped to client."""
data = {
'initr_wwn': initr_wwn,
'client_name': client_name,
'attr': 'list'
}
return self.send_request(method='get', url='/initiator', data=data)
def snapshot_resource_exists(self, lun_name):
"""Whether the snapshot resource exists."""
data = {
'lun_name': lun_name
}
return self.send_request(method='get',
url='/snapshot_resource', data=data)
def initiator_exists(self, initr_wwn):
"""Whether the initiator exists."""
data = {
'attr': 'existence',
'initr_wwn': initr_wwn,
}
return self.send_request(method='get', url='/initiator', data=data)
def get_client(self, name):
"""Get client info."""
return self.send_request(method='get',
url='/client', data={'name': name})
def delete_lun(self, name):
"""Delete a lun."""
return self.send_request(method='delete',
url='/lun', data={'name': name})
def get_lun_sp(self, name):
"""Get lun sp."""
data = {
'attr': 'lun_sp',
'name': name
}
return self.send_request(method='get', url='/lun', data=data)
def get_snapshot_resource_name(self, lun_name):
"""Whether the snapshot resource exists."""
return self.send_request(method='get', url='/snapshot_resource',
data={'lun_name': lun_name})
def rename_lun(self, old_name, new_name):
"""Rename a lun."""
return self.send_request(method='put', url='/lun',
data={'attr': 'name', 'old_name': old_name,
'new_name': new_name})
def create_lun(self, name, owner, pool, raids, lun_mode, size, lun_params):
"""Create a lun."""
data = {'name': name,
'owner': owner,
'pool': pool,
'raids': raids,
'lun_mode': lun_mode,
'size': size,
'lun_params': lun_params}
return self.send_request(method='post', url='/lun', data=data)
def get_raid_list(self, pool):
"""Get a raid list."""
return self.send_request(method='get',
url='/raid_list', data={'pool': pool})
def get_pool_cap(self, pool):
"""Get pool capacity."""
return self.send_request(method='get',
url='/pool', data={'pool': pool})
def get_lun_base_info(self, name):
data = {'attr': 'base_info',
'name': name}
return self.send_request(method='get', url='/lun', data=data)
def extend_lun(self, name, raids, size):
"""Extend a lun."""
data = {
'attr': 'capicity',
'name': name,
'raids': raids,
'size': size
}
return self.send_request(method='put', url='/lun', data=data)
def enable_lun_qos(self, name, strategy):
"""Enable lun qos."""
data = {
'attr': 'qos',
'name': name,
'strategy': strategy
}
return self.send_request(method='put', url='/lun', data=data)
def localclone_exists(self, lun):
"""Whether localclone lun exists."""
return self.send_request(method='get', url='/local_clone',
data={'attr': 'existence', 'lun': lun})
def localclone_completed(self, lun):
"""Whether localclone lun completed."""
return self.send_request(method='get', url='/local_clone',
data={'attr': 'completed', 'lun': lun})
def start_localclone_lun(self, master, slave):
"""start localclone lun."""
return self.send_request(method='post', url='/local_clone',
data={'master': master, 'slave': slave})
def stop_localclone_lun(self, lun):
"""stop localclone lun."""
return self.send_request(method='delete', url='/local_clone',
data={'lun': lun})
def create_snapshot_resource(self, lun_name, raids, size):
"""Create a snapshot resource."""
data = {
'lun_name': lun_name,
'raids': raids,
'size': size
}
return self.send_request(method='post', url='/snapshot_resource',
data=data)
def enable_snapshot_resource_autoexpand(self, lun_name):
"""Enable snapshot resource autoexpand."""
data = {
'attr': 'autoexpand',
'lun_name': lun_name
}
return self.send_request(method='put', url='/snapshot_resource',
data=data)
def enable_snapshot(self, lun_name):
"""Enable snapshot."""
data = {
'attr': 'enable',
'lun_name': lun_name
}
return self.send_request(method='put', url='/snapshot', data=data)
def snapshot_enabled(self, lun_name):
"""Weather enable snapshot"""
params = {
'attr': 'enable',
'lun_name': lun_name
}
return self.send_request(method='get', url='/snapshot', data=params)
def delete_snapshot_resource(self, lun_name):
"""Delete a snapshot resource."""
data = {'lun_name': lun_name}
return self.send_request(method='delete', url='/snapshot_resource',
data=data)
def create_snapshot_point(self, lun_name, snapshot_name):
"""Create a snapshot point."""
data = {
'lun_name': lun_name,
'snapshot_name': snapshot_name
}
return self.send_request(method='post', url='/snapshot_point',
data=data)
def get_snapshot_pointid(self, lun_name, snapshot_name):
"""Get a snapshot pointid."""
params = {
'attr': 'point_id',
'lun_name': lun_name,
'snapshot_name': snapshot_name
}
return self.send_request(method='get', url='/snapshot_point',
data=params)
def rename_snapshot_point(self, lun_name, pointid, name):
data = {
'attr': 'name',
'lun_name': lun_name,
'pointid': pointid,
'name': name
}
return self.send_request(method='put', url='/snapshot_point',
data=data)
def disable_snapshot(self, lun_name):
"""Disable snapshot."""
data = {
'attr': 'disable',
'lun_name': lun_name
}
return self.send_request(method='put', url='/snapshot', data=data)
def delete_snapshot_point(self, lun_name, pointid):
"""Delete a snapshot point."""
data = {
'lun_name': lun_name,
'pointid': pointid
}
return self.send_request(method='delete', url='/snapshot_point',
data=data)
def get_snapshot_point_num(self, lun_name):
"""Get snapshot point number."""
data = {
'attr': 'number',
'lun_name': lun_name
}
return self.send_request(method='get', url='/snapshot_point',
data=data)
def create_client(self, name):
"""Create a client."""
return self.send_request(method='post', url='/client',
data={'name': name})
def create_target(self, port_name, type='fc'):
"""Create a target."""
data = {
'port_name': port_name,
'type': type
}
return self.send_request(method='post', url='/target', data=data)
def delete_target(self, tgt_name):
"""Delete a target."""
return self.send_request(method='delete', url='/target',
data={'tgt_name': tgt_name})
def create_initiator(self, initr_wwn, alias, type='fc'):
"""Create an initiator."""
data = {
'initr_wwn': initr_wwn,
'alias': alias,
'type': type
}
return self.send_request(method='post', url='/initiator', data=data)
def delete_initiator(self, initr_wwn):
"""Delete an initiator."""
return self.send_request(method='delete', url='/initiator',
data={'initr_wwn': initr_wwn})
def map_initiator_to_client(self, initr_wwn, client_name):
"""Map initiator to client."""
data = {
'attr': 'mapinitiator',
'initr_wwn': initr_wwn,
'client_name': client_name
}
return self.send_request(method='put', url='/client', data=data)
def unmap_initiator_from_client(self, initr_wwn, client_name):
"""Unmap target from initiator."""
data = {
'attr': 'unmapinitiator',
'initr_wwn': initr_wwn,
'client_name': client_name
}
return self.send_request(method='put', url='/client', data=data)
def map_target_to_initiator(self, tgt_port_name, initr_wwn):
"""Map target to initiator."""
data = {
'attr': 'maptarget',
'initr_wwn': initr_wwn,
'tgt_port_name': tgt_port_name
}
return self.send_request(method='post', url='/itl', data=data)
def unmap_target_from_initiator(self, tgt_port_name, initr_wwn):
"""Unmap target from initiator."""
data = {
'attr': 'unmaptarget',
'initr_wwn': initr_wwn,
'tgt_port_name': tgt_port_name
}
return self.send_request(method='delete', url='/itl', data=data)
def map_lun_to_it(self, lun_name, initr_wwn, tgt_port_name, lun_id=-1):
"""Map lun to it."""
data = {
'attr': 'maplun',
'lun_name': lun_name,
'initr_wwn': initr_wwn,
'tgt_port_name': tgt_port_name,
'lun_id': lun_id
}
return self.send_request(method='post', url='/itl', data=data)
def unmap_lun_to_it(self, lun_name, initr_wwn, tgt_port_name):
"""Unmap lun to it."""
data = {
'attr': 'unmaplun',
'lun_name': lun_name,
'initr_wwn': initr_wwn,
'tgt_port_name': tgt_port_name,
}
return self.send_request(method='delete', url='/itl', data=data)
def has_initiators_mapped_any_lun(self, initr_wwns, type='fc'):
"""Whether has initiators mapped any lun."""
data = {
'attr': 'itl',
'initr_wwns': initr_wwns,
'type': type
}
return self.send_request(method='get', url='/itl', data=data)
def create_snapshot_view(self, view_name, lun_name, pointid):
"""Create a snapshot view."""
data = {
'view_name': view_name,
'lun_name': lun_name,
'pointid': pointid
}
return self.send_request(method='post', url='/snapshot_view',
data=data)
def delete_snapshot_view(self, view_name):
"""Delete a snapshot view."""
return self.send_request(method='delete', url='/snapshot_view',
data={'view_name': view_name})
def get_fc_initr_mapped_ports(self, initr_wwns):
"""Get initiator mapped port."""
data = {
'attr': 'fc_initr_mapped_ports',
'initr_wwns': initr_wwns
}
return self.send_request(method='get', url='/initiator', data=data)
def get_fc_ports(self):
"""Get FC ports."""
data = {
'attr': 'fc_ports',
}
return self.send_request(method='get', url='/initiator', data=data)
def get_iscsi_ports(self):
"""Get iSCSI ports."""
data = {
'attr': 'iscsi_ports',
}
return self.send_request(method='get', url='/initiator', data=data)
def get_lun_id(self, initr_wwn, tgt_port_name, lun_name):
"""Get lun id."""
data = {
'attr': 'lun_id',
'initr_wwn': initr_wwn,
'tgt_port_name': tgt_port_name,
'lun_name': lun_name
}
return self.send_request(method='get', url='/lun', data=data)
def get_lun_uuid(self, lun_name):
"""Get lun uuid."""
data = {
'attr': 'lun_uuid',
'lun_name': lun_name
}
return self.send_request(method='get', url='/lun', data=data)
def get_lun_name(self, lun_uuid):
"""Get lun name."""
data = {
'attr': 'lun_name',
'lun_uuid': lun_uuid
}
return self.send_request(method='get', url='/lun', data=data)
def copy_volume_from_view(self, lun_name, view_name):
"""Copy volume from view."""
data = {
'attr': 'from_view',
'lun_name': lun_name,
'view_name': view_name
}
return self.send_request(method='post', url='/copy_volume', data=data)
def snapshot_copy_task_completed(self, lun_name):
data = {
'attr': 'snapshot_copy_task_completed',
'lun_name': lun_name
}
return self.send_request(method='get', url='/copy_volume', data=data)
def copy_volume_from_volume(self, lun_name, src_lun_name):
"""Copy volume from volume."""
data = {
'attr': 'from_volume',
'lun_name': lun_name,
'src_lun_name': src_lun_name
}
return self.send_request(method='post', url='/copy_volume', data=data)
def query_bcopy_task(self, task_id):
"""Query bcopy task."""
data = {
'attr': 'bcopy_task',
'task_id': task_id
}
return self.send_request(method='get', url='/copy_volume', data=data)
def get_it_unused_id_list(self, it_type, initr_wwn, tgt_port_name):
data = {
'attr': 'it_unused_id_list',
'it_type': it_type,
'initr_wwn': initr_wwn,
'tgt_port_name': tgt_port_name
}
return self.send_request(method='get', url='/initiator', data=data)
def backup_lun_name_to_rename_file(self, cur_name, original_name):
"""Backup lun name to rename file."""
data = {
'cur_name': cur_name,
'original_name': original_name
}
return self.send_request(method='post', url='/rename_file', data=data)
def get_lun_name_from_rename_file(self, name):
"""Get lun name from rename file."""
data = {'name': name}
return self.send_request(method='get', url='/rename_file', data=data)
def create_dalun(self, lun_name):
data = {'lun_name': lun_name}
return self.send_request(method='post', url='/dalun', data=data)
def delete_dalun(self, lun_name):
data = {'lun_name': lun_name}
return self.send_request(method='delete', url='/dalun', data=data)
def dalun_exists(self, lun_name):
data = {
'attr': 'existence',
'lun_name': lun_name
}
return self.send_request(method='get', url='/dalun', data=data)
def suspend_dalun(self, lun_name):
data = {
'attr': 'suspend',
'lun_name': lun_name
}
return self.send_request(method='put', url='/dalun', data=data)
def resume_dalun(self, lun_name):
data = {
'attr': 'resume',
'lun_name': lun_name
}
return self.send_request(method='put', url='/dalun', data=data)
def setup_snapshot_resource(self, volume_name, size, raids):
if not self.snapshot_resource_exists(volume_name):
self.create_snapshot_resource(volume_name, raids, size)
if self.enable_snapshot_resource_autoexpand(
volume_name).status_code != 200:
LOG.warning('========== Enable snapshot resource auto '
'expand for volume: %s error', volume_name)
def get_raid_list_to_create_lun(self, pool, size):
raids = self.get_raid_list(pool)
free = sum(raid['free_cap'] for raid in raids)
if size > free:
raise exception.VolumeBackendAPIException(
data=_('Pool has not enough free capacity'))
raids = sorted(raids, key=lambda x: x['free_cap'], reverse=True)
selected = []
cap = 0
for raid in raids:
if raid['free_cap']:
cap += raid['free_cap']
selected.append(raid['name'])
if cap >= size:
break
return selected
def get_port_ipaddr(self, port):
data = {
'attr': 'port_ipaddr',
'port': port,
}
return self.send_request(method='get', url='/itl', data=data)
def enable_replication(self, lun_name, sp1, sp2):
data = {
'attr': 'enable',
'lun_name': lun_name,
'sp1': sp1,
'sp2': sp2,
}
return self.send_request(method='put', url='/replication', data=data)
def disable_replication(self, lun_name):
data = {
'attr': 'disable',
'lun_name': lun_name,
}
return self.send_request(method='put', url='/replication', data=data)
def replication_enabled(self, lun_name):
data = {
'attr': 'enabled',
'lun_name': lun_name
}
return self.send_request(method='get', url='/replication', data=data)
def startscan_replication(self, lun_name):
data = {
'attr': 'startscan',
'lun_name': lun_name
}
return self.send_request(method='put', url='/replication', data=data)
def stopscan_replication(self, lun_name):
data = {
'attr': 'stopscan',
'lun_name': lun_name
}
return self.send_request(method='put', url='/replication', data=data)
def pausereplicate(self, lun_name):
data = {
'attr': 'pause',
'lun_name': lun_name
}
return self.send_request(method='put', url='/replication', data=data)
def get_device_uuid(self):
return self.send_request(method='get', url='/device')
def get_lun_it(self, name):
data = {
'attr': 'getitl',
'name': name
}
return self.send_request(method='get', url='/itl', data=data)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,430 @@
==========================================
MacroSAN Fibre Channel and iSCSI drivers
==========================================
The ``MacroSANFCDriver`` and ``MacroSANISCSIDriver`` Cinder drivers allow the
MacroSAN Storage arrays to be used for Block Storage in
OpenStack deployments.
System requirements
~~~~~~~~~~~~~~~~~~~
To use the MacroSAN drivers, the following are required:
- MacroSAN Storage arrays with:
- iSCSI or FC host interfaces
- Enable RESTful service on the MacroSAN Storage Appliance.
- Network connectivity between the OpenStack host and the array management
interfaces
- HTTPS or HTTP must be enabled on the array
When creating a volume from image, install the ``multipath`` tool and add the
following configuration keys in the ``[DEFAULT]`` configuration group of
the ``/etc/cinder/cinder.conf`` file:
.. code-block:: ini
use_multipath_for_image_xfer = True
Add and change the following configuration keys of
the ``/etc/multipath.conf`` file:
.. code-block:: ini
blacklist {
devnode "^sda$"
devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*"
devnode "^hd[a-z]"
devnode "^nbd*"
}
Need to set user_friendly_names to no in the multipath.conf file.
In addition, you need to delete the getuid_callout parameter in
the centos7 system.
Supported operations
~~~~~~~~~~~~~~~~~~~~
- Create, delete, attach, and detach volumes.
- Create, list, and delete volume snapshots.
- Create a volume from a snapshot.
- Copy an image to a volume.
- Copy a volume to an image.
- Clone a volume.
- Extend a volume.
- Volume Migration (Host assisted).
- Volume Migration (Storage Assisted).
- Retype a volume.
- Manage and unmanage a volume.
- Manage and unmanage a snapshot.
- Volume Replication
- Thin Provisioning
Configuring the array
~~~~~~~~~~~~~~~~~~~~~
#. Verify that the array can be managed via an HTTPS connection.
Confirm that virtual pools A and B are present if you plan to use virtual
pools for OpenStack storage.
#. Edit the ``cinder.conf`` file to define a storage backend entry for each
storage pool on the array that will be managed by OpenStack. Each entry
consists of a unique section name, surrounded by square brackets, followed
by options specified in a ``key=value`` format.
* The ``volume_backend_name`` option value can be a unique value, if you
wish to be able to assign volumes to a specific storage pool on the
array, or a name that is shared among multiple storage pools to let the
volume scheduler choose where new volumes are allocated.
In the examples below, two back ends are defined, one for pool A and one
for pool B.
* Add the following configuration keys in the configuration group of
enabled_backends of the ``/etc/cinder/cinder.conf`` file:
**iSCSI example back-end entries**
.. code-block:: ini
[DEFAULT]
enabled_backends = cinder-iscsi-a, cinder-iscsi-b
rpc_response_timeout = 300
[cinder-iscsi-a]
# Storage protocol.
iscsi_protocol = iscsi
#iSCSI target user-land tool.
iscsi_helper = tgtadm
# The iSCSI driver to load
volume_driver = cinder.volume.drivers.macrosan.driver.MacroSANISCSIDriver.
# Name to give this storage back-end.
volume_backend_name = macrosan
#Chose attach/detach volumes in cinder using multipath for volume to image and image to volume transfers.
use_multipath_for_image_xfer = True
# IP address of the Storage if attaching directly.
san_ip = 172.17.251.142, 172.17.251.143
# Storage user name.
san_login = openstack
# Storage user password.
san_password = openstack
#Chose using thin-lun or thick lun.When set san_thin_provision to True,you must set
#macrosan_thin_lun_extent_size, macrosan_thin_lun_low_watermark, macrosan_thin_lun_high_watermark.
san_thin_provision = False
#The name of Pool in the Storage.
macrosan_pool = Pool-a
#The default ports used for initializing connection.
#Separate the controller by semicolons (``;``)
#Separate the ports by semicolons (``,``)
macrosan_client_default = eth-1:0:0, eth-1:0:1; eth-2:0:0, eth-2:0:1
#The switch to force detach volume when deleting
macrosan_force_unmap_itl = True
#Set snapshot's resource ratio
macrosan_snapshot_resource_ratio = 1
#Calculate the time spent on the operation in the log file.
macrosan_log_timing = True
# =============Optional settings=============
#Set the thin lun's extent size when the san_thin_provision is True.
macrosan_thin_lun_extent_size = 8
#Set the thin lun's low watermark when the san_thin_provision is True.
#macrosan_thin_lun_low_watermark = 8
#Set the thin lun's high watermark when the san_thin_provision is True.
macrosan_thin_lun_high_watermark = 40
#The setting of Symmetrical Dual Active Storage
macrosan_sdas_ipaddrs = 172.17.251.142, 172.17.251.143
macrosan_sdas_username = openstack
macrosan_sdas_password = openstack
#The setting of Replication Storage.When you set ip, you must set
#the macrosan_replication_destination_ports parameter.
macrosan_replication_ipaddrs = 172.17.251.142, 172.17.251.143
macrosan_replication_username = openstack
macrosan_replication_password = openstack
##The ports used for the Replication Storage.
#Separate the controller by semicolons (``,``)
#Separate the ports by semicolons (``/``)
macrosan_replication_destination_ports = eth-1:0:0/eth-1:0:1, eth-2:0:0/eth-2:0:1
#Macrosan iscsi_clients list.You can configure multiple clients.Separate the ports by semicolons (``/``)
macrosan_client = (devstack; controller1name; eth-1:0:0/eth-1:0:1; eth-2:0:0/eth-2:0:1), (dev; controller2name; eth-1:0:0/eth-1:0:1; eth-2:0:0/eth-2:0:1)
[cinder-iscsi-b]
iscsi_protocol = iscsi
iscsi_helper = tgtadm
volume_driver = cinder.volume.drivers.macrosan.driver.MacroSANISCSIDriver
volume_backend_name = macrosan
use_multipath_for_image_xfer = True
san_ip = 172.17.251.142, 172.17.251.143
san_login = openstack
san_password = openstack
macrosan_pool = Pool-b
san_thin_provision = False
macrosan_force_unmap_itl = True
macrosan_snapshot_resource_ratio = 1
macrosan_log_timing = True
macrosan_client_default = eth-1:0:0, eth-1:0:1; eth-2:0:0, eth-2:0:1
macrosan_thin_lun_extent_size = 8
macrosan_thin_lun_low_watermark = 8
macrosan_thin_lun_high_watermark = 40
macrosan_sdas_ipaddrs = 172.17.251.142, 172.17.251.143
macrosan_sdas_username = openstack
macrosan_sdas_password = openstack
macrosan_replication_ipaddrs = 172.17.251.142, 172.17.251.143
macrosan_replication_username = openstack
macrosan_replication_password = openstack
macrosan_replication_destination_ports = eth-1:0:0, eth-2:0:0
macrosan_client = (devstack; controller1name; eth-1:0:0; eth-2:0:0), (dev; controller2name; eth-1:0:0; eth-2:0:0)
**Fibre Channel example backend entries**
.. code-block:: ini
[DEFAULT]
enabled_backends = cinder-fc-a, cinder-fc-b
rpc_response_timeout = 300
[cinder-fc-a]
volume_driver = cinder.volume.drivers.macrosan.driver.MacroSANFCDriver
volume_backend_name = macrosan
use_multipath_for_image_xfer = True
san_ip = 172.17.251.142, 172.17.251.143
san_login = openstack
san_password = openstack
macrosan_pool = Pool-a
san_thin_provision = False
macrosan_force_unmap_itl = True
macrosan_snapshot_resource_ratio = 1
macrosan_log_timing = True
#FC Zoning mode configured.
zoning_mode = fabric
#The number of ports used for initializing connection.
macrosan_fc_use_sp_port_nr = 1
#In the case of an FC connection, the configuration item associated with the port is maintained.
macrosan_fc_keep_mapped_ports = True
# =============Optional settings=============
macrosan_thin_lun_extent_size = 8
macrosan_thin_lun_low_watermark = 8
macrosan_thin_lun_high_watermark = 40
macrosan_sdas_ipaddrs = 172.17.251.142, 172.17.251.143
macrosan_sdas_username = openstack
macrosan_sdas_password = openstack
macrosan_replication_ipaddrs = 172.17.251.142, 172.17.251.143
macrosan_replication_username = openstack
macrosan_replication_password = openstack
macrosan_replication_destination_ports = eth-1:0:0, eth-2:0:0
[cinder-fc-b]
volume_driver = cinder.volume.drivers.macrosan.driver.MacroSANFCDriver
volume_backend_name = macrosan
use_multipath_for_image_xfer = True
san_ip = 172.17.251.142, 172.17.251.143
san_login = openstack
san_password = openstack
macrosan_pool = Pool-b
san_thin_provision = False
macrosan_force_unmap_itl = True
macrosan_snapshot_resource_ratio = 1
macrosan_log_timing = True
zoning_mode = fabric
macrosan_fc_use_sp_port_nr = 1
macrosan_fc_keep_mapped_ports = True
macrosan_thin_lun_extent_size = 8
macrosan_thin_lun_low_watermark = 8
macrosan_thin_lun_high_watermark = 40
macrosan_sdas_ipaddrs = 172.17.251.142, 172.17.251.143
macrosan_sdas_username = openstack
macrosan_sdas_password = openstack
macrosan_replication_ipaddrs = 172.17.251.142, 172.17.251.143
macrosan_replication_username = openstack
macrosan_replication_password = openstack
macrosan_replication_destination_ports = eth-1:0:0, eth-2:0:0
#. After modifying the ``cinder.conf`` file, restart the ``cinder-volume``
service.
#. Create and use volume types.
**Create and use sdas volume types**
.. code-block:: console
$ openstack volume type create sdas
$ openstack volume type set --property sdas=True sdas
**Create and use replication volume types**
.. code-block:: console
$ openstack volume type create replication
$ openstack volume type set --property replication_enabled=True replication
Configuration file parameters
-----------------------------
This section describes mandatory and optional configuration file parameters
of the MacroSAN volume driver.
.. list-table:: **Mandatory parameters**
:widths: 10 10 50 10
:header-rows: 1
* - Parameter
- Default value
- Description
- Applicable to
* - volume_backend_name
- ``-``
- indicates the name of the backend
- All
* - volume_driver
- ``cinder.volume.drivers.lvm.LVMVolumeDriver``
- indicates the loaded driver
- All
* - use_multipath_for_image_xfer
- ``False``
- Chose attach/detach volumes in cinder using multipath for volume to image and image to volume transfers.
- All
* - san_thin_provision
- ``True``
- Default volume type setting, True is thin lun, and False is thick lun.
- All
* - macrosan_force_unmap_itl
- ``True``
- Force detach volume when deleting
- All
* - macrosan_log_timing
- ``True``
- Calculate the time spent on the operation in the log file.
- All
* - macrosan_snapshot_resource_ratio
- ``1``
- Set snapshot's resource ratio".
- All
* - iscsi_helper
- ``tgtadm``
- iSCSI target user-land tool to use.
- iSCSI
* - iscsi_protocol
- ``iscsi``
- Determines the iSCSI protocol for new iSCSI volumes, created with tgtadm.
- iSCSI
* - macrosan_client_default
- ``None``
- This is the default connection information for iscsi.This default configuration is used when no host related information is obtained.
- iSCSI
* - zoning_mode
- ``True``
- FC Zoning mode configured.
- Fibre channel
* - macrosan_fc_use_sp_port_nr
- ``1``
- The use_sp_port_nr parameter is the number of online FC ports used by the single-ended memory when the FC connection is established in the switch non-all-pass mode. The maximum is 4.
- Fibre channel
* - macrosan_fc_keep_mapped_ports
- ``True``
- In the case of an FC connection, the configuration item associated with the port is maintained.
- Fibre channel
.. list-table:: **Optional parameters**
:widths: 20 10 50 15
:header-rows: 1
* - Parameter
- Default value
- Description
- Applicable to
* - macrosan_sdas_ipaddrs
- ``-``
- The ip of Symmetrical Dual Active Storage
- All
* - macrosan_sdas_username
- ``-``
- The username of Symmetrical Dual Active Storage
- All
* - macrosan_sdas_password
- ``-``
- The password of Symmetrical Dual Active Storage
- All
* - macrosan_replication_ipaddrs
- ``-``
- The ip of replication Storage.When you set ip, you must set
the macrosan_replication_destination_ports parameter.
- All
* - macrosan_replication_username
- ``-``
- The username of replication Storage
- All
* - macrosan_replication_password
- ``-``
- The password of replication Storage
- All
* - macrosan_replication_destination_ports
- ``-``
- The ports of replication storage when using replication storage.
- All
* - macrosan_thin_lun_extent_size
- ``8``
- Set the thin lun's extent size when the san_thin_provision is True.
- All
* - macrosan_thin_lun_low_watermark
- ``5``
- Set the thin lun's low watermark when the san_thin_provision is True.
- All
* - macrosan_thin_lun_high_watermark
- ``20``
- Set the thin lun's high watermark when the san_thin_provision is True.
- All
* - macrosan_client
- ``True``
- Macrosan iscsi_clients list.You can configure multiple clients.
You can configure it in this format:
(hostname; client_name; sp1_iscsi_port; sp2_iscsi_port),
E.g:
(controller1; decive1; eth-1:0:0; eth-2:0:0),(controller2; decive2; eth-1:0:0/ eth-1:0:1; eth-2:0:0/ eth-2:0:1)
- All
.. important::
Client_name has the following requirements:
[a-zA-Z0-9.-_:], the maximum number of characters is 31
The following are the MacroSAN driver specific options that may be set in
`cinder.conf`:
.. config-table::
:config-target: MacroSAN
cinder.volume.drivers.macrosan.config

View File

@ -120,6 +120,9 @@ title=LINBIT DRBD/LINSTOR Driver (DRBD)
[driver.lvm]
title=Logical Volume Manager (LVM) Reference Driver (iSCSI)
[driver.macrosan]
title=MacroSAN Storage Driver (iSCSI, FC)
[driver.nec]
title=NEC Storage M Series Driver (iSCSI, FC)
@ -231,6 +234,7 @@ driver.kaminario=complete
driver.lenovo=complete
driver.linbit_linstor=complete
driver.lvm=complete
driver.macrosan=complete
driver.nec=complete
driver.netapp_ontap=complete
driver.netapp_solidfire=complete
@ -294,6 +298,7 @@ driver.kaminario=complete
driver.lenovo=complete
driver.linbit_linstor=complete
driver.lvm=complete
driver.macrosan=complete
driver.nec=complete
driver.netapp_ontap=missing
driver.netapp_solidfire=complete
@ -357,6 +362,7 @@ driver.kaminario=missing
driver.lenovo=missing
driver.linbit_linstor=missing
driver.lvm=missing
driver.macrosan=missing
driver.nec=missing
driver.netapp_ontap=missing
driver.netapp_solidfire=missing
@ -423,6 +429,7 @@ driver.kaminario=missing
driver.lenovo=missing
driver.linbit_linstor=missing
driver.lvm=missing
driver.macrosan=complete
driver.nec=complete
driver.netapp_ontap=complete
driver.netapp_solidfire=complete
@ -488,6 +495,7 @@ driver.kaminario=complete
driver.lenovo=missing
driver.linbit_linstor=missing
driver.lvm=missing
driver.macrosan=complete
driver.nec=missing
driver.netapp_ontap=complete
driver.netapp_solidfire=complete
@ -554,6 +562,7 @@ driver.kaminario=missing
driver.lenovo=missing
driver.linbit_linstor=missing
driver.lvm=missing
driver.macrosan=missing
driver.nec=missing
driver.netapp_ontap=complete
driver.netapp_solidfire=complete
@ -619,6 +628,7 @@ driver.kaminario=complete
driver.lenovo=missing
driver.linbit_linstor=missing
driver.lvm=complete
driver.macrosan=complete
driver.nec=complete
driver.netapp_ontap=complete
driver.netapp_solidfire=complete
@ -685,6 +695,7 @@ driver.kaminario=missing
driver.lenovo=missing
driver.linbit_linstor=missing
driver.lvm=missing
driver.macrosan=complete
driver.nec=complete
driver.netapp_ontap=missing
driver.netapp_solidfire=missing
@ -751,6 +762,7 @@ driver.kaminario=missing
driver.lenovo=complete
driver.linbit_linstor=missing
driver.lvm=complete
driver.macrosan=missing
driver.nec=missing
driver.netapp_ontap=complete
driver.netapp_solidfire=complete
@ -814,6 +826,7 @@ driver.kaminario=missing
driver.lenovo=missing
driver.linbit_linstor=missing
driver.lvm=complete
driver.macrosan=missing
driver.nec=missing
driver.netapp_ontap=missing
driver.netapp_solidfire=complete
@ -881,6 +894,7 @@ driver.kaminario=missing
driver.lenovo=missing
driver.linbit_linstor=missing
driver.lvm=missing
driver.macrosan=complete
driver.nec=missing
driver.netapp_ontap=missing
driver.netapp_solidfire=missing

View File

@ -0,0 +1,3 @@
---
features:
- Added MacroSAN drivers that allows cinder to manage volumes in ISCSI and FC environment