Add support for force backup for Nimble Storage
Add capability to support backup of an in-use volume
DocImpact
Implements: blueprint nimble-add-force-backup
(cherry picked from commit 1f7f45e34a
)
Change-Id: I913570ac77951957f01b79b1851642ed99c38f51
This commit is contained in:
parent
b13dddac23
commit
0ea086e113
|
@ -17,10 +17,13 @@ import sys
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_utils import units
|
||||||
|
|
||||||
|
from cinder import context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.objects import volume as obj_volume
|
from cinder.objects import volume as obj_volume
|
||||||
from cinder import test
|
from cinder import test
|
||||||
|
from cinder.tests.unit import fake_constants as fake
|
||||||
from cinder.volume.drivers import nimble
|
from cinder.volume.drivers import nimble
|
||||||
from cinder.volume import volume_types
|
from cinder.volume import volume_types
|
||||||
|
|
||||||
|
@ -29,6 +32,8 @@ CONF = cfg.CONF
|
||||||
NIMBLE_CLIENT = 'cinder.volume.drivers.nimble.client'
|
NIMBLE_CLIENT = 'cinder.volume.drivers.nimble.client'
|
||||||
NIMBLE_URLLIB2 = 'six.moves.urllib.request'
|
NIMBLE_URLLIB2 = 'six.moves.urllib.request'
|
||||||
NIMBLE_RANDOM = 'cinder.volume.drivers.nimble.random'
|
NIMBLE_RANDOM = 'cinder.volume.drivers.nimble.random'
|
||||||
|
NIMBLE_ISCSI_DRIVER = 'cinder.volume.drivers.nimble.NimbleISCSIDriver'
|
||||||
|
DRIVER_VERSION = '2.0.3'
|
||||||
|
|
||||||
FAKE_ENUM_STRING = """
|
FAKE_ENUM_STRING = """
|
||||||
<simpleType name="SmErrorType">
|
<simpleType name="SmErrorType">
|
||||||
|
@ -117,11 +122,29 @@ FAKE_GET_VOL_INFO_RESPONSE = {
|
||||||
'agent-type': 1,
|
'agent-type': 1,
|
||||||
'online': False}}
|
'online': False}}
|
||||||
|
|
||||||
|
FAKE_GET_VOL_INFO_BACKUP_RESPONSE = {
|
||||||
|
'err-list': {'err-list': [{'code': 0}]},
|
||||||
|
'vol': {'target-name': 'iqn.test',
|
||||||
|
'name': 'test_vol',
|
||||||
|
'agent-type': 1,
|
||||||
|
'clone': 1,
|
||||||
|
'base-snap': 'test-backup-snap',
|
||||||
|
'parent-vol': 'volume-' + fake.volume2_id,
|
||||||
|
'online': False}}
|
||||||
|
|
||||||
|
FAKE_GET_SNAP_INFO_BACKUP_RESPONSE = {
|
||||||
|
'err-list': {'err-list': [{'code': 0}]},
|
||||||
|
'snap': {'description': "backup-vol-" + fake.volume2_id,
|
||||||
|
'name': 'test-backup-snap',
|
||||||
|
'vol': 'volume-' + fake.volume_id}
|
||||||
|
}
|
||||||
|
|
||||||
FAKE_GET_VOL_INFO_ONLINE = {
|
FAKE_GET_VOL_INFO_ONLINE = {
|
||||||
'err-list': {'err-list': [{'code': 0}]},
|
'err-list': {'err-list': [{'code': 0}]},
|
||||||
'vol': {'target-name': 'iqn.test',
|
'vol': {'target-name': 'iqn.test',
|
||||||
'name': 'test_vol',
|
'name': 'test_vol',
|
||||||
'agent-type': 1,
|
'agent-type': 1,
|
||||||
|
'size': int(1.75 * units.Gi),
|
||||||
'online': True}}
|
'online': True}}
|
||||||
|
|
||||||
FAKE_GET_VOL_INFO_ERROR = {
|
FAKE_GET_VOL_INFO_ERROR = {
|
||||||
|
@ -134,8 +157,7 @@ FAKE_GET_VOL_INFO_RESPONSE_WITH_SET_AGENT_TYPE = {
|
||||||
'name': 'test_vol',
|
'name': 'test_vol',
|
||||||
'agent-type': 5}}
|
'agent-type': 5}}
|
||||||
|
|
||||||
|
FAKE_TYPE_ID = fake.volume_type_id
|
||||||
FAKE_TYPE_ID = 12345
|
|
||||||
|
|
||||||
|
|
||||||
def create_configuration(username, password, ip_address,
|
def create_configuration(username, password, ip_address,
|
||||||
|
@ -475,6 +497,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
||||||
mock.Mock(return_value=[]))
|
mock.Mock(return_value=[]))
|
||||||
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
|
@mock.patch(NIMBLE_ISCSI_DRIVER + ".is_volume_backup_clone", mock.Mock(
|
||||||
|
return_value = ['', '']))
|
||||||
def test_delete_volume(self):
|
def test_delete_volume(self):
|
||||||
self.mock_client_service.service.onlineVol.return_value = \
|
self.mock_client_service.service.onlineVol.return_value = \
|
||||||
FAKE_GENERIC_POSITIVE_RESPONSE
|
FAKE_GENERIC_POSITIVE_RESPONSE
|
||||||
|
@ -492,6 +516,46 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
||||||
request={'name': 'testvolume', 'sid': 'a9b9aba7'})]
|
request={'name': 'testvolume', 'sid': 'a9b9aba7'})]
|
||||||
self.mock_client_service.assert_has_calls(expected_calls)
|
self.mock_client_service.assert_has_calls(expected_calls)
|
||||||
|
|
||||||
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
|
@mock.patch.object(obj_volume.VolumeList, 'get_all',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
|
@mock.patch(NIMBLE_ISCSI_DRIVER + ".is_volume_backup_clone", mock.Mock(
|
||||||
|
return_value=['test-backup-snap', 'volume-' + fake.volume_id]))
|
||||||
|
def test_delete_volume_with_backup(self):
|
||||||
|
self.mock_client_service.service.onlineVol.return_value = \
|
||||||
|
FAKE_GENERIC_POSITIVE_RESPONSE
|
||||||
|
self.mock_client_service.service.deleteVol.return_value = \
|
||||||
|
FAKE_GENERIC_POSITIVE_RESPONSE
|
||||||
|
self.mock_client_service.service.dissocProtPol.return_value = \
|
||||||
|
FAKE_GENERIC_POSITIVE_RESPONSE
|
||||||
|
self.mock_client_service.service.onlineSnap.return_value = \
|
||||||
|
FAKE_GENERIC_POSITIVE_RESPONSE
|
||||||
|
self.mock_client_service.service.deleteSnap.return_value = \
|
||||||
|
FAKE_GENERIC_POSITIVE_RESPONSE
|
||||||
|
|
||||||
|
self.driver.delete_volume({'name': 'testvolume'})
|
||||||
|
expected_calls = [mock.call.service.onlineVol(
|
||||||
|
request={
|
||||||
|
'online': False, 'name': 'testvolume', 'sid': 'a9b9aba7'}),
|
||||||
|
mock.call.service.dissocProtPol(
|
||||||
|
request={'vol-name': 'testvolume', 'sid': 'a9b9aba7'}),
|
||||||
|
mock.call.service.deleteVol(
|
||||||
|
request={'name': 'testvolume', 'sid': 'a9b9aba7'}),
|
||||||
|
mock.call.service.onlineSnap(
|
||||||
|
request={'vol': 'volume-' + fake.volume_id,
|
||||||
|
'name': 'test-backup-snap',
|
||||||
|
'online': False,
|
||||||
|
'sid': 'a9b9aba7'}),
|
||||||
|
mock.call.service.deleteSnap(
|
||||||
|
request={'vol': 'volume-' + fake.volume_id,
|
||||||
|
'name': 'test-backup-snap',
|
||||||
|
'sid': 'a9b9aba7'})]
|
||||||
|
|
||||||
|
self.mock_client_service.assert_has_calls(expected_calls)
|
||||||
|
|
||||||
@mock.patch(NIMBLE_URLLIB2)
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
@mock.patch(NIMBLE_CLIENT)
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
@mock.patch.object(obj_volume.VolumeList, 'get_all',
|
@mock.patch.object(obj_volume.VolumeList, 'get_all',
|
||||||
|
@ -517,15 +581,18 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
||||||
@mock.patch.object(obj_volume.VolumeList, 'get_all',
|
@mock.patch.object(obj_volume.VolumeList, 'get_all',
|
||||||
mock.Mock(return_value=[]))
|
mock.Mock(return_value=[]))
|
||||||
@mock.patch.object(volume_types, 'get_volume_type_extra_specs',
|
@mock.patch.object(volume_types, 'get_volume_type_extra_specs',
|
||||||
mock.Mock(type_id=FAKE_TYPE_ID, return_value={
|
mock.Mock(type_id=FAKE_TYPE_ID,
|
||||||
'nimble:perfpol-name': 'default',
|
return_value=
|
||||||
'nimble:encryption': 'yes',
|
{'nimble:perfpol-name': 'default',
|
||||||
'nimble:multi-initiator': 'false'}))
|
'nimble:encryption': 'yes',
|
||||||
|
'nimble:multi-initiator': 'false'}))
|
||||||
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*', False))
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*', False))
|
||||||
|
@mock.patch.object(obj_volume.VolumeList, 'get_all')
|
||||||
@mock.patch(NIMBLE_RANDOM)
|
@mock.patch(NIMBLE_RANDOM)
|
||||||
def test_create_cloned_volume(self, mock_random):
|
def test_create_cloned_volume(self, mock_random, mock_volume_list):
|
||||||
mock_random.sample.return_value = 'abcdefghijkl'
|
mock_random.sample.return_value = fake.volume_id
|
||||||
|
mock_volume_list.return_value = []
|
||||||
self.mock_client_service.service.snapVol.return_value = \
|
self.mock_client_service.service.snapVol.return_value = \
|
||||||
FAKE_GENERIC_POSITIVE_RESPONSE
|
FAKE_GENERIC_POSITIVE_RESPONSE
|
||||||
self.mock_client_service.service.cloneVol.return_value = \
|
self.mock_client_service.service.cloneVol.return_value = \
|
||||||
|
@ -534,25 +601,36 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
||||||
FAKE_GET_VOL_INFO_RESPONSE
|
FAKE_GET_VOL_INFO_RESPONSE
|
||||||
self.mock_client_service.service.getNetConfig.return_value = \
|
self.mock_client_service.service.getNetConfig.return_value = \
|
||||||
FAKE_POSITIVE_NETCONFIG_RESPONSE
|
FAKE_POSITIVE_NETCONFIG_RESPONSE
|
||||||
|
|
||||||
|
volume = obj_volume.Volume(context.get_admin_context(),
|
||||||
|
id=fake.volume_id,
|
||||||
|
size=5.0,
|
||||||
|
_name_id=None,
|
||||||
|
display_name='',
|
||||||
|
volume_type_id=FAKE_TYPE_ID
|
||||||
|
)
|
||||||
|
src_volume = obj_volume.Volume(context.get_admin_context(),
|
||||||
|
id=fake.volume2_id,
|
||||||
|
_name_id=None,
|
||||||
|
size=5.0)
|
||||||
self.assertEqual({
|
self.assertEqual({
|
||||||
'provider_location': '172.18.108.21:3260 iqn.test 0',
|
'provider_location': '172.18.108.21:3260 iqn.test 0',
|
||||||
'provider_auth': None},
|
'provider_auth': None},
|
||||||
self.driver.create_cloned_volume({'name': 'volume',
|
self.driver.create_cloned_volume(volume, src_volume))
|
||||||
'size': 5,
|
|
||||||
'volume_type_id': FAKE_TYPE_ID},
|
|
||||||
{'name': 'testvolume',
|
|
||||||
'size': 5}))
|
|
||||||
expected_calls = [mock.call.service.snapVol(
|
expected_calls = [mock.call.service.snapVol(
|
||||||
request={
|
request={
|
||||||
'vol': 'testvolume',
|
'vol': "volume-" + fake.volume2_id,
|
||||||
'snapAttr': {'name': 'openstack-clone-volume-abcdefghijkl',
|
'snapAttr': {'name': 'openstack-clone-volume-' +
|
||||||
|
fake.volume_id +
|
||||||
|
"-" + fake.volume_id,
|
||||||
'description': ''},
|
'description': ''},
|
||||||
'sid': 'a9b9aba7'}),
|
'sid': 'a9b9aba7'}),
|
||||||
mock.call.service.cloneVol(
|
mock.call.service.cloneVol(
|
||||||
request={
|
request={
|
||||||
'snap-name': 'openstack-clone-volume-abcdefghijkl',
|
'snap-name': 'openstack-clone-volume-' + fake.volume_id +
|
||||||
|
"-" + fake.volume_id,
|
||||||
'attr': {'snap-quota': sys.maxsize,
|
'attr': {'snap-quota': sys.maxsize,
|
||||||
'name': 'volume',
|
'name': 'volume-' + fake.volume_id,
|
||||||
'quota': 5368709120,
|
'quota': 5368709120,
|
||||||
'reserve': 5368709120,
|
'reserve': 5368709120,
|
||||||
'online': True,
|
'online': True,
|
||||||
|
@ -561,7 +639,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
||||||
'multi-initiator': 'false',
|
'multi-initiator': 'false',
|
||||||
'perfpol-name': 'default',
|
'perfpol-name': 'default',
|
||||||
'agent-type': 5},
|
'agent-type': 5},
|
||||||
'name': 'testvolume',
|
'name': 'volume-' + fake.volume2_id,
|
||||||
'sid': 'a9b9aba7'})]
|
'sid': 'a9b9aba7'})]
|
||||||
self.mock_client_service.assert_has_calls(expected_calls)
|
self.mock_client_service.assert_has_calls(expected_calls)
|
||||||
|
|
||||||
|
@ -618,6 +696,21 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
||||||
{'name': 'volume-abcdef'},
|
{'name': 'volume-abcdef'},
|
||||||
{'source-name': 'test-vol'})
|
{'source-name': 'test-vol'})
|
||||||
|
|
||||||
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
|
@mock.patch.object(obj_volume.VolumeList, 'get_all',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
|
def test_manage_volume_get_size(self):
|
||||||
|
self.mock_client_service.service.getNetConfig.return_value = (
|
||||||
|
FAKE_POSITIVE_NETCONFIG_RESPONSE)
|
||||||
|
self.mock_client_service.service.getVolInfo.return_value = (
|
||||||
|
FAKE_GET_VOL_INFO_ONLINE)
|
||||||
|
size = self.driver.manage_existing_get_size(
|
||||||
|
{'name': 'volume-abcdef'}, {'source-name': 'test-vol'})
|
||||||
|
self.assertEqual(1, size)
|
||||||
|
|
||||||
@mock.patch(NIMBLE_URLLIB2)
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
@mock.patch(NIMBLE_CLIENT)
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
@mock.patch.object(obj_volume.VolumeList, 'get_all',
|
@mock.patch.object(obj_volume.VolumeList, 'get_all',
|
||||||
|
@ -726,7 +819,7 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
||||||
def test_get_volume_stats(self):
|
def test_get_volume_stats(self):
|
||||||
self.mock_client_service.service.getGroupConfig.return_value = \
|
self.mock_client_service.service.getGroupConfig.return_value = \
|
||||||
FAKE_POSITIVE_GROUP_CONFIG_RESPONSE
|
FAKE_POSITIVE_GROUP_CONFIG_RESPONSE
|
||||||
expected_res = {'driver_version': '2.0.2',
|
expected_res = {'driver_version': DRIVER_VERSION,
|
||||||
'vendor_name': 'Nimble',
|
'vendor_name': 'Nimble',
|
||||||
'volume_backend_name': 'NIMBLE',
|
'volume_backend_name': 'NIMBLE',
|
||||||
'storage_protocol': 'iSCSI',
|
'storage_protocol': 'iSCSI',
|
||||||
|
@ -739,6 +832,33 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
|
||||||
expected_res,
|
expected_res,
|
||||||
self.driver.get_volume_stats(refresh=True))
|
self.driver.get_volume_stats(refresh=True))
|
||||||
|
|
||||||
|
@mock.patch(NIMBLE_URLLIB2)
|
||||||
|
@mock.patch(NIMBLE_CLIENT)
|
||||||
|
@mock.patch.object(obj_volume.VolumeList, 'get_all',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
|
@NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
|
||||||
|
'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
|
||||||
|
def test_is_volume_backup_clone(self):
|
||||||
|
self.mock_client_service.service.getVolInfo.return_value = \
|
||||||
|
FAKE_GET_VOL_INFO_BACKUP_RESPONSE
|
||||||
|
self.mock_client_service.service.getSnapInfo.return_value = \
|
||||||
|
FAKE_GET_SNAP_INFO_BACKUP_RESPONSE
|
||||||
|
volume = obj_volume.Volume(context.get_admin_context(),
|
||||||
|
id=fake.volume_id,
|
||||||
|
_name_id=None)
|
||||||
|
self.assertEqual(("test-backup-snap", "volume-" + fake.volume_id),
|
||||||
|
self.driver.is_volume_backup_clone(volume))
|
||||||
|
expected_calls = [
|
||||||
|
mock.call.service.getVolInfo(
|
||||||
|
request={'name': 'volume-' + fake.volume_id,
|
||||||
|
'sid': 'a9b9aba7'}),
|
||||||
|
mock.call.service.getSnapInfo(
|
||||||
|
request={'sid': 'a9b9aba7',
|
||||||
|
'vol': 'volume-' + fake.volume2_id,
|
||||||
|
'name': 'test-backup-snap'})
|
||||||
|
]
|
||||||
|
self.mock_client_service.assert_has_calls(expected_calls)
|
||||||
|
|
||||||
|
|
||||||
class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase):
|
class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase):
|
||||||
|
|
||||||
|
@ -862,7 +982,7 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
|
||||||
expected_res = {
|
expected_res = {
|
||||||
'driver_volume_type': 'iscsi',
|
'driver_volume_type': 'iscsi',
|
||||||
'data': {
|
'data': {
|
||||||
'target_lun': '14',
|
'target_lun': 14,
|
||||||
'volume_id': 12,
|
'volume_id': 12,
|
||||||
'target_iqn': '13',
|
'target_iqn': '13',
|
||||||
'target_discovered': False,
|
'target_discovered': False,
|
||||||
|
@ -905,7 +1025,7 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
|
||||||
expected_res = {
|
expected_res = {
|
||||||
'driver_volume_type': 'iscsi',
|
'driver_volume_type': 'iscsi',
|
||||||
'data': {
|
'data': {
|
||||||
'target_lun': '14',
|
'target_lun': 14,
|
||||||
'volume_id': 12,
|
'volume_id': 12,
|
||||||
'target_iqn': '13',
|
'target_iqn': '13',
|
||||||
'target_discovered': False,
|
'target_discovered': False,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import functools
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import six
|
import six
|
||||||
|
import ssl
|
||||||
import string
|
import string
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ from cinder.volume.drivers.san import san
|
||||||
from cinder.volume import volume_types
|
from cinder.volume import volume_types
|
||||||
|
|
||||||
|
|
||||||
DRIVER_VERSION = '2.0.2'
|
DRIVER_VERSION = '2.0.3'
|
||||||
AES_256_XTS_CIPHER = 2
|
AES_256_XTS_CIPHER = 2
|
||||||
DEFAULT_CIPHER = 3
|
DEFAULT_CIPHER = 3
|
||||||
EXTRA_SPEC_ENCRYPTION = 'nimble:encryption'
|
EXTRA_SPEC_ENCRYPTION = 'nimble:encryption'
|
||||||
|
@ -61,6 +62,12 @@ SM_SUBNET_MGMT_PLUS_DATA = 4
|
||||||
LUN_ID = '0'
|
LUN_ID = '0'
|
||||||
WARN_LEVEL = 0.8
|
WARN_LEVEL = 0.8
|
||||||
|
|
||||||
|
# Work around for ubuntu_openssl_bug_965371. Python soap client suds
|
||||||
|
# throws the error ssl-certificate-verify-failed-error, workaround to disable
|
||||||
|
# ssl check for now
|
||||||
|
if hasattr(ssl, '_create_unverified_context'):
|
||||||
|
ssl._create_default_https_context = ssl._create_unverified_context
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
nimble_opts = [
|
nimble_opts = [
|
||||||
|
@ -97,6 +104,7 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
|
||||||
Added Manage/Unmanage volume support
|
Added Manage/Unmanage volume support
|
||||||
2.0.1 - Added multi-initiator support through extra-specs
|
2.0.1 - Added multi-initiator support through extra-specs
|
||||||
2.0.2 - Fixed supporting extra specs while cloning vols
|
2.0.2 - Fixed supporting extra specs while cloning vols
|
||||||
|
2.0.3 - Support for Force Backup
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = DRIVER_VERSION
|
VERSION = DRIVER_VERSION
|
||||||
|
@ -211,14 +219,57 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
|
||||||
self.configuration.nimble_pool_name, reserve)
|
self.configuration.nimble_pool_name, reserve)
|
||||||
return self._get_model_info(volume['name'])
|
return self._get_model_info(volume['name'])
|
||||||
|
|
||||||
|
def is_volume_backup_clone(self, volume):
|
||||||
|
"""Check if the volume is created through cinder-backup workflow.
|
||||||
|
|
||||||
|
:param volume: reference to volume from delete_volume()
|
||||||
|
"""
|
||||||
|
vol_info = self.APIExecutor.get_vol_info(volume.name)
|
||||||
|
if vol_info['clone'] and vol_info['base-snap'] and vol_info[
|
||||||
|
'parent-vol']:
|
||||||
|
LOG.debug("Nimble base-snap exists for volume :%s", volume['name'])
|
||||||
|
volume_name_prefix = volume.name.replace(volume.id, "")
|
||||||
|
LOG.debug("volume_name_prefix : %s", volume_name_prefix)
|
||||||
|
snap_info = self.APIExecutor.get_snap_info(vol_info['base-snap'],
|
||||||
|
vol_info['parent-vol'])
|
||||||
|
if snap_info['description'] and "backup-vol-" in snap_info[
|
||||||
|
'description']:
|
||||||
|
parent_vol_id = vol_info['parent-vol'
|
||||||
|
].replace(volume_name_prefix, "")
|
||||||
|
if "backup-vol-" + parent_vol_id in snap_info['description']:
|
||||||
|
LOG.info(_LI("nimble backup-snapshot exists name: %s"),
|
||||||
|
snap_info['name'])
|
||||||
|
return snap_info['name'], snap_info['vol']
|
||||||
|
return "", ""
|
||||||
|
|
||||||
def delete_volume(self, volume):
|
def delete_volume(self, volume):
|
||||||
"""Delete the specified volume."""
|
"""Delete the specified volume."""
|
||||||
|
snap_name, vol_name = self.is_volume_backup_clone(volume)
|
||||||
self.APIExecutor.online_vol(volume['name'], False,
|
self.APIExecutor.online_vol(volume['name'], False,
|
||||||
ignore_list=['SM-enoent'])
|
ignore_list=['SM-enoent'])
|
||||||
self.APIExecutor.dissociate_volcoll(volume['name'],
|
self.APIExecutor.dissociate_volcoll(volume['name'],
|
||||||
ignore_list=['SM-enoent'])
|
ignore_list=['SM-enoent'])
|
||||||
self.APIExecutor.delete_vol(volume['name'], ignore_list=['SM-enoent'])
|
self.APIExecutor.delete_vol(volume['name'], ignore_list=['SM-enoent'])
|
||||||
|
|
||||||
|
# Nimble backend does not delete the snapshot from the parent volume
|
||||||
|
# if there is a dependent clone. So the deletes need to be in reverse
|
||||||
|
# order i.e.
|
||||||
|
# 1. First delete the clone volume used for backup
|
||||||
|
# 2. Delete the base snapshot used for clone from the parent volume.
|
||||||
|
# This is only done for the force backup clone operation as it is
|
||||||
|
# a temporary operation in which we are certain that the snapshot does
|
||||||
|
# not need to be preserved after the backup is completed.
|
||||||
|
|
||||||
|
if snap_name and vol_name:
|
||||||
|
self.APIExecutor.online_snap(vol_name,
|
||||||
|
False,
|
||||||
|
snap_name,
|
||||||
|
ignore_list=['SM-ealready',
|
||||||
|
'SM-enoent'])
|
||||||
|
self.APIExecutor.delete_snap(vol_name,
|
||||||
|
snap_name,
|
||||||
|
ignore_list=['SM-enoent'])
|
||||||
|
|
||||||
def _generate_random_string(self, length):
|
def _generate_random_string(self, length):
|
||||||
"""Generates random_string."""
|
"""Generates random_string."""
|
||||||
char_set = string.ascii_lowercase
|
char_set = string.ascii_lowercase
|
||||||
|
@ -252,7 +303,7 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
|
||||||
snapshot = {'volume_name': src_vref['name'],
|
snapshot = {'volume_name': src_vref['name'],
|
||||||
'name': snapshot_name,
|
'name': snapshot_name,
|
||||||
'volume_size': src_vref['size'],
|
'volume_size': src_vref['size'],
|
||||||
'display_name': '',
|
'display_name': volume.display_name,
|
||||||
'display_description': ''}
|
'display_description': ''}
|
||||||
self.APIExecutor.snap_vol(snapshot)
|
self.APIExecutor.snap_vol(snapshot)
|
||||||
self._clone_volume_from_snapshot(volume, snapshot)
|
self._clone_volume_from_snapshot(volume, snapshot)
|
||||||
|
@ -479,7 +530,7 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
|
||||||
properties['target_discovered'] = False # whether discovery was used
|
properties['target_discovered'] = False # whether discovery was used
|
||||||
properties['target_portal'] = iscsi_portal
|
properties['target_portal'] = iscsi_portal
|
||||||
properties['target_iqn'] = iqn
|
properties['target_iqn'] = iqn
|
||||||
properties['target_lun'] = lun_num
|
properties['target_lun'] = int(lun_num)
|
||||||
properties['volume_id'] = volume['id'] # used by xen currently
|
properties['volume_id'] = volume['id'] # used by xen currently
|
||||||
return {
|
return {
|
||||||
'driver_volume_type': 'iscsi',
|
'driver_volume_type': 'iscsi',
|
||||||
|
@ -750,6 +801,31 @@ class NimbleAPIExecutor(object):
|
||||||
vol_name)
|
vol_name)
|
||||||
return response['vol']
|
return response['vol']
|
||||||
|
|
||||||
|
@_connection_checker
|
||||||
|
@_response_checker
|
||||||
|
def _execute_get_snap_info(self, snap_name, vol_name):
|
||||||
|
LOG.info(_LI('Getting snapshot information for %(vol_name)s '
|
||||||
|
'%(snap_name)s'), {'vol_name': vol_name,
|
||||||
|
'snap_name': snap_name})
|
||||||
|
return self.client.service.getSnapInfo(request={'sid': self.sid,
|
||||||
|
'vol': vol_name,
|
||||||
|
'name': snap_name})
|
||||||
|
|
||||||
|
def get_snap_info(self, snap_name, vol_name):
|
||||||
|
"""Get snapshot information.
|
||||||
|
|
||||||
|
:param snap_name: snapshot name
|
||||||
|
:param vol_name: volume name
|
||||||
|
:return: response object
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = self._execute_get_snap_info(snap_name, vol_name)
|
||||||
|
LOG.info(_LI('Successfully got snapshot information for snapshot '
|
||||||
|
'%(snap)s and %(volume)s'),
|
||||||
|
{'snap': snap_name,
|
||||||
|
'volume': vol_name})
|
||||||
|
return response['snap']
|
||||||
|
|
||||||
@_connection_checker
|
@_connection_checker
|
||||||
@_response_checker
|
@_response_checker
|
||||||
def online_vol(self, vol_name, online_flag, *args, **kwargs):
|
def online_vol(self, vol_name, online_flag, *args, **kwargs):
|
||||||
|
@ -795,10 +871,12 @@ class NimbleAPIExecutor(object):
|
||||||
volume_name = snapshot['volume_name']
|
volume_name = snapshot['volume_name']
|
||||||
snap_name = snapshot['name']
|
snap_name = snapshot['name']
|
||||||
# Set snapshot description
|
# Set snapshot description
|
||||||
display_list = [getattr(snapshot, 'display_name', ''),
|
display_list = [getattr(snapshot, 'display_name', snapshot[
|
||||||
|
'display_name']),
|
||||||
getattr(snapshot, 'display_description', '')]
|
getattr(snapshot, 'display_description', '')]
|
||||||
snap_description = ':'.join(filter(None, display_list))
|
snap_description = ':'.join(filter(None, display_list))
|
||||||
# Limit to 254 characters
|
# Limit to 254 characters
|
||||||
|
LOG.debug("snap_description %s", snap_description)
|
||||||
snap_description = snap_description[:254]
|
snap_description = snap_description[:254]
|
||||||
LOG.info(_LI('Creating snapshot for volume_name=%(vol)s'
|
LOG.info(_LI('Creating snapshot for volume_name=%(vol)s'
|
||||||
' snap_name=%(name)s snap_description=%(desc)s'),
|
' snap_name=%(name)s snap_description=%(desc)s'),
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- Support Force backup of in-use cinder volumes
|
||||||
|
for Nimble Storage.
|
Loading…
Reference in New Issue