# 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
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
</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>
# 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
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 (
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)
# 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
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']})
# 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
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)
# 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
'name': SNAPSHOT_NAME,
'volume_size': SIZE,
'volume_id': 'fake_volume_id',
+ 'busy': False,
}
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',
FAKE_7MODE_POOLS = [
{
'pool_name': 'open123',
+ 'consistencygroup_support': True,
'QoS_support': False,
'reserved_percentage': 0,
'total_capacity_gb': 0.0,
}
]
+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
# 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
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."""
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."""
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."""
expected = [{
'pool_name': 'vol1',
+ 'consistencygroup_support': True,
'QoS_support': False,
'thin_provisioning_support': not thick,
'thick_provisioning_support': thick,
# 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
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)
# 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
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."""
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."""
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],
goodness_function='goodness')
expected = [{'pool_name': 'vola',
+ 'consistencygroup_support': True,
'netapp_unmirrored': 'true',
'QoS_support': True,
'thin_provisioning_support': not thick,
# 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
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
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)
pool['filter_function'] = filter_function
pool['goodness_function'] = goodness_function
+ pool['consistencygroup_support'] = True
+
pools.append(pool)
return pools
# 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
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()
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
# 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
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'
pool['filter_function'] = filter_function
pool['goodness_function'] = goodness_function
+ pool['consistencygroup_support'] = True
+
pools.append(pool)
return pools
EAPINOTFOUND = '13005'
ESIS_CLONE_NOT_LICENSED = '14956'
+ESNAPSHOTNOTALLOWED = '13023'
class NaServer(object):
# 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
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__)
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
zbc -= z_limit
else:
block_count = zbc
+
+ zapi_args = {
+ 'source-path': path,
+ 'destination-path': clone_path,
+ 'no-snap': 'true',
+ }
+ if source_snapshot:
+ zapi_args['snapshot-name'] = source_snapshot
clone_start = netapp_api.NaElement.create_node_with_children(
- 'clone-start', **{'source-path': path,
- 'destination-path': clone_path,
- 'no-snap': 'true'})
+ 'clone-start', **zapi_args)
if block_count > 0:
block_ranges = netapp_api.NaElement("block-ranges")
# zAPI can only handle 2^24 block ranges
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
# 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
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)
# 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
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_'
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
zbc -= z_limit
else:
block_count = zbc
+
+ zapi_args = {
+ 'volume': volume,
+ 'source-path': name,
+ 'destination-path': new_name,
+ 'space-reserve': space_reserved,
+ }
+ if source_snapshot:
+ zapi_args['snapshot-name'] = source_snapshot
clone_create = netapp_api.NaElement.create_node_with_children(
- 'clone-create',
- **{'volume': volume, 'source-path': name,
- 'destination-path': new_name,
- 'space-reserve': space_reserved})
+ '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)
})
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)
# 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
class NetApp7modeFibreChannelDriver(driver.BaseVD,
+ driver.ConsistencyGroupVD,
driver.ManageableVD,
driver.ExtendVD,
driver.TransferVD,
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)
# 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
class NetAppCmodeFibreChannelDriver(driver.BaseVD,
+ driver.ConsistencyGroupVD,
driver.ManageableVD,
driver.ExtendVD,
driver.TransferVD,
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)
# 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
class NetApp7modeISCSIDriver(driver.BaseVD,
+ driver.ConsistencyGroupVD,
driver.ManageableVD,
driver.ExtendVD,
driver.TransferVD,
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)
# 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
class NetAppCmodeISCSIDriver(driver.BaseVD,
+ driver.ConsistencyGroupVD,
driver.ManageableVD,
driver.ExtendVD,
driver.TransferVD,
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)
--- /dev/null
+---
+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.