NetApp: Implement CGs for ONTAP Drivers

This patch includes the driver changes necessary for NetApp 7mode and
CDOT backends to support all consistency group and cgsnapshot
functionality.

Co-Authored-By: Alex Meade <mr.alex.meade@gmail.com>
Co-Authored-By: Chuck Fouts <fchuck@netapp.com>

DocImpact
Implements: blueprint cinder-consistency-groups
Change-Id: Ia74c634835958876d97daf6766f2ef110b33ddc4
This commit is contained in:
Mike Rooney 2015-12-17 17:01:05 -05:00
parent 8ed2d59395
commit 3b2d17a5db
20 changed files with 897 additions and 21 deletions

View File

@ -1,4 +1,5 @@
# Copyright (c) - 2015, Tom Barron. All rights reserved.
# Copyright (c) - 2016 Mike Rooney. 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
@ -17,6 +18,7 @@ from lxml import etree
import mock
from six.moves import urllib
from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
import cinder.volume.drivers.netapp.dataontap.client.api as netapp_api
@ -204,6 +206,80 @@ VOLUME_LIST_INFO_RESPONSE = etree.XML("""
</results>
""")
SNAPSHOT_INFO_FOR_PRESENT_NOT_BUSY_SNAPSHOT_CMODE = etree.XML("""
<results status="passed">
<attributes-list>
<snapshot-info>
<name>%(snapshot_name)s</name>
<busy>False</busy>
<volume>%(vol_name)s</volume>
</snapshot-info>
</attributes-list>
<num-records>1</num-records>
</results>
""" % {
'snapshot_name': fake.SNAPSHOT['name'],
'vol_name': fake.SNAPSHOT['volume_id'],
})
SNAPSHOT_INFO_FOR_PRESENT_BUSY_SNAPSHOT_CMODE = etree.XML("""
<results status="passed">
<attributes-list>
<snapshot-info>
<name>%(snapshot_name)s</name>
<busy>True</busy>
<volume>%(vol_name)s</volume>
</snapshot-info>
</attributes-list>
<num-records>1</num-records>
</results>
""" % {
'snapshot_name': fake.SNAPSHOT['name'],
'vol_name': fake.SNAPSHOT['volume_id'],
})
SNAPSHOT_INFO_FOR_PRESENT_NOT_BUSY_SNAPSHOT_7MODE = etree.XML("""
<results status="passed">
<snapshots>
<snapshot-info>
<name>%(snapshot_name)s</name>
<busy>False</busy>
<volume>%(vol_name)s</volume>
</snapshot-info>
</snapshots>
</results>
""" % {
'snapshot_name': fake.SNAPSHOT['name'],
'vol_name': fake.SNAPSHOT['volume_id'],
})
SNAPSHOT_INFO_FOR_PRESENT_BUSY_SNAPSHOT_7MODE = etree.XML("""
<results status="passed">
<snapshots>
<snapshot-info>
<name>%(snapshot_name)s</name>
<busy>True</busy>
<volume>%(vol_name)s</volume>
</snapshot-info>
</snapshots>
</results>
""" % {
'snapshot_name': fake.SNAPSHOT['name'],
'vol_name': fake.SNAPSHOT['volume_id'],
})
SNAPSHOT_NOT_PRESENT_7MODE = etree.XML("""
<results status="passed">
<snapshots>
<snapshot-info>
<name>NOT_THE_RIGHT_SNAPSHOT</name>
<busy>false</busy>
<volume>%(vol_name)s</volume>
</snapshot-info>
</snapshots>
</results>
""" % {'vol_name': fake.SNAPSHOT['volume_id']})
NO_RECORDS_RESPONSE = etree.XML("""
<results status="passed">
<num-records>0</num-records>

View File

@ -1,5 +1,6 @@
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
# Copyright (c) 2016 Mike Rooney. All rights reserved.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -21,6 +22,7 @@ import mock
import paramiko
import six
from cinder import exception
from cinder import ssh_utils
from cinder import test
from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
@ -768,3 +770,42 @@ class NetApp7modeClientTestCase(test.TestCase):
self.client.ssh_client.execute_command.assert_has_calls(
[mock.call(ssh, command)]
)
def test_get_snapshot_if_snapshot_present_not_busy(self):
expected_vol_name = fake.SNAPSHOT['volume_id']
expected_snapshot_name = fake.SNAPSHOT['name']
response = netapp_api.NaElement(
fake_client.SNAPSHOT_INFO_FOR_PRESENT_NOT_BUSY_SNAPSHOT_7MODE)
self.connection.invoke_successfully.return_value = response
snapshot = self.client.get_snapshot(expected_vol_name,
expected_snapshot_name)
self.assertEqual(expected_vol_name, snapshot['volume'])
self.assertEqual(expected_snapshot_name, snapshot['name'])
self.assertEqual(set([]), snapshot['owners'])
self.assertFalse(snapshot['busy'])
def test_get_snapshot_if_snapshot_present_busy(self):
expected_vol_name = fake.SNAPSHOT['volume_id']
expected_snapshot_name = fake.SNAPSHOT['name']
response = netapp_api.NaElement(
fake_client.SNAPSHOT_INFO_FOR_PRESENT_BUSY_SNAPSHOT_7MODE)
self.connection.invoke_successfully.return_value = response
snapshot = self.client.get_snapshot(expected_vol_name,
expected_snapshot_name)
self.assertEqual(expected_vol_name, snapshot['volume'])
self.assertEqual(expected_snapshot_name, snapshot['name'])
self.assertEqual(set([]), snapshot['owners'])
self.assertTrue(snapshot['busy'])
def test_get_snapshot_if_snapshot_not_present(self):
expected_vol_name = fake.SNAPSHOT['volume_id']
expected_snapshot_name = fake.SNAPSHOT['name']
response = netapp_api.NaElement(fake_client.SNAPSHOT_NOT_PRESENT_7MODE)
self.connection.invoke_successfully.return_value = response
self.assertRaises(exception.SnapshotNotFound, self.client.get_snapshot,
expected_vol_name, expected_snapshot_name)

View File

@ -1,5 +1,6 @@
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -507,3 +508,54 @@ class NetAppBaseClientTestCase(test.TestCase):
self.client.get_performance_counter_info,
'wafl',
'invalid')
def test_delete_snapshot(self):
api_args = {
'volume': fake.SNAPSHOT['volume_id'],
'snapshot': fake.SNAPSHOT['name'],
}
self.mock_object(self.client, 'send_request')
self.client.delete_snapshot(api_args['volume'],
api_args['snapshot'])
asserted_api_args = {
'volume': api_args['volume'],
'snapshot': api_args['snapshot'],
}
self.client.send_request.assert_called_once_with('snapshot-delete',
asserted_api_args)
def test_create_cg_snapshot(self):
self.mock_object(self.client, '_start_cg_snapshot', mock.Mock(
return_value=fake.CONSISTENCY_GROUP_ID))
self.mock_object(self.client, '_commit_cg_snapshot')
self.client.create_cg_snapshot([fake.CG_VOLUME_NAME],
fake.CG_SNAPSHOT_NAME)
self.client._commit_cg_snapshot.assert_called_once_with(
fake.CONSISTENCY_GROUP_ID)
def test_start_cg_snapshot(self):
snapshot_init = {
'snapshot': fake.CG_SNAPSHOT_NAME,
'timeout': 'relaxed',
'volumes': [{'volume-name': fake.CG_VOLUME_NAME}],
}
self.mock_object(self.client, 'send_request')
self.client._start_cg_snapshot([fake.CG_VOLUME_NAME],
snapshot_init['snapshot'])
self.client.send_request.assert_called_once_with('cg-start',
snapshot_init)
def test_commit_cg_snapshot(self):
snapshot_commit = {'cg-id': fake.CG_VOLUME_ID}
self.mock_object(self.client, 'send_request')
self.client._commit_cg_snapshot(snapshot_commit['cg-id'])
self.client.send_request.assert_called_once_with(
'cg-commit', {'cg-id': snapshot_commit['cg-id']})

View File

@ -1,6 +1,7 @@
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -1246,3 +1247,42 @@ class NetAppCmodeClientTestCase(test.TestCase):
fake_client.INITIATOR_IQN,
fake_client.USER_NAME,
fake_client.PASSWORD)
def test_get_snapshot_if_snapshot_present_not_busy(self):
expected_vol_name = fake.SNAPSHOT['volume_id']
expected_snapshot_name = fake.SNAPSHOT['name']
response = netapp_api.NaElement(
fake_client.SNAPSHOT_INFO_FOR_PRESENT_NOT_BUSY_SNAPSHOT_CMODE)
self.mock_send_request.return_value = response
snapshot = self.client.get_snapshot(expected_vol_name,
expected_snapshot_name)
self.assertEqual(expected_vol_name, snapshot['volume'])
self.assertEqual(expected_snapshot_name, snapshot['name'])
self.assertEqual(set([]), snapshot['owners'])
self.assertFalse(snapshot['busy'])
def test_get_snapshot_if_snapshot_present_busy(self):
expected_vol_name = fake.SNAPSHOT['volume_id']
expected_snapshot_name = fake.SNAPSHOT['name']
response = netapp_api.NaElement(
fake_client.SNAPSHOT_INFO_FOR_PRESENT_BUSY_SNAPSHOT_CMODE)
self.mock_send_request.return_value = response
snapshot = self.client.get_snapshot(expected_vol_name,
expected_snapshot_name)
self.assertEqual(expected_vol_name, snapshot['volume'])
self.assertEqual(expected_snapshot_name, snapshot['name'])
self.assertEqual(set([]), snapshot['owners'])
self.assertTrue(snapshot['busy'])
def test_get_snapshot_if_snapshot_not_present(self):
expected_vol_name = fake.SNAPSHOT['volume_id']
expected_snapshot_name = fake.SNAPSHOT['name']
response = netapp_api.NaElement(fake_client.NO_RECORDS_RESPONSE)
self.mock_send_request.return_value = response
self.assertRaises(exception.SnapshotNotFound, self.client.get_snapshot,
expected_vol_name, expected_snapshot_name)

View File

@ -1,5 +1,6 @@
# Copyright (c) - 2014, Clinton Knight. All rights reserved.
# Copyright (c) - 2015, Tom Barron. All rights reserved.
# Copyright (c) - 2016 Chuck Fouts. 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
@ -216,6 +217,7 @@ SNAPSHOT = {
'name': SNAPSHOT_NAME,
'volume_size': SIZE,
'volume_id': 'fake_volume_id',
'busy': False,
}
VOLUME_REF = {'name': 'fake_vref_name', 'size': 42}
@ -223,6 +225,7 @@ VOLUME_REF = {'name': 'fake_vref_name', 'size': 42}
FAKE_CMODE_POOLS = [
{
'QoS_support': True,
'consistencygroup_support': True,
'free_capacity_gb': 3.72,
'netapp_compression': u'true',
'netapp_dedup': u'true',
@ -338,6 +341,7 @@ FAKE_7MODE_VOL1 = [netapp_api.NaElement(
FAKE_7MODE_POOLS = [
{
'pool_name': 'open123',
'consistencygroup_support': True,
'QoS_support': False,
'reserved_percentage': 0,
'total_capacity_gb': 0.0,
@ -352,6 +356,74 @@ FAKE_7MODE_POOLS = [
}
]
CG_VOLUME_NAME = 'fake_cg_volume'
CG_GROUP_NAME = 'fake_consistency_group'
SOURCE_CG_VOLUME_NAME = 'fake_source_cg_volume'
CG_VOLUME_ID = 'fake_cg_volume_id'
CG_VOLUME_SIZE = 100
SOURCE_CG_VOLUME_ID = 'fake_source_cg_volume_id'
CONSISTENCY_GROUP_NAME = 'fake_cg'
SOURCE_CONSISTENCY_GROUP_ID = 'fake_source_cg_id'
CONSISTENCY_GROUP_ID = 'fake_cg_id'
CG_SNAPSHOT_ID = 'fake_cg_snapshot_id'
CG_SNAPSHOT_NAME = 'snapshot-' + CG_SNAPSHOT_ID
CG_VOLUME_SNAPSHOT_ID = 'fake_cg_volume_snapshot_id'
CG_LUN_METADATA = {
'OsType': None,
'Path': '/vol/aggr1/fake_cg_volume',
'SpaceReserved': 'true',
'Qtree': None,
'Volume': POOL_NAME,
}
SOURCE_CG_VOLUME = {
'name': SOURCE_CG_VOLUME_NAME,
'size': CG_VOLUME_SIZE,
'id': SOURCE_CG_VOLUME_ID,
'host': 'hostname@backend#cdot',
'consistencygroup_id': None,
'status': 'fake_status',
}
CG_VOLUME = {
'name': CG_VOLUME_NAME,
'size': 100,
'id': CG_VOLUME_ID,
'host': 'hostname@backend#cdot',
'consistencygroup_id': CONSISTENCY_GROUP_ID,
'status': 'fake_status',
}
SOURCE_CONSISTENCY_GROUP = {
'id': SOURCE_CONSISTENCY_GROUP_ID,
'status': 'fake_status',
}
CONSISTENCY_GROUP = {
'id': CONSISTENCY_GROUP_ID,
'status': 'fake_status',
'name': CG_GROUP_NAME,
}
CG_SNAPSHOT = {
'id': CG_SNAPSHOT_ID,
'name': CG_SNAPSHOT_NAME,
'volume_size': CG_VOLUME_SIZE,
'consistencygroup_id': CONSISTENCY_GROUP_ID,
'status': 'fake_status',
'volume_id': 'fake_source_volume_id',
}
CG_VOLUME_SNAPSHOT = {
'name': CG_SNAPSHOT_NAME,
'volume_size': CG_VOLUME_SIZE,
'cgsnapshot_id': CG_SNAPSHOT_ID,
'id': CG_VOLUME_SNAPSHOT_ID,
'status': 'fake_status',
'volume_id': CG_VOLUME_ID,
}
class test_volume(object):
pass

View File

@ -2,6 +2,7 @@
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2015 Goutham Pacha Ravi. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -302,7 +303,8 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
self.library.zapi_client.clone_lun.assert_called_once_with(
'/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN',
'newFakeLUN', 'false', block_count=0, dest_block=0, src_block=0)
'newFakeLUN', 'false', block_count=0, dest_block=0,
source_snapshot=None, src_block=0)
def test_clone_lun_blocks(self):
"""Test for when clone lun is passed block information."""
@ -322,7 +324,8 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
self.library.zapi_client.clone_lun.assert_called_once_with(
'/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN',
'newFakeLUN', 'false', block_count=block_count,
dest_block=dest_block, src_block=src_block)
dest_block=dest_block, src_block=src_block,
source_snapshot=None)
def test_clone_lun_no_space_reservation(self):
"""Test for when space_reservation is not passed."""
@ -337,7 +340,8 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
self.library.zapi_client.clone_lun.assert_called_once_with(
'/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN',
'newFakeLUN', 'false', block_count=0, dest_block=0, src_block=0)
'newFakeLUN', 'false', block_count=0, dest_block=0, src_block=0,
source_snapshot=None)
def test_clone_lun_qos_supplied(self):
"""Test for qos supplied in clone lun invocation."""
@ -526,6 +530,7 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
expected = [{
'pool_name': 'vol1',
'consistencygroup_support': True,
'QoS_support': False,
'thin_provisioning_support': not thick,
'thick_provisioning_support': thick,

View File

@ -4,6 +4,7 @@
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2015 Goutham Pacha Ravi. All rights reserved.
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
# Copyright (c) 2016 Chuck Fouts. All rights reserved.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -1047,3 +1048,155 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
self.assertEqual('CHAP', data['discovery_auth_method'])
self.assertEqual('user1', data['discovery_auth_username'])
self.assertEqual('pass1', data['discovery_auth_password'])
def test_create_cgsnapshot(self):
snapshot = fake.CG_SNAPSHOT
snapshot['volume'] = fake.CG_VOLUME
mock_extract_host = self.mock_object(
volume_utils, 'extract_host',
mock.Mock(return_value=fake.POOL_NAME))
mock_clone_lun = self.mock_object(self.library, '_clone_lun')
mock_busy = self.mock_object(self.library, '_handle_busy_snapshot')
self.library.create_cgsnapshot(fake.CG_SNAPSHOT, [snapshot])
mock_extract_host.assert_called_once_with(fake.CG_VOLUME['host'],
level='pool')
self.zapi_client.create_cg_snapshot.assert_called_once_with(
set([fake.POOL_NAME]), fake.CG_SNAPSHOT_ID)
mock_clone_lun.assert_called_once_with(
fake.CG_VOLUME_NAME, fake.CG_SNAPSHOT_NAME,
source_snapshot=fake.CG_SNAPSHOT_ID)
mock_busy.assert_called_once_with(fake.POOL_NAME, fake.CG_SNAPSHOT_ID)
def test_delete_cgsnapshot(self):
mock_delete_snapshot = self.mock_object(
self.library, '_delete_lun')
self.library.delete_cgsnapshot(fake.CG_SNAPSHOT, [fake.CG_SNAPSHOT])
mock_delete_snapshot.assert_called_once_with(fake.CG_SNAPSHOT['name'])
def test_delete_cgsnapshot_not_found(self):
self.mock_object(block_base, 'LOG')
self.mock_object(self.library, '_get_lun_attr',
mock.Mock(return_value=None))
self.library.delete_cgsnapshot(fake.CG_SNAPSHOT, [fake.CG_SNAPSHOT])
self.assertEqual(0, block_base.LOG.error.call_count)
self.assertEqual(1, block_base.LOG.warning.call_count)
self.assertEqual(0, block_base.LOG.info.call_count)
def test_create_volume_with_cg(self):
volume_size_in_bytes = int(fake.CG_VOLUME_SIZE) * units.Gi
self._create_volume_test_helper()
self.library.create_volume(fake.CG_VOLUME)
self.library._create_lun.assert_called_once_with(
fake.POOL_NAME, fake.CG_VOLUME_NAME, volume_size_in_bytes,
fake.CG_LUN_METADATA, None)
self.assertEqual(0, self.library.
_mark_qos_policy_group_for_deletion.call_count)
self.assertEqual(0, block_base.LOG.error.call_count)
def _create_volume_test_helper(self):
self.mock_object(na_utils, 'get_volume_extra_specs')
self.mock_object(na_utils, 'log_extra_spec_warnings')
self.mock_object(block_base, 'LOG')
self.mock_object(volume_utils, 'extract_host',
mock.Mock(return_value=fake.POOL_NAME))
self.mock_object(self.library, '_setup_qos_for_volume',
mock.Mock(return_value=None))
self.mock_object(self.library, '_create_lun')
self.mock_object(self.library, '_create_lun_handle')
self.mock_object(self.library, '_add_lun_to_table')
self.mock_object(self.library, '_mark_qos_policy_group_for_deletion')
def test_create_consistency_group(self):
model_update = self.library.create_consistencygroup(
fake.CONSISTENCY_GROUP)
self.assertEqual('available', model_update['status'])
def test_delete_consistencygroup_volume_delete_failure(self):
self.mock_object(block_base, 'LOG')
self.mock_object(self.library, '_delete_lun',
mock.Mock(side_effect=Exception))
model_update, volumes = self.library.delete_consistencygroup(
fake.CONSISTENCY_GROUP, [fake.CG_VOLUME])
self.assertEqual('deleted', model_update['status'])
self.assertEqual('error_deleting', volumes[0]['status'])
self.assertEqual(1, block_base.LOG.exception.call_count)
def test_delete_consistencygroup_not_found(self):
self.mock_object(block_base, 'LOG')
self.mock_object(self.library, '_get_lun_attr',
mock.Mock(return_value=None))
model_update, volumes = self.library.delete_consistencygroup(
fake.CONSISTENCY_GROUP, [fake.CG_VOLUME])
self.assertEqual(0, block_base.LOG.error.call_count)
self.assertEqual(1, block_base.LOG.warning.call_count)
self.assertEqual(0, block_base.LOG.info.call_count)
self.assertEqual('deleted', model_update['status'])
self.assertEqual('deleted', volumes[0]['status'])
def test_create_consistencygroup_from_src_cg_snapshot(self):
mock_clone_source_to_destination = self.mock_object(
self.library, '_clone_source_to_destination')
self.library.create_consistencygroup_from_src(
fake.CONSISTENCY_GROUP, [fake.VOLUME], cgsnapshot=fake.CG_SNAPSHOT,
snapshots=[fake.CG_VOLUME_SNAPSHOT])
clone_source_to_destination_args = {
'name': fake.CG_SNAPSHOT['name'],
'size': fake.CG_SNAPSHOT['volume_size'],
}
mock_clone_source_to_destination.assert_called_once_with(
clone_source_to_destination_args, fake.VOLUME)
def test_create_consistencygroup_from_src_cg(self):
class fake_lun_name(object):
pass
fake_lun_name_instance = fake_lun_name()
fake_lun_name_instance.name = fake.SOURCE_CG_VOLUME['name']
self.mock_object(self.library, '_get_lun_from_table', mock.Mock(
return_value=fake_lun_name_instance)
)
mock_clone_source_to_destination = self.mock_object(
self.library, '_clone_source_to_destination')
self.library.create_consistencygroup_from_src(
fake.CONSISTENCY_GROUP, [fake.VOLUME],
source_cg=fake.SOURCE_CONSISTENCY_GROUP,
source_vols=[fake.SOURCE_CG_VOLUME])
clone_source_to_destination_args = {
'name': fake.SOURCE_CG_VOLUME['name'],
'size': fake.SOURCE_CG_VOLUME['size'],
}
mock_clone_source_to_destination.assert_called_once_with(
clone_source_to_destination_args, fake.VOLUME)
def test_handle_busy_snapshot(self):
self.mock_object(block_base, 'LOG')
mock_get_snapshot = self.mock_object(
self.zapi_client, 'get_snapshot',
mock.Mock(return_value=fake.SNAPSHOT)
)
self.library._handle_busy_snapshot(fake.FLEXVOL, fake.SNAPSHOT_NAME)
self.assertEqual(1, block_base.LOG.info.call_count)
mock_get_snapshot.assert_called_once_with(fake.FLEXVOL,
fake.SNAPSHOT_NAME)

View File

@ -1,6 +1,7 @@
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -199,7 +200,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.library.zapi_client.clone_lun.assert_called_once_with(
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'false', block_count=0,
dest_block=0, src_block=0, qos_policy_group_name=None)
dest_block=0, src_block=0, qos_policy_group_name=None,
source_snapshot=None)
def test_clone_lun_blocks(self):
"""Test for when clone lun is passed block information."""
@ -224,7 +226,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.library.zapi_client.clone_lun.assert_called_once_with(
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'false',
block_count=block_count, dest_block=dest_block,
src_block=src_block, qos_policy_group_name=None)
src_block=src_block, qos_policy_group_name=None,
source_snapshot=None)
def test_clone_lun_no_space_reservation(self):
"""Test for when space_reservation is not passed."""
@ -244,7 +247,8 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
self.library.zapi_client.clone_lun.assert_called_once_with(
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'false', block_count=0,
dest_block=0, src_block=0, qos_policy_group_name=None)
dest_block=0, src_block=0, qos_policy_group_name=None,
source_snapshot=None)
def test_get_fc_target_wwpns(self):
ports = [fake.FC_FORMATTED_TARGET_WWPNS[0],
@ -372,6 +376,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
goodness_function='goodness')
expected = [{'pool_name': 'vola',
'consistencygroup_support': True,
'netapp_unmirrored': 'true',
'QoS_support': True,
'thin_provisioning_support': not thick,

View File

@ -7,6 +7,7 @@
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2015 Goutham Pacha Ravi. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -193,7 +194,7 @@ class NetAppBlockStorage7modeLibrary(block_base.NetAppBlockStorageLibrary):
def _clone_lun(self, name, new_name, space_reserved=None,
qos_policy_group_name=None, src_block=0, dest_block=0,
block_count=0):
block_count=0, source_snapshot=None):
"""Clone LUN with the given handle to the new name."""
if not space_reserved:
space_reserved = self.lun_space_reservation
@ -210,7 +211,8 @@ class NetAppBlockStorage7modeLibrary(block_base.NetAppBlockStorageLibrary):
self.zapi_client.clone_lun(path, clone_path, name, new_name,
space_reserved, src_block=src_block,
dest_block=dest_block,
block_count=block_count)
block_count=block_count,
source_snapshot=source_snapshot)
self.vol_refresh_voluntary = True
luns = self.zapi_client.get_lun_by_args(path=clone_path)
@ -322,6 +324,8 @@ class NetAppBlockStorage7modeLibrary(block_base.NetAppBlockStorageLibrary):
pool['filter_function'] = filter_function
pool['goodness_function'] = goodness_function
pool['consistencygroup_support'] = True
pools.append(pool)
return pools

View File

@ -6,8 +6,9 @@
# Copyright (c) 2014 Andrew Kerr. All rights reserved.
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2015 Chuck Fouts. All rights reserved.
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
# Copyright (c) 2016 Chuck Fouts. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -452,7 +453,7 @@ class NetAppBlockStorageLibrary(object):
def _clone_lun(self, name, new_name, space_reserved='true',
qos_policy_group_name=None, src_block=0, dest_block=0,
block_count=0):
block_count=0, source_snapshot=None):
"""Clone LUN with the given name to the new name."""
raise NotImplementedError()
@ -983,3 +984,143 @@ class NetAppBlockStorageLibrary(object):
init_targ_map[initiator] = target_wwpns
return target_wwpns, init_targ_map, num_paths
def create_consistencygroup(self, group):
"""Driver entry point for creating a consistency group.
ONTAP does not maintain an actual CG construct. As a result, no
communication to the backend is necessary for consistency group
creation.
:return: Hard-coded model update for consistency group model.
"""
model_update = {'status': 'available'}
return model_update
def delete_consistencygroup(self, group, volumes):
"""Driver entry point for deleting a consistency group.
:return: Updated consistency group model and list of volume models
for the volumes that were deleted.
"""
model_update = {'status': 'deleted'}
volumes_model_update = []
for volume in volumes:
try:
self._delete_lun(volume['name'])
volumes_model_update.append(
{'id': volume['id'], 'status': 'deleted'})
except Exception:
volumes_model_update.append(
{'id': volume['id'], 'status': 'error_deleting'})
LOG.exception(_LE("Volume %(vol) in the consistency group "
"could not be deleted."), {'vol': volume})
return model_update, volumes_model_update
def update_consistencygroup(self, group, add_volumes=None,
remove_volumes=None):
"""Driver entry point for updating a consistency group.
Since no actual CG construct is ever created in ONTAP, it is not
necessary to update any metadata on the backend. Since this is a NO-OP,
there is guaranteed to be no change in any of the volumes' statuses.
"""
return None, None, None
def create_cgsnapshot(self, cgsnapshot, snapshots):
"""Creates a Cinder cgsnapshot object.
The Cinder cgsnapshot object is created by making use of an
ephemeral ONTAP CG in order to provide write-order consistency for a
set of flexvol snapshots. First, a list of the flexvols backing the
given Cinder CG must be gathered. An ONTAP cg-snapshot of these
flexvols will create a snapshot copy of all the Cinder volumes in the
CG group. For each Cinder volume in the CG, it is then necessary to
clone its backing LUN from the ONTAP cg-snapshot. The naming convention
used for the clones is what indicates the clone's role as a Cinder
snapshot and its inclusion in a Cinder CG. The ONTAP CG-snapshot of
the flexvols is no longer required after having cloned the LUNs
backing the Cinder volumes in the Cinder CG.
:return: An implicit update for cgsnapshot and snapshots models that
is interpreted by the manager to set their models to available.
"""
flexvols = set()
for snapshot in snapshots:
flexvols.add(volume_utils.extract_host(snapshot['volume']['host'],
level='pool'))
self.zapi_client.create_cg_snapshot(flexvols, cgsnapshot['id'])
for snapshot in snapshots:
self._clone_lun(snapshot['volume']['name'], snapshot['name'],
source_snapshot=cgsnapshot['id'])
for flexvol in flexvols:
self._handle_busy_snapshot(flexvol, cgsnapshot['id'])
self.zapi_client.delete_snapshot(flexvol, cgsnapshot['id'])
return None, None
@utils.retry(exception.SnapshotIsBusy)
def _handle_busy_snapshot(self, flexvol, snapshot_name):
"""Checks for and handles a busy snapshot.
If a snapshot is not busy, take no action. If a snapshot is busy for
reasons other than a clone dependency, raise immediately. Otherwise,
since we always start a clone split operation after cloning a share,
wait up to a minute for a clone dependency to clear before giving up.
"""
snapshot = self.zapi_client.get_snapshot(flexvol, snapshot_name)
if not snapshot['busy']:
LOG.info(_LI("Backing consistency group snapshot %s "
"available for deletion"), snapshot_name)
return
else:
LOG.debug('Snapshot %(snap)s for vol %(vol)s is busy, waiting '
'for volume clone dependency to clear.',
{'snap': snapshot_name, 'vol': flexvol})
raise exception.SnapshotIsBusy(snapshot_name=snapshot_name)
def delete_cgsnapshot(self, cgsnapshot, snapshots):
"""Delete LUNs backing each snapshot in the cgsnapshot.
:return: An implicit update for snapshots models that is interpreted
by the manager to set their models to deleted.
"""
for snapshot in snapshots:
self._delete_lun(snapshot['name'])
LOG.debug("Snapshot %s deletion successful", snapshot['name'])
return None, None
def create_consistencygroup_from_src(self, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
"""Creates a CG from a either a cgsnapshot or group of cinder vols.
:return: An implicit update for the volumes model that is
interpreted by the manager as a successful operation.
"""
LOG.debug("VOLUMES %s ", [dict(vol) for vol in volumes])
if cgsnapshot:
vols = zip(volumes, snapshots)
for volume, snapshot in vols:
source = {
'name': snapshot['name'],
'size': snapshot['volume_size'],
}
self._clone_source_to_destination(source, volume)
else:
vols = zip(volumes, source_vols)
for volume, old_src_vref in vols:
src_lun = self._get_lun_from_table(old_src_vref['name'])
source = {'name': src_lun.name, 'size': old_src_vref['size']}
self._clone_source_to_destination(source, volume)
return None, None

View File

@ -7,6 +7,7 @@
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2015 Goutham Pacha Ravi. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -128,16 +129,19 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
def _clone_lun(self, name, new_name, space_reserved=None,
qos_policy_group_name=None, src_block=0, dest_block=0,
block_count=0):
block_count=0, source_snapshot=None):
"""Clone LUN with the given handle to the new name."""
if not space_reserved:
space_reserved = self.lun_space_reservation
metadata = self._get_lun_attr(name, 'metadata')
volume = metadata['Volume']
self.zapi_client.clone_lun(volume, name, new_name, space_reserved,
qos_policy_group_name=qos_policy_group_name,
src_block=src_block, dest_block=dest_block,
block_count=block_count)
block_count=block_count,
source_snapshot=source_snapshot)
LOG.debug("Cloned LUN with new name %s", new_name)
lun = self.zapi_client.get_lun_by_args(vserver=self.vserver,
path='/vol/%s/%s'
@ -267,6 +271,8 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
pool['filter_function'] = filter_function
pool['goodness_function'] = goodness_function
pool['consistencygroup_support'] = True
pools.append(pool)
return pools

View File

@ -40,6 +40,7 @@ LOG = logging.getLogger(__name__)
EAPINOTFOUND = '13005'
ESIS_CLONE_NOT_LICENSED = '14956'
ESNAPSHOTNOTALLOWED = '13023'
class NaServer(object):

View File

@ -1,5 +1,6 @@
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -27,6 +28,7 @@ from cinder import utils
from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_base
from oslo_utils import strutils
LOG = logging.getLogger(__name__)
@ -228,7 +230,7 @@ class Client(client_base.Client):
def clone_lun(self, path, clone_path, name, new_name,
space_reserved='true', src_block=0,
dest_block=0, block_count=0):
dest_block=0, block_count=0, source_snapshot=None):
# zAPI can only handle 2^24 blocks per range
bc_limit = 2 ** 24 # 8GB
# zAPI can only handle 32 block ranges per call
@ -244,10 +246,16 @@ class Client(client_base.Client):
zbc -= z_limit
else:
block_count = zbc
clone_start = netapp_api.NaElement.create_node_with_children(
'clone-start', **{'source-path': path,
zapi_args = {
'source-path': path,
'destination-path': clone_path,
'no-snap': 'true'})
'no-snap': 'true',
}
if source_snapshot:
zapi_args['snapshot-name'] = source_snapshot
clone_start = netapp_api.NaElement.create_node_with_children(
'clone-start', **zapi_args)
if block_count > 0:
block_ranges = netapp_api.NaElement("block-ranges")
# zAPI can only handle 2^24 block ranges
@ -536,3 +544,42 @@ class Client(client_base.Client):
system_info = result.get_child_by_name('system-info')
system_name = system_info.get_child_content('system-name')
return system_name
def get_snapshot(self, volume_name, snapshot_name):
"""Gets a single snapshot."""
snapshot_list_info = netapp_api.NaElement('snapshot-list-info')
snapshot_list_info.add_new_child('volume', volume_name)
result = self.connection.invoke_successfully(snapshot_list_info,
enable_tunneling=True)
snapshots = result.get_child_by_name('snapshots')
if not snapshots:
msg = _('No snapshots could be found on volume %s.')
raise exception.VolumeBackendAPIException(data=msg % volume_name)
snapshot_list = snapshots.get_children()
snapshot = None
for s in snapshot_list:
if (snapshot_name == s.get_child_content('name')) and (snapshot
is None):
snapshot = {
'name': s.get_child_content('name'),
'volume': s.get_child_content('volume'),
'busy': strutils.bool_from_string(
s.get_child_content('busy')),
}
snapshot_owners_list = s.get_child_by_name(
'snapshot-owners-list') or netapp_api.NaElement('none')
snapshot_owners = set([snapshot_owner.get_child_content(
'owner') for snapshot_owner in
snapshot_owners_list.get_children()])
snapshot['owners'] = snapshot_owners
elif (snapshot_name == s.get_child_content('name')) and (
snapshot is not None):
msg = _('Could not find unique snapshot %(snap)s on '
'volume %(vol)s.')
msg_args = {'snap': snapshot_name, 'vol': volume_name}
raise exception.VolumeBackendAPIException(data=msg % msg_args)
if not snapshot:
raise exception.SnapshotNotFound(snapshot_id=snapshot_name)
return snapshot

View File

@ -1,6 +1,7 @@
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -396,3 +397,37 @@ class Client(object):
LOG.warning(_LW("Failed to invoke ems. Message : %s"), e)
finally:
requester.last_ems = timeutils.utcnow()
def delete_snapshot(self, volume_name, snapshot_name):
"""Deletes a volume snapshot."""
api_args = {'volume': volume_name, 'snapshot': snapshot_name}
self.send_request('snapshot-delete', api_args)
def create_cg_snapshot(self, volume_names, snapshot_name):
"""Creates a consistency group snapshot out of one or more flexvols.
ONTAP requires an invocation of cg-start to first fence off the
flexvols to be included in the snapshot. If cg-start returns
success, a cg-commit must be executed to finalized the snapshot and
unfence the flexvols.
"""
cg_id = self._start_cg_snapshot(volume_names, snapshot_name)
if not cg_id:
msg = _('Could not start consistency group snapshot %s.')
raise exception.VolumeBackendAPIException(data=msg % snapshot_name)
self._commit_cg_snapshot(cg_id)
def _start_cg_snapshot(self, volume_names, snapshot_name):
snapshot_init = {
'snapshot': snapshot_name,
'timeout': 'relaxed',
'volumes': [
{'volume-name': volume_name} for volume_name in volume_names
],
}
result = self.send_request('cg-start', snapshot_init)
return result.get_child_content('cg-id')
def _commit_cg_snapshot(self, cg_id):
snapshot_commit = {'cg-id': cg_id}
self.send_request('cg-commit', snapshot_commit)

View File

@ -1,6 +1,7 @@
# Copyright (c) 2014 Alex Meade. All rights reserved.
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# Copyright (c) 2015 Tom Barron. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -28,6 +29,8 @@ from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
from cinder.volume.drivers.netapp.dataontap.client import client_base
from cinder.volume.drivers.netapp import utils as na_utils
from oslo_utils import strutils
LOG = logging.getLogger(__name__)
DELETED_PREFIX = 'deleted_cinder_'
@ -314,7 +317,7 @@ class Client(client_base.Client):
def clone_lun(self, volume, name, new_name, space_reserved='true',
qos_policy_group_name=None, src_block=0, dest_block=0,
block_count=0):
block_count=0, source_snapshot=None):
# zAPI can only handle 2^24 blocks per range
bc_limit = 2 ** 24 # 8GB
# zAPI can only handle 32 block ranges per call
@ -330,11 +333,17 @@ class Client(client_base.Client):
zbc -= z_limit
else:
block_count = zbc
clone_create = netapp_api.NaElement.create_node_with_children(
'clone-create',
**{'volume': volume, 'source-path': name,
zapi_args = {
'volume': volume,
'source-path': name,
'destination-path': new_name,
'space-reserve': space_reserved})
'space-reserve': space_reserved,
}
if source_snapshot:
zapi_args['snapshot-name'] = source_snapshot
clone_create = netapp_api.NaElement.create_node_with_children(
'clone-create', **zapi_args)
if qos_policy_group_name is not None:
clone_create.add_new_child('qos-policy-group-name',
qos_policy_group_name)
@ -860,3 +869,82 @@ class Client(client_base.Client):
})
return counter_data
def get_snapshot(self, volume_name, snapshot_name):
"""Gets a single snapshot."""
api_args = {
'query': {
'snapshot-info': {
'name': snapshot_name,
'volume': volume_name,
},
},
'desired-attributes': {
'snapshot-info': {
'name': None,
'volume': None,
'busy': None,
'snapshot-owners-list': {
'snapshot-owner': None,
}
},
},
}
result = self.send_request('snapshot-get-iter', api_args)
self._handle_get_snapshot_return_failure(result, snapshot_name)
attributes_list = result.get_child_by_name(
'attributes-list') or netapp_api.NaElement('none')
snapshot_info_list = attributes_list.get_children()
self._handle_snapshot_not_found(result, snapshot_info_list,
snapshot_name, volume_name)
snapshot_info = snapshot_info_list[0]
snapshot = {
'name': snapshot_info.get_child_content('name'),
'volume': snapshot_info.get_child_content('volume'),
'busy': strutils.bool_from_string(
snapshot_info.get_child_content('busy')),
}
snapshot_owners_list = snapshot_info.get_child_by_name(
'snapshot-owners-list') or netapp_api.NaElement('none')
snapshot_owners = set([
snapshot_owner.get_child_content('owner')
for snapshot_owner in snapshot_owners_list.get_children()])
snapshot['owners'] = snapshot_owners
return snapshot
def _handle_get_snapshot_return_failure(self, result, snapshot_name):
error_record_list = result.get_child_by_name(
'volume-errors') or netapp_api.NaElement('none')
errors = error_record_list.get_children()
if errors:
error = errors[0]
error_code = error.get_child_content('errno')
error_reason = error.get_child_content('reason')
msg = _('Could not read information for snapshot %(name)s. '
'Code: %(code)s. Reason: %(reason)s')
msg_args = {
'name': snapshot_name,
'code': error_code,
'reason': error_reason,
}
if error_code == netapp_api.ESNAPSHOTNOTALLOWED:
raise exception.SnapshotUnavailable(msg % msg_args)
else:
raise exception.VolumeBackendAPIException(data=msg % msg_args)
def _handle_snapshot_not_found(self, result, snapshot_info_list,
snapshot_name, volume_name):
if not self._has_records(result):
raise exception.SnapshotNotFound(snapshot_id=snapshot_name)
elif len(snapshot_info_list) > 1:
msg = _('Could not find unique snapshot %(snap)s on '
'volume %(vol)s.')
msg_args = {'snap': snapshot_name, 'vol': volume_name}
raise exception.VolumeBackendAPIException(data=msg % msg_args)

View File

@ -1,4 +1,5 @@
# Copyright (c) - 2014, Clinton Knight. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -26,6 +27,7 @@ LOG = logging.getLogger(__name__)
class NetApp7modeFibreChannelDriver(driver.BaseVD,
driver.ConsistencyGroupVD,
driver.ManageableVD,
driver.ExtendVD,
driver.TransferVD,
@ -106,3 +108,27 @@ class NetApp7modeFibreChannelDriver(driver.BaseVD,
def get_pool(self, volume):
return self.library.get_pool(volume)
def create_consistencygroup(self, context, group):
return self.library.create_consistencygroup(group)
def delete_consistencygroup(self, context, group, volumes):
return self.library.delete_consistencygroup(group, volumes)
def update_consistencygroup(self, context, group,
add_volumes=None, remove_volumes=None):
return self.library.update_consistencygroup(group, add_volumes=None,
remove_volumes=None)
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.create_cgsnapshot(cgsnapshot, snapshots)
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.delete_cgsnapshot(cgsnapshot, snapshots)
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
return self.library.create_consistencygroup_from_src(
group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots,
source_cg=source_cg, source_vols=source_vols)

View File

@ -1,4 +1,5 @@
# Copyright (c) - 2014, Clinton Knight. All rights reserved.
# Copyright (c) - 2016 Mike Rooney. 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
@ -26,6 +27,7 @@ LOG = logging.getLogger(__name__)
class NetAppCmodeFibreChannelDriver(driver.BaseVD,
driver.ConsistencyGroupVD,
driver.ManageableVD,
driver.ExtendVD,
driver.TransferVD,
@ -106,3 +108,27 @@ class NetAppCmodeFibreChannelDriver(driver.BaseVD,
def get_pool(self, volume):
return self.library.get_pool(volume)
def create_consistencygroup(self, context, group):
return self.library.create_consistencygroup(group)
def delete_consistencygroup(self, context, group, volumes):
return self.library.delete_consistencygroup(group, volumes)
def update_consistencygroup(self, context, group,
add_volumes=None, remove_volumes=None):
return self.library.update_consistencygroup(group, add_volumes=None,
remove_volumes=None)
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.create_cgsnapshot(cgsnapshot, snapshots)
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.delete_cgsnapshot(cgsnapshot, snapshots)
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
return self.library.create_consistencygroup_from_src(
group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots,
source_cg=source_cg, source_vols=source_vols)

View File

@ -1,4 +1,5 @@
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -25,6 +26,7 @@ LOG = logging.getLogger(__name__)
class NetApp7modeISCSIDriver(driver.BaseVD,
driver.ConsistencyGroupVD,
driver.ManageableVD,
driver.ExtendVD,
driver.TransferVD,
@ -103,3 +105,27 @@ class NetApp7modeISCSIDriver(driver.BaseVD,
def get_pool(self, volume):
return self.library.get_pool(volume)
def create_consistencygroup(self, context, group):
return self.library.create_consistencygroup(group)
def delete_consistencygroup(self, context, group, volumes):
return self.library.delete_consistencygroup(group, volumes)
def update_consistencygroup(self, context, group,
add_volumes=None, remove_volumes=None):
return self.library.update_consistencygroup(group, add_volumes=None,
remove_volumes=None)
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.create_cgsnapshot(cgsnapshot, snapshots)
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.delete_cgsnapshot(cgsnapshot, snapshots)
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
return self.library.create_consistencygroup_from_src(
group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots,
source_cg=source_cg, source_vols=source_vols)

View File

@ -1,4 +1,5 @@
# Copyright (c) 2014 Clinton Knight. All rights reserved.
# Copyright (c) 2016 Mike Rooney. 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
@ -25,6 +26,7 @@ LOG = logging.getLogger(__name__)
class NetAppCmodeISCSIDriver(driver.BaseVD,
driver.ConsistencyGroupVD,
driver.ManageableVD,
driver.ExtendVD,
driver.TransferVD,
@ -103,3 +105,27 @@ class NetAppCmodeISCSIDriver(driver.BaseVD,
def get_pool(self, volume):
return self.library.get_pool(volume)
def create_consistencygroup(self, context, group):
return self.library.create_consistencygroup(group)
def delete_consistencygroup(self, context, group, volumes):
return self.library.delete_consistencygroup(group, volumes)
def update_consistencygroup(self, context, group,
add_volumes=None, remove_volumes=None):
return self.library.update_consistencygroup(group, add_volumes=None,
remove_volumes=None)
def create_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.create_cgsnapshot(cgsnapshot, snapshots)
def delete_cgsnapshot(self, context, cgsnapshot, snapshots):
return self.library.delete_cgsnapshot(cgsnapshot, snapshots)
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
return self.library.create_consistencygroup_from_src(
group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots,
source_cg=source_cg, source_vols=source_vols)

View File

@ -0,0 +1,6 @@
---
features:
- Added support for creating, deleting, and updating consistency groups for
NetApp 7mode and CDOT backends.
- Added support for taking, deleting, and restoring a cgsnapshot for NetApp
7mode and CDOT backends.