Add Synology DSM storage driver
This driver supports below features: - Volume Create/Delete - Volume Attach/Detach - Snapshot Create/Delete - Create Volume from Snapshot - Get Volume Stats - Copy Image to Volume - Copy Volume to Image - Clone Volume - Extend Volume DocImpact Implements: blueprint synology-cinder-driver Co-Authored-By: Taylor Huang<taylorh@synology.com> Change-Id: I18d47f5d4cf10e4f481722406c6a5c071a521616
This commit is contained in:
parent
751af8c86c
commit
78d124dee2
@ -1181,3 +1181,16 @@ class GCSOAuth2Failure(BackupDriverException):
|
||||
# Kaminario K2
|
||||
class KaminarioCinderDriverException(VolumeDriverException):
|
||||
message = _("KaminarioCinderDriver failure: %(reason)s")
|
||||
|
||||
|
||||
# Synology driver
|
||||
class SynoAPIHTTPError(CinderException):
|
||||
message = _("HTTP exit code: [%(code)s]")
|
||||
|
||||
|
||||
class SynoAuthError(CinderException):
|
||||
pass
|
||||
|
||||
|
||||
class SynoLUNNotExist(CinderException):
|
||||
message = _("LUN not found by UUID: %(uuid)s.")
|
||||
|
@ -152,6 +152,8 @@ from cinder.volume.drivers import scality as cinder_volume_drivers_scality
|
||||
from cinder.volume.drivers import sheepdog as cinder_volume_drivers_sheepdog
|
||||
from cinder.volume.drivers import smbfs as cinder_volume_drivers_smbfs
|
||||
from cinder.volume.drivers import solidfire as cinder_volume_drivers_solidfire
|
||||
from cinder.volume.drivers.synology import synology_common as \
|
||||
cinder_volume_drivers_synology_synologycommon
|
||||
from cinder.volume.drivers import tegile as cinder_volume_drivers_tegile
|
||||
from cinder.volume.drivers import tintri as cinder_volume_drivers_tintri
|
||||
from cinder.volume.drivers.violin import v7000_common as \
|
||||
@ -306,6 +308,7 @@ def list_opts():
|
||||
cinder_volume_drivers_hitachi_hbsdfc.volume_opts,
|
||||
cinder_quota.quota_opts,
|
||||
cinder_volume_drivers_huawei_huaweidriver.huawei_opts,
|
||||
cinder_volume_drivers_synology_synologycommon.cinder_opts,
|
||||
cinder_volume_drivers_dell_dellstoragecentercommon.
|
||||
common_opts,
|
||||
cinder_scheduler_hostmanager.host_manager_opts,
|
||||
|
1611
cinder/tests/unit/test_synology_common.py
Normal file
1611
cinder/tests/unit/test_synology_common.py
Normal file
File diff suppressed because it is too large
Load Diff
358
cinder/tests/unit/test_synology_iscsi.py
Normal file
358
cinder/tests/unit/test_synology_iscsi.py
Normal file
@ -0,0 +1,358 @@
|
||||
# Copyright (c) 2016 Synology 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 the Synology iSCSI volume driver."""
|
||||
|
||||
import mock
|
||||
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.tests.unit import fake_constants as fake
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.synology import synology_common as common
|
||||
from cinder.volume.drivers.synology import synology_iscsi
|
||||
|
||||
VOLUME_ID = fake.VOLUME_ID
|
||||
TARGET_NAME_PREFIX = 'Cinder-Target-'
|
||||
IP = '10.10.10.10'
|
||||
IQN = 'iqn.2000-01.com.synology:' + TARGET_NAME_PREFIX + VOLUME_ID
|
||||
TRG_ID = 1
|
||||
VOLUME = {
|
||||
'name': fake.VOLUME_NAME,
|
||||
'id': VOLUME_ID,
|
||||
'display_name': 'fake_volume',
|
||||
'size': 10,
|
||||
'provider_location': '%s:3260,%d %s 1' % (IP, TRG_ID, IQN),
|
||||
}
|
||||
NEW_VOLUME_ID = fake.VOLUME2_ID
|
||||
IQN2 = 'iqn.2000-01.com.synology:' + TARGET_NAME_PREFIX + NEW_VOLUME_ID
|
||||
NEW_TRG_ID = 2
|
||||
NEW_VOLUME = {
|
||||
'name': fake.VOLUME2_NAME,
|
||||
'id': NEW_VOLUME_ID,
|
||||
'display_name': 'new_fake_volume',
|
||||
'size': 10,
|
||||
'provider_location': '%s:3260,%d %s 1' % (IP, NEW_TRG_ID, IQN2),
|
||||
}
|
||||
SNAPSHOT_ID = fake.SNAPSHOT_ID
|
||||
SNAPSHOT = {
|
||||
'name': fake.SNAPSHOT_NAME,
|
||||
'id': SNAPSHOT_ID,
|
||||
'volume_id': VOLUME_ID,
|
||||
'volume_name': VOLUME['name'],
|
||||
'volume_size': 10,
|
||||
'display_name': 'fake_snapshot',
|
||||
}
|
||||
DS_SNAPSHOT_UUID = 'ca86a56a-40d8-4210-974c-ef15dbf01cba'
|
||||
SNAPSHOT_METADATA = {
|
||||
'metadata': {
|
||||
'ds_snapshot_UUID': DS_SNAPSHOT_UUID
|
||||
}
|
||||
}
|
||||
INITIATOR_IQN = 'iqn.1993-08.org.debian:01:604af6a341'
|
||||
CONNECTOR = {
|
||||
'initiator': INITIATOR_IQN,
|
||||
}
|
||||
CONTEXT = {
|
||||
}
|
||||
LOCAL_PATH = '/dev/isda'
|
||||
IMAGE_SERVICE = 'image_service'
|
||||
IMAGE_ID = 1
|
||||
IMAGE_META = {
|
||||
'id': IMAGE_ID
|
||||
}
|
||||
NODE_UUID = '72003c93-2db2-4f00-a169-67c5eae86bb1'
|
||||
HOST = {
|
||||
}
|
||||
|
||||
|
||||
class SynoISCSIDriverTestCase(test.TestCase):
|
||||
|
||||
@mock.patch.object(common.SynoCommon,
|
||||
'_get_node_uuid',
|
||||
return_value=NODE_UUID)
|
||||
@mock.patch.object(common, 'APIRequest')
|
||||
def setUp(self, _request, _get_node_uuid):
|
||||
super(SynoISCSIDriverTestCase, self).setUp()
|
||||
|
||||
self.conf = self.setup_configuration()
|
||||
self.driver = synology_iscsi.SynoISCSIDriver(configuration=self.conf)
|
||||
self.driver.common = common.SynoCommon(self.conf, 'iscsi')
|
||||
|
||||
def tearDown(self):
|
||||
super(SynoISCSIDriverTestCase, self).tearDown()
|
||||
|
||||
def setup_configuration(self):
|
||||
config = mock.Mock(spec=conf.Configuration)
|
||||
config.use_chap_auth = False
|
||||
config.iscsi_protocol = 'iscsi'
|
||||
config.iscsi_ip_address = IP
|
||||
config.admin_port = 5000
|
||||
config.username = 'admin'
|
||||
config.password = 'admin'
|
||||
config.ssl_verify = True
|
||||
config.one_time_pass = '123456'
|
||||
config.volume_dd_blocksize = 1
|
||||
|
||||
return config
|
||||
|
||||
def test_check_for_setup_error(self):
|
||||
self.driver.common.check_for_setup_error = mock.Mock()
|
||||
|
||||
result = self.driver.check_for_setup_error()
|
||||
|
||||
self.driver.common.check_for_setup_error.assert_called_with()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_create_volume(self):
|
||||
self.driver.common.create_volume = mock.Mock()
|
||||
|
||||
result = self.driver.create_volume(VOLUME)
|
||||
|
||||
self.driver.common.create_volume.assert_called_with(VOLUME)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_delete_volume(self):
|
||||
self.driver.common.delete_volume = mock.Mock()
|
||||
|
||||
result = self.driver.delete_volume(VOLUME)
|
||||
|
||||
self.driver.common.delete_volume.assert_called_with(VOLUME)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_create_cloned_volume(self):
|
||||
self.driver.common.create_cloned_volume = mock.Mock()
|
||||
|
||||
result = self.driver.create_cloned_volume(VOLUME, NEW_VOLUME)
|
||||
|
||||
self.driver.common.create_cloned_volume.assert_called_with(
|
||||
VOLUME, NEW_VOLUME)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_extend_volume(self):
|
||||
new_size = 20
|
||||
|
||||
self.driver.common.extend_volume = mock.Mock()
|
||||
|
||||
result = self.driver.extend_volume(VOLUME, new_size)
|
||||
|
||||
self.driver.common.extend_volume.assert_called_with(
|
||||
VOLUME, new_size)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_extend_volume_wrong_size(self):
|
||||
wrong_new_size = 1
|
||||
|
||||
self.driver.common.extend_volume = mock.Mock()
|
||||
|
||||
result = self.driver.extend_volume(VOLUME, wrong_new_size)
|
||||
|
||||
self.driver.common.extend_volume.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
self.driver.common.create_volume_from_snapshot = mock.Mock()
|
||||
|
||||
result = self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT)
|
||||
|
||||
(self.driver.common.
|
||||
create_volume_from_snapshot.assert_called_with(VOLUME, SNAPSHOT))
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_update_migrated_volume(self):
|
||||
fake_ret = {'_name_id': VOLUME['id']}
|
||||
status = ''
|
||||
self.driver.common.update_migrated_volume = (
|
||||
mock.Mock(return_value=fake_ret))
|
||||
|
||||
result = self.driver.update_migrated_volume(CONTEXT,
|
||||
VOLUME,
|
||||
NEW_VOLUME,
|
||||
status)
|
||||
|
||||
(self.driver.common.update_migrated_volume.
|
||||
assert_called_with(VOLUME, NEW_VOLUME))
|
||||
self.assertEqual(fake_ret, result)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
self.driver.common.create_snapshot = (
|
||||
mock.Mock(return_value=SNAPSHOT_METADATA))
|
||||
|
||||
result = self.driver.create_snapshot(SNAPSHOT)
|
||||
|
||||
self.driver.common.create_snapshot.assert_called_with(SNAPSHOT)
|
||||
self.assertDictMatch(SNAPSHOT_METADATA, result)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
self.driver.common.delete_snapshot = mock.Mock()
|
||||
|
||||
result = self.driver.delete_snapshot(SNAPSHOT)
|
||||
|
||||
self.driver.common.delete_snapshot.assert_called_with(SNAPSHOT)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_get_volume_stats(self):
|
||||
self.driver.common.update_volume_stats = mock.MagicMock()
|
||||
|
||||
result = self.driver.get_volume_stats(True)
|
||||
|
||||
self.driver.common.update_volume_stats.assert_called_with()
|
||||
self.assertDictMatch(self.driver.stats, result)
|
||||
|
||||
result = self.driver.get_volume_stats(False)
|
||||
|
||||
self.driver.common.update_volume_stats.assert_called_with()
|
||||
self.assertDictMatch(self.driver.stats, result)
|
||||
|
||||
def test_get_volume_stats_error(self):
|
||||
self.driver.common.update_volume_stats = (
|
||||
mock.MagicMock(side_effect=exception.VolumeDriverException(
|
||||
message='dont care')))
|
||||
|
||||
self.assertRaises(exception.VolumeDriverException,
|
||||
self.driver.get_volume_stats,
|
||||
True)
|
||||
|
||||
def test_create_export(self):
|
||||
provider_auth = 'CHAP username password'
|
||||
provider_location = '%s:3260,%d %s 1' % (IP, TRG_ID, IQN)
|
||||
|
||||
self.driver.common.is_lun_mapped = mock.Mock(return_value=False)
|
||||
self.driver.common.create_iscsi_export = (
|
||||
mock.Mock(return_value=(IQN, TRG_ID, provider_auth)))
|
||||
self.driver.common.get_provider_location = (
|
||||
mock.Mock(return_value=provider_location))
|
||||
|
||||
result = self.driver.create_export(CONTEXT, VOLUME, CONNECTOR)
|
||||
|
||||
self.driver.common.is_lun_mapped.assert_called_with(VOLUME['name'])
|
||||
(self.driver.common.create_iscsi_export.
|
||||
assert_called_with(VOLUME['name'], VOLUME['id']))
|
||||
self.driver.common.get_provider_location.assert_called_with(IQN,
|
||||
TRG_ID)
|
||||
self.assertEqual(provider_location, result['provider_location'])
|
||||
self.assertEqual(provider_auth, result['provider_auth'])
|
||||
|
||||
def test_create_export_is_mapped(self):
|
||||
self.driver.common.is_lun_mapped = mock.Mock(return_value=True)
|
||||
self.driver.common.create_iscsi_export = mock.Mock()
|
||||
self.driver.common.get_provider_location = mock.Mock()
|
||||
|
||||
result = self.driver.create_export(CONTEXT, VOLUME, CONNECTOR)
|
||||
|
||||
self.driver.common.is_lun_mapped.assert_called_with(VOLUME['name'])
|
||||
self.driver.common.create_iscsi_export.assert_not_called()
|
||||
self.driver.common.get_provider_location.assert_not_called()
|
||||
self.assertEqual({}, result)
|
||||
|
||||
def test_create_export_error(self):
|
||||
provider_location = '%s:3260,%d %s 1' % (IP, TRG_ID, IQN)
|
||||
|
||||
self.driver.common.is_lun_mapped = mock.Mock(return_value=False)
|
||||
self.driver.common.create_iscsi_export = (
|
||||
mock.Mock(side_effect=exception.InvalidInput(reason='dont care')))
|
||||
self.driver.common.get_provider_location = (
|
||||
mock.Mock(return_value=provider_location))
|
||||
|
||||
self.assertRaises(exception.ExportFailure,
|
||||
self.driver.create_export,
|
||||
CONTEXT,
|
||||
VOLUME,
|
||||
CONNECTOR)
|
||||
self.driver.common.is_lun_mapped.assert_called_with(VOLUME['name'])
|
||||
self.driver.common.get_provider_location.assert_not_called()
|
||||
|
||||
def test_remove_export(self):
|
||||
self.driver.common.is_lun_mapped = mock.Mock(return_value=True)
|
||||
self.driver.common.remove_iscsi_export = mock.Mock()
|
||||
self.driver.common.get_iqn_and_trgid = (
|
||||
mock.Mock(return_value=('', TRG_ID)))
|
||||
|
||||
_, trg_id = (self.driver.common.
|
||||
get_iqn_and_trgid(VOLUME['provider_location']))
|
||||
result = self.driver.remove_export(CONTEXT, VOLUME)
|
||||
|
||||
self.driver.common.is_lun_mapped.assert_called_with(VOLUME['name'])
|
||||
(self.driver.common.get_iqn_and_trgid.
|
||||
assert_called_with(VOLUME['provider_location']))
|
||||
(self.driver.common.remove_iscsi_export.
|
||||
assert_called_with(VOLUME['name'], trg_id))
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_remove_export_not_mapped(self):
|
||||
self.driver.common.is_lun_mapped = mock.Mock(return_value=False)
|
||||
self.driver.common.remove_iscsi_export = mock.Mock()
|
||||
self.driver.common.get_iqn_and_trgid = mock.Mock()
|
||||
|
||||
result = self.driver.remove_export(CONTEXT, VOLUME)
|
||||
|
||||
self.driver.common.is_lun_mapped.assert_called_with(VOLUME['name'])
|
||||
self.driver.common.get_iqn_and_trgid.assert_not_called()
|
||||
self.driver.common.remove_iscsi_export.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_remove_export_error(self):
|
||||
self.driver.common.is_lun_mapped = mock.Mock(return_value=True)
|
||||
self.driver.common.remove_iscsi_export = (
|
||||
mock.Mock(side_effect= exception.RemoveExportException(
|
||||
volume=VOLUME, reason='dont care')))
|
||||
|
||||
self.assertRaises(exception.RemoveExportException,
|
||||
self.driver.remove_export,
|
||||
CONTEXT,
|
||||
VOLUME)
|
||||
|
||||
def test_remove_export_error_get_lun_mapped(self):
|
||||
self.driver.common.remove_iscsi_export = mock.Mock()
|
||||
self.driver.common.get_iqn_and_trgid = mock.Mock()
|
||||
self.driver.common.is_lun_mapped = (
|
||||
mock.Mock(side_effect=exception.SynoLUNNotExist(
|
||||
message='dont care')))
|
||||
|
||||
result = self.driver.remove_export(CONTEXT, VOLUME)
|
||||
|
||||
self.assertIsNone(result)
|
||||
self.driver.common.get_iqn_and_trgid.assert_not_called()
|
||||
self.driver.common.remove_iscsi_export.assert_not_called()
|
||||
|
||||
def test_initialize_connection(self):
|
||||
iscsi_properties = {
|
||||
'target_discovered': False,
|
||||
'target_iqn': IQN,
|
||||
'target_portal': '%s:3260' % self.conf.iscsi_ip_address,
|
||||
'volume_id': VOLUME['id'],
|
||||
'access_mode': 'rw',
|
||||
'discard': False
|
||||
}
|
||||
|
||||
self.driver.common.get_iscsi_properties = (
|
||||
mock.Mock(return_value=iscsi_properties))
|
||||
self.conf.safe_get = mock.Mock(return_value='iscsi')
|
||||
|
||||
result = self.driver.initialize_connection(VOLUME, CONNECTOR)
|
||||
|
||||
self.driver.common.get_iscsi_properties.assert_called_with(VOLUME)
|
||||
self.conf.safe_get.assert_called_with('iscsi_protocol')
|
||||
self.assertEqual('iscsi', result['driver_volume_type'])
|
||||
self.assertDictMatch(iscsi_properties, result['data'])
|
||||
|
||||
def test_initialize_connection_error(self):
|
||||
self.driver.common.get_iscsi_properties = (
|
||||
mock.Mock(side_effect=exception.InvalidInput(reason='dont care')))
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.driver.initialize_connection,
|
||||
VOLUME,
|
||||
CONNECTOR)
|
0
cinder/volume/drivers/synology/__init__.py
Normal file
0
cinder/volume/drivers/synology/__init__.py
Normal file
1236
cinder/volume/drivers/synology/synology_common.py
Normal file
1236
cinder/volume/drivers/synology/synology_common.py
Normal file
File diff suppressed because it is too large
Load Diff
168
cinder/volume/drivers/synology/synology_iscsi.py
Normal file
168
cinder/volume/drivers/synology/synology_iscsi.py
Normal file
@ -0,0 +1,168 @@
|
||||
# Copyright (c) 2016 Synology Inc. 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
|
||||
from cinder import exception
|
||||
from cinder import interface
|
||||
from cinder.i18n import _LE, _LW
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.synology import synology_common as common
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@interface.volumedriver
|
||||
class SynoISCSIDriver(driver.ISCSIDriver):
|
||||
"""Openstack Cinder drivers for Synology storage.
|
||||
|
||||
Version history:
|
||||
1.0.0 - Initial driver. Provide Cinder minimum features
|
||||
"""
|
||||
|
||||
VERSION = '1.0.0'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SynoISCSIDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
self.common = None
|
||||
self.configuration.append_config_values(common.cinder_opts)
|
||||
self.stats = {}
|
||||
|
||||
def do_setup(self, context):
|
||||
self.common = common.SynoCommon(self.configuration, 'iscsi')
|
||||
|
||||
def check_for_setup_error(self):
|
||||
self.common.check_for_setup_error()
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Creates a logical volume."""
|
||||
|
||||
self.common.create_volume(volume)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a logical volume."""
|
||||
|
||||
self.common.delete_volume(volume)
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Creates a clone of the specified volume."""
|
||||
|
||||
self.common.create_cloned_volume(volume, src_vref)
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
"""Extend an existing volume's size."""
|
||||
|
||||
if volume['size'] >= new_size:
|
||||
LOG.error(_LE('New size is smaller than original size. '
|
||||
'New: [%(new)d] Old: [%(old)d]'),
|
||||
{'new': new_size,
|
||||
'old': volume['size']})
|
||||
return
|
||||
|
||||
self.common.extend_volume(volume, new_size)
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot."""
|
||||
|
||||
self.common.create_volume_from_snapshot(volume, snapshot)
|
||||
|
||||
def update_migrated_volume(self, ctxt, volume, new_volume, status):
|
||||
"""Return model update for migrated volume."""
|
||||
|
||||
return self.common.update_migrated_volume(volume, new_volume)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
"""Creates a snapshot."""
|
||||
|
||||
return self.common.create_snapshot(snapshot)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Deletes a snapshot."""
|
||||
|
||||
self.common.delete_snapshot(snapshot)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume status.
|
||||
|
||||
If 'refresh' is True, run update the stats first.
|
||||
"""
|
||||
|
||||
try:
|
||||
if refresh or not self.stats:
|
||||
self.stats = self.common.update_volume_stats()
|
||||
self.stats['driver_version'] = self.VERSION
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Failed to get_volume_stats.'))
|
||||
|
||||
return self.stats
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def create_export(self, context, volume, connector):
|
||||
model_update = {}
|
||||
|
||||
try:
|
||||
if self.common.is_lun_mapped(volume['name']):
|
||||
return model_update
|
||||
iqn, trg_id, provider_auth = (self.common.create_iscsi_export
|
||||
(volume['name'], volume['id']))
|
||||
except Exception as e:
|
||||
LOG.exception(_LE('Failed to remove_export.'))
|
||||
raise exception.ExportFailure(reason=e)
|
||||
|
||||
model_update['provider_location'] = (self.common.get_provider_location
|
||||
(iqn, trg_id))
|
||||
model_update['provider_auth'] = provider_auth
|
||||
|
||||
return model_update
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
try:
|
||||
if not self.common.is_lun_mapped(volume['name']):
|
||||
return
|
||||
except exception.SynoLUNNotExist:
|
||||
LOG.warning(_LW("Volume not exist"))
|
||||
return
|
||||
|
||||
try:
|
||||
_, trg_id = (self.common.get_iqn_and_trgid
|
||||
(volume['provider_location']))
|
||||
self.common.remove_iscsi_export(volume['name'], trg_id)
|
||||
except Exception as e:
|
||||
LOG.exception(_LE('Failed to remove_export.'))
|
||||
raise exception.RemoveExportException(volume=volume,
|
||||
reason=e.msg)
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
LOG.debug('iSCSI initiator: %s', connector['initiator'])
|
||||
|
||||
try:
|
||||
iscsi_properties = self.common.get_iscsi_properties(volume)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.exception(_LE('Failed to initialize_connection.'))
|
||||
|
||||
volume_type = self.configuration.safe_get('iscsi_protocol') or 'iscsi'
|
||||
|
||||
return {
|
||||
'driver_volume_type': volume_type,
|
||||
'data': iscsi_properties
|
||||
}
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
pass
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Added backend driver for Synology iSCSI-supported storage.
|
Loading…
Reference in New Issue
Block a user