JovianDSS: Rework Open-E JovianDSS driver
Provide major rework of Open-E JovianDSS driver that involves: - improve performance of volume and snapshot creation and deletion - remove revert to snapshot support - improve storage net interface picking algorithm Change-Id: Ifc2aa5d3622315ae5c70b2d8e809e1b1553684ea Implements: blueprint open-e-joviandss-iscsi-rework
This commit is contained in:
parent
712032959c
commit
40a178e3d3
305
cinder/tests/unit/volume/drivers/open_e/test_common.py
Normal file
305
cinder/tests/unit/volume/drivers/open_e/test_common.py
Normal file
@ -0,0 +1,305 @@
|
||||
# Copyright (c) 2023 Open-E, 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.
|
||||
|
||||
import copy
|
||||
|
||||
from cinder import exception
|
||||
from cinder.tests.unit import test
|
||||
from cinder.volume.drivers.open_e.jovian_common import jdss_common as jcom
|
||||
|
||||
UUID_1 = '12345678-1234-1234-1234-000000000001'
|
||||
UUID_2 = '12345678-1234-1234-1234-000000000002'
|
||||
UUID_S1 = '12345678-1234-1234-1234-100000000001'
|
||||
UUID_S2 = '12345678-1234-1234-1234-100000000002'
|
||||
|
||||
V_UUID_1 = 'v_12345678-1234-1234-1234-000000000001'
|
||||
V_UUID_2 = 'v_12345678-1234-1234-1234-000000000002'
|
||||
V_UUID_3 = 'v_12345678-1234-1234-1234-000000000003'
|
||||
S_UUID_1 = f's_{UUID_S1}_{UUID_1}'
|
||||
S_UUID_2 = f's_{UUID_S2}_{UUID_1}'
|
||||
T_UUID_1 = 't_12345678-1234-1234-1234-000000000001'
|
||||
|
||||
|
||||
VOLUME_GET_THAT_IS_CLONE = {
|
||||
"origin": f"Pool-0/{V_UUID_1}@{S_UUID_1}",
|
||||
"relatime": None,
|
||||
"acltype": None,
|
||||
"vscan": None,
|
||||
"full_name": f"Pool-0/{jcom.vname(UUID_2)}",
|
||||
"userrefs": None,
|
||||
"primarycache": "all",
|
||||
"logbias": "latency",
|
||||
"creation": "1695078560",
|
||||
"sync": "always",
|
||||
"is_clone": True,
|
||||
"dedup": "off",
|
||||
"sharenfs": None,
|
||||
"receive_resume_token": None,
|
||||
"volsize": "1073741824",
|
||||
"referenced": "57344",
|
||||
"sharesmb": None,
|
||||
"createtxg": "19812058",
|
||||
"reservation": "0",
|
||||
"scontext": None,
|
||||
"mountpoint": None,
|
||||
"casesensitivity": None,
|
||||
"guid": "4947994863040470005",
|
||||
"usedbyrefreservation": "0",
|
||||
"dnodesize": None,
|
||||
"written": "0",
|
||||
"logicalused": "0",
|
||||
"compressratio": "1.00",
|
||||
"rootcontext": "none",
|
||||
"default_scsi_id": "5c02d042ed8dbce2",
|
||||
"type": "volume",
|
||||
"compression": "lz4",
|
||||
"snapdir": None,
|
||||
"overlay": None,
|
||||
"encryption": "off",
|
||||
"xattr": None,
|
||||
"volmode": "default",
|
||||
"copies": "1",
|
||||
"snapshot_limit": "18446744073709551615",
|
||||
"aclinherit": None,
|
||||
"defcontext": "none",
|
||||
"readonly": "off",
|
||||
"version": None,
|
||||
"recordsize": None,
|
||||
"filesystem_limit": None,
|
||||
"mounted": None,
|
||||
"mlslabel": "none",
|
||||
"secondarycache": "all",
|
||||
"refreservation": "0",
|
||||
"available": "954751713280",
|
||||
"san:volume_id": "5c02d042ed8dbce2570c8d5dc276dd6a2431e138",
|
||||
"encryptionroot": None,
|
||||
"exec": None,
|
||||
"refquota": None,
|
||||
"refcompressratio": "1.00",
|
||||
"quota": None,
|
||||
"utf8only": None,
|
||||
"keylocation": "none",
|
||||
"snapdev": "hidden",
|
||||
"snapshot_count": "18446744073709551615",
|
||||
"fscontext": "none",
|
||||
"clones": None,
|
||||
"canmount": None,
|
||||
"keystatus": None,
|
||||
"atime": None,
|
||||
"usedbysnapshots": "0",
|
||||
"normalization": None,
|
||||
"usedbychildren": "0",
|
||||
"volblocksize": "65536",
|
||||
"usedbydataset": "0",
|
||||
"objsetid": "19228",
|
||||
"name": "a2",
|
||||
"defer_destroy": None,
|
||||
"pbkdf2iters": "0",
|
||||
"checksum": "on",
|
||||
"redundant_metadata": "all",
|
||||
"filesystem_count": None,
|
||||
"devices": None,
|
||||
"keyformat": "none",
|
||||
"setuid": None,
|
||||
"used": "0",
|
||||
"logicalreferenced": "28672",
|
||||
"context": "none",
|
||||
"zoned": None,
|
||||
"nbmand": None,
|
||||
}
|
||||
|
||||
SNAPSHOT_GET = {
|
||||
'referenced': '57344',
|
||||
'userrefs': '0',
|
||||
'primarycache': 'all',
|
||||
'creation': '2023-06-28 16:49:33',
|
||||
'volsize': '1073741824',
|
||||
'createtxg': '18402390',
|
||||
'guid': '15554334551928551694',
|
||||
'compressratio': '1.00',
|
||||
'rootcontext': 'none',
|
||||
'encryption': 'off',
|
||||
'defcontext': 'none',
|
||||
'written': '0',
|
||||
'type': 'snapshot',
|
||||
'secondarycache': 'all',
|
||||
'used': '0',
|
||||
'refcompressratio': '1.00',
|
||||
'fscontext': 'none',
|
||||
'objsetid': '106843',
|
||||
'name': S_UUID_1,
|
||||
'defer_destroy': 'off',
|
||||
'san:volume_id': 'e82c7fcbd78df0ffe67d363412e5091421d313ca',
|
||||
'mlslabel': 'none',
|
||||
'logicalreferenced': '28672',
|
||||
'context': 'none'}
|
||||
|
||||
SNAPSHOT_MULTIPLE_CLONES = {
|
||||
'referenced': '57344',
|
||||
'userrefs': '0',
|
||||
'primarycache': 'all',
|
||||
'creation': '2023-06-28 18:44:49',
|
||||
'volsize': '1073741824',
|
||||
'createtxg': '18403768',
|
||||
'guid': '18319280142829358721',
|
||||
'compressratio': '1.00',
|
||||
'rootcontext': 'none',
|
||||
'encryption': 'off',
|
||||
'defcontext': 'none',
|
||||
'written': '0',
|
||||
'type': 'snapshot',
|
||||
'secondarycache': 'all',
|
||||
'used': '0',
|
||||
'refcompressratio': '1.00',
|
||||
'fscontext': 'none',
|
||||
'objsetid': '107416',
|
||||
'clones': f'Pool-0/{V_UUID_2},Pool-0/{V_UUID_3}',
|
||||
'name': S_UUID_1,
|
||||
'defer_destroy': 'off',
|
||||
'san:volume_id': 'e82c7fcbd78df0ffe67d363412e5091421d313ca',
|
||||
'mlslabel': 'none',
|
||||
'logicalreferenced': '28672',
|
||||
'context': 'none'}
|
||||
|
||||
SNAPSHOTS_GET_NO_CLONES = [
|
||||
{'referenced': '57344',
|
||||
'userrefs': '0',
|
||||
'primarycache': 'all',
|
||||
'creation': '2023-06-28 16:49:33',
|
||||
'volsize': '1073741824',
|
||||
'createtxg': '18402390',
|
||||
'guid': '15554334551928551694',
|
||||
'compressratio': '1.00',
|
||||
'rootcontext': 'none',
|
||||
'encryption': 'off',
|
||||
'defcontext': 'none',
|
||||
'written': '0',
|
||||
'type': 'snapshot',
|
||||
'secondarycache': 'all',
|
||||
'used': '0',
|
||||
'refcompressratio': '1.00',
|
||||
'fscontext': 'none',
|
||||
'objsetid': '106843',
|
||||
'name': S_UUID_1,
|
||||
'defer_destroy': 'off',
|
||||
'san:volume_id': 'e82c7fcbd78df0ffe67d363412e5091421d313ca',
|
||||
'mlslabel': 'none',
|
||||
'logicalreferenced': '28672',
|
||||
'context': 'none'},
|
||||
{'referenced': '57344',
|
||||
'userrefs': '0',
|
||||
'primarycache': 'all',
|
||||
'creation': '2023-06-28 18:44:49',
|
||||
'volsize': '1073741824',
|
||||
'createtxg': '18403768',
|
||||
'guid': '18319280142829358721',
|
||||
'compressratio': '1.00',
|
||||
'rootcontext': 'none',
|
||||
'encryption': 'off',
|
||||
'defcontext': 'none',
|
||||
'written': '0',
|
||||
'type': 'snapshot',
|
||||
'secondarycache': 'all',
|
||||
'used': '0',
|
||||
'refcompressratio': '1.00',
|
||||
'fscontext': 'none',
|
||||
'objsetid': '107416',
|
||||
'name': S_UUID_2,
|
||||
'defer_destroy': 'off',
|
||||
'san:volume_id': 'e82c7fcbd78df0ffe67d363412e5091421d313ca',
|
||||
'mlslabel': 'none',
|
||||
'logicalreferenced': '28672',
|
||||
'context': 'none'}]
|
||||
|
||||
|
||||
class TestOpenEJovianDSSCommon(test.TestCase):
|
||||
|
||||
def test_is_volume(self):
|
||||
|
||||
self.assertFalse(jcom.is_volume("asdasd"))
|
||||
self.assertFalse(jcom.is_volume(UUID_1))
|
||||
self.assertTrue(jcom.is_volume(V_UUID_1))
|
||||
|
||||
def test_is_snapshot(self):
|
||||
|
||||
self.assertFalse(jcom.is_snapshot("asdasd"))
|
||||
self.assertFalse(jcom.is_snapshot(UUID_S1))
|
||||
self.assertTrue(jcom.is_snapshot(S_UUID_1))
|
||||
|
||||
def test_idname(self):
|
||||
|
||||
self.assertEqual(UUID_1, jcom.idname(V_UUID_1))
|
||||
self.assertEqual(UUID_S1, jcom.idname(S_UUID_1))
|
||||
self.assertEqual(UUID_1, jcom.idname(T_UUID_1))
|
||||
|
||||
self.assertRaises(exception.VolumeDriverException, jcom.idname, 'asd')
|
||||
|
||||
def test_vname(self):
|
||||
|
||||
self.assertEqual(V_UUID_1, jcom.vname(UUID_1))
|
||||
self.assertEqual(V_UUID_1, jcom.vname(V_UUID_1))
|
||||
self.assertRaises(exception.VolumeDriverException,
|
||||
jcom.vname, S_UUID_1)
|
||||
|
||||
def test_sname_to_id(self):
|
||||
|
||||
self.assertEqual((UUID_S1, UUID_1), jcom.sname_to_id(S_UUID_1))
|
||||
|
||||
def test_sid_from_sname(self):
|
||||
|
||||
self.assertEqual(UUID_S1, jcom.sid_from_sname(S_UUID_1))
|
||||
|
||||
def test_vid_from_sname(self):
|
||||
self.assertEqual(UUID_1, jcom.vid_from_sname(S_UUID_1))
|
||||
|
||||
def test_sname(self):
|
||||
self.assertEqual(S_UUID_1, jcom.sname(UUID_S1, UUID_1))
|
||||
|
||||
def test_sname_from_snap(self):
|
||||
|
||||
snap = copy.deepcopy(SNAPSHOT_GET)
|
||||
self.assertEqual(S_UUID_1, jcom.sname_from_snap(snap))
|
||||
|
||||
def test_is_hidden(self):
|
||||
|
||||
self.assertTrue(jcom.is_hidden(T_UUID_1))
|
||||
self.assertFalse(jcom.is_hidden(S_UUID_1))
|
||||
|
||||
def test_origin_snapshot(self):
|
||||
|
||||
vol = copy.deepcopy(VOLUME_GET_THAT_IS_CLONE)
|
||||
|
||||
self.assertEqual(S_UUID_1, jcom.origin_snapshot(vol))
|
||||
|
||||
def test_origin_volume(self):
|
||||
|
||||
vol = copy.deepcopy(VOLUME_GET_THAT_IS_CLONE)
|
||||
|
||||
self.assertEqual(V_UUID_1, jcom.origin_volume(vol))
|
||||
|
||||
def test_snapshot_clones(self):
|
||||
|
||||
clones = [V_UUID_2, V_UUID_3]
|
||||
snap = copy.deepcopy(SNAPSHOT_MULTIPLE_CLONES)
|
||||
self.assertEqual(clones, jcom.snapshot_clones(snap))
|
||||
|
||||
def test_hidden(self):
|
||||
|
||||
self.assertEqual(T_UUID_1, jcom.hidden(V_UUID_1))
|
||||
|
||||
def test_get_newest_snapshot_name(self):
|
||||
snaps = copy.deepcopy(SNAPSHOTS_GET_NO_CLONES)
|
||||
|
||||
self.assertEqual(S_UUID_2, jcom.get_newest_snapshot_name(snaps))
|
1781
cinder/tests/unit/volume/drivers/open_e/test_driver.py
Normal file
1781
cinder/tests/unit/volume/drivers/open_e/test_driver.py
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,6 @@ from unittest import mock
|
||||
from oslo_utils import units as o_units
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.tests.unit import test
|
||||
from cinder.volume.drivers.open_e.jovian_common import exception as jexc
|
||||
from cinder.volume.drivers.open_e.jovian_common import jdss_common as jcom
|
||||
@ -28,6 +27,10 @@ UUID_1 = '12345678-1234-1234-1234-000000000001'
|
||||
UUID_2 = '12345678-1234-1234-1234-000000000002'
|
||||
UUID_3 = '12345678-1234-1234-1234-000000000003'
|
||||
|
||||
UUID_S1 = '12345678-1234-1234-1234-100000000001'
|
||||
UUID_S2 = '12345678-1234-1234-1234-100000000002'
|
||||
UUID_S3 = '12345678-1234-1234-1234-100000000003'
|
||||
|
||||
CONFIG_OK = {
|
||||
'san_hosts': ['192.168.0.2'],
|
||||
'san_api_port': 82,
|
||||
@ -39,7 +42,7 @@ CONFIG_OK = {
|
||||
'jovian_ignore_tpath': [],
|
||||
'target_port': 3260,
|
||||
'jovian_pool': 'Pool-0',
|
||||
'iscsi_target_prefix': 'iqn.2020-04.com.open-e.cinder:',
|
||||
'target_prefix': 'iqn.2020-04.com.open-e.cinder:',
|
||||
'chap_password_len': 12,
|
||||
'san_thin_provision': False,
|
||||
'jovian_block_size': '128K'
|
||||
@ -408,7 +411,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
delete_lun_expected += [mock.call('DELETE', addr)]
|
||||
jrest.rproxy.pool_request.return_value = resp
|
||||
self.assertRaises(
|
||||
exception.VolumeIsBusy,
|
||||
jexc.JDSSResourceIsBusyException,
|
||||
jrest.delete_lun,
|
||||
'v_' + UUID_1)
|
||||
|
||||
@ -424,7 +427,6 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
'error': None,
|
||||
'code': 204}
|
||||
req = {'recursively_children': True,
|
||||
'recursively_dependents': True,
|
||||
'force_umount': True}
|
||||
|
||||
delete_lun_expected = [mock.call('DELETE', addr, json_data=req)]
|
||||
@ -432,7 +434,6 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
self.assertIsNone(
|
||||
jrest.delete_lun('v_' + UUID_1,
|
||||
recursively_children=True,
|
||||
recursively_dependents=True,
|
||||
force_umount=True))
|
||||
|
||||
jrest.rproxy.pool_request.assert_has_calls(delete_lun_expected)
|
||||
@ -441,7 +442,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
addr = '/san/iscsi/targets/{}'.format(tname)
|
||||
data = {'incoming_users_active': True,
|
||||
'name': tname,
|
||||
@ -483,7 +484,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
# Create OK
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
addr = '/san/iscsi/targets'
|
||||
data = {'incoming_users_active': True,
|
||||
'name': tname,
|
||||
@ -506,7 +507,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
self.assertIsNone(jrest.create_target(tname))
|
||||
|
||||
# Target exists
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
addr = '/san/iscsi/targets'
|
||||
data = {'incoming_users_active': True,
|
||||
'name': tname,
|
||||
@ -541,7 +542,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
jrest.create_target, tname)
|
||||
|
||||
# Unknown error
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
addr = "/san/iscsi/targets"
|
||||
|
||||
resp = {'data': data,
|
||||
@ -576,7 +577,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
# Delete OK
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
addr = '/san/iscsi/targets/{}'.format(tname)
|
||||
|
||||
resp = {'data': None,
|
||||
@ -628,7 +629,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
# Modify OK
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
addr = '/san/iscsi/targets/{}/incoming-users'.format(tname)
|
||||
|
||||
chap_cred = {"name": "chapuser",
|
||||
@ -682,7 +683,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
# Get OK
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
addr = '/san/iscsi/targets/{}/incoming-users'.format(tname)
|
||||
|
||||
chap_users = {"name": "chapuser"}
|
||||
@ -736,7 +737,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
# Delete OK
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
user = "chapuser"
|
||||
addr = '/san/iscsi/targets/{}/incoming-users/chapuser'.format(tname)
|
||||
|
||||
@ -791,7 +792,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
# lun present
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
vname = jcom.vname(UUID_1)
|
||||
addr = '/san/iscsi/targets/{target}/luns/{lun}'.format(
|
||||
target=tname, lun=vname)
|
||||
@ -852,7 +853,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
# attach ok
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
vname = jcom.vname(UUID_1)
|
||||
|
||||
addr = '/san/iscsi/targets/{}/luns'.format(tname)
|
||||
@ -875,7 +876,38 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
mock.call('POST', addr, json_data=jbody)]
|
||||
self.assertIsNone(jrest.attach_target_vol(tname, vname))
|
||||
|
||||
# attach with mode and lun
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
vname = jcom.vname(UUID_1)
|
||||
|
||||
addr = '/san/iscsi/targets/{}/luns'.format(tname)
|
||||
jbody = {"name": vname, "lun": 1, "mode": 'ro'}
|
||||
|
||||
data = {"block_size": 512,
|
||||
"device_handler": "vdisk_fileio",
|
||||
"lun": 0,
|
||||
"mode": "ro",
|
||||
"name": vname,
|
||||
"prod_id": "Storage",
|
||||
"scsi_id": "99e2c883331edf87"}
|
||||
|
||||
resp = {'data': data,
|
||||
'error': None,
|
||||
'code': 201}
|
||||
|
||||
jrest.rproxy.pool_request.return_value = resp
|
||||
attach_target_vol_expected = [
|
||||
mock.call('POST', addr, json_data=jbody)]
|
||||
self.assertIsNone(jrest.attach_target_vol(tname, vname,
|
||||
lun_id=1, mode='ro'))
|
||||
jrest.rproxy.pool_request.assert_has_calls(attach_target_vol_expected)
|
||||
|
||||
# lun attached already
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
jbody = {"name": vname, "lun": 0}
|
||||
|
||||
url = 'http://85.14.118.246:11582/api/v3/pools/Pool-0/{}'.format(addr)
|
||||
msg = 'Volume /dev/Pool-0/{} is already used.'.format(vname)
|
||||
err = {"class": "opene.exceptions.ItemConflictError",
|
||||
@ -893,6 +925,9 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
jrest.attach_target_vol, tname, vname)
|
||||
|
||||
# no such target
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
jbody = {"name": vname, "lun": 0}
|
||||
|
||||
url = 'http://85.14.118.246:11582/api/v3/pools/Pool-0/{}'.format(addr)
|
||||
msg = 'Target {} not exists.'.format(vname)
|
||||
err = {"class": "opene.exceptions.ItemNotFoundError",
|
||||
@ -904,12 +939,14 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
'code': 404}
|
||||
|
||||
jrest.rproxy.pool_request.return_value = resp
|
||||
attach_target_vol_expected += [
|
||||
attach_target_vol_expected = [
|
||||
mock.call('POST', addr, json_data=jbody)]
|
||||
self.assertRaises(jexc.JDSSResourceNotFoundException,
|
||||
jrest.attach_target_vol, tname, vname)
|
||||
|
||||
# error unknown
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
jbody = {"name": vname, "lun": 0}
|
||||
url = 'http://85.14.118.246:11582/api/v3/pools/Pool-0/{}'.format(addr)
|
||||
msg = 'Target {} not exists.'.format(vname)
|
||||
|
||||
@ -923,17 +960,28 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
'code': 500}
|
||||
|
||||
jrest.rproxy.pool_request.return_value = resp
|
||||
attach_target_vol_expected += [
|
||||
attach_target_vol_expected = [
|
||||
mock.call('POST', addr, json_data=jbody)]
|
||||
self.assertRaises(jexc.JDSSException,
|
||||
jrest.attach_target_vol, tname, vname)
|
||||
jrest.rproxy.pool_request.assert_has_calls(attach_target_vol_expected)
|
||||
|
||||
# error incorrect mode
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
jbody = {"name": vname, "lun": 0}
|
||||
url = 'http://85.14.118.246:11582/api/v3/pools/Pool-0/{}'.format(addr)
|
||||
|
||||
attach_target_vol_expected = [
|
||||
mock.call('POST', addr, json_data=jbody)]
|
||||
self.assertRaises(jexc.JDSSException,
|
||||
jrest.attach_target_vol, tname, vname, mode='bad')
|
||||
jrest.rproxy.pool_request.assert_not_called()
|
||||
|
||||
def test_detach_target_vol(self):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
# detach target vol ok
|
||||
tname = CONFIG_OK['iscsi_target_prefix'] + UUID_1
|
||||
tname = CONFIG_OK['target_prefix'] + UUID_1
|
||||
vname = jcom.vname(UUID_1)
|
||||
|
||||
addr = '/san/iscsi/targets/{tar}/luns/{vol}'.format(
|
||||
@ -989,9 +1037,9 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
vname = jcom.vname(UUID_1)
|
||||
sname = jcom.sname(UUID_2)
|
||||
sname = jcom.sname(UUID_S1, UUID_1)
|
||||
|
||||
data = {'name': jcom.sname(UUID_2)}
|
||||
data = {'name': jcom.sname(UUID_S2, UUID_1)}
|
||||
resp = {'data': data,
|
||||
'error': None,
|
||||
'code': 201}
|
||||
@ -1003,7 +1051,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
vname = jcom.vname(UUID_1)
|
||||
sname = jcom.sname(UUID_2)
|
||||
sname = jcom.sname(UUID_S1, UUID_1)
|
||||
|
||||
addr = '/volumes/{vol}/snapshots'.format(vol=vname)
|
||||
req = {'snapshot_name': sname}
|
||||
@ -1066,7 +1114,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
vname = jcom.vname(UUID_1)
|
||||
sname = jcom.sname(UUID_2)
|
||||
sname = jcom.sname(UUID_S1, UUID_1)
|
||||
cname = jcom.vname(UUID_3)
|
||||
|
||||
addr = '/volumes/{vol}/clone'.format(vol=vname)
|
||||
@ -1101,7 +1149,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
vname = jcom.vname(UUID_1)
|
||||
sname = jcom.sname(UUID_2)
|
||||
sname = jcom.sname(UUID_S2, UUID_2)
|
||||
cname = jcom.vname(UUID_3)
|
||||
|
||||
addr = '/volumes/{vol}/clone'.format(vol=vname)
|
||||
@ -1177,7 +1225,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
vname = jcom.vname(UUID_1)
|
||||
sname = jcom.sname(UUID_2)
|
||||
sname = jcom.sname(UUID_S2, UUID_2)
|
||||
|
||||
req = ('/volumes/{vol}/snapshots/'
|
||||
'{snap}/rollback').format(vol=vname, snap=sname)
|
||||
@ -1198,7 +1246,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
vname = jcom.vname(UUID_1)
|
||||
sname = jcom.sname(UUID_2)
|
||||
sname = jcom.sname(UUID_S2, UUID_2)
|
||||
|
||||
req = ('/volumes/{vol}/snapshots/'
|
||||
'{snap}/rollback').format(vol=vname,
|
||||
@ -1253,13 +1301,12 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
vname = jcom.vname(UUID_1)
|
||||
sname = jcom.sname(UUID_2)
|
||||
sname = jcom.sname(UUID_S2, UUID_2)
|
||||
|
||||
addr = '/volumes/{vol}/snapshots/{snap}'.format(vol=vname, snap=sname)
|
||||
|
||||
jbody = {
|
||||
'recursively_children': True,
|
||||
'recursively_dependents': True,
|
||||
'force_umount': True
|
||||
}
|
||||
|
||||
@ -1268,7 +1315,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
'code': 204}
|
||||
|
||||
jrest.rproxy.pool_request.return_value = resp
|
||||
delete_snapshot_expected = [mock.call('DELETE', addr)]
|
||||
delete_snapshot_expected = [mock.call('DELETE', addr, json_data={})]
|
||||
self.assertIsNone(jrest.delete_snapshot(vname, sname))
|
||||
|
||||
delete_snapshot_expected += [
|
||||
@ -1276,7 +1323,6 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
self.assertIsNone(jrest.delete_snapshot(vname,
|
||||
sname,
|
||||
recursively_children=True,
|
||||
recursively_dependents=True,
|
||||
force_umount=True))
|
||||
|
||||
jrest.rproxy.pool_request.assert_has_calls(delete_snapshot_expected)
|
||||
@ -1285,8 +1331,8 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest, ctx = self.get_rest(CONFIG_OK)
|
||||
vname = jcom.vname(UUID_1)
|
||||
sname = jcom.sname(UUID_2)
|
||||
cname = jcom.sname(UUID_3)
|
||||
sname = jcom.sname(UUID_S2, UUID_1)
|
||||
cname = jcom.sname(UUID_S3, UUID_1)
|
||||
|
||||
addr = '/volumes/{vol}/snapshots/{snap}'.format(vol=vname, snap=sname)
|
||||
|
||||
@ -1307,7 +1353,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
jrest.rproxy.pool_request.return_value = resp
|
||||
delete_snapshot_expected = [
|
||||
mock.call('DELETE', addr)]
|
||||
mock.call('DELETE', addr, json_data={})]
|
||||
|
||||
self.assertRaises(jexc.JDSSSnapshotIsBusyException,
|
||||
jrest.delete_snapshot,
|
||||
@ -1325,7 +1371,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
'code': 500}
|
||||
|
||||
jrest.rproxy.pool_request.return_value = resp
|
||||
delete_snapshot_expected += [mock.call('DELETE', addr)]
|
||||
delete_snapshot_expected += [mock.call('DELETE', addr, json_data={})]
|
||||
self.assertRaises(jexc.JDSSException,
|
||||
jrest.delete_snapshot, vname, sname)
|
||||
|
||||
@ -1340,7 +1386,7 @@ class TestOpenEJovianRESTAPI(test.TestCase):
|
||||
|
||||
data = {"results": 2,
|
||||
"entries": {"referenced": "65536",
|
||||
"name": jcom.sname(UUID_2),
|
||||
"name": jcom.sname(UUID_S1, UUID_1),
|
||||
"defer_destroy": "off",
|
||||
"userrefs": "0",
|
||||
"primarycache": "all",
|
||||
|
@ -41,7 +41,7 @@ CONFIG_OK = {
|
||||
'jovian_ignore_tpath': [],
|
||||
'target_port': 3260,
|
||||
'jovian_pool': 'Pool-0',
|
||||
'iscsi_target_prefix': 'iqn.2020-04.com.open-e.cinder:',
|
||||
'target_prefix': 'iqn.2020-04.com.open-e.cinder:',
|
||||
'chap_password_len': 12,
|
||||
'san_thin_provision': False,
|
||||
'jovian_block_size': '128K'
|
||||
@ -61,7 +61,7 @@ CONFIG_BAD_IP = {
|
||||
'jovian_ignore_tpath': [],
|
||||
'target_port': 3260,
|
||||
'jovian_pool': 'Pool-0',
|
||||
'iscsi_target_prefix': 'iqn.2020-04.com.open-e.cinder:',
|
||||
'target_prefix': 'iqn.2020-04.com.open-e.cinder:',
|
||||
'chap_password_len': 12,
|
||||
'san_thin_provision': False,
|
||||
'jovian_block_size': '128K'
|
||||
@ -81,7 +81,7 @@ CONFIG_MULTIHOST = {
|
||||
'jovian_ignore_tpath': [],
|
||||
'target_port': 3260,
|
||||
'jovian_pool': 'Pool-0',
|
||||
'iscsi_target_prefix': 'iqn.2020-04.com.open-e.cinder:',
|
||||
'target_prefix': 'iqn.2020-04.com.open-e.cinder:',
|
||||
'chap_password_len': 12,
|
||||
'san_thin_provision': False,
|
||||
'jovian_block_size': '128K'
|
||||
@ -102,12 +102,12 @@ class TestOpenEJovianRESTProxy(test.TestCase):
|
||||
def test_init(self):
|
||||
|
||||
self.assertRaises(exception.InvalidConfigurationValue,
|
||||
rest_proxy.JovianRESTProxy,
|
||||
rest_proxy.JovianDSSRESTProxy,
|
||||
CONFIG_BAD_IP)
|
||||
|
||||
def test_get_base_url(self):
|
||||
|
||||
proxy = rest_proxy.JovianRESTProxy(CONFIG_OK)
|
||||
proxy = rest_proxy.JovianDSSRESTProxy(CONFIG_OK)
|
||||
|
||||
url = proxy._get_base_url()
|
||||
|
||||
@ -119,7 +119,7 @@ class TestOpenEJovianRESTProxy(test.TestCase):
|
||||
|
||||
def test_next_host(self):
|
||||
|
||||
proxy = rest_proxy.JovianRESTProxy(CONFIG_MULTIHOST)
|
||||
proxy = rest_proxy.JovianDSSRESTProxy(CONFIG_MULTIHOST)
|
||||
|
||||
self.assertEqual(0, proxy.active_host)
|
||||
proxy._next_host()
|
||||
@ -134,7 +134,7 @@ class TestOpenEJovianRESTProxy(test.TestCase):
|
||||
|
||||
def test_request(self):
|
||||
|
||||
proxy = rest_proxy.JovianRESTProxy(CONFIG_MULTIHOST)
|
||||
proxy = rest_proxy.JovianDSSRESTProxy(CONFIG_MULTIHOST)
|
||||
|
||||
patches = [
|
||||
mock.patch.object(requests, "Request", return_value="request"),
|
||||
@ -153,7 +153,7 @@ class TestOpenEJovianRESTProxy(test.TestCase):
|
||||
|
||||
def test_request_host_failure(self):
|
||||
|
||||
proxy = rest_proxy.JovianRESTProxy(CONFIG_MULTIHOST)
|
||||
proxy = rest_proxy.JovianDSSRESTProxy(CONFIG_MULTIHOST)
|
||||
|
||||
patches = [
|
||||
mock.patch.object(requests, "Request", return_value="request"),
|
||||
@ -185,7 +185,7 @@ class TestOpenEJovianRESTProxy(test.TestCase):
|
||||
|
||||
def test_pool_request(self):
|
||||
|
||||
proxy = rest_proxy.JovianRESTProxy(CONFIG_OK)
|
||||
proxy = rest_proxy.JovianDSSRESTProxy(CONFIG_OK)
|
||||
|
||||
patches = [mock.patch.object(proxy, "request")]
|
||||
|
||||
@ -199,7 +199,7 @@ class TestOpenEJovianRESTProxy(test.TestCase):
|
||||
|
||||
def test_send(self):
|
||||
|
||||
proxy = rest_proxy.JovianRESTProxy(CONFIG_MULTIHOST)
|
||||
proxy = rest_proxy.JovianDSSRESTProxy(CONFIG_MULTIHOST)
|
||||
|
||||
json_data = {"data": [{"available": "949998694400",
|
||||
"status": 26,
|
||||
@ -236,70 +236,52 @@ class TestOpenEJovianRESTProxy(test.TestCase):
|
||||
self.assertEqual(json_data['error'], ret['error'])
|
||||
self.stop_patches(patches)
|
||||
|
||||
def test_send_connection_error(self):
|
||||
|
||||
proxy = rest_proxy.JovianRESTProxy(CONFIG_MULTIHOST)
|
||||
|
||||
json_data = {"data": None,
|
||||
"error": None}
|
||||
|
||||
session_ret = mock.Mock()
|
||||
session_ret.text = json.dumps(json_data)
|
||||
session_ret.status_code = 200
|
||||
patches = [mock.patch.object(proxy.session, "send")]
|
||||
|
||||
pr = 'prepared_request'
|
||||
|
||||
def test_request_host_change(self):
|
||||
proxy = rest_proxy.JovianDSSRESTProxy(CONFIG_MULTIHOST)
|
||||
patches = [
|
||||
mock.patch.object(requests, "Request", return_value="request"),
|
||||
mock.patch.object(proxy.session,
|
||||
"prepare_request",
|
||||
return_value="out_data"),
|
||||
mock.patch.object(proxy, "_send", return_value="out_data")]
|
||||
request_expected = [
|
||||
mock.call('GET',
|
||||
'https://192.168.0.2:82/api/v3/pools/Pool-0'),
|
||||
mock.call('GET',
|
||||
'https://192.168.0.3:82/api/v3/pools/Pool-0'),
|
||||
mock.call('GET',
|
||||
'https://192.168.0.4:82/api/v3/pools/Pool-0'),
|
||||
mock.call('GET',
|
||||
'https://192.168.0.2:82/api/v3/pools/Pool-0')]
|
||||
self.start_patches(patches)
|
||||
|
||||
side_effect = [requests.exceptions.ConnectionError()] * 4
|
||||
side_effect += [session_ret]
|
||||
|
||||
proxy.session.send.side_effect = side_effect
|
||||
|
||||
send_expected = [mock.call(pr)] * 4
|
||||
|
||||
ret = proxy._send(pr)
|
||||
|
||||
proxy.session.send.assert_has_calls(send_expected)
|
||||
|
||||
proxy._send.side_effect = [
|
||||
requests.exceptions.ConnectionError(),
|
||||
requests.exceptions.ConnectionError(),
|
||||
requests.exceptions.ConnectionError(),
|
||||
"out_data"]
|
||||
proxy.request('GET', '/pools/Pool-0')
|
||||
self.assertEqual(0, proxy.active_host)
|
||||
|
||||
self.assertEqual(200, ret['code'])
|
||||
self.assertEqual(json_data['data'], ret['data'])
|
||||
self.assertEqual(json_data['error'], ret['error'])
|
||||
requests.Request.assert_has_calls(request_expected)
|
||||
self.stop_patches(patches)
|
||||
|
||||
def test_send_mixed_error(self):
|
||||
|
||||
proxy = rest_proxy.JovianRESTProxy(CONFIG_MULTIHOST)
|
||||
|
||||
json_data = {"data": None,
|
||||
"error": None}
|
||||
def test_send_jsondecode_error(self):
|
||||
proxy = rest_proxy.JovianDSSRESTProxy(CONFIG_MULTIHOST)
|
||||
|
||||
session_ret = mock.Mock()
|
||||
session_ret.text = json.dumps(json_data)
|
||||
session_ret.text = "{ some-bad-json"
|
||||
session_ret.status_code = 200
|
||||
patches = [mock.patch.object(proxy.session, "send")]
|
||||
|
||||
patches = [mock.patch.object(proxy.session, "send")]
|
||||
pr = 'prepared_request'
|
||||
|
||||
self.start_patches(patches)
|
||||
|
||||
side_effect = [requests.exceptions.ConnectionError()] * 4
|
||||
side_effect += [jexc.JDSSOSException()] * 4
|
||||
side_effect += [session_ret]
|
||||
|
||||
side_effect = [session_ret] * 3
|
||||
proxy.session.send.side_effect = side_effect
|
||||
send_expected = [mock.call(pr)] * 3
|
||||
|
||||
send_expected = [mock.call(pr)] * 7
|
||||
|
||||
self.assertRaises(jexc.JDSSOSException, proxy._send, pr)
|
||||
|
||||
self.assertRaises(json.JSONDecodeError, proxy._send, pr)
|
||||
proxy.session.send.assert_has_calls(send_expected)
|
||||
|
||||
self.assertEqual(0, proxy.active_host)
|
||||
|
||||
def test_handle_500(self):
|
||||
|
||||
error = {"class": "exceptions.OSError",
|
||||
@ -314,7 +296,7 @@ class TestOpenEJovianRESTProxy(test.TestCase):
|
||||
session_ret.status_code = 500
|
||||
|
||||
self.assertRaises(jexc.JDSSOSException,
|
||||
rest_proxy.JovianRESTProxy._handle_500,
|
||||
rest_proxy.JovianDSSRESTProxy._handle_500,
|
||||
session_ret)
|
||||
|
||||
session_ret.status_code = 200
|
||||
@ -322,4 +304,5 @@ class TestOpenEJovianRESTProxy(test.TestCase):
|
||||
"error": None}
|
||||
|
||||
session_ret.text = json.dumps(json_data)
|
||||
self.assertIsNone(rest_proxy.JovianRESTProxy._handle_500(session_ret))
|
||||
self.assertIsNone(
|
||||
rest_proxy.JovianDSSRESTProxy._handle_500(session_ret))
|
||||
|
File diff suppressed because it is too large
Load Diff
814
cinder/volume/drivers/open_e/jovian_common/driver.py
Normal file
814
cinder/volume/drivers/open_e/jovian_common/driver.py
Normal file
@ -0,0 +1,814 @@
|
||||
# Copyright (c) 2023 Open-E, 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 units as o_units
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.volume.drivers.open_e.jovian_common import exception as jexc
|
||||
from cinder.volume.drivers.open_e.jovian_common import jdss_common as jcom
|
||||
from cinder.volume.drivers.open_e.jovian_common import rest
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JovianDSSDriver(object):
|
||||
|
||||
def __init__(self, config):
|
||||
|
||||
self.configuration = config
|
||||
self._pool = self.configuration.get('jovian_pool', 'Pool-0')
|
||||
self.jovian_iscsi_target_portal_port = self.configuration.get(
|
||||
'target_port', 3260)
|
||||
|
||||
self.jovian_target_prefix = self.configuration.get(
|
||||
'target_prefix',
|
||||
'iqn.2020-04.com.open-e.cinder:')
|
||||
self.jovian_chap_pass_len = self.configuration.get(
|
||||
'chap_password_len', 12)
|
||||
self.block_size = (
|
||||
self.configuration.get('jovian_block_size', '64K'))
|
||||
self.jovian_sparse = (
|
||||
self.configuration.get('san_thin_provision', True))
|
||||
self.jovian_ignore_tpath = self.configuration.get(
|
||||
'jovian_ignore_tpath', None)
|
||||
self.jovian_hosts = self.configuration.get(
|
||||
'san_hosts', [])
|
||||
|
||||
self.ra = rest.JovianRESTAPI(config)
|
||||
|
||||
def rest_config_is_ok(self):
|
||||
"""Check config correctness by checking pool availability"""
|
||||
|
||||
return self.ra.is_pool_exists()
|
||||
|
||||
def get_active_ifaces(self):
|
||||
"""Return list of ip addresses for iSCSI connection"""
|
||||
|
||||
return self.jovian_hosts
|
||||
|
||||
def get_provider_location(self, volume_name):
|
||||
"""Return volume iscsiadm-formatted provider location string."""
|
||||
return '%(host)s:%(port)s,1 %(name)s 0' % {
|
||||
'host': self.ra.get_active_host(),
|
||||
'port': self.jovian_iscsi_target_portal_port,
|
||||
'name': self._get_target_name(volume_name)}
|
||||
|
||||
def create_volume(self, volume_id, volume_size, sparse=False,
|
||||
block_size=None):
|
||||
"""Create a volume.
|
||||
|
||||
:param str volume_id: volume id
|
||||
:param int volume_size: size in Gi
|
||||
:param bool sparse: thin or thick volume flag (default thin)
|
||||
:param int block_size: size of block (default None)
|
||||
|
||||
:return: None
|
||||
"""
|
||||
vname = jcom.vname(volume_id)
|
||||
LOG.debug("Create volume:%(name)s with size:%(size)s",
|
||||
{'name': volume_id, 'size': volume_size})
|
||||
|
||||
self.ra.create_lun(vname,
|
||||
volume_size * o_units.Gi,
|
||||
sparse=sparse,
|
||||
block_size=block_size)
|
||||
return
|
||||
|
||||
def _promote_newest_delete(self, vname, snapshots=None):
|
||||
'''Promotes and delete volume
|
||||
|
||||
This function deletes volume.
|
||||
It will promote volume if needed before deletion.
|
||||
|
||||
:param str vname: physical volume id
|
||||
:param list snapshots: snapshot data list (default None)
|
||||
|
||||
:return: None
|
||||
'''
|
||||
|
||||
if snapshots is None:
|
||||
try:
|
||||
snapshots = self.ra.get_snapshots(vname)
|
||||
except jexc.JDSSResourceNotFoundException:
|
||||
LOG.debug('volume %s do not exists, it was already '
|
||||
'deleted', vname)
|
||||
return
|
||||
|
||||
bsnaps = self._list_busy_snapshots(vname, snapshots)
|
||||
|
||||
if len(bsnaps) != 0:
|
||||
|
||||
promote_target = None
|
||||
|
||||
sname = jcom.get_newest_snapshot_name(bsnaps)
|
||||
|
||||
for snap in bsnaps:
|
||||
if snap['name'] == sname:
|
||||
cvnames = jcom.snapshot_clones(snap)
|
||||
for cvname in cvnames:
|
||||
if jcom.is_volume(cvname):
|
||||
promote_target = cvname
|
||||
if jcom.is_snapshot(cvname):
|
||||
self._promote_newest_delete(cvname)
|
||||
if jcom.is_hidden(cvname):
|
||||
self._promote_newest_delete(cvname)
|
||||
break
|
||||
|
||||
if promote_target is None:
|
||||
self._promote_newest_delete(vname)
|
||||
return
|
||||
|
||||
self.ra.promote(vname, sname, promote_target)
|
||||
|
||||
self._delete_vol_with_source_snap(vname, recursive=True)
|
||||
|
||||
def _delete_vol_with_source_snap(self, vname, recursive=False):
|
||||
'''Delete volume and its source snapshot if required
|
||||
|
||||
This function deletes volume.
|
||||
If volume is a clone it will check its source snapshot if
|
||||
one is originates from volume to delete.
|
||||
|
||||
:param str vname: physical volume id
|
||||
:param bool recursive: recursive flag (default False)
|
||||
|
||||
:return: None
|
||||
'''
|
||||
vol = None
|
||||
|
||||
try:
|
||||
vol = self.ra.get_lun(vname)
|
||||
except jexc.JDSSResourceNotFoundException:
|
||||
LOG.debug('unable to get volume %s info, '
|
||||
'assume it was already deleted', vname)
|
||||
return
|
||||
try:
|
||||
self.ra.delete_lun(vname,
|
||||
force_umount=True,
|
||||
recursively_children=recursive)
|
||||
except jexc.JDSSResourceNotFoundException:
|
||||
LOG.debug('volume %s do not exists, it was already '
|
||||
'deleted', vname)
|
||||
return
|
||||
|
||||
if vol is not None and \
|
||||
'origin' in vol and \
|
||||
vol['origin'] is not None:
|
||||
if jcom.is_volume(jcom.origin_snapshot(vol)) or \
|
||||
jcom.is_hidden(jcom.origin_snapshot(vol)) or \
|
||||
(jcom.vid_from_sname(jcom.origin_snapshot(vol)) ==
|
||||
jcom.idname(vname)):
|
||||
self.ra.delete_snapshot(jcom.origin_volume(vol),
|
||||
jcom.origin_snapshot(vol),
|
||||
recursively_children=True,
|
||||
force_umount=True)
|
||||
|
||||
def _clean_garbage_resources(self, vname, snapshots=None):
|
||||
'''Removes resources that is not related to volume
|
||||
|
||||
Goes through volume snapshots and it clones to identify one
|
||||
that is clearly not related to vname volume and therefore
|
||||
have to be deleted.
|
||||
|
||||
:param str vname: physical volume id
|
||||
:param list snapshots: list of snapshot info dictionaries
|
||||
|
||||
:return: updated list of snapshots
|
||||
'''
|
||||
|
||||
if snapshots is None:
|
||||
try:
|
||||
snapshots = self.ra.get_snapshots(vname)
|
||||
except jexc.JDSSResourceNotFoundException:
|
||||
LOG.debug('volume %s do not exists, it was already '
|
||||
'deleted', vname)
|
||||
return
|
||||
update = False
|
||||
for snap in snapshots:
|
||||
if jcom.is_volume(jcom.sname_from_snap(snap)):
|
||||
cvnames = jcom.snapshot_clones(snap)
|
||||
if len(cvnames) == 0:
|
||||
self._delete_snapshot(vname, jcom.sname_from_snap(snap))
|
||||
update = True
|
||||
if jcom.is_snapshot(jcom.sname_from_snap(snap)):
|
||||
cvnames = jcom.snapshot_clones(snap)
|
||||
for cvname in cvnames:
|
||||
if jcom.is_hidden(cvname):
|
||||
self._promote_newest_delete(cvname)
|
||||
update = True
|
||||
if jcom.is_snapshot(cvname):
|
||||
if jcom.idname(vname) != jcom.vid_from_sname(cvname):
|
||||
self._promote_newest_delete(cvname)
|
||||
update = True
|
||||
if update:
|
||||
snapshots = self.ra.get_snapshots(vname)
|
||||
return snapshots
|
||||
|
||||
def _list_busy_snapshots(self, vname, snapshots,
|
||||
exclude_dedicated_volumes=False) -> list:
|
||||
"""List all volume snapshots with clones
|
||||
|
||||
Goes through provided list of snapshots.
|
||||
If additional parameters are given, will filter list of snapshots
|
||||
accordingly.
|
||||
|
||||
Keyword arguments:
|
||||
:param str vname: zvol id
|
||||
:param list snapshots: list of snapshots data dicts
|
||||
:param bool exclude_dedicated_volumes: list snapshots that has clones
|
||||
(default False)
|
||||
|
||||
:return: filtered list of snapshot data dicts
|
||||
:rtype: list
|
||||
"""
|
||||
|
||||
out = []
|
||||
for snap in snapshots:
|
||||
clones = jcom.snapshot_clones(snap)
|
||||
add = False
|
||||
for cvname in clones:
|
||||
if exclude_dedicated_volumes and jcom.is_volume(cvname):
|
||||
continue
|
||||
add = True
|
||||
if add:
|
||||
out.append(snap)
|
||||
|
||||
return out
|
||||
|
||||
def _clean_volume_snapshots_mount_points(self, vname, snapshots):
|
||||
update = False
|
||||
for snap in snapshots:
|
||||
clones = jcom.snapshot_clones(snap)
|
||||
for cname in [c for c in clones if jcom.is_snapshot(c)]:
|
||||
update = True
|
||||
self._delete_volume(cname, cascade=True)
|
||||
if update:
|
||||
snapshots = self.ra.get_snapshots(vname)
|
||||
return snapshots
|
||||
|
||||
def _delete_volume(self, vname, cascade=False):
|
||||
"""_delete_volume delete routine containing delete logic
|
||||
|
||||
:param str vname: physical volume id
|
||||
:param bool cascade: flag for cascade volume deletion
|
||||
with its snapshots
|
||||
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self.ra.delete_lun(vname,
|
||||
force_umount=True,
|
||||
recursively_children=cascade)
|
||||
except jexc.JDSSResourceIsBusyException:
|
||||
LOG.debug('unable to conduct direct volume %s deletion', vname)
|
||||
except jexc.JDSSResourceNotFoundException:
|
||||
LOG.debug('volume %s do not exists, it was already '
|
||||
'deleted', vname)
|
||||
return
|
||||
except jexc.JDSSRESTException as jerr:
|
||||
LOG.debug(
|
||||
"Unable to delete physical volume %(volume)s "
|
||||
"with error %(err)s.", {
|
||||
"volume": vname,
|
||||
"err": jerr})
|
||||
else:
|
||||
LOG.debug('in place deletion suceeded')
|
||||
return
|
||||
|
||||
snapshots = None
|
||||
try:
|
||||
snapshots = self.ra.get_snapshots(vname)
|
||||
except jexc.JDSSResourceNotFoundException:
|
||||
LOG.debug('volume %s do not exists, it was already '
|
||||
'deleted', vname)
|
||||
return
|
||||
|
||||
if cascade is False:
|
||||
bsnaps = self._list_busy_snapshots(vname,
|
||||
snapshots,
|
||||
exclude_dedicated_volumes=True)
|
||||
if len(bsnaps) > 0:
|
||||
raise exception.VolumeIsBusy('Volume has snapshots')
|
||||
|
||||
snaps = self._clean_garbage_resources(vname, snapshots)
|
||||
snaps = self._clean_volume_snapshots_mount_points(vname, snapshots)
|
||||
|
||||
self._promote_newest_delete(vname, snapshots=snaps)
|
||||
|
||||
def delete_volume(self, volume_name, cascade=False):
|
||||
"""Delete volume
|
||||
|
||||
:param volume: volume reference
|
||||
:param cascade: remove snapshots of a volume as well
|
||||
"""
|
||||
vname = jcom.vname(volume_name)
|
||||
|
||||
LOG.debug('deleting volume %s', vname)
|
||||
|
||||
self._delete_volume(vname, cascade=cascade)
|
||||
|
||||
def _clone_object(self, cvname, sname, ovname,
|
||||
sparse=None,
|
||||
create_snapshot=False):
|
||||
"""Creates a clone of specified object
|
||||
|
||||
Will create snapshot if it is not provided
|
||||
|
||||
:param str cvname: clone volume name
|
||||
:param str sname: snapshot name
|
||||
:param str ovname: original volume name
|
||||
:param bool sparse: sparse property of new volume
|
||||
:param bool create_snapshot:
|
||||
"""
|
||||
LOG.debug('cloning %(ovname)s to %(coname)s', {
|
||||
"ovname": ovname,
|
||||
"coname": cvname})
|
||||
|
||||
if create_snapshot:
|
||||
self.ra.create_snapshot(ovname, sname)
|
||||
try:
|
||||
self.ra.create_volume_from_snapshot(
|
||||
cvname,
|
||||
sname,
|
||||
ovname,
|
||||
sparse=sparse)
|
||||
except jexc.JDSSException as jerr:
|
||||
# This is a garbage collecting section responsible for cleaning
|
||||
# all the mess of request failed
|
||||
if create_snapshot:
|
||||
try:
|
||||
self.ra.delete_snapshot(ovname,
|
||||
cvname,
|
||||
recursively_children=True,
|
||||
force_umount=True)
|
||||
except jexc.JDSSException as jerrd:
|
||||
LOG.warning("Because of %s physical snapshot %s of volume"
|
||||
" %s have to be removed manually",
|
||||
jerrd,
|
||||
sname,
|
||||
ovname)
|
||||
|
||||
raise jerr
|
||||
|
||||
def resize_volume(self, volume_name, new_size):
|
||||
"""Extend an existing volume.
|
||||
|
||||
:param str volume_name: volume id
|
||||
:param int new_size: volume new size in Gi
|
||||
"""
|
||||
LOG.debug("Extend volume:%(name)s to size:%(size)s",
|
||||
{'name': volume_name, 'size': new_size})
|
||||
|
||||
self.ra.extend_lun(jcom.vname(volume_name),
|
||||
int(new_size) * o_units.Gi)
|
||||
|
||||
def create_cloned_volume(self,
|
||||
clone_name,
|
||||
volume_name,
|
||||
size,
|
||||
snapshot_name=None,
|
||||
sparse=False):
|
||||
"""Create a clone of the specified volume.
|
||||
|
||||
:param str clone_name: new volume id
|
||||
:param volume_name: original volume id
|
||||
:param int size: size in Gi
|
||||
:param str snapshot_name: openstack snapshot id to use for cloning
|
||||
:param bool sparse: sparse flag
|
||||
"""
|
||||
cvname = jcom.vname(clone_name)
|
||||
|
||||
ovname = jcom.vname(volume_name)
|
||||
|
||||
LOG.debug('clone volume %(id)s to %(id_clone)s', {
|
||||
"id": volume_name,
|
||||
"id_clone": clone_name})
|
||||
|
||||
if snapshot_name:
|
||||
sname = jcom.sname(snapshot_name, volume_name)
|
||||
self._clone_object(cvname, sname, ovname,
|
||||
create_snapshot=False,
|
||||
sparse=sparse)
|
||||
else:
|
||||
sname = jcom.vname(clone_name)
|
||||
self._clone_object(cvname, sname, ovname,
|
||||
create_snapshot=True,
|
||||
sparse=sparse)
|
||||
|
||||
clone_size = 0
|
||||
|
||||
try:
|
||||
clone_size = int(self.ra.get_lun(cvname)['volsize'])
|
||||
except jexc.JDSSException as jerr:
|
||||
|
||||
self.delete_volume(clone_name, cascade=False)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
_("Fail in cloning volume %(vol)s to %(clone)s.") % {
|
||||
'vol': volume_name, 'clone': clone_name}) from jerr
|
||||
|
||||
try:
|
||||
if int(clone_size) < o_units.Gi * int(size):
|
||||
self.resize_volume(clone_name, int(size))
|
||||
|
||||
except jexc.JDSSException as jerr:
|
||||
# If volume can't be set to a proper size make sure to clean it
|
||||
# before failing
|
||||
try:
|
||||
self.delete_volume(clone_name, cascade=False)
|
||||
except jexc.JDSSException as jerrex:
|
||||
LOG.warning("Error %s during cleaning failed volume %s",
|
||||
jerrex, volume_name)
|
||||
raise jerr from jerrex
|
||||
|
||||
def create_snapshot(self, snapshot_name, volume_name):
|
||||
"""Create snapshot of existing volume.
|
||||
|
||||
:param str snapshot_name: new snapshot id
|
||||
:param str volume_name: original volume id
|
||||
"""
|
||||
LOG.debug('create snapshot %(snap)s for volume %(vol)s', {
|
||||
'snap': snapshot_name,
|
||||
'vol': volume_name})
|
||||
|
||||
vname = jcom.vname(volume_name)
|
||||
sname = jcom.sname(snapshot_name, volume_name)
|
||||
|
||||
self.ra.create_snapshot(vname, sname)
|
||||
|
||||
def create_export_snapshot(self, snapshot_name, volume_name,
|
||||
provider_auth):
|
||||
"""Creates iscsi resources needed to start using snapshot
|
||||
|
||||
:param str snapshot_name: openstack snapshot id
|
||||
:param str volume_name: openstack volume id
|
||||
:param str provider_auth: space-separated triple
|
||||
'<auth method> <auth username> <auth password>'
|
||||
"""
|
||||
|
||||
sname = jcom.sname(snapshot_name, volume_name)
|
||||
ovname = jcom.vname(volume_name)
|
||||
self._clone_object(sname, sname, ovname,
|
||||
sparse=True,
|
||||
create_snapshot=False)
|
||||
try:
|
||||
self._ensure_target_volume(snapshot_name, sname, provider_auth,
|
||||
ro=True)
|
||||
except jexc.JDSSException as jerr:
|
||||
self._delete_volume(sname, cascade=True)
|
||||
raise jerr
|
||||
|
||||
def remove_export(self, volume_name):
|
||||
"""Remove iscsi target created to make volume attachable
|
||||
|
||||
:param str volume_name: openstack volume id
|
||||
"""
|
||||
vname = jcom.vname(volume_name)
|
||||
try:
|
||||
self._remove_target_volume(volume_name, vname)
|
||||
except jexc.JDSSException as jerr:
|
||||
LOG.warning(jerr)
|
||||
|
||||
def remove_export_snapshot(self, snapshot_name, volume_name):
|
||||
"""Remove tmp vol and iscsi target created to make snap attachable
|
||||
|
||||
:param str snapshot_name: openstack snapshot id
|
||||
:param str volume_name: openstack volume id
|
||||
"""
|
||||
|
||||
sname = jcom.sname(snapshot_name, volume_name)
|
||||
|
||||
try:
|
||||
self._remove_target_volume(snapshot_name, sname)
|
||||
except jexc.JDSSException as jerr:
|
||||
self._delete_volume(sname, cascade=True)
|
||||
raise jerr
|
||||
|
||||
self._delete_volume(sname, cascade=True)
|
||||
|
||||
def _delete_snapshot(self, vname, sname):
|
||||
"""Delete snapshot
|
||||
|
||||
This method will delete snapshot mount point and snapshot if possible
|
||||
|
||||
:param str vname: zvol name
|
||||
:param dict snap: snapshot info dictionary
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
try:
|
||||
self.ra.delete_snapshot(vname, sname, force_umount=True)
|
||||
except jexc.JDSSResourceIsBusyException:
|
||||
LOG.debug('Direct deletion of snapshot %s failed', vname)
|
||||
else:
|
||||
return
|
||||
|
||||
snap = self.ra.get_snapshot(vname, sname)
|
||||
|
||||
clones = jcom.snapshot_clones(snap)
|
||||
busy = False
|
||||
for cvname in clones:
|
||||
if jcom.is_snapshot(cvname):
|
||||
self._promote_newest_delete(cvname)
|
||||
if jcom.is_volume(cvname):
|
||||
LOG.debug('Will not delete snap %(snap)s,'
|
||||
'becasue it is used by %(vol)s',
|
||||
{'snap': sname,
|
||||
'vol': cvname})
|
||||
busy = True
|
||||
if busy:
|
||||
return
|
||||
try:
|
||||
self.ra.delete_snapshot(vname, sname, force_umount=True)
|
||||
except jexc.JDSSResourceIsBusyException:
|
||||
LOG.debug('Unable to delete snap %(snap)s because it is busy',
|
||||
{'snap': jcom.sname_from_snap(snap)})
|
||||
|
||||
def delete_snapshot(self, volume_name, snapshot_name):
|
||||
"""Delete snapshot of existing volume.
|
||||
|
||||
:param str volume_name: volume id
|
||||
:param str snapshot_name: snapshot id
|
||||
"""
|
||||
vname = jcom.vname(volume_name)
|
||||
sname = jcom.sname(snapshot_name, volume_name)
|
||||
|
||||
self._delete_snapshot(vname, sname)
|
||||
|
||||
def _ensure_target_volume(self, id, vid, provider_auth, ro=False):
|
||||
"""Checks if target configured properly and volume is attached to it
|
||||
|
||||
:param str id: id that would be used for target naming
|
||||
:param str vname: physical volume id
|
||||
:param str provider_auth: space-separated triple
|
||||
'<auth method> <auth username> <auth password>'
|
||||
"""
|
||||
LOG.debug("ensure volume %s assigned to a proper target", id)
|
||||
|
||||
target_name = self._get_target_name(id)
|
||||
|
||||
if not provider_auth:
|
||||
msg = _("volume %s is missing provider_auth") % jcom.idname(id)
|
||||
raise jexc.JDSSException(msg)
|
||||
|
||||
if not self.ra.is_target(target_name):
|
||||
|
||||
return self._create_target_volume(id, vid, provider_auth)
|
||||
|
||||
if not self.ra.is_target_lun(target_name, vid):
|
||||
self._attach_target_volume(target_name, vid)
|
||||
|
||||
(__, auth_username, auth_secret) = provider_auth.split()
|
||||
chap_cred = {"name": auth_username,
|
||||
"password": auth_secret}
|
||||
|
||||
try:
|
||||
users = self.ra.get_target_user(target_name)
|
||||
if len(users) == 1:
|
||||
if users[0]['name'] == chap_cred['name']:
|
||||
return
|
||||
self.ra.delete_target_user(
|
||||
target_name,
|
||||
users[0]['name'])
|
||||
for user in users:
|
||||
self.ra.delete_target_user(
|
||||
target_name,
|
||||
user['name'])
|
||||
self._set_target_credentials(target_name, chap_cred)
|
||||
|
||||
except jexc.JDSSException as jerr:
|
||||
self.ra.delete_target(target_name)
|
||||
raise exception.VolumeBackendAPIException(jerr)
|
||||
|
||||
def _get_target_name(self, volume_id):
|
||||
"""Return iSCSI target name to access volume."""
|
||||
return f'{self.jovian_target_prefix}{volume_id}'
|
||||
|
||||
def _get_iscsi_properties(self, volume_id, provider_auth, multipath=False):
|
||||
"""Return dict according to cinder/driver.py implementation.
|
||||
|
||||
:param volume_id: UUID of volume, might take snapshot UUID
|
||||
:param str provider_auth: space-separated triple
|
||||
'<auth method> <auth username> <auth password>'
|
||||
:return:
|
||||
"""
|
||||
tname = self._get_target_name(volume_id)
|
||||
iface_info = []
|
||||
if multipath:
|
||||
iface_info = self.get_active_ifaces()
|
||||
if not iface_info:
|
||||
raise exception.InvalidConfigurationValue(
|
||||
_('No available interfaces '
|
||||
'or config excludes them'))
|
||||
|
||||
iscsi_properties = {}
|
||||
|
||||
if multipath:
|
||||
iscsi_properties['target_iqns'] = []
|
||||
iscsi_properties['target_portals'] = []
|
||||
iscsi_properties['target_luns'] = []
|
||||
LOG.debug('tpaths %s.', iface_info)
|
||||
for iface in iface_info:
|
||||
iscsi_properties['target_iqns'].append(
|
||||
self._get_target_name(volume_id))
|
||||
iscsi_properties['target_portals'].append(
|
||||
iface +
|
||||
":" +
|
||||
str(self.jovian_iscsi_target_portal_port))
|
||||
iscsi_properties['target_luns'].append(0)
|
||||
else:
|
||||
iscsi_properties['target_iqn'] = tname
|
||||
iscsi_properties['target_portal'] = (
|
||||
self.ra.get_active_host() +
|
||||
":" +
|
||||
str(self.jovian_iscsi_target_portal_port))
|
||||
|
||||
iscsi_properties['target_discovered'] = False
|
||||
|
||||
if provider_auth:
|
||||
(auth_method, auth_username, auth_secret) = provider_auth.split()
|
||||
|
||||
iscsi_properties['auth_method'] = auth_method
|
||||
iscsi_properties['auth_username'] = auth_username
|
||||
iscsi_properties['auth_password'] = auth_secret
|
||||
|
||||
iscsi_properties['target_lun'] = 0
|
||||
return iscsi_properties
|
||||
|
||||
def _remove_target_volume(self, id, vid):
|
||||
"""_remove_target_volume
|
||||
|
||||
Ensure that volume is not attached to target and target do not exists.
|
||||
"""
|
||||
|
||||
target_name = self._get_target_name(id)
|
||||
LOG.debug("remove export")
|
||||
LOG.debug("detach volume:%(vol)s from target:%(targ)s.", {
|
||||
'vol': id,
|
||||
'targ': target_name})
|
||||
|
||||
try:
|
||||
self.ra.detach_target_vol(target_name, vid)
|
||||
except jexc.JDSSResourceNotFoundException as jerrrnf:
|
||||
LOG.debug('failed to remove resource %(t)s because of %(err)s', {
|
||||
't': target_name,
|
||||
'err': jerrrnf.args[0]})
|
||||
except jexc.JDSSException as jerr:
|
||||
LOG.warning('failed to Terminate_connection for target %(targ)s '
|
||||
'because of: %(err)s', {'targ': target_name,
|
||||
'err': jerr.args[0]})
|
||||
raise jerr
|
||||
|
||||
LOG.debug("delete target: %s", target_name)
|
||||
|
||||
try:
|
||||
self.ra.delete_target(target_name)
|
||||
except jexc.JDSSResourceNotFoundException as jerrrnf:
|
||||
LOG.debug('failed to remove resource %(target)s because '
|
||||
'of %(err)s',
|
||||
{'target': target_name, 'err': jerrrnf.args[0]})
|
||||
|
||||
except jexc.JDSSException as jerr:
|
||||
LOG.warning('Failed to Terminate_connection for target %(targ)s '
|
||||
'because of: %(err)s ',
|
||||
{'targ': target_name, 'err': jerr.args[0]})
|
||||
|
||||
raise jerr
|
||||
|
||||
def ensure_export(self, volume_id, provider_auth):
|
||||
|
||||
vname = jcom.vname(volume_id)
|
||||
|
||||
self._ensure_target_volume(volume_id, vname, provider_auth)
|
||||
|
||||
def initialize_connection(self, volume_id, provider_auth,
|
||||
snapshot_id=None,
|
||||
multipath=False):
|
||||
"""Ensures volume is ready for connection and return connection data
|
||||
|
||||
Ensures that particular volume is ready to be used over iscsi
|
||||
with credentials provided in provider_auth
|
||||
If snapshot name is provided method will ensure that connection
|
||||
leads to read only volume object associated with particular snapshot
|
||||
|
||||
:param str volume_id: Volume id string
|
||||
:param str provider_auth: space-separated triple
|
||||
'<auth method> <auth username> <auth password>'
|
||||
:param str snapshot_id: id of snapshot that should be connected
|
||||
:param bool multipath: specifies if multipath should be used
|
||||
"""
|
||||
|
||||
id_of_disk_to_attach = volume_id
|
||||
vid = jcom.vname(volume_id)
|
||||
if provider_auth is None:
|
||||
raise jexc.JDSSException(_("CHAP credentials missing"))
|
||||
if snapshot_id:
|
||||
id_of_disk_to_attach = snapshot_id
|
||||
vid = jcom.sname(snapshot_id, volume_id)
|
||||
iscsi_properties = self._get_iscsi_properties(id_of_disk_to_attach,
|
||||
provider_auth,
|
||||
multipath=multipath)
|
||||
if snapshot_id:
|
||||
self._ensure_target_volume(id_of_disk_to_attach,
|
||||
vid,
|
||||
provider_auth,
|
||||
mode='ro')
|
||||
else:
|
||||
self._ensure_target_volume(id_of_disk_to_attach,
|
||||
vid,
|
||||
provider_auth)
|
||||
|
||||
LOG.debug(
|
||||
"initialize_connection for physical disk %(vid)s with %(id)s",
|
||||
{'vid': vid, 'id': id_of_disk_to_attach})
|
||||
|
||||
return {
|
||||
'driver_volume_type': 'iscsi',
|
||||
'data': iscsi_properties,
|
||||
}
|
||||
|
||||
def _create_target_volume(self, id, vid, provider_auth):
|
||||
"""Creates target and attach volume to it
|
||||
|
||||
:param id: uuid of particular resource
|
||||
:param vid: physical volume id, might identify snapshot mount
|
||||
:param str provider_auth: space-separated triple
|
||||
'<auth method> <auth username> <auth password>'
|
||||
:return:
|
||||
"""
|
||||
LOG.debug("create target and attach volume %s to it", vid)
|
||||
|
||||
target_name = self._get_target_name(id)
|
||||
|
||||
(__, auth_username, auth_secret) = provider_auth.split()
|
||||
chap_cred = {"name": auth_username,
|
||||
"password": auth_secret}
|
||||
|
||||
# Create target
|
||||
self.ra.create_target(target_name, use_chap=True)
|
||||
|
||||
# Attach volume
|
||||
self._attach_target_volume(target_name, vid)
|
||||
|
||||
# Set credentials
|
||||
self._set_target_credentials(target_name, chap_cred)
|
||||
|
||||
def _attach_target_volume(self, target_name, vname):
|
||||
"""Attach target to volume and handles exceptions
|
||||
|
||||
Attempts to set attach volume to specific target.
|
||||
In case of failure will remove target.
|
||||
:param target_name: name of target
|
||||
:param use_chap: flag for using chap
|
||||
"""
|
||||
try:
|
||||
self.ra.attach_target_vol(target_name, vname)
|
||||
except jexc.JDSSException as jerr:
|
||||
msg = ('Unable to attach volume {volume} to target {target} '
|
||||
'because of {error}.')
|
||||
LOG.warning(msg, {"volume": vname,
|
||||
"target": target_name,
|
||||
"error": jerr})
|
||||
self.ra.delete_target(target_name)
|
||||
raise jerr
|
||||
|
||||
def _set_target_credentials(self, target_name, cred):
|
||||
"""Set CHAP configuration for target and handle exceptions
|
||||
|
||||
Attempts to set CHAP credentials for specific target.
|
||||
In case of failure will remove target.
|
||||
:param target_name: name of target
|
||||
:param cred: CHAP user name and password
|
||||
"""
|
||||
try:
|
||||
self.ra.create_target_user(target_name, cred)
|
||||
|
||||
except jexc.JDSSException as jerr:
|
||||
try:
|
||||
self.ra.delete_target(target_name)
|
||||
except jexc.JDSSException:
|
||||
pass
|
||||
|
||||
err_msg = (('Unable to create user %(user)s '
|
||||
'for target %(target)s '
|
||||
'because of %(error)s.') % {
|
||||
'target': target_name,
|
||||
'user': cred['name'],
|
||||
'error': jerr})
|
||||
|
||||
LOG.error(err_msg)
|
||||
raise jexc.JDSSException(_(err_msg))
|
@ -13,6 +13,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
|
||||
@ -30,13 +32,16 @@ def is_snapshot(name):
|
||||
|
||||
|
||||
def idname(name):
|
||||
"""Convert id into name"""
|
||||
"""Extract UUID from physical volume name"""
|
||||
|
||||
if name.startswith(('s_', 'v_', 't_')):
|
||||
if name.startswith(('v_', 't_')):
|
||||
return name[2:]
|
||||
|
||||
if name.startswith(('s_')):
|
||||
return sid_from_sname(name)
|
||||
|
||||
msg = _('Object name %s is incorrect') % name
|
||||
raise exception.VolumeBackendAPIException(message=msg)
|
||||
raise exception.VolumeDriverException(message=msg)
|
||||
|
||||
|
||||
def vname(name):
|
||||
@ -47,30 +52,46 @@ def vname(name):
|
||||
|
||||
if name.startswith('s_'):
|
||||
msg = _('Attempt to use snapshot %s as a volume') % name
|
||||
raise exception.VolumeBackendAPIException(message=msg)
|
||||
raise exception.VolumeDriverException(message=msg)
|
||||
|
||||
if name.startswith('t_'):
|
||||
msg = _('Attempt to use deleted object %s as a volume') % name
|
||||
raise exception.VolumeBackendAPIException(message=msg)
|
||||
raise exception.VolumeDriverException(message=msg)
|
||||
|
||||
return 'v_' + name
|
||||
return f'v_{name}'
|
||||
|
||||
|
||||
def sname(name):
|
||||
"""Convert id into snapshot name"""
|
||||
def sname_to_id(sname):
|
||||
|
||||
if name.startswith('s_'):
|
||||
return name
|
||||
spl = sname.split('_')
|
||||
|
||||
if name.startswith('v_'):
|
||||
msg = _('Attempt to use volume %s as a snapshot') % name
|
||||
raise exception.VolumeBackendAPIException(message=msg)
|
||||
if len(spl) == 2:
|
||||
return (spl[1], None)
|
||||
|
||||
if name.startswith('t_'):
|
||||
msg = _('Attempt to use deleted object %s as a snapshot') % name
|
||||
raise exception.VolumeBackendAPIException(message=msg)
|
||||
return (spl[1], spl[2])
|
||||
|
||||
return 's_' + name
|
||||
|
||||
def sid_from_sname(name):
|
||||
return sname_to_id(name)[0]
|
||||
|
||||
|
||||
def vid_from_sname(name):
|
||||
return sname_to_id(name)[1]
|
||||
|
||||
|
||||
def sname(sid, vid):
|
||||
"""Convert id into snapshot name
|
||||
|
||||
:param: vid: volume id
|
||||
:param: sid: snapshot id
|
||||
"""
|
||||
if vid is None:
|
||||
return 's_%(sid)s' % {'sid': sid}
|
||||
return 's_%(sid)s_%(vid)s' % {'sid': sid, 'vid': vid}
|
||||
|
||||
|
||||
def sname_from_snap(snapshot_struct):
|
||||
return snapshot_struct['name']
|
||||
|
||||
|
||||
def is_hidden(name):
|
||||
@ -83,22 +104,33 @@ def is_hidden(name):
|
||||
return False
|
||||
|
||||
|
||||
def origin_snapshot(origin_str):
|
||||
"""Extracts original physical snapshot name from origin record"""
|
||||
|
||||
return origin_str.split("@")[1]
|
||||
def origin_snapshot(vol):
|
||||
"""Extracts original physical snapshot name from volume dict"""
|
||||
if 'origin' in vol and vol['origin'] is not None:
|
||||
return vol['origin'].split("@")[1]
|
||||
return None
|
||||
|
||||
|
||||
def origin_volume(origin_str):
|
||||
"""Extracts original physical volume name from origin record"""
|
||||
def origin_volume(vol):
|
||||
"""Extracts original physical volume name from volume dict"""
|
||||
|
||||
return origin_str.split("@")[0].split("/")[1]
|
||||
if 'origin' in vol and vol['origin'] is not None:
|
||||
return vol['origin'].split("@")[0].split("/")[1]
|
||||
return None
|
||||
|
||||
|
||||
def full_name_volume(name):
|
||||
"""Get volume id from full_name"""
|
||||
def snapshot_clones(snap):
|
||||
"""Return list of clones associated with snapshot or return empty list"""
|
||||
out = []
|
||||
clones = []
|
||||
if 'clones' not in snap:
|
||||
return out
|
||||
else:
|
||||
clones = snap['clones'].split(',')
|
||||
|
||||
return name.split('/')[1]
|
||||
for clone in clones:
|
||||
out.append(clone.split('/')[1])
|
||||
return out
|
||||
|
||||
|
||||
def hidden(name):
|
||||
@ -110,3 +142,14 @@ def hidden(name):
|
||||
if name[:2] == 'v_' or name[:2] == 's_':
|
||||
return 't_' + name[2:]
|
||||
return 't_' + name
|
||||
|
||||
|
||||
def get_newest_snapshot_name(snapshots):
|
||||
newest_date = None
|
||||
sname = None
|
||||
for snap in snapshots:
|
||||
current_date = datetime.strptime(snap['creation'], "%Y-%m-%d %H:%M:%S")
|
||||
if newest_date is None or current_date > newest_date:
|
||||
newest_date = current_date
|
||||
sname = snap['name']
|
||||
return sname
|
||||
|
@ -19,7 +19,6 @@ import re
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.volume.drivers.open_e.jovian_common import exception as jexc
|
||||
from cinder.volume.drivers.open_e.jovian_common import rest_proxy
|
||||
@ -28,23 +27,36 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JovianRESTAPI(object):
|
||||
"""Jovian REST API proxy."""
|
||||
"""Jovian REST API"""
|
||||
|
||||
def __init__(self, config):
|
||||
|
||||
self.pool = config.get('jovian_pool', 'Pool-0')
|
||||
self.rproxy = rest_proxy.JovianRESTProxy(config)
|
||||
self.rproxy = rest_proxy.JovianDSSRESTProxy(config)
|
||||
|
||||
self.resource_dne_msg = (
|
||||
re.compile(r'^Zfs resource: .* not found in this collection\.$'))
|
||||
|
||||
self.resource_has_clones_msg = (
|
||||
re.compile(r'^In order to delete a zvol, you must delete all of '
|
||||
'its clones first.$'))
|
||||
self.resource_has_clones_class = (
|
||||
re.compile(r'^opene.storage.zfs.ZfsOeError$'))
|
||||
|
||||
self.resource_has_snapshots_msg = (
|
||||
re.compile(r"^cannot destroy '.*/.*': volume has children\nuse "
|
||||
"'-r' to destroy the following datasets:\n.*"))
|
||||
self.resource_has_snapshots_class = (
|
||||
re.compile(r'^zfslib.wrap.zfs.ZfsCmdError$'))
|
||||
|
||||
def _general_error(self, url, resp):
|
||||
reason = "Request %s failure" % url
|
||||
LOG.debug("error resp %s", resp)
|
||||
if 'error' in resp:
|
||||
|
||||
eclass = resp.get('class', 'Unknown')
|
||||
code = resp.get('code', 'Unknown')
|
||||
msg = resp.get('message', 'Unknown')
|
||||
eclass = resp['error'].get('class', 'Unknown')
|
||||
code = resp['error'].get('code', 'Unknown')
|
||||
msg = resp['error'].get('message', 'Unknown')
|
||||
|
||||
reason = _("Request to %(url)s failed with code: %(code)s "
|
||||
"of type:%(eclass)s reason:%(message)s")
|
||||
@ -119,8 +131,11 @@ class JovianRESTAPI(object):
|
||||
|
||||
:param volume_name:
|
||||
:param volume_size:
|
||||
:param sparse: thin or thick volume flag
|
||||
:param block_size: size of block
|
||||
:return:
|
||||
"""
|
||||
LOG.debug("create volume start")
|
||||
volume_size_str = str(volume_size)
|
||||
jbody = {
|
||||
'name': volume_name,
|
||||
@ -190,13 +205,11 @@ class JovianRESTAPI(object):
|
||||
return False
|
||||
|
||||
def get_lun(self, volume_name):
|
||||
"""get_lun.
|
||||
"""get_lun
|
||||
|
||||
GET /volumes/<volume_name>
|
||||
:param volume_name:
|
||||
:return:
|
||||
{
|
||||
"data":
|
||||
:param volume_name: zvol id
|
||||
:return: volume dict
|
||||
{
|
||||
"origin": null,
|
||||
"referenced": "65536",
|
||||
@ -230,9 +243,7 @@ class JovianRESTAPI(object):
|
||||
"name": "v1",
|
||||
"checksum": "on",
|
||||
"refreservation": "1076101120"
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
}
|
||||
"""
|
||||
req = '/volumes/' + volume_name
|
||||
|
||||
@ -252,8 +263,8 @@ class JovianRESTAPI(object):
|
||||
def modify_lun(self, volume_name, prop=None):
|
||||
"""Update volume properties
|
||||
|
||||
:prop volume_name: volume name
|
||||
:prop prop: dictionary
|
||||
:param volume_name: volume name
|
||||
:param prop: dictionary
|
||||
{
|
||||
<property>: <value>
|
||||
}
|
||||
@ -287,8 +298,8 @@ class JovianRESTAPI(object):
|
||||
def modify_property_lun(self, volume_name, prop=None):
|
||||
"""Change volume properties
|
||||
|
||||
:prop: volume_name: volume name
|
||||
:prop: prop: dictionary of volume properties in format
|
||||
:param volume_name: volume name
|
||||
:param prop: dictionary of volume properties in format
|
||||
{ "property_name": "<name of property>",
|
||||
"property_value":"<value of a property>"}
|
||||
"""
|
||||
@ -311,9 +322,33 @@ class JovianRESTAPI(object):
|
||||
reason=resp['error']['message'])
|
||||
raise jexc.JDSSRESTException(request=req, reason="unknown")
|
||||
|
||||
def promote(self, volume_name, snapshot_name, clone_name):
|
||||
"""promote volume
|
||||
|
||||
POST /volumes/<volumename>/snapshots/<snapshotname>/clones/
|
||||
<clonename>/promoteclone_promote
|
||||
:param volume_name: parent volume for the one that should be promoted
|
||||
:param snapshot_name: snapshot that is linking parent and clone
|
||||
:param clone_name: volume name that is going to be promoted
|
||||
:return:
|
||||
"""
|
||||
jbody = {}
|
||||
|
||||
req = '/volumes/' + volume_name + \
|
||||
'/snapshots/' + snapshot_name + \
|
||||
'/clones/' + clone_name + '/promote'
|
||||
|
||||
LOG.debug("promote clone %s", clone_name)
|
||||
resp = self.rproxy.pool_request('POST', req, json_data=jbody)
|
||||
|
||||
if resp["code"] == 200:
|
||||
LOG.debug("clone %s promoted", clone_name)
|
||||
return
|
||||
|
||||
self._general_error(req, resp)
|
||||
|
||||
def delete_lun(self, volume_name,
|
||||
recursively_children=False,
|
||||
recursively_dependents=False,
|
||||
force_umount=False):
|
||||
"""delete_volume.
|
||||
|
||||
@ -325,15 +360,12 @@ class JovianRESTAPI(object):
|
||||
if recursively_children:
|
||||
jbody['recursively_children'] = True
|
||||
|
||||
if recursively_dependents:
|
||||
jbody['recursively_dependents'] = True
|
||||
|
||||
if force_umount:
|
||||
jbody['force_umount'] = True
|
||||
|
||||
req = '/volumes/' + volume_name
|
||||
LOG.debug(("delete volume:%(vol)s "
|
||||
"recursively children:%(args)s"),
|
||||
"args:%(args)s"),
|
||||
{'vol': volume_name,
|
||||
'args': jbody})
|
||||
|
||||
@ -357,10 +389,20 @@ class JovianRESTAPI(object):
|
||||
|
||||
# Handle volume busy
|
||||
if resp["code"] == 500 and resp["error"]:
|
||||
if resp["error"]["errno"] == 1000:
|
||||
LOG.warning(
|
||||
"volume %s is busy", volume_name)
|
||||
raise exception.VolumeIsBusy(volume_name=volume_name)
|
||||
if 'message' in resp['error'] and \
|
||||
'class' in resp['error']:
|
||||
if self.resource_has_clones_msg.match(
|
||||
resp['error']['message']) and \
|
||||
self.resource_has_clones_class.match(
|
||||
resp['error']['class']):
|
||||
LOG.warning("volume %s is busy", volume_name)
|
||||
raise jexc.JDSSResourceIsBusyException(res=volume_name)
|
||||
if self.resource_has_snapshots_msg.match(
|
||||
resp['error']['message']) and \
|
||||
self.resource_has_snapshots_class.match(
|
||||
resp['error']['class']):
|
||||
LOG.warning("volume %s is busy", volume_name)
|
||||
raise jexc.JDSSResourceIsBusyException(res=volume_name)
|
||||
|
||||
raise jexc.JDSSRESTException('Failed to delete volume.')
|
||||
|
||||
@ -562,17 +604,27 @@ class JovianRESTAPI(object):
|
||||
|
||||
self._general_error(req, resp)
|
||||
|
||||
def attach_target_vol(self, target_name, lun_name, lun_id=0):
|
||||
def attach_target_vol(self, target_name, lun_name,
|
||||
lun_id=0,
|
||||
mode=None):
|
||||
"""attach_target_vol.
|
||||
|
||||
POST /san/iscsi/targets/<target_name>/luns
|
||||
:param target_name:
|
||||
:param lun_name:
|
||||
:param target_name: name of the target
|
||||
:param lun_name: phisical volume name to be attached
|
||||
:param lun_id: id that would be assigned to volume
|
||||
:param mode: one of "wt", "wb" or "ro"
|
||||
:return:
|
||||
"""
|
||||
req = '/san/iscsi/targets/%s/luns' % target_name
|
||||
|
||||
jbody = {"name": lun_name, "lun": lun_id}
|
||||
if mode is not None:
|
||||
if mode in ['wt', 'wb', 'ro']:
|
||||
jbody['mode'] = mode
|
||||
else:
|
||||
raise jexc.JDSSException(
|
||||
_("Incoret mode for target %s" % mode))
|
||||
LOG.debug("atach volume %(vol)s to target %(tar)s",
|
||||
{'vol': lun_name,
|
||||
'tar': target_name})
|
||||
@ -671,6 +723,9 @@ class JovianRESTAPI(object):
|
||||
if 'sparse' in options:
|
||||
jbody['sparse'] = options['sparse']
|
||||
|
||||
if 'ro' in options:
|
||||
jbody['ro'] = options['sparse']
|
||||
|
||||
LOG.debug("create volume %(vol)s from snapshot %(snap)s",
|
||||
{'vol': volume_name,
|
||||
'snap': snapshot_name})
|
||||
@ -722,11 +777,42 @@ class JovianRESTAPI(object):
|
||||
|
||||
self._general_error(req, resp)
|
||||
|
||||
def count_rollback_dependents(self, volume_name, snapshot_name):
|
||||
"""Count volumes and snapshots affected by rollback
|
||||
|
||||
GET /volumes/<volume_name>/snapshots/<snapshot_name>/rollback
|
||||
|
||||
:param str volume_name: volume that is going to be reverted
|
||||
:param str snapshot_name: snapshot of a volume above
|
||||
|
||||
:return: None
|
||||
"""
|
||||
req = ('/volumes/%(vol)s/snapshots/'
|
||||
'%(snap)s/rollback') % {'vol': volume_name,
|
||||
'snap': snapshot_name}
|
||||
|
||||
LOG.debug("get rollback count for volume %(vol)s to snapshot %(snap)s",
|
||||
{'vol': volume_name,
|
||||
'snap': snapshot_name})
|
||||
|
||||
resp = self.rproxy.pool_request('GET', req)
|
||||
|
||||
if not resp["error"] and resp["code"] == 200:
|
||||
return resp['data']
|
||||
|
||||
if resp["code"] == 500:
|
||||
if resp["error"]:
|
||||
if resp["error"]["errno"] == 1:
|
||||
raise jexc.JDSSResourceNotFoundException(
|
||||
res="%(vol)s@%(snap)s" % {'vol': volume_name,
|
||||
'snap': snapshot_name})
|
||||
|
||||
self._general_error(req, resp)
|
||||
|
||||
def delete_snapshot(self,
|
||||
volume_name,
|
||||
snapshot_name,
|
||||
recursively_children=False,
|
||||
recursively_dependents=False,
|
||||
force_umount=False):
|
||||
"""delete_snapshot.
|
||||
|
||||
@ -756,17 +842,10 @@ class JovianRESTAPI(object):
|
||||
if recursively_children:
|
||||
jbody['recursively_children'] = True
|
||||
|
||||
if recursively_dependents:
|
||||
jbody['recursively_dependents'] = True
|
||||
|
||||
if force_umount:
|
||||
jbody['force_umount'] = True
|
||||
|
||||
resp = dict()
|
||||
if len(jbody) > 0:
|
||||
resp = self.rproxy.pool_request('DELETE', req, json_data=jbody)
|
||||
else:
|
||||
resp = self.rproxy.pool_request('DELETE', req)
|
||||
resp = self.rproxy.pool_request('DELETE', req, json_data=jbody)
|
||||
|
||||
if resp["code"] in (200, 201, 204):
|
||||
LOG.debug("snapshot %s deleted", snapshot_name)
|
||||
@ -779,6 +858,25 @@ class JovianRESTAPI(object):
|
||||
snapshot=snapshot_name)
|
||||
self._general_error(req, resp)
|
||||
|
||||
def get_snapshot(self, volume_name, snapshot_name):
|
||||
|
||||
req = (('/volumes/%(vol)s/snapshots/%(snap)s') %
|
||||
{'vol': volume_name, 'snap': snapshot_name})
|
||||
|
||||
LOG.debug("get snapshots for volume %s ", volume_name)
|
||||
|
||||
resp = self.rproxy.pool_request('GET', req)
|
||||
|
||||
if not resp["error"] and resp["code"] == 200:
|
||||
return resp["data"]
|
||||
|
||||
if resp['code'] == 500:
|
||||
if 'message' in resp['error']:
|
||||
if self.resource_dne_msg.match(resp['error']['message']):
|
||||
raise jexc.JDSSResourceNotFoundException(volume_name)
|
||||
|
||||
self._general_error(req, resp)
|
||||
|
||||
def get_snapshots(self, volume_name):
|
||||
"""get_snapshots.
|
||||
|
||||
|
@ -31,8 +31,8 @@ from cinder.volume.drivers.open_e.jovian_common import exception as jexc
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JovianRESTProxy(object):
|
||||
"""Jovian REST API proxy."""
|
||||
class JovianDSSRESTProxy(object):
|
||||
"""Jovian REST API proxy"""
|
||||
|
||||
def __init__(self, config):
|
||||
""":param config: list of config values."""
|
||||
@ -62,7 +62,7 @@ class JovianRESTProxy(object):
|
||||
self.user = config.get('san_login', 'admin')
|
||||
self.password = config.get('san_password', 'admin')
|
||||
self.verify = config.get('driver_ssl_cert_verify', True)
|
||||
self.cert = config.get('driver_ssl_cert_path')
|
||||
self.cert = config.get('driver_ssl_cert_path', None)
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
@ -76,7 +76,7 @@ class JovianRESTProxy(object):
|
||||
session.headers.update({'Connection': 'keep-alive',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Basic'})
|
||||
session.hooks['response'] = [JovianRESTProxy._handle_500]
|
||||
session.hooks['response'] = [JovianDSSRESTProxy._handle_500]
|
||||
session.verify = self.verify
|
||||
if self.verify and self.cert:
|
||||
session.verify = self.cert
|
||||
@ -101,34 +101,36 @@ class JovianRESTProxy(object):
|
||||
"""Send request to the specific url.
|
||||
|
||||
:param request_method: GET, POST, DELETE
|
||||
:param url: where to send
|
||||
:param req: where to send
|
||||
:param json_data: data
|
||||
"""
|
||||
out = None
|
||||
for i in range(len(self.hosts)):
|
||||
try:
|
||||
addr = "%(base)s%(req)s" % {'base': self._get_base_url(),
|
||||
'req': req}
|
||||
LOG.debug("Sending %(t)s to %(addr)s",
|
||||
{'t': request_method, 'addr': addr})
|
||||
r = None
|
||||
if json_data:
|
||||
r = requests.Request(request_method,
|
||||
addr,
|
||||
data=json.dumps(json_data))
|
||||
else:
|
||||
r = requests.Request(request_method, addr)
|
||||
for i in range(3):
|
||||
for i in range(len(self.hosts)):
|
||||
try:
|
||||
addr = "%(base)s%(req)s" % {'base': self._get_base_url(),
|
||||
'req': req}
|
||||
LOG.debug("Sending %(t)s to %(addr)s data %(data)s",
|
||||
{'t': request_method,
|
||||
'addr': addr,
|
||||
'data': json_data})
|
||||
r = None
|
||||
if json_data:
|
||||
r = requests.Request(request_method,
|
||||
addr,
|
||||
data=json.dumps(json_data))
|
||||
else:
|
||||
r = requests.Request(request_method, addr)
|
||||
|
||||
pr = self.session.prepare_request(r)
|
||||
out = self._send(pr)
|
||||
except requests.exceptions.ConnectionError:
|
||||
self._next_host()
|
||||
continue
|
||||
break
|
||||
pr = self.session.prepare_request(r)
|
||||
out = self._send(pr)
|
||||
except requests.exceptions.ConnectionError:
|
||||
self._next_host()
|
||||
continue
|
||||
|
||||
LOG.debug("Geting %(data)s from %(t)s to %(addr)s",
|
||||
{'data': out, 't': request_method, 'addr': addr})
|
||||
return out
|
||||
LOG.debug("Geting %(data)s from %(t)s to %(addr)s",
|
||||
{'data': out, 't': request_method, 'addr': addr})
|
||||
return out
|
||||
|
||||
def pool_request(self, request_method, req, json_data=None):
|
||||
"""Send request to the specific url.
|
||||
@ -143,28 +145,25 @@ class JovianRESTProxy(object):
|
||||
{'t': request_method, 'addr': addr})
|
||||
return self.request(request_method, req, json_data=json_data)
|
||||
|
||||
@retry((requests.exceptions.ConnectionError,
|
||||
jexc.JDSSOSException),
|
||||
interval=2,
|
||||
backoff_rate=2,
|
||||
retries=7)
|
||||
@retry(json.JSONDecodeError,
|
||||
retries=3)
|
||||
def _send(self, pr):
|
||||
"""Send prepared request
|
||||
|
||||
:param pr: prepared request
|
||||
"""
|
||||
ret = dict()
|
||||
ret = {}
|
||||
|
||||
response_obj = self.session.send(pr)
|
||||
|
||||
ret['code'] = response_obj.status_code
|
||||
if ret['code'] == 204:
|
||||
ret["data"] = None
|
||||
return ret
|
||||
|
||||
try:
|
||||
data = json.loads(response_obj.text)
|
||||
ret["error"] = data.get("error")
|
||||
ret["data"] = data.get("data")
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
data = json.loads(response_obj.text)
|
||||
ret["error"] = data.get("error")
|
||||
ret["data"] = data.get("data")
|
||||
|
||||
return ret
|
||||
|
||||
|
@ -932,7 +932,7 @@ driver.netapp_ontap=complete
|
||||
driver.netapp_solidfire=complete
|
||||
driver.nexenta=missing
|
||||
driver.nfs=missing
|
||||
driver.opene_joviandss=complete
|
||||
driver.opene_joviandss=missing
|
||||
driver.prophetstor=missing
|
||||
driver.pure=complete
|
||||
driver.qnap=missing
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Open-E JovianDSS driver: revert-to-snapshot has been removed.
|
||||
other:
|
||||
- |
|
||||
Open-E JovianDSS driver: general rework of volume and snapshot creation and deletion.
|
||||
- |
|
||||
Open-E JovianDSS driver: network interfaces selection on JovianDSS storage has been reworked.
|
Loading…
x
Reference in New Issue
Block a user