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:
parent
8ed2d59395
commit
3b2d17a5db
@ -1,4 +1,5 @@
|
|||||||
# Copyright (c) - 2015, Tom Barron. 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
|
# 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
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@ -17,6 +18,7 @@ from lxml import etree
|
|||||||
import mock
|
import mock
|
||||||
from six.moves import urllib
|
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
|
import cinder.volume.drivers.netapp.dataontap.client.api as netapp_api
|
||||||
|
|
||||||
|
|
||||||
@ -204,6 +206,80 @@ VOLUME_LIST_INFO_RESPONSE = etree.XML("""
|
|||||||
</results>
|
</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("""
|
NO_RECORDS_RESPONSE = etree.XML("""
|
||||||
<results status="passed">
|
<results status="passed">
|
||||||
<num-records>0</num-records>
|
<num-records>0</num-records>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||||
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
|
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
|
||||||
|
# Copyright (c) 2016 Mike Rooney. All rights reserved.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
@ -21,6 +22,7 @@ import mock
|
|||||||
import paramiko
|
import paramiko
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from cinder import exception
|
||||||
from cinder import ssh_utils
|
from cinder import ssh_utils
|
||||||
from cinder import test
|
from cinder import test
|
||||||
from cinder.tests.unit.volume.drivers.netapp.dataontap.client import (
|
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(
|
self.client.ssh_client.execute_command.assert_has_calls(
|
||||||
[mock.call(ssh, command)]
|
[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)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||||
# Copyright (c) 2015 Tom Barron. 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
|
# 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
|
# 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,
|
self.client.get_performance_counter_info,
|
||||||
'wafl',
|
'wafl',
|
||||||
'invalid')
|
'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']})
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||||
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
|
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
|
||||||
# Copyright (c) 2015 Tom Barron. 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
|
# 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
|
# 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.INITIATOR_IQN,
|
||||||
fake_client.USER_NAME,
|
fake_client.USER_NAME,
|
||||||
fake_client.PASSWORD)
|
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)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Copyright (c) - 2014, Clinton Knight. All rights reserved.
|
# Copyright (c) - 2014, Clinton Knight. All rights reserved.
|
||||||
# Copyright (c) - 2015, Tom Barron. 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
|
# 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
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@ -216,6 +217,7 @@ SNAPSHOT = {
|
|||||||
'name': SNAPSHOT_NAME,
|
'name': SNAPSHOT_NAME,
|
||||||
'volume_size': SIZE,
|
'volume_size': SIZE,
|
||||||
'volume_id': 'fake_volume_id',
|
'volume_id': 'fake_volume_id',
|
||||||
|
'busy': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
VOLUME_REF = {'name': 'fake_vref_name', 'size': 42}
|
VOLUME_REF = {'name': 'fake_vref_name', 'size': 42}
|
||||||
@ -223,6 +225,7 @@ VOLUME_REF = {'name': 'fake_vref_name', 'size': 42}
|
|||||||
FAKE_CMODE_POOLS = [
|
FAKE_CMODE_POOLS = [
|
||||||
{
|
{
|
||||||
'QoS_support': True,
|
'QoS_support': True,
|
||||||
|
'consistencygroup_support': True,
|
||||||
'free_capacity_gb': 3.72,
|
'free_capacity_gb': 3.72,
|
||||||
'netapp_compression': u'true',
|
'netapp_compression': u'true',
|
||||||
'netapp_dedup': u'true',
|
'netapp_dedup': u'true',
|
||||||
@ -338,6 +341,7 @@ FAKE_7MODE_VOL1 = [netapp_api.NaElement(
|
|||||||
FAKE_7MODE_POOLS = [
|
FAKE_7MODE_POOLS = [
|
||||||
{
|
{
|
||||||
'pool_name': 'open123',
|
'pool_name': 'open123',
|
||||||
|
'consistencygroup_support': True,
|
||||||
'QoS_support': False,
|
'QoS_support': False,
|
||||||
'reserved_percentage': 0,
|
'reserved_percentage': 0,
|
||||||
'total_capacity_gb': 0.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):
|
class test_volume(object):
|
||||||
pass
|
pass
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||||
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
||||||
# Copyright (c) 2015 Goutham Pacha Ravi. 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
|
# 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
|
# 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(
|
self.library.zapi_client.clone_lun.assert_called_once_with(
|
||||||
'/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN',
|
'/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):
|
def test_clone_lun_blocks(self):
|
||||||
"""Test for when clone lun is passed block information."""
|
"""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(
|
self.library.zapi_client.clone_lun.assert_called_once_with(
|
||||||
'/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN',
|
'/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN',
|
||||||
'newFakeLUN', 'false', block_count=block_count,
|
'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):
|
def test_clone_lun_no_space_reservation(self):
|
||||||
"""Test for when space_reservation is not passed."""
|
"""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(
|
self.library.zapi_client.clone_lun.assert_called_once_with(
|
||||||
'/vol/fake/fakeLUN', '/vol/fake/newFakeLUN', 'fakeLUN',
|
'/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):
|
def test_clone_lun_qos_supplied(self):
|
||||||
"""Test for qos supplied in clone lun invocation."""
|
"""Test for qos supplied in clone lun invocation."""
|
||||||
@ -526,6 +530,7 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
|
|||||||
|
|
||||||
expected = [{
|
expected = [{
|
||||||
'pool_name': 'vol1',
|
'pool_name': 'vol1',
|
||||||
|
'consistencygroup_support': True,
|
||||||
'QoS_support': False,
|
'QoS_support': False,
|
||||||
'thin_provisioning_support': not thick,
|
'thin_provisioning_support': not thick,
|
||||||
'thick_provisioning_support': thick,
|
'thick_provisioning_support': thick,
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
||||||
# Copyright (c) 2015 Goutham Pacha Ravi. All rights reserved.
|
# Copyright (c) 2015 Goutham Pacha Ravi. All rights reserved.
|
||||||
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
|
# Copyright (c) 2015 Dustin Schoenbrun. All rights reserved.
|
||||||
|
# Copyright (c) 2016 Chuck Fouts. All rights reserved.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# 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('CHAP', data['discovery_auth_method'])
|
||||||
self.assertEqual('user1', data['discovery_auth_username'])
|
self.assertEqual('user1', data['discovery_auth_username'])
|
||||||
self.assertEqual('pass1', data['discovery_auth_password'])
|
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)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||||
# Copyright (c) 2015 Tom Barron. 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
|
# 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
|
# 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(
|
self.library.zapi_client.clone_lun.assert_called_once_with(
|
||||||
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'false', block_count=0,
|
'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):
|
def test_clone_lun_blocks(self):
|
||||||
"""Test for when clone lun is passed block information."""
|
"""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(
|
self.library.zapi_client.clone_lun.assert_called_once_with(
|
||||||
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'false',
|
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'false',
|
||||||
block_count=block_count, dest_block=dest_block,
|
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):
|
def test_clone_lun_no_space_reservation(self):
|
||||||
"""Test for when space_reservation is not passed."""
|
"""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(
|
self.library.zapi_client.clone_lun.assert_called_once_with(
|
||||||
'fakeLUN', 'fakeLUN', 'newFakeLUN', 'false', block_count=0,
|
'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):
|
def test_get_fc_target_wwpns(self):
|
||||||
ports = [fake.FC_FORMATTED_TARGET_WWPNS[0],
|
ports = [fake.FC_FORMATTED_TARGET_WWPNS[0],
|
||||||
@ -372,6 +376,7 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
|
|||||||
goodness_function='goodness')
|
goodness_function='goodness')
|
||||||
|
|
||||||
expected = [{'pool_name': 'vola',
|
expected = [{'pool_name': 'vola',
|
||||||
|
'consistencygroup_support': True,
|
||||||
'netapp_unmirrored': 'true',
|
'netapp_unmirrored': 'true',
|
||||||
'QoS_support': True,
|
'QoS_support': True,
|
||||||
'thin_provisioning_support': not thick,
|
'thin_provisioning_support': not thick,
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
|
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
|
||||||
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
||||||
# Copyright (c) 2015 Goutham Pacha Ravi. 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
|
# 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
|
# 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,
|
def _clone_lun(self, name, new_name, space_reserved=None,
|
||||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
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."""
|
"""Clone LUN with the given handle to the new name."""
|
||||||
if not space_reserved:
|
if not space_reserved:
|
||||||
space_reserved = self.lun_space_reservation
|
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,
|
self.zapi_client.clone_lun(path, clone_path, name, new_name,
|
||||||
space_reserved, src_block=src_block,
|
space_reserved, src_block=src_block,
|
||||||
dest_block=dest_block,
|
dest_block=dest_block,
|
||||||
block_count=block_count)
|
block_count=block_count,
|
||||||
|
source_snapshot=source_snapshot)
|
||||||
|
|
||||||
self.vol_refresh_voluntary = True
|
self.vol_refresh_voluntary = True
|
||||||
luns = self.zapi_client.get_lun_by_args(path=clone_path)
|
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['filter_function'] = filter_function
|
||||||
pool['goodness_function'] = goodness_function
|
pool['goodness_function'] = goodness_function
|
||||||
|
|
||||||
|
pool['consistencygroup_support'] = True
|
||||||
|
|
||||||
pools.append(pool)
|
pools.append(pool)
|
||||||
|
|
||||||
return pools
|
return pools
|
||||||
|
@ -6,8 +6,9 @@
|
|||||||
# Copyright (c) 2014 Andrew Kerr. All rights reserved.
|
# Copyright (c) 2014 Andrew Kerr. All rights reserved.
|
||||||
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
|
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
|
||||||
# Copyright (c) 2015 Tom Barron. 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) 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
|
# 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
|
# 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',
|
def _clone_lun(self, name, new_name, space_reserved='true',
|
||||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
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."""
|
"""Clone LUN with the given name to the new name."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@ -983,3 +984,143 @@ class NetAppBlockStorageLibrary(object):
|
|||||||
init_targ_map[initiator] = target_wwpns
|
init_targ_map[initiator] = target_wwpns
|
||||||
|
|
||||||
return target_wwpns, init_targ_map, num_paths
|
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
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
|
# Copyright (c) 2014 Jeff Applewhite. All rights reserved.
|
||||||
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
# Copyright (c) 2015 Tom Barron. All rights reserved.
|
||||||
# Copyright (c) 2015 Goutham Pacha Ravi. 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
|
# 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
|
# 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,
|
def _clone_lun(self, name, new_name, space_reserved=None,
|
||||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
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."""
|
"""Clone LUN with the given handle to the new name."""
|
||||||
if not space_reserved:
|
if not space_reserved:
|
||||||
space_reserved = self.lun_space_reservation
|
space_reserved = self.lun_space_reservation
|
||||||
metadata = self._get_lun_attr(name, 'metadata')
|
metadata = self._get_lun_attr(name, 'metadata')
|
||||||
volume = metadata['Volume']
|
volume = metadata['Volume']
|
||||||
|
|
||||||
self.zapi_client.clone_lun(volume, name, new_name, space_reserved,
|
self.zapi_client.clone_lun(volume, name, new_name, space_reserved,
|
||||||
qos_policy_group_name=qos_policy_group_name,
|
qos_policy_group_name=qos_policy_group_name,
|
||||||
src_block=src_block, dest_block=dest_block,
|
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)
|
LOG.debug("Cloned LUN with new name %s", new_name)
|
||||||
lun = self.zapi_client.get_lun_by_args(vserver=self.vserver,
|
lun = self.zapi_client.get_lun_by_args(vserver=self.vserver,
|
||||||
path='/vol/%s/%s'
|
path='/vol/%s/%s'
|
||||||
@ -267,6 +271,8 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
|
|||||||
pool['filter_function'] = filter_function
|
pool['filter_function'] = filter_function
|
||||||
pool['goodness_function'] = goodness_function
|
pool['goodness_function'] = goodness_function
|
||||||
|
|
||||||
|
pool['consistencygroup_support'] = True
|
||||||
|
|
||||||
pools.append(pool)
|
pools.append(pool)
|
||||||
|
|
||||||
return pools
|
return pools
|
||||||
|
@ -40,6 +40,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
EAPINOTFOUND = '13005'
|
EAPINOTFOUND = '13005'
|
||||||
ESIS_CLONE_NOT_LICENSED = '14956'
|
ESIS_CLONE_NOT_LICENSED = '14956'
|
||||||
|
ESNAPSHOTNOTALLOWED = '13023'
|
||||||
|
|
||||||
|
|
||||||
class NaServer(object):
|
class NaServer(object):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||||
# Copyright (c) 2014 Clinton Knight. 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
|
# 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
|
# 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 api as netapp_api
|
||||||
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
from cinder.volume.drivers.netapp.dataontap.client import client_base
|
||||||
|
|
||||||
|
from oslo_utils import strutils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -228,7 +230,7 @@ class Client(client_base.Client):
|
|||||||
|
|
||||||
def clone_lun(self, path, clone_path, name, new_name,
|
def clone_lun(self, path, clone_path, name, new_name,
|
||||||
space_reserved='true', src_block=0,
|
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
|
# zAPI can only handle 2^24 blocks per range
|
||||||
bc_limit = 2 ** 24 # 8GB
|
bc_limit = 2 ** 24 # 8GB
|
||||||
# zAPI can only handle 32 block ranges per call
|
# zAPI can only handle 32 block ranges per call
|
||||||
@ -244,10 +246,16 @@ class Client(client_base.Client):
|
|||||||
zbc -= z_limit
|
zbc -= z_limit
|
||||||
else:
|
else:
|
||||||
block_count = zbc
|
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 = netapp_api.NaElement.create_node_with_children(
|
||||||
'clone-start', **{'source-path': path,
|
'clone-start', **zapi_args)
|
||||||
'destination-path': clone_path,
|
|
||||||
'no-snap': 'true'})
|
|
||||||
if block_count > 0:
|
if block_count > 0:
|
||||||
block_ranges = netapp_api.NaElement("block-ranges")
|
block_ranges = netapp_api.NaElement("block-ranges")
|
||||||
# zAPI can only handle 2^24 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_info = result.get_child_by_name('system-info')
|
||||||
system_name = system_info.get_child_content('system-name')
|
system_name = system_info.get_child_content('system-name')
|
||||||
return 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
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||||
# Copyright (c) 2015 Tom Barron. 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
|
# 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
|
# 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)
|
LOG.warning(_LW("Failed to invoke ems. Message : %s"), e)
|
||||||
finally:
|
finally:
|
||||||
requester.last_ems = timeutils.utcnow()
|
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)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
# Copyright (c) 2014 Alex Meade. All rights reserved.
|
||||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||||
# Copyright (c) 2015 Tom Barron. 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
|
# 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
|
# 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.dataontap.client import client_base
|
||||||
from cinder.volume.drivers.netapp import utils as na_utils
|
from cinder.volume.drivers.netapp import utils as na_utils
|
||||||
|
|
||||||
|
from oslo_utils import strutils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
DELETED_PREFIX = 'deleted_cinder_'
|
DELETED_PREFIX = 'deleted_cinder_'
|
||||||
@ -314,7 +317,7 @@ class Client(client_base.Client):
|
|||||||
|
|
||||||
def clone_lun(self, volume, name, new_name, space_reserved='true',
|
def clone_lun(self, volume, name, new_name, space_reserved='true',
|
||||||
qos_policy_group_name=None, src_block=0, dest_block=0,
|
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
|
# zAPI can only handle 2^24 blocks per range
|
||||||
bc_limit = 2 ** 24 # 8GB
|
bc_limit = 2 ** 24 # 8GB
|
||||||
# zAPI can only handle 32 block ranges per call
|
# zAPI can only handle 32 block ranges per call
|
||||||
@ -330,11 +333,17 @@ class Client(client_base.Client):
|
|||||||
zbc -= z_limit
|
zbc -= z_limit
|
||||||
else:
|
else:
|
||||||
block_count = zbc
|
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 = netapp_api.NaElement.create_node_with_children(
|
||||||
'clone-create',
|
'clone-create', **zapi_args)
|
||||||
**{'volume': volume, 'source-path': name,
|
|
||||||
'destination-path': new_name,
|
|
||||||
'space-reserve': space_reserved})
|
|
||||||
if qos_policy_group_name is not None:
|
if qos_policy_group_name is not None:
|
||||||
clone_create.add_new_child('qos-policy-group-name',
|
clone_create.add_new_child('qos-policy-group-name',
|
||||||
qos_policy_group_name)
|
qos_policy_group_name)
|
||||||
@ -860,3 +869,82 @@ class Client(client_base.Client):
|
|||||||
})
|
})
|
||||||
|
|
||||||
return counter_data
|
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)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Copyright (c) - 2014, Clinton Knight. 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
|
# 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
|
# 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,
|
class NetApp7modeFibreChannelDriver(driver.BaseVD,
|
||||||
|
driver.ConsistencyGroupVD,
|
||||||
driver.ManageableVD,
|
driver.ManageableVD,
|
||||||
driver.ExtendVD,
|
driver.ExtendVD,
|
||||||
driver.TransferVD,
|
driver.TransferVD,
|
||||||
@ -106,3 +108,27 @@ class NetApp7modeFibreChannelDriver(driver.BaseVD,
|
|||||||
|
|
||||||
def get_pool(self, volume):
|
def get_pool(self, volume):
|
||||||
return self.library.get_pool(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)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Copyright (c) - 2014, Clinton Knight. 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
|
# 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
|
# 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,
|
class NetAppCmodeFibreChannelDriver(driver.BaseVD,
|
||||||
|
driver.ConsistencyGroupVD,
|
||||||
driver.ManageableVD,
|
driver.ManageableVD,
|
||||||
driver.ExtendVD,
|
driver.ExtendVD,
|
||||||
driver.TransferVD,
|
driver.TransferVD,
|
||||||
@ -106,3 +108,27 @@ class NetAppCmodeFibreChannelDriver(driver.BaseVD,
|
|||||||
|
|
||||||
def get_pool(self, volume):
|
def get_pool(self, volume):
|
||||||
return self.library.get_pool(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)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Copyright (c) 2014 Clinton Knight. 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
|
# 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
|
# 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,
|
class NetApp7modeISCSIDriver(driver.BaseVD,
|
||||||
|
driver.ConsistencyGroupVD,
|
||||||
driver.ManageableVD,
|
driver.ManageableVD,
|
||||||
driver.ExtendVD,
|
driver.ExtendVD,
|
||||||
driver.TransferVD,
|
driver.TransferVD,
|
||||||
@ -103,3 +105,27 @@ class NetApp7modeISCSIDriver(driver.BaseVD,
|
|||||||
|
|
||||||
def get_pool(self, volume):
|
def get_pool(self, volume):
|
||||||
return self.library.get_pool(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)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Copyright (c) 2014 Clinton Knight. 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
|
# 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
|
# 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,
|
class NetAppCmodeISCSIDriver(driver.BaseVD,
|
||||||
|
driver.ConsistencyGroupVD,
|
||||||
driver.ManageableVD,
|
driver.ManageableVD,
|
||||||
driver.ExtendVD,
|
driver.ExtendVD,
|
||||||
driver.TransferVD,
|
driver.TransferVD,
|
||||||
@ -103,3 +105,27 @@ class NetAppCmodeISCSIDriver(driver.BaseVD,
|
|||||||
|
|
||||||
def get_pool(self, volume):
|
def get_pool(self, volume):
|
||||||
return self.library.get_pool(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)
|
||||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user