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:
parent
0e6d77e497
commit
22a0477799
@ -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,
|
||||
|
722
cinder/tests/unit/test_macrosan_drivers.py
Normal file
722
cinder/tests/unit/test_macrosan_drivers.py
Normal 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)
|
0
cinder/volume/drivers/macrosan/__init__.py
Normal file
0
cinder/volume/drivers/macrosan/__init__.py
Normal file
100
cinder/volume/drivers/macrosan/config.py
Normal file
100
cinder/volume/drivers/macrosan/config.py
Normal 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.")
|
||||
]
|
679
cinder/volume/drivers/macrosan/devop_client.py
Normal file
679
cinder/volume/drivers/macrosan/devop_client.py
Normal 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)
|
1629
cinder/volume/drivers/macrosan/driver.py
Normal file
1629
cinder/volume/drivers/macrosan/driver.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added MacroSAN drivers that allows cinder to manage volumes in ISCSI and FC environment
|
Loading…
Reference in New Issue
Block a user