From 294ee65bd3850f2b1a8c1ef10c0bd64782ed7afe Mon Sep 17 00:00:00 2001
From: Goutham Pacha Ravi <gouthamr@netapp.com>
Date: Mon, 13 Jun 2016 11:03:15 -0400
Subject: [PATCH] NetApp cDOT: Add cheesecake replication support

Add ability to failover a given host to a replication
target provided, or chosen from amongst those
configured.

DocImpact

Implements: blueprint netapp-cheesecake-replication-support
Co-Authored-By: Clinton Knight <cknight@netapp.com>

Change-Id: I87b92e76d0d5022e9be610b9e237b89417309c05
---
 cinder/opts.py                                |   1 +
 .../drivers/netapp/dataontap/client/fakes.py  | 192 +++-
 .../dataontap/client/test_client_cmode.py     | 986 ++++++++++++++++++
 .../volume/drivers/netapp/dataontap/fakes.py  |   1 +
 .../dataontap/performance/test_perf_cmode.py  |  10 +
 .../netapp/dataontap/test_block_7mode.py      |   5 +-
 .../netapp/dataontap/test_block_base.py       |  42 +-
 .../netapp/dataontap/test_block_cmode.py      | 139 ++-
 .../netapp/dataontap/test_nfs_7mode.py        |   5 +-
 .../drivers/netapp/dataontap/test_nfs_base.py |  33 +-
 .../netapp/dataontap/test_nfs_cmode.py        | 175 +++-
 .../drivers/netapp/dataontap/utils/fakes.py   |  32 +
 .../dataontap/utils/test_capabilities.py      |  10 +
 .../dataontap/utils/test_data_motion.py       | 749 +++++++++++++
 .../netapp/dataontap/utils/test_utils.py      | 103 ++
 .../unit/volume/drivers/netapp/test_common.py |  28 +-
 .../drivers/netapp/dataontap/block_base.py    |  19 +
 .../drivers/netapp/dataontap/block_cmode.py   |  85 +-
 .../drivers/netapp/dataontap/client/api.py    |  10 +-
 .../netapp/dataontap/client/client_base.py    |   5 +
 .../netapp/dataontap/client/client_cmode.py   | 618 ++++++++++-
 .../drivers/netapp/dataontap/fc_7mode.py      |   3 +
 .../drivers/netapp/dataontap/fc_cmode.py      |   4 +
 .../drivers/netapp/dataontap/iscsi_7mode.py   |   3 +
 .../drivers/netapp/dataontap/iscsi_cmode.py   |   4 +
 .../drivers/netapp/dataontap/nfs_base.py      |  17 +
 .../drivers/netapp/dataontap/nfs_cmode.py     |  78 +-
 .../dataontap/performance/perf_cmode.py       |   4 +
 .../netapp/dataontap/utils/capabilities.py    |  10 +
 .../netapp/dataontap/utils/data_motion.py     | 640 ++++++++++++
 .../drivers/netapp/dataontap/utils/utils.py   |  74 ++
 cinder/volume/drivers/netapp/options.py       |  26 +
 ...-replication-support-59d7537fe3d0eb05.yaml |   8 +
 33 files changed, 3983 insertions(+), 136 deletions(-)
 create mode 100644 cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py
 create mode 100644 cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_utils.py
 create mode 100644 cinder/volume/drivers/netapp/dataontap/utils/data_motion.py
 create mode 100644 cinder/volume/drivers/netapp/dataontap/utils/utils.py
 create mode 100644 releasenotes/notes/netapp-cDOT-whole-backend-replication-support-59d7537fe3d0eb05.yaml

diff --git a/cinder/opts.py b/cinder/opts.py
index 54c1015c38a..ca83068fdc7 100644
--- a/cinder/opts.py
+++ b/cinder/opts.py
@@ -226,6 +226,7 @@ def list_opts():
                 cinder_volume_drivers_netapp_options.netapp_eseries_opts,
                 cinder_volume_drivers_netapp_options.netapp_nfs_extra_opts,
                 cinder_volume_drivers_netapp_options.netapp_san_opts,
+                cinder_volume_drivers_netapp_options.netapp_replication_opts,
                 cinder_volume_drivers_ibm_storwize_svc_storwizesvciscsi.
                 storwize_svc_iscsi_opts,
                 cinder_backup_drivers_glusterfs.glusterfsbackup_service_opts,
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py
index f6431071105..2d64e62fdc2 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py
@@ -68,6 +68,10 @@ FAKE_NA_SERVER_API_1_20.set_vfiler('filer')
 FAKE_NA_SERVER_API_1_20.set_vserver('server')
 FAKE_NA_SERVER_API_1_20.set_api_version(1, 20)
 
+VOLUME_VSERVER_NAME = 'fake_vserver'
+VOLUME_NAMES = ('volume1', 'volume2')
+VOLUME_NAME = 'volume1'
+
 
 FAKE_QUERY = {'volume-attributes': None}
 
@@ -104,6 +108,20 @@ NO_RECORDS_RESPONSE = etree.XML("""
   </results>
 """)
 
+VOLUME_GET_NAME_RESPONSE = etree.XML("""
+  <results status="passed">
+    <attributes-list>
+      <volume-attributes>
+        <volume-id-attributes>
+          <name>%(volume)s</name>
+          <owning-vserver-name>%(vserver)s</owning-vserver-name>
+        </volume-id-attributes>
+      </volume-attributes>
+    </attributes-list>
+    <num-records>1</num-records>
+  </results>
+""" % {'volume': VOLUME_NAMES[0], 'vserver': VOLUME_VSERVER_NAME})
+
 INVALID_GET_ITER_RESPONSE_NO_ATTRIBUTES = etree.XML("""
   <results status="passed">
     <num-records>1</num-records>
@@ -697,9 +715,6 @@ VOLUME_GET_ITER_CAPACITY_RESPONSE = etree.XML("""
     'total_size': VOLUME_SIZE_TOTAL,
 })
 
-VOLUME_VSERVER_NAME = 'fake_vserver'
-VOLUME_NAMES = ('volume1', 'volume2')
-
 VOLUME_GET_ITER_LIST_RESPONSE = etree.XML("""
   <results status="passed">
     <attributes-list>
@@ -733,6 +748,7 @@ VOLUME_GET_ITER_SSC_RESPONSE = etree.XML("""
           <junction-path>/%(volume)s</junction-path>
           <name>%(volume)s</name>
           <owning-vserver-name>%(vserver)s</owning-vserver-name>
+          <type>rw</type>
         </volume-id-attributes>
         <volume-mirror-attributes>
           <is-data-protection-mirror>false</is-data-protection-mirror>
@@ -744,7 +760,15 @@ VOLUME_GET_ITER_SSC_RESPONSE = etree.XML("""
         <volume-space-attributes>
           <is-space-guarantee-enabled>true</is-space-guarantee-enabled>
           <space-guarantee>none</space-guarantee>
+          <percentage-snapshot-reserve>5</percentage-snapshot-reserve>
+          <size>12345</size>
         </volume-space-attributes>
+        <volume-snapshot-attributes>
+          <snapshot-policy>default</snapshot-policy>
+        </volume-snapshot-attributes>
+        <volume-language-attributes>
+          <language-code>en_US</language-code>
+        </volume-language-attributes>
       </volume-attributes>
     </attributes-list>
     <num-records>1</num-records>
@@ -761,6 +785,11 @@ VOLUME_INFO_SSC = {
     'junction-path': '/%s' % VOLUME_NAMES[0],
     'aggregate': VOLUME_AGGREGATE_NAMES[0],
     'space-guarantee-enabled': True,
+    'language': 'en_US',
+    'percentage-snapshot-reserve': '5',
+    'snapshot-policy': 'default',
+    'type': 'rw',
+    'size': '12345',
     'space-guarantee': 'none',
     'qos-policy-group': 'fake_qos_policy_group_name',
 }
@@ -782,27 +811,6 @@ VOLUME_DEDUPE_INFO_SSC = {
     'dedupe': True,
 }
 
-SNAPMIRROR_GET_ITER_RESPONSE = etree.XML("""
-  <results status="passed">
-    <attributes-list>
-      <snapmirror-info>
-        <destination-location>%(vserver)s:%(volume2)s</destination-location>
-        <destination-volume>%(volume2)s</destination-volume>
-        <destination-vserver>%(vserver)s</destination-vserver>
-        <source-location>%(vserver)s:%(volume1)s</source-location>
-        <source-volume>%(volume1)s</source-volume>
-        <source-vserver>%(vserver)s</source-vserver>
-      </snapmirror-info>
-    </attributes-list>
-    <num-records>1</num-records>
-  </results>
-""" % {
-    'volume1': VOLUME_NAMES[0],
-    'volume2': VOLUME_NAMES[1],
-    'vserver': VOLUME_VSERVER_NAME,
-})
-
-
 STORAGE_DISK_GET_ITER_RESPONSE_PAGE_1 = etree.XML("""
   <results status="passed">
     <attributes-list>
@@ -1123,3 +1131,139 @@ ISCSI_INITIATOR_GET_AUTH_ELEM = etree.XML("""
 ISCSI_INITIATOR_AUTH_LIST_INFO_FAILURE = etree.XML("""
 <results status="failed" errno="13112" reason="Initiator %s not found,
  please use default authentication." />""" % INITIATOR_IQN)
+
+CLUSTER_NAME = 'fake_cluster'
+REMOTE_CLUSTER_NAME = 'fake_cluster_2'
+CLUSTER_ADDRESS_1 = 'fake_cluster_address'
+CLUSTER_ADDRESS_2 = 'fake_cluster_address_2'
+VSERVER_NAME = 'fake_vserver'
+VSERVER_NAME_2 = 'fake_vserver_2'
+SM_SOURCE_VSERVER = 'fake_source_vserver'
+SM_SOURCE_VOLUME = 'fake_source_volume'
+SM_DEST_VSERVER = 'fake_destination_vserver'
+SM_DEST_VOLUME = 'fake_destination_volume'
+
+CLUSTER_PEER_GET_ITER_RESPONSE = etree.XML("""
+  <results status="passed">
+    <attributes-list>
+      <cluster-peer-info>
+        <active-addresses>
+          <remote-inet-address>%(addr1)s</remote-inet-address>
+          <remote-inet-address>%(addr2)s</remote-inet-address>
+        </active-addresses>
+        <availability>available</availability>
+        <cluster-name>%(cluster)s</cluster-name>
+        <cluster-uuid>fake_uuid</cluster-uuid>
+        <peer-addresses>
+          <remote-inet-address>%(addr1)s</remote-inet-address>
+        </peer-addresses>
+        <remote-cluster-name>%(remote_cluster)s</remote-cluster-name>
+        <serial-number>fake_serial_number</serial-number>
+        <timeout>60</timeout>
+      </cluster-peer-info>
+    </attributes-list>
+    <num-records>1</num-records>
+  </results>
+""" % {
+    'addr1': CLUSTER_ADDRESS_1,
+    'addr2': CLUSTER_ADDRESS_2,
+    'cluster': CLUSTER_NAME,
+    'remote_cluster': REMOTE_CLUSTER_NAME,
+})
+
+CLUSTER_PEER_POLICY_GET_RESPONSE = etree.XML("""
+  <results status="passed">
+    <attributes>
+      <cluster-peer-policy>
+        <is-unauthenticated-access-permitted>false</is-unauthenticated-access-permitted>
+        <passphrase-minimum-length>8</passphrase-minimum-length>
+      </cluster-peer-policy>
+    </attributes>
+  </results>
+""")
+
+VSERVER_PEER_GET_ITER_RESPONSE = etree.XML("""
+  <results status="passed">
+    <attributes-list>
+      <vserver-peer-info>
+        <applications>
+          <vserver-peer-application>snapmirror</vserver-peer-application>
+        </applications>
+        <peer-cluster>%(cluster)s</peer-cluster>
+        <peer-state>peered</peer-state>
+        <peer-vserver>%(vserver2)s</peer-vserver>
+        <vserver>%(vserver1)s</vserver>
+      </vserver-peer-info>
+    </attributes-list>
+    <num-records>2</num-records>
+  </results>
+""" % {
+    'cluster': CLUSTER_NAME,
+    'vserver1': VSERVER_NAME,
+    'vserver2': VSERVER_NAME_2
+})
+
+SNAPMIRROR_GET_ITER_RESPONSE = etree.XML("""
+  <results status="passed">
+    <attributes-list>
+      <snapmirror-info>
+        <destination-location>%(vserver)s:%(volume2)s</destination-location>
+        <destination-volume>%(volume2)s</destination-volume>
+        <destination-volume-node>fake_destination_node</destination-volume-node>
+        <destination-vserver>%(vserver)s</destination-vserver>
+        <exported-snapshot>fake_snapshot</exported-snapshot>
+        <exported-snapshot-timestamp>1442701782</exported-snapshot-timestamp>
+        <is-constituent>false</is-constituent>
+        <is-healthy>true</is-healthy>
+        <lag-time>2187</lag-time>
+        <last-transfer-duration>109</last-transfer-duration>
+        <last-transfer-end-timestamp>1442701890</last-transfer-end-timestamp>
+        <last-transfer-from>test:manila</last-transfer-from>
+        <last-transfer-size>1171456</last-transfer-size>
+        <last-transfer-type>initialize</last-transfer-type>
+        <max-transfer-rate>0</max-transfer-rate>
+        <mirror-state>snapmirrored</mirror-state>
+        <newest-snapshot>fake_snapshot</newest-snapshot>
+        <newest-snapshot-timestamp>1442701782</newest-snapshot-timestamp>
+        <policy>DPDefault</policy>
+        <relationship-control-plane>v2</relationship-control-plane>
+        <relationship-id>ea8bfcc6-5f1d-11e5-8446-123478563412</relationship-id>
+        <relationship-status>idle</relationship-status>
+        <relationship-type>data_protection</relationship-type>
+        <schedule>daily</schedule>
+        <source-location>%(vserver)s:%(volume1)s</source-location>
+        <source-volume>%(volume1)s</source-volume>
+        <source-vserver>%(vserver)s</source-vserver>
+        <vserver>fake_destination_vserver</vserver>
+      </snapmirror-info>
+    </attributes-list>
+    <num-records>1</num-records>
+  </results>
+""" % {
+    'volume1': VOLUME_NAMES[0],
+    'volume2': VOLUME_NAMES[1],
+    'vserver': VOLUME_VSERVER_NAME,
+})
+
+SNAPMIRROR_GET_ITER_FILTERED_RESPONSE = etree.XML("""
+  <results status="passed">
+    <attributes-list>
+      <snapmirror-info>
+        <destination-vserver>fake_destination_vserver</destination-vserver>
+        <destination-volume>fake_destination_volume</destination-volume>
+        <is-healthy>true</is-healthy>
+        <mirror-state>snapmirrored</mirror-state>
+        <schedule>daily</schedule>
+        <source-vserver>fake_source_vserver</source-vserver>
+        <source-volume>fake_source_volume</source-volume>
+      </snapmirror-info>
+    </attributes-list>
+    <num-records>1</num-records>
+  </results>
+""")
+
+SNAPMIRROR_INITIALIZE_RESULT = etree.XML("""
+  <results status="passed">
+    <result-status>succeeded</result-status>
+  </results>
+""")
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py
index 78df0d2ad98..7b493c7ebf3 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode.py
@@ -1385,6 +1385,7 @@ class NetAppCmodeClientTestCase(test.TestCase):
                         'name': None,
                         'owning-vserver-name': None,
                         'junction-path': None,
+                        'type': None,
                         'containing-aggregate-name': None,
                     },
                     'volume-mirror-attributes': {
@@ -1394,9 +1395,17 @@ class NetAppCmodeClientTestCase(test.TestCase):
                     'volume-space-attributes': {
                         'is-space-guarantee-enabled': None,
                         'space-guarantee': None,
+                        'percentage-snapshot-reserve': None,
+                        'size': None,
                     },
                     'volume-qos-attributes': {
                         'policy-group-name': None,
+                    },
+                    'volume-snapshot-attributes': {
+                        'snapshot-policy': None,
+                    },
+                    'volume-language-attributes': {
+                        'language-code': None,
                     }
                 },
             },
@@ -1417,6 +1426,193 @@ class NetAppCmodeClientTestCase(test.TestCase):
                           self.client.get_flexvol,
                           flexvol_name=fake_client.VOLUME_NAMES[0])
 
+    def test_create_flexvol(self):
+        self.mock_object(self.client, 'send_request')
+
+        self.client.create_flexvol(
+            fake_client.VOLUME_NAME, fake_client.VOLUME_AGGREGATE_NAME, 100)
+
+        volume_create_args = {
+            'containing-aggr-name': fake_client.VOLUME_AGGREGATE_NAME,
+            'size': '100g',
+            'volume': fake_client.VOLUME_NAME,
+            'volume-type': 'rw',
+            'junction-path': '/%s' % fake_client.VOLUME_NAME,
+        }
+
+        self.client.send_request.assert_called_once_with('volume-create',
+                                                         volume_create_args)
+
+    @ddt.data('dp', 'rw', None)
+    def test_create_volume_with_extra_specs(self, volume_type):
+
+        self.mock_object(self.client, 'enable_flexvol_dedupe')
+        self.mock_object(self.client, 'enable_flexvol_compression')
+        self.mock_object(self.client, 'send_request')
+
+        self.client.create_flexvol(
+            fake_client.VOLUME_NAME, fake_client.VOLUME_AGGREGATE_NAME, 100,
+            space_guarantee_type='volume', language='en-US',
+            snapshot_policy='default', dedupe_enabled=True,
+            compression_enabled=True, snapshot_reserve=15,
+            volume_type=volume_type)
+
+        volume_create_args = {
+            'containing-aggr-name': fake_client.VOLUME_AGGREGATE_NAME,
+            'size': '100g',
+            'volume': fake_client.VOLUME_NAME,
+            'space-reserve': 'volume',
+            'language-code': 'en-US',
+            'volume-type': volume_type,
+            'percentage-snapshot-reserve': '15',
+        }
+
+        if volume_type != 'dp':
+            volume_create_args['snapshot-policy'] = 'default'
+            volume_create_args['junction-path'] = ('/%s' %
+                                                   fake_client.VOLUME_NAME)
+
+        self.client.send_request.assert_called_with('volume-create',
+                                                    volume_create_args)
+        self.client.enable_flexvol_dedupe.assert_called_once_with(
+            fake_client.VOLUME_NAME)
+        self.client.enable_flexvol_compression.assert_called_once_with(
+            fake_client.VOLUME_NAME)
+
+    def test_flexvol_exists(self):
+
+        api_response = netapp_api.NaElement(
+            fake_client.VOLUME_GET_NAME_RESPONSE)
+        self.mock_object(self.client,
+                         'send_iter_request',
+                         mock.Mock(return_value=api_response))
+
+        result = self.client.flexvol_exists(fake_client.VOLUME_NAME)
+
+        volume_get_iter_args = {
+            'query': {
+                'volume-attributes': {
+                    'volume-id-attributes': {
+                        'name': fake_client.VOLUME_NAME
+                    }
+                }
+            },
+            'desired-attributes': {
+                'volume-attributes': {
+                    'volume-id-attributes': {
+                        'name': None
+                    }
+                }
+            }
+        }
+
+        self.client.send_iter_request.assert_has_calls([
+            mock.call('volume-get-iter', volume_get_iter_args)])
+        self.assertTrue(result)
+
+    def test_flexvol_exists_not_found(self):
+
+        api_response = netapp_api.NaElement(fake_client.NO_RECORDS_RESPONSE)
+        self.mock_object(self.client,
+                         'send_request',
+                         mock.Mock(return_value=api_response))
+
+        self.assertFalse(self.client.flexvol_exists(fake_client.VOLUME_NAME))
+
+    def test_rename_flexvol(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.rename_flexvol(fake_client.VOLUME_NAME, 'new_name')
+
+        volume_rename_api_args = {
+            'volume': fake_client.VOLUME_NAME,
+            'new-volume-name': 'new_name',
+        }
+
+        self.client.send_request.assert_called_once_with(
+            'volume-rename', volume_rename_api_args)
+
+    def test_mount_flexvol_default_junction_path(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.mount_flexvol(fake_client.VOLUME_NAME)
+
+        volume_mount_args = {
+            'volume-name': fake_client.VOLUME_NAME,
+            'junction-path': '/%s' % fake_client.VOLUME_NAME,
+        }
+
+        self.client.send_request.assert_has_calls([
+            mock.call('volume-mount', volume_mount_args)])
+
+    def test_mount_flexvol(self):
+
+        self.mock_object(self.client, 'send_request')
+        fake_path = '/fake_path'
+
+        self.client.mount_flexvol(fake_client.VOLUME_NAME,
+                                  junction_path=fake_path)
+
+        volume_mount_args = {
+            'volume-name': fake_client.VOLUME_NAME,
+            'junction-path': fake_path,
+        }
+
+        self.client.send_request.assert_has_calls([
+            mock.call('volume-mount', volume_mount_args)])
+
+    def test_enable_flexvol_dedupe(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.enable_flexvol_dedupe(fake_client.VOLUME_NAME)
+
+        sis_enable_args = {'path': '/vol/%s' % fake_client.VOLUME_NAME}
+
+        self.client.send_request.assert_called_once_with('sis-enable',
+                                                         sis_enable_args)
+
+    def test_disable_flexvol_dedupe(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.disable_flexvol_dedupe(fake_client.VOLUME_NAME)
+
+        sis_disable_args = {'path': '/vol/%s' % fake_client.VOLUME_NAME}
+
+        self.client.send_request.assert_called_once_with('sis-disable',
+                                                         sis_disable_args)
+
+    def test_enable_flexvol_compression(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.enable_flexvol_compression(fake_client.VOLUME_NAME)
+
+        sis_set_config_args = {
+            'path': '/vol/%s' % fake_client.VOLUME_NAME,
+            'enable-compression': 'true'
+        }
+
+        self.client.send_request.assert_called_once_with('sis-set-config',
+                                                         sis_set_config_args)
+
+    def test_disable_flexvol_compression(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.disable_flexvol_compression(fake_client.VOLUME_NAME)
+
+        sis_set_config_args = {
+            'path': '/vol/%s' % fake_client.VOLUME_NAME,
+            'enable-compression': 'false'
+        }
+
+        self.client.send_request.assert_called_once_with('sis-set-config',
+                                                         sis_set_config_args)
+
     def test_get_flexvol_dedupe_info(self):
 
         api_response = netapp_api.NaElement(
@@ -2161,3 +2357,793 @@ class NetAppCmodeClientTestCase(test.TestCase):
 
         self.assertRaises(exception.SnapshotNotFound, self.client.get_snapshot,
                           expected_vol_name, expected_snapshot_name)
+
+    def test_create_cluster_peer(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.create_cluster_peer(['fake_address_1', 'fake_address_2'],
+                                        'fake_user', 'fake_password',
+                                        'fake_passphrase')
+
+        cluster_peer_create_args = {
+            'peer-addresses': [
+                {'remote-inet-address': 'fake_address_1'},
+                {'remote-inet-address': 'fake_address_2'},
+            ],
+            'user-name': 'fake_user',
+            'password': 'fake_password',
+            'passphrase': 'fake_passphrase',
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('cluster-peer-create', cluster_peer_create_args)])
+
+    def test_get_cluster_peers(self):
+
+        api_response = netapp_api.NaElement(
+            fake_client.CLUSTER_PEER_GET_ITER_RESPONSE)
+        self.mock_object(self.client,
+                         'send_iter_request',
+                         mock.Mock(return_value=api_response))
+
+        result = self.client.get_cluster_peers()
+
+        cluster_peer_get_iter_args = {}
+        self.client.send_iter_request.assert_has_calls([
+            mock.call('cluster-peer-get-iter', cluster_peer_get_iter_args)])
+
+        expected = [{
+            'active-addresses': [
+                fake_client.CLUSTER_ADDRESS_1,
+                fake_client.CLUSTER_ADDRESS_2
+            ],
+            'availability': 'available',
+            'cluster-name': fake_client.CLUSTER_NAME,
+            'cluster-uuid': 'fake_uuid',
+            'peer-addresses': [fake_client.CLUSTER_ADDRESS_1],
+            'remote-cluster-name': fake_client.REMOTE_CLUSTER_NAME,
+            'serial-number': 'fake_serial_number',
+            'timeout': '60',
+        }]
+
+        self.assertEqual(expected, result)
+
+    def test_get_cluster_peers_single(self):
+
+        api_response = netapp_api.NaElement(
+            fake_client.CLUSTER_PEER_GET_ITER_RESPONSE)
+        self.mock_object(self.client,
+                         'send_iter_request',
+                         mock.Mock(return_value=api_response))
+
+        self.client.get_cluster_peers(
+            remote_cluster_name=fake_client.CLUSTER_NAME)
+
+        cluster_peer_get_iter_args = {
+            'query': {
+                'cluster-peer-info': {
+                    'remote-cluster-name': fake_client.CLUSTER_NAME,
+                }
+            },
+        }
+        self.client.send_iter_request.assert_has_calls([
+            mock.call('cluster-peer-get-iter', cluster_peer_get_iter_args)])
+
+    def test_get_cluster_peers_not_found(self):
+
+        api_response = netapp_api.NaElement(fake_client.NO_RECORDS_RESPONSE)
+        self.mock_object(self.client,
+                         'send_iter_request',
+                         mock.Mock(return_value=api_response))
+
+        result = self.client.get_cluster_peers(
+            remote_cluster_name=fake_client.CLUSTER_NAME)
+
+        self.assertEqual([], result)
+        self.assertTrue(self.client.send_iter_request.called)
+
+    def test_delete_cluster_peer(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.delete_cluster_peer(fake_client.CLUSTER_NAME)
+
+        cluster_peer_delete_args = {'cluster-name': fake_client.CLUSTER_NAME}
+        self.client.send_request.assert_has_calls([
+            mock.call('cluster-peer-delete', cluster_peer_delete_args)])
+
+    def test_get_cluster_peer_policy(self):
+
+        self.client.features.add_feature('CLUSTER_PEER_POLICY')
+
+        api_response = netapp_api.NaElement(
+            fake_client.CLUSTER_PEER_POLICY_GET_RESPONSE)
+        self.mock_object(self.client,
+                         'send_request',
+                         mock.Mock(return_value=api_response))
+
+        result = self.client.get_cluster_peer_policy()
+
+        expected = {
+            'is-unauthenticated-access-permitted': False,
+            'passphrase-minimum-length': 8,
+        }
+        self.assertEqual(expected, result)
+        self.assertTrue(self.client.send_request.called)
+
+    def test_get_cluster_peer_policy_not_supported(self):
+
+        result = self.client.get_cluster_peer_policy()
+
+        self.assertEqual({}, result)
+
+    def test_set_cluster_peer_policy_not_supported(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.set_cluster_peer_policy()
+
+        self.assertFalse(self.client.send_request.called)
+
+    def test_set_cluster_peer_policy_no_arguments(self):
+
+        self.client.features.add_feature('CLUSTER_PEER_POLICY')
+        self.mock_object(self.client, 'send_request')
+
+        self.client.set_cluster_peer_policy()
+
+        self.assertFalse(self.client.send_request.called)
+
+    def test_set_cluster_peer_policy(self):
+
+        self.client.features.add_feature('CLUSTER_PEER_POLICY')
+        self.mock_object(self.client, 'send_request')
+
+        self.client.set_cluster_peer_policy(
+            is_unauthenticated_access_permitted=True,
+            passphrase_minimum_length=12)
+
+        cluster_peer_policy_modify_args = {
+            'is-unauthenticated-access-permitted': 'true',
+            'passphrase-minlength': '12',
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('cluster-peer-policy-modify',
+                      cluster_peer_policy_modify_args)])
+
+    def test_create_vserver_peer(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.create_vserver_peer('fake_vserver', 'fake_vserver_peer')
+
+        vserver_peer_create_args = {
+            'vserver': 'fake_vserver',
+            'peer-vserver': 'fake_vserver_peer',
+            'applications': [
+                {'vserver-peer-application': 'snapmirror'},
+            ],
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('vserver-peer-create', vserver_peer_create_args)])
+
+    def test_delete_vserver_peer(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.delete_vserver_peer('fake_vserver', 'fake_vserver_peer')
+
+        vserver_peer_delete_args = {
+            'vserver': 'fake_vserver',
+            'peer-vserver': 'fake_vserver_peer',
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('vserver-peer-delete', vserver_peer_delete_args)])
+
+    def test_accept_vserver_peer(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.accept_vserver_peer('fake_vserver', 'fake_vserver_peer')
+
+        vserver_peer_accept_args = {
+            'vserver': 'fake_vserver',
+            'peer-vserver': 'fake_vserver_peer',
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('vserver-peer-accept', vserver_peer_accept_args)])
+
+    def test_get_vserver_peers(self):
+
+        api_response = netapp_api.NaElement(
+            fake_client.VSERVER_PEER_GET_ITER_RESPONSE)
+        self.mock_object(self.client,
+                         'send_iter_request',
+                         mock.Mock(return_value=api_response))
+
+        result = self.client.get_vserver_peers(
+            vserver_name=fake_client.VSERVER_NAME,
+            peer_vserver_name=fake_client.VSERVER_NAME_2)
+
+        vserver_peer_get_iter_args = {
+            'query': {
+                'vserver-peer-info': {
+                    'vserver': fake_client.VSERVER_NAME,
+                    'peer-vserver': fake_client.VSERVER_NAME_2,
+                }
+            },
+        }
+        self.client.send_iter_request.assert_has_calls([
+            mock.call('vserver-peer-get-iter', vserver_peer_get_iter_args)])
+
+        expected = [{
+            'vserver': 'fake_vserver',
+            'peer-vserver': 'fake_vserver_2',
+            'peer-state': 'peered',
+            'peer-cluster': 'fake_cluster'
+        }]
+        self.assertEqual(expected, result)
+
+    def test_get_vserver_peers_not_found(self):
+
+        api_response = netapp_api.NaElement(fake_client.NO_RECORDS_RESPONSE)
+        self.mock_object(self.client,
+                         'send_iter_request',
+                         mock.Mock(return_value=api_response))
+
+        result = self.client.get_vserver_peers(
+            vserver_name=fake_client.VSERVER_NAME,
+            peer_vserver_name=fake_client.VSERVER_NAME_2)
+
+        self.assertEqual([], result)
+        self.assertTrue(self.client.send_iter_request.called)
+
+    def test_ensure_snapmirror_v2(self):
+
+        self.assertIsNone(self.client._ensure_snapmirror_v2())
+
+    def test_ensure_snapmirror_v2_not_supported(self):
+
+        self.client.features.add_feature('SNAPMIRROR_V2', supported=False)
+
+        self.assertRaises(exception.NetAppDriverException,
+                          self.client._ensure_snapmirror_v2)
+
+    @ddt.data({'schedule': 'fake_schedule', 'policy': 'fake_policy'},
+              {'schedule': None, 'policy': None})
+    @ddt.unpack
+    def test_create_snapmirror(self, schedule, policy):
+        self.mock_object(self.client, 'send_request')
+
+        self.client.create_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
+            schedule=schedule, policy=policy)
+
+        snapmirror_create_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+            'relationship-type': 'data_protection',
+        }
+        if schedule:
+            snapmirror_create_args['schedule'] = schedule
+        if policy:
+            snapmirror_create_args['policy'] = policy
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-create', snapmirror_create_args)])
+
+    def test_create_snapmirror_already_exists(self):
+        mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
+            code=netapp_api.ERELATION_EXISTS))
+        self.mock_object(self.client, 'send_request', mock_send_req)
+
+        self.client.create_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_create_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+            'relationship-type': 'data_protection',
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-create', snapmirror_create_args)])
+
+    def test_create_snapmirror_error(self):
+        mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
+            code=0))
+        self.mock_object(self.client, 'send_request', mock_send_req)
+
+        self.assertRaises(netapp_api.NaApiError,
+                          self.client.create_snapmirror,
+                          fake_client.SM_SOURCE_VSERVER,
+                          fake_client.SM_SOURCE_VOLUME,
+                          fake_client.SM_DEST_VSERVER,
+                          fake_client.SM_DEST_VOLUME)
+        self.assertTrue(self.client.send_request.called)
+
+    @ddt.data(
+        {
+            'source_snapshot': 'fake_snapshot',
+            'transfer_priority': 'fake_priority'
+        },
+        {
+            'source_snapshot': None,
+            'transfer_priority': None
+        }
+    )
+    @ddt.unpack
+    def test_initialize_snapmirror(self, source_snapshot, transfer_priority):
+
+        api_response = netapp_api.NaElement(
+            fake_client.SNAPMIRROR_INITIALIZE_RESULT)
+        self.mock_object(self.client,
+                         'send_request',
+                         mock.Mock(return_value=api_response))
+
+        result = self.client.initialize_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
+            source_snapshot=source_snapshot,
+            transfer_priority=transfer_priority)
+
+        snapmirror_initialize_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+        }
+        if source_snapshot:
+            snapmirror_initialize_args['source-snapshot'] = source_snapshot
+        if transfer_priority:
+            snapmirror_initialize_args['transfer-priority'] = transfer_priority
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-initialize', snapmirror_initialize_args)])
+
+        expected = {
+            'operation-id': None,
+            'status': 'succeeded',
+            'jobid': None,
+            'error-code': None,
+            'error-message': None
+        }
+        self.assertEqual(expected, result)
+
+    @ddt.data(True, False)
+    def test_release_snapmirror(self, relationship_info_only):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.release_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
+            relationship_info_only=relationship_info_only)
+
+        snapmirror_release_args = {
+            'query': {
+                'snapmirror-destination-info': {
+                    'source-vserver': fake_client.SM_SOURCE_VSERVER,
+                    'source-volume': fake_client.SM_SOURCE_VOLUME,
+                    'destination-vserver': fake_client.SM_DEST_VSERVER,
+                    'destination-volume': fake_client.SM_DEST_VOLUME,
+                    'relationship-info-only': ('true' if relationship_info_only
+                                               else 'false'),
+                }
+            }
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-release-iter', snapmirror_release_args)])
+
+    def test_quiesce_snapmirror(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.quiesce_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_quiesce_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-quiesce', snapmirror_quiesce_args)])
+
+    @ddt.data(True, False)
+    def test_abort_snapmirror(self, clear_checkpoint):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.abort_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
+            clear_checkpoint=clear_checkpoint)
+
+        snapmirror_abort_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+            'clear-checkpoint': 'true' if clear_checkpoint else 'false',
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-abort', snapmirror_abort_args)])
+
+    def test_abort_snapmirror_no_transfer_in_progress(self):
+        mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
+            code=netapp_api.ENOTRANSFER_IN_PROGRESS))
+        self.mock_object(self.client, 'send_request', mock_send_req)
+
+        self.client.abort_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_abort_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+            'clear-checkpoint': 'false',
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-abort', snapmirror_abort_args)])
+
+    def test_abort_snapmirror_error(self):
+        mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
+        self.mock_object(self.client, 'send_request', mock_send_req)
+
+        self.assertRaises(netapp_api.NaApiError,
+                          self.client.abort_snapmirror,
+                          fake_client.SM_SOURCE_VSERVER,
+                          fake_client.SM_SOURCE_VOLUME,
+                          fake_client.SM_DEST_VSERVER,
+                          fake_client.SM_DEST_VOLUME)
+
+    def test_break_snapmirror(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.break_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_break_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-break', snapmirror_break_args)])
+
+    @ddt.data(
+        {
+            'schedule': 'fake_schedule',
+            'policy': 'fake_policy',
+            'tries': 5,
+            'max_transfer_rate': 1024,
+        },
+        {
+            'schedule': None,
+            'policy': None,
+            'tries': None,
+            'max_transfer_rate': None,
+        }
+    )
+    @ddt.unpack
+    def test_modify_snapmirror(self, schedule, policy, tries,
+                               max_transfer_rate):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.modify_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
+            schedule=schedule, policy=policy, tries=tries,
+            max_transfer_rate=max_transfer_rate)
+
+        snapmirror_modify_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+        }
+        if schedule:
+            snapmirror_modify_args['schedule'] = schedule
+        if policy:
+            snapmirror_modify_args['policy'] = policy
+        if tries:
+            snapmirror_modify_args['tries'] = tries
+        if max_transfer_rate:
+            snapmirror_modify_args['max-transfer-rate'] = max_transfer_rate
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-modify', snapmirror_modify_args)])
+
+    def test_delete_snapmirror(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.delete_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_delete_args = {
+            'query': {
+                'snapmirror-info': {
+                    'source-vserver': fake_client.SM_SOURCE_VSERVER,
+                    'source-volume': fake_client.SM_SOURCE_VOLUME,
+                    'destination-vserver': fake_client.SM_DEST_VSERVER,
+                    'destination-volume': fake_client.SM_DEST_VOLUME,
+                }
+            }
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-destroy-iter', snapmirror_delete_args)])
+
+    def test_update_snapmirror(self):
+
+        self.mock_object(self.client, 'send_request')
+
+        self.client.update_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_update_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-update', snapmirror_update_args)])
+
+    def test_update_snapmirror_already_transferring(self):
+        mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
+            code=netapp_api.ETRANSFER_IN_PROGRESS))
+        self.mock_object(self.client, 'send_request', mock_send_req)
+
+        self.client.update_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_update_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-update', snapmirror_update_args)])
+
+    def test_update_snapmirror_already_transferring_two(self):
+        mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
+            code=netapp_api.EANOTHER_OP_ACTIVE))
+        self.mock_object(self.client, 'send_request', mock_send_req)
+
+        self.client.update_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_update_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-update', snapmirror_update_args)])
+
+    def test_update_snapmirror_error(self):
+        mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
+        self.mock_object(self.client, 'send_request', mock_send_req)
+
+        self.assertRaises(netapp_api.NaApiError,
+                          self.client.update_snapmirror,
+                          fake_client.SM_SOURCE_VSERVER,
+                          fake_client.SM_SOURCE_VOLUME,
+                          fake_client.SM_DEST_VSERVER,
+                          fake_client.SM_DEST_VOLUME)
+
+    def test_resume_snapmirror(self):
+        self.mock_object(self.client, 'send_request')
+
+        self.client.resume_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_resume_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-resume', snapmirror_resume_args)])
+
+    def test_resume_snapmirror_not_quiesed(self):
+        mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(
+            code=netapp_api.ERELATION_NOT_QUIESCED))
+        self.mock_object(self.client, 'send_request', mock_send_req)
+
+        self.client.resume_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_resume_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-resume', snapmirror_resume_args)])
+
+    def test_resume_snapmirror_error(self):
+        mock_send_req = mock.Mock(side_effect=netapp_api.NaApiError(code=0))
+        self.mock_object(self.client, 'send_request', mock_send_req)
+
+        self.assertRaises(netapp_api.NaApiError,
+                          self.client.resume_snapmirror,
+                          fake_client.SM_SOURCE_VSERVER,
+                          fake_client.SM_SOURCE_VOLUME,
+                          fake_client.SM_DEST_VSERVER,
+                          fake_client.SM_DEST_VOLUME)
+
+    def test_resync_snapmirror(self):
+        self.mock_object(self.client, 'send_request')
+
+        self.client.resync_snapmirror(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME)
+
+        snapmirror_resync_args = {
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+        }
+        self.client.send_request.assert_has_calls([
+            mock.call('snapmirror-resync', snapmirror_resync_args)])
+
+    def test__get_snapmirrors(self):
+
+        api_response = netapp_api.NaElement(
+            fake_client.SNAPMIRROR_GET_ITER_RESPONSE)
+        self.mock_object(self.client,
+                         'send_iter_request',
+                         mock.Mock(return_value=api_response))
+
+        desired_attributes = {
+            'snapmirror-info': {
+                'source-vserver': None,
+                'source-volume': None,
+                'destination-vserver': None,
+                'destination-volume': None,
+                'is-healthy': None,
+            }
+        }
+
+        result = self.client._get_snapmirrors(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
+            desired_attributes=desired_attributes)
+
+        snapmirror_get_iter_args = {
+            'query': {
+                'snapmirror-info': {
+                    'source-vserver': fake_client.SM_SOURCE_VSERVER,
+                    'source-volume': fake_client.SM_SOURCE_VOLUME,
+                    'destination-vserver': fake_client.SM_DEST_VSERVER,
+                    'destination-volume': fake_client.SM_DEST_VOLUME,
+                },
+            },
+            'desired-attributes': {
+                'snapmirror-info': {
+                    'source-vserver': None,
+                    'source-volume': None,
+                    'destination-vserver': None,
+                    'destination-volume': None,
+                    'is-healthy': None,
+                },
+            },
+        }
+        self.client.send_iter_request.assert_has_calls([
+            mock.call('snapmirror-get-iter', snapmirror_get_iter_args)])
+        self.assertEqual(1, len(result))
+
+    def test__get_snapmirrors_not_found(self):
+
+        api_response = netapp_api.NaElement(fake_client.NO_RECORDS_RESPONSE)
+        self.mock_object(self.client,
+                         'send_iter_request',
+                         mock.Mock(return_value=api_response))
+
+        result = self.client._get_snapmirrors()
+
+        self.client.send_iter_request.assert_has_calls([
+            mock.call('snapmirror-get-iter', {})])
+
+        self.assertEqual([], result)
+
+    def test_get_snapmirrors(self):
+
+        api_response = netapp_api.NaElement(
+            fake_client.SNAPMIRROR_GET_ITER_FILTERED_RESPONSE)
+        self.mock_object(self.client,
+                         'send_iter_request',
+                         mock.Mock(return_value=api_response))
+
+        desired_attributes = ['source-vserver', 'source-volume',
+                              'destination-vserver', 'destination-volume',
+                              'is-healthy', 'mirror-state', 'schedule']
+
+        result = self.client.get_snapmirrors(
+            fake_client.SM_SOURCE_VSERVER, fake_client.SM_SOURCE_VOLUME,
+            fake_client.SM_DEST_VSERVER, fake_client.SM_DEST_VOLUME,
+            desired_attributes=desired_attributes)
+
+        snapmirror_get_iter_args = {
+            'query': {
+                'snapmirror-info': {
+                    'source-vserver': fake_client.SM_SOURCE_VSERVER,
+                    'source-volume': fake_client.SM_SOURCE_VOLUME,
+                    'destination-vserver': fake_client.SM_DEST_VSERVER,
+                    'destination-volume': fake_client.SM_DEST_VOLUME,
+                },
+            },
+            'desired-attributes': {
+                'snapmirror-info': {
+                    'source-vserver': None,
+                    'source-volume': None,
+                    'destination-vserver': None,
+                    'destination-volume': None,
+                    'is-healthy': None,
+                    'mirror-state': None,
+                    'schedule': None,
+                },
+            },
+        }
+
+        expected = [{
+            'source-vserver': fake_client.SM_SOURCE_VSERVER,
+            'source-volume': fake_client.SM_SOURCE_VOLUME,
+            'destination-vserver': fake_client.SM_DEST_VSERVER,
+            'destination-volume': fake_client.SM_DEST_VOLUME,
+            'is-healthy': 'true',
+            'mirror-state': 'snapmirrored',
+            'schedule': 'daily',
+        }]
+
+        self.client.send_iter_request.assert_has_calls([
+            mock.call('snapmirror-get-iter', snapmirror_get_iter_args)])
+        self.assertEqual(expected, result)
+
+    def test_get_provisioning_options_from_flexvol(self):
+
+        self.mock_object(self.client, 'get_flexvol',
+                         mock.Mock(return_value=fake_client.VOLUME_INFO_SSC))
+        self.mock_object(self.client, 'get_flexvol_dedupe_info', mock.Mock(
+            return_value=fake_client.VOLUME_DEDUPE_INFO_SSC))
+
+        expected_prov_opts = {
+            'aggregate': 'fake_aggr1',
+            'compression_enabled': False,
+            'dedupe_enabled': True,
+            'language': 'en_US',
+            'size': 1,
+            'snapshot_policy': 'default',
+            'snapshot_reserve': '5',
+            'space_guarantee_type': 'none',
+            'volume_type': 'rw'
+        }
+
+        actual_prov_opts = self.client.get_provisioning_options_from_flexvol(
+            fake_client.VOLUME_NAME)
+
+        self.assertEqual(expected_prov_opts, actual_prov_opts)
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py
index 0c84b7094b7..dbbac893ca0 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py
@@ -34,6 +34,7 @@ EXPORT_PATH = '/fake/export/path'
 NFS_SHARE = '%s:%s' % (SHARE_IP, EXPORT_PATH)
 HOST_STRING = '%s@%s#%s' % (HOST_NAME, BACKEND_NAME, POOL_NAME)
 NFS_HOST_STRING = '%s@%s#%s' % (HOST_NAME, BACKEND_NAME, NFS_SHARE)
+AGGREGATE = 'aggr1'
 FLEXVOL = 'openstack-flexvol'
 NFS_FILE_PATH = 'nfsvol'
 PATH = '/vol/%s/%s' % (POOL_NAME, LUN_NAME)
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/performance/test_perf_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/performance/test_perf_cmode.py
index da52f356a22..caf2bee9e96 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/performance/test_perf_cmode.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/performance/test_perf_cmode.py
@@ -331,6 +331,16 @@ class PerformanceCmodeLibraryTestCase(test.TestCase):
 
         self.assertAlmostEqual(expected, result)
 
+    def test__update_for_failover(self):
+        self.mock_object(self.perf_library, 'update_performance_cache')
+        mock_client = mock.Mock(name='FAKE_ZAPI_CLIENT')
+
+        self.perf_library._update_for_failover(mock_client, self.fake_volumes)
+
+        self.assertEqual(mock_client, self.perf_library.zapi_client)
+        self.perf_library.update_performance_cache.assert_called_once_with(
+            self.fake_volumes)
+
     def test_get_aggregates_for_pools(self):
 
         result = self.perf_library._get_aggregates_for_pools(self.fake_volumes)
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_7mode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_7mode.py
index 53471b57a27..a3f43b92b2b 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_7mode.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_7mode.py
@@ -47,7 +47,10 @@ class NetAppBlockStorage7modeLibraryTestCase(test.TestCase):
     def setUp(self):
         super(NetAppBlockStorage7modeLibraryTestCase, self).setUp()
 
-        kwargs = {'configuration': self.get_config_7mode()}
+        kwargs = {
+            'configuration': self.get_config_7mode(),
+            'host': 'openstack@7modeblock',
+        }
         self.library = block_7mode.NetAppBlockStorage7modeLibrary(
             'driver', 'protocol', **kwargs)
 
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_base.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_base.py
index a2294950414..253bda3b93f 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_base.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_base.py
@@ -28,11 +28,12 @@ import uuid
 import ddt
 import mock
 from oslo_log import versionutils
+from oslo_service import loopingcall
 from oslo_utils import units
 import six
 
 from cinder import exception
-from cinder.i18n import _, _LW
+from cinder.i18n import _LW
 from cinder import test
 from cinder.tests.unit.volume.drivers.netapp.dataontap import fakes as fake
 import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes
@@ -48,7 +49,10 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
     def setUp(self):
         super(NetAppBlockStorageLibraryTestCase, self).setUp()
 
-        kwargs = {'configuration': self.get_config_base()}
+        kwargs = {
+            'configuration': self.get_config_base(),
+            'host': 'openstack@netappblock',
+        }
         self.library = block_base.NetAppBlockStorageLibrary(
             'driver', 'protocol', **kwargs)
         self.library.zapi_client = mock.Mock()
@@ -740,11 +744,11 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
     def test_setup_error_invalid_lun_os(self):
         self.library.configuration.netapp_lun_ostype = 'unknown'
         self.library.do_setup(mock.Mock())
+
         self.assertRaises(exception.NetAppDriverException,
                           self.library.check_for_setup_error)
-        msg = _("Invalid value for NetApp configuration"
-                " option netapp_lun_ostype.")
-        block_base.LOG.error.assert_called_once_with(msg)
+
+        block_base.LOG.error.assert_called_once_with(mock.ANY)
 
     @mock.patch.object(na_utils, 'check_flags', mock.Mock())
     @mock.patch.object(block_base, 'LOG', mock.Mock())
@@ -756,9 +760,7 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
         self.assertRaises(exception.NetAppDriverException,
                           self.library.check_for_setup_error)
 
-        msg = _("Invalid value for NetApp configuration"
-                " option netapp_host_type.")
-        block_base.LOG.error.assert_called_once_with(msg)
+        block_base.LOG.error.assert_called_once_with(mock.ANY)
 
     @mock.patch.object(na_utils, 'check_flags', mock.Mock())
     def test_check_for_setup_error_both_config(self):
@@ -767,9 +769,13 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
         self.library.do_setup(mock.Mock())
         self.zapi_client.get_lun_list.return_value = ['lun1']
         self.library._extract_and_populate_luns = mock.Mock()
+        mock_start_periodic_tasks = self.mock_object(
+            self.library, '_start_periodic_tasks')
         self.library.check_for_setup_error()
+
         self.library._extract_and_populate_luns.assert_called_once_with(
             ['lun1'])
+        mock_start_periodic_tasks.assert_called_once_with()
 
     @mock.patch.object(na_utils, 'check_flags', mock.Mock())
     def test_check_for_setup_error_no_os_host(self):
@@ -778,9 +784,29 @@ class NetAppBlockStorageLibraryTestCase(test.TestCase):
         self.library.do_setup(mock.Mock())
         self.zapi_client.get_lun_list.return_value = ['lun1']
         self.library._extract_and_populate_luns = mock.Mock()
+        mock_start_periodic_tasks = self.mock_object(
+            self.library, '_start_periodic_tasks')
+
         self.library.check_for_setup_error()
         self.library._extract_and_populate_luns.assert_called_once_with(
             ['lun1'])
+        mock_start_periodic_tasks.assert_called_once_with()
+
+    def test_start_periodic_tasks(self):
+
+        mock_handle_housekeeping_tasks = self.mock_object(
+            self.library, '_handle_housekeeping_tasks')
+
+        housekeeping_periodic_task = mock.Mock()
+        mock_loopingcall = self.mock_object(
+            loopingcall, 'FixedIntervalLoopingCall',
+            mock.Mock(return_value=housekeeping_periodic_task))
+
+        self.library._start_periodic_tasks()
+
+        mock_loopingcall.assert_called_once_with(
+            mock_handle_housekeeping_tasks)
+        self.assertTrue(housekeeping_periodic_task.start.called)
 
     def test_delete_volume(self):
         mock_delete_lun = self.mock_object(self.library, '_delete_lun')
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py
index 335ae3df9f7..56036183ff0 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_block_cmode.py
@@ -25,12 +25,16 @@ from oslo_service import loopingcall
 from cinder import exception
 from cinder import test
 import cinder.tests.unit.volume.drivers.netapp.dataontap.fakes as fake
+from cinder.tests.unit.volume.drivers.netapp.dataontap.utils import fakes as\
+    fake_utils
 import cinder.tests.unit.volume.drivers.netapp.fakes as na_fakes
 from cinder.volume.drivers.netapp.dataontap import block_base
 from cinder.volume.drivers.netapp.dataontap import block_cmode
 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.performance import perf_cmode
+from cinder.volume.drivers.netapp.dataontap.utils import data_motion
+from cinder.volume.drivers.netapp.dataontap.utils import utils as config_utils
 from cinder.volume.drivers.netapp import utils as na_utils
 
 
@@ -41,7 +45,10 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
     def setUp(self):
         super(NetAppBlockStorageCmodeLibraryTestCase, self).setUp()
 
-        kwargs = {'configuration': self.get_config_cmode()}
+        kwargs = {
+            'configuration': self.get_config_cmode(),
+            'host': 'openstack@cdotblock',
+        }
         self.library = block_cmode.NetAppBlockStorageCmodeLibrary(
             'driver', 'protocol', **kwargs)
 
@@ -82,6 +89,9 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
     @mock.patch.object(block_base.NetAppBlockStorageLibrary, 'do_setup')
     def test_do_setup(self, super_do_setup, mock_check_flags):
         self.mock_object(client_base.Client, '_init_ssh_client')
+        self.mock_object(
+            config_utils, 'get_backend_configuration',
+            mock.Mock(return_value=self.get_config_cmode()))
         context = mock.Mock()
 
         self.library.do_setup(context)
@@ -94,8 +104,6 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
             block_base.NetAppBlockStorageLibrary, 'check_for_setup_error')
         mock_check_api_permissions = self.mock_object(
             self.library.ssc_library, 'check_api_permissions')
-        mock_start_periodic_tasks = self.mock_object(
-            self.library, '_start_periodic_tasks')
         mock_get_pool_map = self.mock_object(
             self.library, '_get_flexvol_to_pool_map',
             mock.Mock(return_value={'fake_map': None}))
@@ -104,7 +112,6 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
 
         self.assertEqual(1, super_check_for_setup_error.call_count)
         mock_check_api_permissions.assert_called_once_with()
-        self.assertEqual(1, mock_start_periodic_tasks.call_count)
         mock_get_pool_map.assert_called_once_with()
 
     def test_check_for_setup_error_no_filtered_pools(self):
@@ -112,7 +119,6 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
                          'check_for_setup_error')
         mock_check_api_permissions = self.mock_object(
             self.library.ssc_library, 'check_api_permissions')
-        self.mock_object(self.library, '_start_periodic_tasks')
         self.mock_object(
             self.library, '_get_flexvol_to_pool_map',
             mock.Mock(return_value={}))
@@ -122,6 +128,51 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
 
         mock_check_api_permissions.assert_called_once_with()
 
+    def test_start_periodic_tasks(self):
+
+        mock_update_ssc = self.mock_object(
+            self.library, '_update_ssc')
+        super_start_periodic_tasks = self.mock_object(
+            block_base.NetAppBlockStorageLibrary, '_start_periodic_tasks')
+
+        update_ssc_periodic_task = mock.Mock()
+        mock_loopingcall = self.mock_object(
+            loopingcall, 'FixedIntervalLoopingCall',
+            mock.Mock(return_value=update_ssc_periodic_task))
+
+        self.library._start_periodic_tasks()
+
+        mock_loopingcall.assert_called_once_with(mock_update_ssc)
+        self.assertTrue(update_ssc_periodic_task.start.called)
+        mock_update_ssc.assert_called_once_with()
+        super_start_periodic_tasks.assert_called_once_with()
+
+    @ddt.data({'replication_enabled': True, 'failed_over': False},
+              {'replication_enabled': True, 'failed_over': True},
+              {'replication_enabled': False, 'failed_over': False})
+    @ddt.unpack
+    def test_handle_housekeeping_tasks(self, replication_enabled, failed_over):
+        ensure_mirrors = self.mock_object(data_motion.DataMotionMixin,
+                                          'ensure_snapmirrors')
+        self.mock_object(self.library.ssc_library, 'get_ssc_flexvol_names',
+                         mock.Mock(return_value=fake_utils.SSC.keys()))
+        self.library.replication_enabled = replication_enabled
+        self.library.failed_over = failed_over
+        super_handle_housekeeping_tasks = self.mock_object(
+            block_base.NetAppBlockStorageLibrary, '_handle_housekeeping_tasks')
+
+        self.library._handle_housekeeping_tasks()
+
+        super_handle_housekeeping_tasks.assert_called_once_with()
+        (self.zapi_client.remove_unused_qos_policy_groups.
+         assert_called_once_with())
+        if replication_enabled and not failed_over:
+            ensure_mirrors.assert_called_once_with(
+                self.library.configuration, self.library.backend_name,
+                fake_utils.SSC.keys())
+        else:
+            self.assertFalse(ensure_mirrors.called)
+
     def test_find_mapped_lun_igroup(self):
         igroups = [fake.IGROUP1]
         self.zapi_client.get_igroup_by_initiators.return_value = igroups
@@ -590,25 +641,67 @@ class NetAppBlockStorageCmodeLibraryTestCase(test.TestCase):
         self.zapi_client.move_lun.assert_called_once_with(
             '/vol/FAKE_CMODE_VOL1/name', '/vol/FAKE_CMODE_VOL1/volume')
 
-    def test_start_periodic_tasks(self):
+    @ddt.data({'secondary_id': 'dev0', 'configured_targets': ['dev1']},
+              {'secondary_id': 'dev3', 'configured_targets': ['dev1', 'dev2']},
+              {'secondary_id': 'dev1', 'configured_targets': []},
+              {'secondary_id': None, 'configured_targets': []})
+    @ddt.unpack
+    def test_failover_host_invalid_replication_target(self, secondary_id,
+                                                      configured_targets):
+        """This tests executes a method in the DataMotionMixin."""
+        self.library.backend_name = 'dev0'
+        self.mock_object(data_motion.DataMotionMixin,
+                         'get_replication_backend_names',
+                         mock.Mock(return_value=configured_targets))
+        complete_failover_call = self.mock_object(
+            data_motion.DataMotionMixin, '_complete_failover')
 
-        mock_update_ssc = self.mock_object(
-            self.library, '_update_ssc')
-        mock_remove_unused_qos_policy_groups = self.mock_object(
-            self.zapi_client, 'remove_unused_qos_policy_groups')
+        self.assertRaises(exception.InvalidReplicationTarget,
+                          self.library.failover_host, 'fake_context', [],
+                          secondary_id=secondary_id)
+        self.assertFalse(complete_failover_call.called)
 
-        update_ssc_periodic_task = mock.Mock()
-        harvest_qos_periodic_task = mock.Mock()
-        side_effect = [update_ssc_periodic_task, harvest_qos_periodic_task]
-        mock_loopingcall = self.mock_object(
-            loopingcall, 'FixedIntervalLoopingCall',
-            mock.Mock(side_effect=side_effect))
+    def test_failover_host_unable_to_failover(self):
+        """This tests executes a method in the DataMotionMixin."""
+        self.library.backend_name = 'dev0'
+        self.mock_object(
+            data_motion.DataMotionMixin, '_complete_failover',
+            mock.Mock(side_effect=exception.NetAppDriverException))
+        self.mock_object(data_motion.DataMotionMixin,
+                         'get_replication_backend_names',
+                         mock.Mock(return_value=['dev1', 'dev2']))
+        self.mock_object(self.library.ssc_library, 'get_ssc_flexvol_names',
+                         mock.Mock(return_value=fake_utils.SSC.keys()))
+        self.mock_object(self.library, '_update_zapi_client')
 
-        self.library._start_periodic_tasks()
+        self.assertRaises(exception.UnableToFailOver,
+                          self.library.failover_host, 'fake_context', [],
+                          secondary_id='dev1')
+        data_motion.DataMotionMixin._complete_failover.assert_called_once_with(
+            'dev0', ['dev1', 'dev2'], fake_utils.SSC.keys(), [],
+            failover_target='dev1')
+        self.assertFalse(self.library._update_zapi_client.called)
 
-        mock_loopingcall.assert_has_calls([
-            mock.call(mock_update_ssc),
-            mock.call(mock_remove_unused_qos_policy_groups)])
-        self.assertTrue(update_ssc_periodic_task.start.called)
-        self.assertTrue(harvest_qos_periodic_task.start.called)
-        mock_update_ssc.assert_called_once_with()
+    def test_failover_host(self):
+        """This tests executes a method in the DataMotionMixin."""
+        self.library.backend_name = 'dev0'
+        self.mock_object(data_motion.DataMotionMixin, '_complete_failover',
+                         mock.Mock(return_value=('dev1', [])))
+        self.mock_object(data_motion.DataMotionMixin,
+                         'get_replication_backend_names',
+                         mock.Mock(return_value=['dev1', 'dev2']))
+        self.mock_object(self.library.ssc_library, 'get_ssc_flexvol_names',
+                         mock.Mock(return_value=fake_utils.SSC.keys()))
+        self.mock_object(self.library, '_update_zapi_client')
+
+        actual_active, vol_updates = self.library.failover_host(
+            'fake_context', [], secondary_id='dev1')
+
+        data_motion.DataMotionMixin._complete_failover.assert_called_once_with(
+            'dev0', ['dev1', 'dev2'], fake_utils.SSC.keys(), [],
+            failover_target='dev1')
+        self.library._update_zapi_client.assert_called_once_with('dev1')
+        self.assertTrue(self.library.failed_over)
+        self.assertEqual('dev1', self.library.failed_over_backend_name)
+        self.assertEqual('dev1', actual_active)
+        self.assertEqual([], vol_updates)
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_7mode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_7mode.py
index 8f666c0bd7e..82f62b5f081 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_7mode.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_7mode.py
@@ -33,7 +33,10 @@ class NetApp7modeNfsDriverTestCase(test.TestCase):
     def setUp(self):
         super(NetApp7modeNfsDriverTestCase, self).setUp()
 
-        kwargs = {'configuration': self.get_config_7mode()}
+        kwargs = {
+            'configuration': self.get_config_7mode(),
+            'host': 'openstack@7modenfs',
+        }
 
         with mock.patch.object(utils, 'get_root_helper',
                                return_value=mock.Mock()):
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py
index 10cb64dd63e..9e68320da92 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py
@@ -25,6 +25,7 @@ import ddt
 import mock
 from os_brick.remotefs import remotefs as remotefs_brick
 from oslo_concurrency import processutils
+from oslo_service import loopingcall
 from oslo_utils import units
 import shutil
 
@@ -56,7 +57,10 @@ class NetAppNfsDriverTestCase(test.TestCase):
         self.fake_mount_point = fake.MOUNT_POINT
         self.ctxt = context.RequestContext('fake', 'fake', auth_token=True)
 
-        kwargs = {'configuration': configuration}
+        kwargs = {
+            'configuration': configuration,
+            'host': 'openstack@netappnfs',
+        }
 
         with mock.patch.object(utils, 'get_root_helper',
                                return_value=mock.Mock()):
@@ -92,6 +96,33 @@ class NetAppNfsDriverTestCase(test.TestCase):
         self.assertEqual(expected_reserved_percentage,
                          round(result['reserved_percentage']))
 
+    def test_check_for_setup_error(self):
+        super_check_for_setup_error = self.mock_object(
+            nfs.NfsDriver, 'check_for_setup_error')
+        mock_start_periodic_tasks = self.mock_object(
+            self.driver, '_start_periodic_tasks')
+
+        self.driver.check_for_setup_error()
+
+        super_check_for_setup_error.assert_called_once_with()
+        mock_start_periodic_tasks.assert_called_once_with()
+
+    def test_start_periodic_tasks(self):
+
+        mock_handle_housekeeping_tasks = self.mock_object(
+            self.driver, '_handle_housekeeping_tasks')
+
+        housekeeping_periodic_task = mock.Mock()
+        mock_loopingcall = self.mock_object(
+            loopingcall, 'FixedIntervalLoopingCall',
+            mock.Mock(return_value=housekeeping_periodic_task))
+
+        self.driver._start_periodic_tasks()
+
+        mock_loopingcall.assert_called_once_with(
+            mock_handle_housekeeping_tasks)
+        self.assertTrue(housekeeping_periodic_task.start.called)
+
     def test_get_capacity_info_ipv4_share(self):
         expected = fake.CAPACITY_VALUES
         self.driver.zapi_client = mock.Mock()
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py
index 63c97936d2d..2cdb6c0f233 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py
@@ -35,6 +35,8 @@ from cinder.volume.drivers.netapp.dataontap.client import client_cmode
 from cinder.volume.drivers.netapp.dataontap import nfs_base
 from cinder.volume.drivers.netapp.dataontap import nfs_cmode
 from cinder.volume.drivers.netapp.dataontap.performance import perf_cmode
+from cinder.volume.drivers.netapp.dataontap.utils import data_motion
+from cinder.volume.drivers.netapp.dataontap.utils import utils as config_utils
 from cinder.volume.drivers.netapp import utils as na_utils
 from cinder.volume.drivers import nfs
 from cinder.volume import utils as volume_utils
@@ -45,7 +47,10 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
     def setUp(self):
         super(NetAppCmodeNfsDriverTestCase, self).setUp()
 
-        kwargs = {'configuration': self.get_config_cmode()}
+        kwargs = {
+            'configuration': self.get_config_cmode(),
+            'host': 'openstack@nfscmode',
+        }
 
         with mock.patch.object(utils, 'get_root_helper',
                                return_value=mock.Mock()):
@@ -72,11 +77,42 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
         config.netapp_copyoffload_tool_path = 'copyoffload_tool_path'
         return config
 
+    @ddt.data({'active_backend_id': None, 'targets': ['dev1', 'dev2']},
+              {'active_backend_id': None, 'targets': []},
+              {'active_backend_id': 'dev1', 'targets': []},
+              {'active_backend_id': 'dev1', 'targets': ['dev1', 'dev2']})
+    @ddt.unpack
+    def test_init_driver_for_replication(self, active_backend_id,
+                                         targets):
+        kwargs = {
+            'configuration': self.get_config_cmode(),
+            'host': 'openstack@nfscmode',
+            'active_backend_id': active_backend_id,
+        }
+        self.mock_object(data_motion.DataMotionMixin,
+                         'get_replication_backend_names',
+                         mock.Mock(return_value=targets))
+        with mock.patch.object(utils, 'get_root_helper',
+                               return_value=mock.Mock()):
+            with mock.patch.object(remotefs_brick, 'RemoteFsClient',
+                                   return_value=mock.Mock()):
+                nfs_driver = nfs_cmode.NetAppCmodeNfsDriver(**kwargs)
+
+                self.assertEqual(active_backend_id,
+                                 nfs_driver.failed_over_backend_name)
+                self.assertEqual(active_backend_id is not None,
+                                 nfs_driver.failed_over)
+                self.assertEqual(len(targets) > 0,
+                                 nfs_driver.replication_enabled)
+
     @mock.patch.object(perf_cmode, 'PerformanceCmodeLibrary', mock.Mock())
     @mock.patch.object(client_cmode, 'Client', mock.Mock())
     @mock.patch.object(nfs.NfsDriver, 'do_setup')
     @mock.patch.object(na_utils, 'check_flags')
     def test_do_setup(self, mock_check_flags, mock_super_do_setup):
+        self.mock_object(
+            config_utils, 'get_backend_configuration',
+            mock.Mock(return_value=self.get_config_cmode()))
         self.driver.do_setup(mock.Mock())
 
         self.assertTrue(mock_check_flags.called)
@@ -94,6 +130,7 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
             'driver_version': self.driver.VERSION,
             'pools': {},
             'sparse_copy_volume': True,
+            'replication_enabled': False,
             'storage_protocol': 'nfs',
             'vendor_name': 'NetApp',
             'volume_backend_name': 'NetApp_NFS_Cluster_direct',
@@ -342,8 +379,6 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
     def test_check_for_setup_error(self):
         super_check_for_setup_error = self.mock_object(
             nfs_base.NetAppNfsDriver, 'check_for_setup_error')
-        mock_start_periodic_tasks = self.mock_object(
-            self.driver, '_start_periodic_tasks')
         mock_check_api_permissions = self.mock_object(
             self.driver.ssc_library, 'check_api_permissions')
 
@@ -351,7 +386,51 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
 
         self.assertEqual(1, super_check_for_setup_error.call_count)
         mock_check_api_permissions.assert_called_once_with()
-        self.assertEqual(1, mock_start_periodic_tasks.call_count)
+
+    def test_start_periodic_tasks(self):
+
+        mock_update_ssc = self.mock_object(
+            self.driver, '_update_ssc')
+        super_start_periodic_tasks = self.mock_object(
+            nfs_base.NetAppNfsDriver, '_start_periodic_tasks')
+
+        update_ssc_periodic_task = mock.Mock()
+        mock_loopingcall = self.mock_object(
+            loopingcall, 'FixedIntervalLoopingCall',
+            mock.Mock(return_value=update_ssc_periodic_task))
+
+        self.driver._start_periodic_tasks()
+
+        mock_loopingcall.assert_called_once_with(mock_update_ssc)
+        self.assertTrue(update_ssc_periodic_task.start.called)
+        mock_update_ssc.assert_called_once_with()
+        super_start_periodic_tasks.assert_called_once_with()
+
+    @ddt.data({'replication_enabled': True, 'failed_over': False},
+              {'replication_enabled': True, 'failed_over': True},
+              {'replication_enabled': False, 'failed_over': False})
+    @ddt.unpack
+    def test_handle_housekeeping_tasks(self, replication_enabled, failed_over):
+        ensure_mirrors = self.mock_object(data_motion.DataMotionMixin,
+                                          'ensure_snapmirrors')
+        self.mock_object(self.driver.ssc_library, 'get_ssc_flexvol_names',
+                         mock.Mock(return_value=fake_ssc.SSC.keys()))
+        self.driver.replication_enabled = replication_enabled
+        self.driver.failed_over = failed_over
+        super_handle_housekeeping_tasks = self.mock_object(
+            nfs_base.NetAppNfsDriver, '_handle_housekeeping_tasks')
+
+        self.driver._handle_housekeeping_tasks()
+
+        super_handle_housekeeping_tasks.assert_called_once_with()
+        (self.driver.zapi_client.remove_unused_qos_policy_groups.
+         assert_called_once_with())
+        if replication_enabled and not failed_over:
+            ensure_mirrors.assert_called_once_with(
+                self.driver.configuration, self.driver.backend_name,
+                fake_ssc.SSC.keys())
+        else:
+            self.assertFalse(ensure_mirrors.called)
 
     def test_delete_volume(self):
         fake_provider_location = 'fake_provider_location'
@@ -836,29 +915,6 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
         mock_get_info.assert_has_calls([mock.call(fake.NFS_VOLUME)])
         super_unmanage.assert_has_calls([mock.call(fake.NFS_VOLUME)])
 
-    def test_start_periodic_tasks(self):
-
-        mock_update_ssc = self.mock_object(self.driver, '_update_ssc')
-        mock_remove_unused_qos_policy_groups = self.mock_object(
-            self.driver.zapi_client,
-            'remove_unused_qos_policy_groups')
-
-        update_ssc_periodic_task = mock.Mock()
-        harvest_qos_periodic_task = mock.Mock()
-        side_effect = [update_ssc_periodic_task, harvest_qos_periodic_task]
-        mock_loopingcall = self.mock_object(
-            loopingcall, 'FixedIntervalLoopingCall',
-            mock.Mock(side_effect=side_effect))
-
-        self.driver._start_periodic_tasks()
-
-        mock_loopingcall.assert_has_calls([
-            mock.call(mock_update_ssc),
-            mock.call(mock_remove_unused_qos_policy_groups)])
-        self.assertTrue(update_ssc_periodic_task.start.called)
-        self.assertTrue(harvest_qos_periodic_task.start.called)
-        mock_update_ssc.assert_called_once_with()
-
     @ddt.data({'has_space': True, 'type_match': True, 'expected': True},
               {'has_space': True, 'type_match': False, 'expected': False},
               {'has_space': False, 'type_match': True, 'expected': False},
@@ -1220,3 +1276,68 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase):
         self.driver._copy_from_remote_cache.assert_called_once_with(
             fake.VOLUME, fake.IMAGE_FILE_ID, cache_result[0])
         self.assertFalse(self.driver._post_clone_image.called)
+
+    @ddt.data({'secondary_id': 'dev0', 'configured_targets': ['dev1']},
+              {'secondary_id': 'dev3', 'configured_targets': ['dev1', 'dev2']},
+              {'secondary_id': 'dev1', 'configured_targets': []},
+              {'secondary_id': None, 'configured_targets': []})
+    @ddt.unpack
+    def test_failover_host_invalid_replication_target(self, secondary_id,
+                                                      configured_targets):
+        """This tests executes a method in the DataMotionMixin."""
+        self.driver.backend_name = 'dev0'
+        self.mock_object(data_motion.DataMotionMixin,
+                         'get_replication_backend_names',
+                         mock.Mock(return_value=configured_targets))
+        complete_failover_call = self.mock_object(
+            data_motion.DataMotionMixin, '_complete_failover')
+
+        self.assertRaises(exception.InvalidReplicationTarget,
+                          self.driver.failover_host, 'fake_context', [],
+                          secondary_id=secondary_id)
+        self.assertFalse(complete_failover_call.called)
+
+    def test_failover_host_unable_to_failover(self):
+        """This tests executes a method in the DataMotionMixin."""
+        self.driver.backend_name = 'dev0'
+        self.mock_object(
+            data_motion.DataMotionMixin, '_complete_failover',
+            mock.Mock(side_effect=exception.NetAppDriverException))
+        self.mock_object(data_motion.DataMotionMixin,
+                         'get_replication_backend_names',
+                         mock.Mock(return_value=['dev1', 'dev2']))
+        self.mock_object(self.driver.ssc_library, 'get_ssc_flexvol_names',
+                         mock.Mock(return_value=fake_ssc.SSC.keys()))
+        self.mock_object(self.driver, '_update_zapi_client')
+
+        self.assertRaises(exception.UnableToFailOver,
+                          self.driver.failover_host, 'fake_context', [],
+                          secondary_id='dev1')
+        data_motion.DataMotionMixin._complete_failover.assert_called_once_with(
+            'dev0', ['dev1', 'dev2'], fake_ssc.SSC.keys(), [],
+            failover_target='dev1')
+        self.assertFalse(self.driver._update_zapi_client.called)
+
+    def test_failover_host(self):
+        """This tests executes a method in the DataMotionMixin."""
+        self.driver.backend_name = 'dev0'
+        self.mock_object(data_motion.DataMotionMixin, '_complete_failover',
+                         mock.Mock(return_value=('dev1', [])))
+        self.mock_object(data_motion.DataMotionMixin,
+                         'get_replication_backend_names',
+                         mock.Mock(return_value=['dev1', 'dev2']))
+        self.mock_object(self.driver.ssc_library, 'get_ssc_flexvol_names',
+                         mock.Mock(return_value=fake_ssc.SSC.keys()))
+        self.mock_object(self.driver, '_update_zapi_client')
+
+        actual_active, vol_updates = self.driver.failover_host(
+            'fake_context', [], secondary_id='dev1')
+
+        data_motion.DataMotionMixin._complete_failover.assert_called_once_with(
+            'dev0', ['dev1', 'dev2'], fake_ssc.SSC.keys(), [],
+            failover_target='dev1')
+        self.driver._update_zapi_client.assert_called_once_with('dev1')
+        self.assertTrue(self.driver.failed_over)
+        self.assertEqual('dev1', self.driver.failed_over_backend_name)
+        self.assertEqual('dev1', actual_active)
+        self.assertEqual([], vol_updates)
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/fakes.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/fakes.py
index 5d2abceaafe..e104f9098ed 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/fakes.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/fakes.py
@@ -13,6 +13,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from cinder.volume import configuration
+from cinder.volume import driver
+from cinder.volume.drivers.netapp import options as na_opts
+
 SSC_VSERVER = 'fake_vserver'
 SSC_VOLUMES = ('volume1', 'volume2')
 SSC_VOLUME_MAP = {
@@ -101,3 +105,31 @@ SSC_AGGREGATE_INFO = {
         'netapp_hybrid_aggregate': True,
     },
 }
+
+PROVISIONING_OPTS = {
+    'aggregate': 'fake_aggregate',
+    'thin_provisioned': True,
+    'snapshot_policy': None,
+    'language': 'en_US',
+    'dedupe_enabled': False,
+    'compression_enabled': False,
+    'snapshot_reserve': '12',
+    'volume_type': 'rw',
+    'size': 20,
+}
+
+
+def get_fake_cmode_config(backend_name):
+
+    config = configuration.Configuration(driver.volume_opts,
+                                         config_group=backend_name)
+    config.append_config_values(na_opts.netapp_proxy_opts)
+    config.append_config_values(na_opts.netapp_connection_opts)
+    config.append_config_values(na_opts.netapp_transport_opts)
+    config.append_config_values(na_opts.netapp_basicauth_opts)
+    config.append_config_values(na_opts.netapp_provisioning_opts)
+    config.append_config_values(na_opts.netapp_cluster_opts)
+    config.append_config_values(na_opts.netapp_san_opts)
+    config.append_config_values(na_opts.netapp_replication_opts)
+
+    return config
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_capabilities.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_capabilities.py
index fbb82a2ac9b..d8df5833af2 100644
--- a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_capabilities.py
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_capabilities.py
@@ -153,6 +153,16 @@ class CapabilitiesLibraryTestCase(test.TestCase):
         mock_get_ssc_aggregate_info.assert_has_calls([
             mock.call('aggr1'), mock.call('aggr2')])
 
+    def test__update_for_failover(self):
+        self.mock_object(self.ssc_library, 'update_ssc')
+        flexvol_map = {'volume1': fake.SSC_VOLUME_MAP['volume1']}
+        mock_client = mock.Mock(name='FAKE_ZAPI_CLIENT')
+
+        self.ssc_library._update_for_failover(mock_client, flexvol_map)
+
+        self.assertEqual(mock_client, self.ssc_library.zapi_client)
+        self.ssc_library.update_ssc.assert_called_once_with(flexvol_map)
+
     @ddt.data({'lun_space_guarantee': True},
               {'lun_space_guarantee': False})
     @ddt.unpack
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py
new file mode 100644
index 00000000000..6906d9f33d5
--- /dev/null
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_data_motion.py
@@ -0,0 +1,749 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import time
+
+import copy
+import ddt
+import mock
+from oslo_config import cfg
+
+from cinder import exception
+from cinder import test
+from cinder.tests.unit.volume.drivers.netapp.dataontap.utils import fakes
+from cinder.volume import configuration
+from cinder.volume import driver
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
+from cinder.volume.drivers.netapp.dataontap.client import client_cmode
+from cinder.volume.drivers.netapp.dataontap.utils import data_motion
+from cinder.volume.drivers.netapp.dataontap.utils import utils
+from cinder.volume.drivers.netapp import options as na_opts
+
+
+CONF = cfg.CONF
+
+
+@ddt.ddt
+class NetAppCDOTDataMotionMixinTestCase(test.TestCase):
+
+    def setUp(self):
+        super(NetAppCDOTDataMotionMixinTestCase, self).setUp()
+        self.dm_mixin = data_motion.DataMotionMixin()
+        self.src_backend = 'backend1'
+        self.dest_backend = 'backend2'
+        self.src_vserver = 'source_vserver'
+        self.dest_vserver = 'dest_vserver'
+        self._setup_mock_config()
+        self.mock_cmode_client = self.mock_object(client_cmode, 'Client')
+        self.src_flexvol_name = 'volume_c02d497a_236c_4852_812a_0d39373e312a'
+        self.dest_flexvol_name = self.src_flexvol_name
+        self.mock_src_client = mock.Mock()
+        self.mock_dest_client = mock.Mock()
+        self.config = fakes.get_fake_cmode_config(self.src_backend)
+        self.mock_object(utils, 'get_backend_configuration',
+                         mock.Mock(side_effect=[self.mock_dest_config,
+                                                self.mock_src_config]))
+        self.mock_object(utils, 'get_client_for_backend',
+                         mock.Mock(side_effect=[self.mock_dest_client,
+                                                self.mock_src_client]))
+
+    def _setup_mock_config(self):
+        self.mock_src_config = configuration.Configuration(
+            driver.volume_opts, config_group=self.src_backend)
+        self.mock_dest_config = configuration.Configuration(
+            driver.volume_opts, config_group=self.dest_backend)
+
+        for config in (self.mock_src_config, self.mock_dest_config):
+            config.append_config_values(na_opts.netapp_proxy_opts)
+            config.append_config_values(na_opts.netapp_connection_opts)
+            config.append_config_values(na_opts.netapp_transport_opts)
+            config.append_config_values(na_opts.netapp_basicauth_opts)
+            config.append_config_values(na_opts.netapp_provisioning_opts)
+            config.append_config_values(na_opts.netapp_cluster_opts)
+            config.append_config_values(na_opts.netapp_san_opts)
+            config.append_config_values(na_opts.netapp_replication_opts)
+            config.netapp_snapmirror_quiesce_timeout = 10
+
+        CONF.set_override('netapp_vserver', self.src_vserver,
+                          group=self.src_backend, enforce_type=True)
+        CONF.set_override('netapp_vserver', self.dest_vserver,
+                          group=self.dest_backend, enforce_type=True)
+
+    @ddt.data(None, [], [{'some_key': 'some_value'}])
+    def test_get_replication_backend_names_none(self, replication_device):
+        CONF.set_override('replication_device', replication_device,
+                          group=self.src_backend, enforce_type=True)
+
+        devices = self.dm_mixin.get_replication_backend_names(self.config)
+
+        self.assertEqual(0, len(devices))
+
+    @ddt.data([{'backend_id': 'xyzzy'}, {'backend_id': 'spoon!'}],
+              [{'backend_id': 'foobar'}])
+    def test_get_replication_backend_names_valid(self, replication_device):
+        CONF.set_override('replication_device', replication_device,
+                          group=self.src_backend, enforce_type=True)
+
+        devices = self.dm_mixin.get_replication_backend_names(self.config)
+
+        self.assertEqual(len(replication_device), len(devices))
+
+    def test_get_snapmirrors(self):
+        self.mock_object(self.mock_dest_client, 'get_snapmirrors')
+
+        self.dm_mixin.get_snapmirrors(self.src_backend,
+                                      self.dest_backend,
+                                      self.src_flexvol_name,
+                                      self.dest_flexvol_name)
+
+        self.mock_dest_client.get_snapmirrors.assert_called_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name,
+            desired_attributes=['relationship-status',
+                                'mirror-state',
+                                'source-vserver',
+                                'source-volume',
+                                'destination-vserver',
+                                'destination-volume',
+                                'last-transfer-end-timestamp',
+                                'lag-time'])
+        self.assertEqual(1, self.mock_dest_client.get_snapmirrors.call_count)
+
+    @ddt.data([], ['backend1'], ['backend1', 'backend2'])
+    def test_get_replication_backend_stats(self, replication_backend_names):
+        self.mock_object(self.dm_mixin, 'get_replication_backend_names',
+                         mock.Mock(return_value=replication_backend_names))
+        enabled_stats = {
+            'replication_count': len(replication_backend_names),
+            'replication_targets': replication_backend_names,
+            'replication_type': 'async',
+        }
+        expected_stats = {
+            'replication_enabled': len(replication_backend_names) > 0,
+        }
+        if len(replication_backend_names) > 0:
+            expected_stats.update(enabled_stats)
+
+        actual_stats = self.dm_mixin.get_replication_backend_stats(self.config)
+
+        self.assertDictMatch(expected_stats, actual_stats)
+
+    @ddt.data(None, [],
+              [{'backend_id': 'replication_backend_2', 'aggr2': 'aggr20'}])
+    def test_get_replication_aggregate_map_none(self, replication_aggr_map):
+
+        self.mock_object(utils, 'get_backend_configuration',
+                         mock.Mock(return_value=self.config))
+        CONF.set_override('netapp_replication_aggregate_map',
+                          replication_aggr_map,
+                          group=self.src_backend, enforce_type=True)
+
+        aggr_map = self.dm_mixin._get_replication_aggregate_map(
+            self.src_backend, 'replication_backend_1')
+
+        self.assertEqual(0, len(aggr_map))
+
+    @ddt.data([{'backend_id': 'replication_backend_1', 'aggr1': 'aggr10'}],
+              [{'backend_id': 'replication_backend_1', 'aggr1': 'aggr10'},
+               {'backend_id': 'replication_backend_2', 'aggr2': 'aggr20'}])
+    def test_get_replication_aggregate_map_valid(self, replication_aggr_map):
+        self.mock_object(utils, 'get_backend_configuration',
+                         mock.Mock(return_value=self.config))
+        CONF.set_override('netapp_replication_aggregate_map',
+                          replication_aggr_map, group=self.src_backend,
+                          enforce_type=True)
+
+        aggr_map = self.dm_mixin._get_replication_aggregate_map(
+            self.src_backend, 'replication_backend_1')
+
+        self.assertDictMatch({'aggr1': 'aggr10'}, aggr_map)
+
+    @ddt.data(True, False)
+    def test_create_snapmirror_dest_flexvol_exists(self, dest_exists):
+        mock_dest_client = mock.Mock()
+        self.mock_object(mock_dest_client, 'flexvol_exists',
+                         mock.Mock(return_value=dest_exists))
+        self.mock_object(mock_dest_client, 'get_snapmirrors',
+                         mock.Mock(return_value=None))
+        create_destination_flexvol = self.mock_object(
+            self.dm_mixin, 'create_destination_flexvol')
+        self.mock_object(utils, 'get_client_for_backend',
+                         mock.Mock(return_value=mock_dest_client))
+
+        self.dm_mixin.create_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        if not dest_exists:
+            create_destination_flexvol.assert_called_once_with(
+                self.src_backend, self.dest_backend, self.src_flexvol_name,
+                self.dest_flexvol_name)
+        else:
+            self.assertFalse(create_destination_flexvol.called)
+        mock_dest_client.create_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name, schedule='hourly')
+        mock_dest_client.initialize_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+
+    @ddt.data('uninitialized', 'broken-off', 'snapmirrored')
+    def test_create_snapmirror_snapmirror_exists_state(self, mirror_state):
+        mock_dest_client = mock.Mock()
+        existing_snapmirrors = [{'mirror-state': mirror_state}]
+        self.mock_object(self.dm_mixin, 'create_destination_flexvol')
+        self.mock_object(utils, 'get_client_for_backend',
+                         mock.Mock(return_value=mock_dest_client))
+        self.mock_object(mock_dest_client, 'flexvol_exists',
+                         mock.Mock(return_value=True))
+        self.mock_object(mock_dest_client, 'get_snapmirrors',
+                         mock.Mock(return_value=existing_snapmirrors))
+
+        self.dm_mixin.create_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        self.assertFalse(mock_dest_client.create_snapmirror.called)
+        self.assertFalse(mock_dest_client.initialize_snapmirror.called)
+        self.assertFalse(self.dm_mixin.create_destination_flexvol.called)
+        if mirror_state == 'snapmirrored':
+            self.assertFalse(mock_dest_client.resume_snapmirror.called)
+            self.assertFalse(mock_dest_client.resync_snapmirror.called)
+        else:
+            mock_dest_client.resume_snapmirror.assert_called_once_with(
+                self.src_vserver, self.src_flexvol_name,
+                self.dest_vserver, self.dest_flexvol_name)
+            mock_dest_client.resume_snapmirror.assert_called_once_with(
+                self.src_vserver, self.src_flexvol_name,
+                self.dest_vserver, self.dest_flexvol_name)
+
+    @ddt.data('resume_snapmirror', 'resync_snapmirror')
+    def test_create_snapmirror_snapmirror_exists_repair_exception(self,
+                                                                  failed_call):
+        mock_dest_client = mock.Mock()
+        mock_exception_log = self.mock_object(data_motion.LOG, 'exception')
+        existing_snapmirrors = [{'mirror-state': 'broken-off'}]
+        self.mock_object(self.dm_mixin, 'create_destination_flexvol')
+        self.mock_object(utils, 'get_client_for_backend',
+                         mock.Mock(return_value=mock_dest_client))
+        self.mock_object(mock_dest_client, 'flexvol_exists',
+                         mock.Mock(return_value=True))
+        self.mock_object(mock_dest_client, 'get_snapmirrors',
+                         mock.Mock(return_value=existing_snapmirrors))
+        self.mock_object(mock_dest_client, failed_call,
+                         mock.Mock(side_effect=netapp_api.NaApiError))
+
+        self.dm_mixin.create_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        self.assertFalse(mock_dest_client.create_snapmirror.called)
+        self.assertFalse(mock_dest_client.initialize_snapmirror.called)
+        self.assertFalse(self.dm_mixin.create_destination_flexvol.called)
+        mock_dest_client.resume_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name)
+        mock_dest_client.resume_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name)
+        self.assertEqual(1, mock_exception_log.call_count)
+
+    def test_delete_snapmirror(self):
+        mock_src_client = mock.Mock()
+        mock_dest_client = mock.Mock()
+        self.mock_object(utils, 'get_client_for_backend',
+                         mock.Mock(side_effect=[mock_dest_client,
+                                                mock_src_client]))
+
+        self.dm_mixin.delete_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        mock_dest_client.abort_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name, clear_checkpoint=False)
+        mock_dest_client.delete_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+        mock_src_client.release_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+
+    def test_delete_snapmirror_does_not_exist(self):
+        """Ensure delete succeeds when the snapmirror does not exist."""
+        mock_src_client = mock.Mock()
+        mock_dest_client = mock.Mock()
+        mock_dest_client.abort_snapmirror.side_effect = netapp_api.NaApiError(
+            code=netapp_api.EAPIERROR)
+        self.mock_object(utils, 'get_client_for_backend',
+                         mock.Mock(side_effect=[mock_dest_client,
+                                                mock_src_client]))
+
+        self.dm_mixin.delete_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        mock_dest_client.abort_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name, clear_checkpoint=False)
+        mock_dest_client.delete_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+        mock_src_client.release_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+
+    def test_delete_snapmirror_error_deleting(self):
+        """Ensure delete succeeds when the snapmirror does not exist."""
+        mock_src_client = mock.Mock()
+        mock_dest_client = mock.Mock()
+        mock_dest_client.delete_snapmirror.side_effect = netapp_api.NaApiError(
+            code=netapp_api.ESOURCE_IS_DIFFERENT
+        )
+        self.mock_object(utils, 'get_client_for_backend',
+                         mock.Mock(side_effect=[mock_dest_client,
+                                                mock_src_client]))
+
+        self.dm_mixin.delete_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        mock_dest_client.abort_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name, clear_checkpoint=False)
+        mock_dest_client.delete_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+        mock_src_client.release_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+
+    def test_delete_snapmirror_error_releasing(self):
+        """Ensure delete succeeds when the snapmirror does not exist."""
+        mock_src_client = mock.Mock()
+        mock_dest_client = mock.Mock()
+        mock_src_client.release_snapmirror.side_effect = (
+            netapp_api.NaApiError(code=netapp_api.EOBJECTNOTFOUND))
+        self.mock_object(utils, 'get_client_for_backend',
+                         mock.Mock(side_effect=[mock_dest_client,
+                                                mock_src_client]))
+
+        self.dm_mixin.delete_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        mock_dest_client.abort_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name, clear_checkpoint=False)
+        mock_dest_client.delete_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+        mock_src_client.release_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+
+    def test_delete_snapmirror_without_release(self):
+        mock_src_client = mock.Mock()
+        mock_dest_client = mock.Mock()
+        self.mock_object(utils, 'get_client_for_backend',
+                         mock.Mock(side_effect=[mock_dest_client,
+                                                mock_src_client]))
+
+        self.dm_mixin.delete_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name,
+                                        release=False)
+
+        mock_dest_client.abort_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name, clear_checkpoint=False)
+        mock_dest_client.delete_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+        self.assertFalse(mock_src_client.release_snapmirror.called)
+
+    def test_delete_snapmirror_source_unreachable(self):
+        mock_src_client = mock.Mock()
+        mock_dest_client = mock.Mock()
+        self.mock_object(utils, 'get_client_for_backend',
+                         mock.Mock(side_effect=[mock_dest_client,
+                                                Exception]))
+
+        self.dm_mixin.delete_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        mock_dest_client.abort_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name, clear_checkpoint=False)
+        mock_dest_client.delete_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+
+        self.assertFalse(mock_src_client.release_snapmirror.called)
+
+    def test_quiesce_then_abort_timeout(self):
+        self.mock_object(time, 'sleep')
+        mock_get_snapmirrors = mock.Mock(
+            return_value=[{'relationship-status': 'transferring'}])
+        self.mock_object(self.mock_dest_client, 'get_snapmirrors',
+                         mock_get_snapmirrors)
+
+        self.dm_mixin.quiesce_then_abort(self.src_backend,
+                                         self.dest_backend,
+                                         self.src_flexvol_name,
+                                         self.dest_flexvol_name)
+
+        self.mock_dest_client.get_snapmirrors.assert_called_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name,
+            desired_attributes=['relationship-status', 'mirror-state'])
+        self.assertEqual(2, self.mock_dest_client.get_snapmirrors.call_count)
+        self.mock_dest_client.quiesce_snapmirror.assert_called_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name)
+        self.mock_dest_client.abort_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name, clear_checkpoint=False)
+
+    def test_update_snapmirror(self):
+        self.mock_object(self.mock_dest_client, 'get_snapmirrors')
+
+        self.dm_mixin.update_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        self.mock_dest_client.update_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name)
+
+    def test_quiesce_then_abort_wait_for_quiesced(self):
+        self.mock_object(time, 'sleep')
+        self.mock_object(self.mock_dest_client, 'get_snapmirrors',
+                         mock.Mock(side_effect=[
+                             [{'relationship-status': 'transferring'}],
+                             [{'relationship-status': 'quiesced'}]]))
+
+        self.dm_mixin.quiesce_then_abort(self.src_backend,
+                                         self.dest_backend,
+                                         self.src_flexvol_name,
+                                         self.dest_flexvol_name)
+
+        self.mock_dest_client.get_snapmirrors.assert_called_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name,
+            desired_attributes=['relationship-status', 'mirror-state'])
+        self.assertEqual(2, self.mock_dest_client.get_snapmirrors.call_count)
+        self.mock_dest_client.quiesce_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name)
+
+    def test_break_snapmirror(self):
+        self.mock_object(self.dm_mixin, 'quiesce_then_abort')
+
+        self.dm_mixin.break_snapmirror(self.src_backend,
+                                       self.dest_backend,
+                                       self.src_flexvol_name,
+                                       self.dest_flexvol_name)
+
+        self.dm_mixin.quiesce_then_abort.assert_called_once_with(
+            self.src_backend, self.dest_backend,
+            self.src_flexvol_name, self.dest_flexvol_name)
+        self.mock_dest_client.break_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name)
+        self.mock_dest_client.mount_flexvol.assert_called_once_with(
+            self.dest_flexvol_name)
+
+    def test_break_snapmirror_wait_for_quiesced(self):
+        self.mock_object(self.dm_mixin, 'quiesce_then_abort')
+
+        self.dm_mixin.break_snapmirror(self.src_backend,
+                                       self.dest_backend,
+                                       self.src_flexvol_name,
+                                       self.dest_flexvol_name)
+
+        self.dm_mixin.quiesce_then_abort.assert_called_once_with(
+            self.src_backend, self.dest_backend,
+            self.src_flexvol_name, self.dest_flexvol_name,)
+        self.mock_dest_client.break_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name)
+        self.mock_dest_client.mount_flexvol.assert_called_once_with(
+            self.dest_flexvol_name)
+
+    def test_resync_snapmirror(self):
+        self.dm_mixin.resync_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        self.mock_dest_client.resync_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name,
+            self.dest_vserver, self.dest_flexvol_name)
+
+    def test_resume_snapmirror(self):
+        self.dm_mixin.resume_snapmirror(self.src_backend,
+                                        self.dest_backend,
+                                        self.src_flexvol_name,
+                                        self.dest_flexvol_name)
+
+        self.mock_dest_client.resume_snapmirror.assert_called_once_with(
+            self.src_vserver, self.src_flexvol_name, self.dest_vserver,
+            self.dest_flexvol_name)
+
+    @ddt.data({'size': 1, 'aggr_map': {}},
+              {'size': 1, 'aggr_map': {'aggr02': 'aggr20'}},
+              {'size': None, 'aggr_map': {'aggr01': 'aggr10'}})
+    @ddt.unpack
+    def test_create_destination_flexvol_exception(self, size, aggr_map):
+        self.mock_object(
+            self.mock_src_client, 'get_provisioning_options_from_flexvol',
+            mock.Mock(return_value={'size': size, 'aggregate': 'aggr01'}))
+        self.mock_object(self.dm_mixin, '_get_replication_aggregate_map',
+                         mock.Mock(return_value=aggr_map))
+        mock_client_call = self.mock_object(
+            self.mock_dest_client, 'create_flexvol')
+
+        self.assertRaises(exception.NetAppDriverException,
+                          self.dm_mixin.create_destination_flexvol,
+                          self.src_backend, self.dest_backend,
+                          self.src_flexvol_name, self.dest_flexvol_name)
+        if size:
+            self.dm_mixin._get_replication_aggregate_map.\
+                assert_called_once_with(self.src_backend, self.dest_backend)
+        else:
+            self.assertFalse(
+                self.dm_mixin._get_replication_aggregate_map.called)
+        self.assertFalse(mock_client_call.called)
+
+    def test_create_destination_flexvol(self):
+        aggr_map = {
+            fakes.PROVISIONING_OPTS['aggregate']: 'aggr01',
+            'aggr20': 'aggr02',
+        }
+        provisioning_opts = copy.deepcopy(fakes.PROVISIONING_OPTS)
+        expected_prov_opts = copy.deepcopy(fakes.PROVISIONING_OPTS)
+        expected_prov_opts.pop('volume_type', None)
+        expected_prov_opts.pop('size', None)
+        expected_prov_opts.pop('aggregate', None)
+        mock_get_provisioning_opts_call = self.mock_object(
+            self.mock_src_client, 'get_provisioning_options_from_flexvol',
+            mock.Mock(return_value=provisioning_opts))
+        self.mock_object(self.dm_mixin, '_get_replication_aggregate_map',
+                         mock.Mock(return_value=aggr_map))
+        mock_client_call = self.mock_object(
+            self.mock_dest_client, 'create_flexvol')
+
+        retval = self.dm_mixin.create_destination_flexvol(
+            self.src_backend, self.dest_backend,
+            self.src_flexvol_name, self.dest_flexvol_name)
+
+        self.assertIsNone(retval)
+        mock_get_provisioning_opts_call.assert_called_once_with(
+            self.src_flexvol_name)
+        self.dm_mixin._get_replication_aggregate_map.assert_called_once_with(
+            self.src_backend, self.dest_backend)
+        mock_client_call.assert_called_once_with(
+            self.dest_flexvol_name, 'aggr01', fakes.PROVISIONING_OPTS['size'],
+            volume_type='dp', **expected_prov_opts)
+
+    def test_ensure_snapmirrors(self):
+        flexvols = ['nvol1', 'nvol2']
+        replication_backends = ['fallback1', 'fallback2']
+        self.mock_object(self.dm_mixin, 'get_replication_backend_names',
+                         mock.Mock(return_value=replication_backends))
+        self.mock_object(self.dm_mixin, 'create_snapmirror')
+        expected_calls = [
+            mock.call(self.src_backend, replication_backends[0],
+                      flexvols[0], flexvols[0]),
+            mock.call(self.src_backend, replication_backends[0],
+                      flexvols[1], flexvols[1]),
+            mock.call(self.src_backend, replication_backends[1],
+                      flexvols[0], flexvols[0]),
+            mock.call(self.src_backend, replication_backends[1],
+                      flexvols[1], flexvols[1]),
+        ]
+
+        retval = self.dm_mixin.ensure_snapmirrors(self.mock_src_config,
+                                                  self.src_backend,
+                                                  flexvols)
+
+        self.assertIsNone(retval)
+        self.dm_mixin.get_replication_backend_names.assert_called_once_with(
+            self.mock_src_config)
+        self.dm_mixin.create_snapmirror.assert_has_calls(expected_calls)
+
+    def test_break_snapmirrors(self):
+        flexvols = ['nvol1', 'nvol2']
+        replication_backends = ['fallback1', 'fallback2']
+        side_effects = [None, netapp_api.NaApiError, None, None]
+        self.mock_object(self.dm_mixin, 'get_replication_backend_names',
+                         mock.Mock(return_value=replication_backends))
+        self.mock_object(self.dm_mixin, 'break_snapmirror',
+                         mock.Mock(side_effect=side_effects))
+        mock_exc_log = self.mock_object(data_motion.LOG, 'exception')
+        expected_calls = [
+            mock.call(self.src_backend, replication_backends[0],
+                      flexvols[0], flexvols[0]),
+            mock.call(self.src_backend, replication_backends[0],
+                      flexvols[1], flexvols[1]),
+            mock.call(self.src_backend, replication_backends[1],
+                      flexvols[0], flexvols[0]),
+            mock.call(self.src_backend, replication_backends[1],
+                      flexvols[1], flexvols[1]),
+        ]
+
+        failed_to_break = self.dm_mixin.break_snapmirrors(
+            self.mock_src_config, self.src_backend, flexvols, 'fallback1')
+
+        self.assertEqual(1, len(failed_to_break))
+        self.assertEqual(1, mock_exc_log.call_count)
+        self.dm_mixin.get_replication_backend_names.assert_called_once_with(
+            self.mock_src_config)
+        self.dm_mixin.break_snapmirror.assert_has_calls(expected_calls)
+
+    def test_update_snapmirrors(self):
+        flexvols = ['nvol1', 'nvol2']
+        replication_backends = ['fallback1', 'fallback2']
+        self.mock_object(self.dm_mixin, 'get_replication_backend_names',
+                         mock.Mock(return_value=replication_backends))
+        side_effects = [None, netapp_api.NaApiError, None, None]
+        self.mock_object(self.dm_mixin, 'update_snapmirror',
+                         mock.Mock(side_effect=side_effects))
+        expected_calls = [
+            mock.call(self.src_backend, replication_backends[0],
+                      flexvols[0], flexvols[0]),
+            mock.call(self.src_backend, replication_backends[0],
+                      flexvols[1], flexvols[1]),
+            mock.call(self.src_backend, replication_backends[1],
+                      flexvols[0], flexvols[0]),
+            mock.call(self.src_backend, replication_backends[1],
+                      flexvols[1], flexvols[1]),
+        ]
+
+        retval = self.dm_mixin.update_snapmirrors(self.mock_src_config,
+                                                  self.src_backend,
+                                                  flexvols)
+
+        self.assertIsNone(retval)
+        self.dm_mixin.get_replication_backend_names.assert_called_once_with(
+            self.mock_src_config)
+        self.dm_mixin.update_snapmirror.assert_has_calls(expected_calls)
+
+    @ddt.data([{'destination-volume': 'nvol3', 'lag-time': '3223'},
+               {'destination-volume': 'nvol5', 'lag-time': '32'}],
+              [])
+    def test__choose_failover_target_no_failover_targets(self, snapmirrors):
+        flexvols = ['nvol1', 'nvol2']
+        replication_backends = ['fallback1', 'fallback2']
+        mock_debug_log = self.mock_object(data_motion.LOG, 'debug')
+        self.mock_object(self.dm_mixin, 'get_snapmirrors',
+                         mock.Mock(return_value=snapmirrors))
+
+        target = self.dm_mixin._choose_failover_target(
+            self.src_backend, flexvols, replication_backends)
+
+        self.assertIsNone(target)
+        self.assertEqual(2, mock_debug_log.call_count)
+
+    def test__choose_failover_target(self):
+        flexvols = ['nvol1', 'nvol2']
+        replication_backends = ['fallback1', 'fallback2']
+        target_1_snapmirrors = [
+            {'destination-volume': 'nvol3', 'lag-time': '12'},
+            {'destination-volume': 'nvol1', 'lag-time': '1541'},
+            {'destination-volume': 'nvol2', 'lag-time': '16'},
+        ]
+        target_2_snapmirrors = [
+            {'destination-volume': 'nvol2', 'lag-time': '717'},
+            {'destination-volume': 'nvol1', 'lag-time': '323'},
+            {'destination-volume': 'nvol3', 'lag-time': '720'},
+        ]
+        mock_debug_log = self.mock_object(data_motion.LOG, 'debug')
+        self.mock_object(self.dm_mixin, 'get_snapmirrors',
+                         mock.Mock(side_effect=[target_1_snapmirrors,
+                                                target_2_snapmirrors]))
+
+        target = self.dm_mixin._choose_failover_target(
+            self.src_backend, flexvols, replication_backends)
+
+        self.assertEqual('fallback2', target)
+        self.assertFalse(mock_debug_log.called)
+
+    def test__failover_host_no_suitable_target(self):
+        flexvols = ['nvol1', 'nvol2']
+        replication_backends = ['fallback1', 'fallback2']
+        self.mock_object(self.dm_mixin, '_choose_failover_target',
+                         mock.Mock(return_value=None))
+        self.mock_object(utils, 'get_backend_configuration')
+        self.mock_object(self.dm_mixin, 'update_snapmirrors')
+        self.mock_object(self.dm_mixin, 'break_snapmirrors')
+
+        self.assertRaises(exception.NetAppDriverException,
+                          self.dm_mixin._complete_failover,
+                          self.src_backend, replication_backends, flexvols,
+                          [], failover_target=None)
+        self.assertFalse(utils.get_backend_configuration.called)
+        self.assertFalse(self.dm_mixin.update_snapmirrors.called)
+        self.assertFalse(self.dm_mixin.break_snapmirrors.called)
+
+    @ddt.data('fallback1', None)
+    def test__failover_host(self, failover_target):
+        flexvols = ['nvol1', 'nvol2', 'nvol3']
+        replication_backends = ['fallback1', 'fallback2']
+        volumes = [
+            {'id': 'xyzzy', 'host': 'openstack@backend1#nvol1'},
+            {'id': 'foobar', 'host': 'openstack@backend1#nvol2'},
+            {'id': 'waldofred', 'host': 'openstack@backend1#nvol3'},
+        ]
+        expected_volume_updates = [
+            {
+                'volume_id': 'xyzzy',
+                'updates': {'replication_status': 'failed-over'},
+            },
+            {
+                'volume_id': 'foobar',
+                'updates': {'replication_status': 'failed-over'},
+            },
+            {
+                'volume_id': 'waldofred',
+                'updates': {'replication_status': 'error'},
+            },
+        ]
+        expected_active_backend_name = failover_target or 'fallback2'
+        self.mock_object(self.dm_mixin, '_choose_failover_target',
+                         mock.Mock(return_value='fallback2'))
+        self.mock_object(utils, 'get_backend_configuration')
+        self.mock_object(self.dm_mixin, 'update_snapmirrors')
+        self.mock_object(self.dm_mixin, 'break_snapmirrors',
+                         mock.Mock(return_value=['nvol3']))
+
+        actual_active_backend_name, actual_volume_updates = (
+            self.dm_mixin._complete_failover(
+                self.src_backend, replication_backends, flexvols,
+                volumes, failover_target=failover_target)
+        )
+
+        self.assertEqual(expected_active_backend_name,
+                         actual_active_backend_name)
+        self.assertEqual(expected_volume_updates, actual_volume_updates)
diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_utils.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_utils.py
new file mode 100644
index 00000000000..d903859f85f
--- /dev/null
+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_utils.py
@@ -0,0 +1,103 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+
+import ddt
+import mock
+from oslo_config import cfg
+
+from cinder import exception
+from cinder import test
+from cinder.tests.unit.volume.drivers.netapp.dataontap.utils import fakes
+from cinder.volume.drivers.netapp.dataontap.client import client_cmode
+from cinder.volume.drivers.netapp.dataontap.utils import utils
+
+CONF = cfg.CONF
+
+
+@ddt.ddt
+class NetAppCDOTDataMotionTestCase(test.TestCase):
+
+    def setUp(self):
+        super(NetAppCDOTDataMotionTestCase, self).setUp()
+        self.backend = 'backend1'
+        self.mock_cmode_client = self.mock_object(client_cmode, 'Client')
+        self.config = fakes.get_fake_cmode_config(self.backend)
+        CONF.set_override('volume_backend_name', self.backend,
+                          group=self.backend, enforce_type=True)
+        CONF.set_override('netapp_transport_type', 'https',
+                          group=self.backend, enforce_type=True)
+        CONF.set_override('netapp_login', 'fake_user',
+                          group=self.backend, enforce_type=True)
+        CONF.set_override('netapp_password', 'fake_password',
+                          group=self.backend, enforce_type=True)
+        CONF.set_override('netapp_server_hostname', 'fake_hostname',
+                          group=self.backend, enforce_type=True)
+        CONF.set_override('netapp_server_port', 8866,
+                          group=self.backend, enforce_type=True)
+
+    def test_get_backend_configuration(self):
+        self.mock_object(utils, 'CONF')
+        CONF.set_override('netapp_vserver', 'fake_vserver',
+                          group=self.backend, enforce_type=True)
+        utils.CONF.list_all_sections.return_value = [self.backend]
+
+        config = utils.get_backend_configuration(self.backend)
+
+        self.assertEqual('fake_vserver', config.netapp_vserver)
+
+    def test_get_backend_configuration_different_backend_name(self):
+        self.mock_object(utils, 'CONF')
+        CONF.set_override('netapp_vserver', 'fake_vserver',
+                          group=self.backend, enforce_type=True)
+        CONF.set_override('volume_backend_name', 'fake_backend_name',
+                          group=self.backend, enforce_type=True)
+        utils.CONF.list_all_sections.return_value = [self.backend]
+
+        config = utils.get_backend_configuration(self.backend)
+
+        self.assertEqual('fake_vserver', config.netapp_vserver)
+        self.assertEqual('fake_backend_name', config.volume_backend_name)
+
+    @ddt.data([], ['fake_backend1', 'fake_backend2'])
+    def test_get_backend_configuration_not_configured(self, conf_sections):
+        self.mock_object(utils, 'CONF')
+        utils.CONF.list_all_sections.return_value = conf_sections
+
+        self.assertRaises(exception.ConfigNotFound,
+                          utils.get_backend_configuration,
+                          self.backend)
+
+    def test_get_client_for_backend(self):
+        self.mock_object(utils, 'get_backend_configuration',
+                         mock.Mock(return_value=self.config))
+
+        utils.get_client_for_backend(self.backend)
+
+        self.mock_cmode_client.assert_called_once_with(
+            hostname='fake_hostname', password='fake_password',
+            username='fake_user', transport_type='https', port=8866,
+            trace=mock.ANY, vserver=None)
+
+    def test_get_client_for_backend_with_vserver(self):
+        self.mock_object(utils, 'get_backend_configuration',
+                         mock.Mock(return_value=self.config))
+
+        CONF.set_override('netapp_vserver', 'fake_vserver',
+                          group=self.backend, enforce_type=True)
+
+        utils.get_client_for_backend(self.backend)
+
+        self.mock_cmode_client.assert_called_once_with(
+            hostname='fake_hostname', password='fake_password',
+            username='fake_user', transport_type='https', port=8866,
+            trace=mock.ANY, vserver='fake_vserver')
diff --git a/cinder/tests/unit/volume/drivers/netapp/test_common.py b/cinder/tests/unit/volume/drivers/netapp/test_common.py
index 34caf4162d4..fe50b1a8351 100644
--- a/cinder/tests/unit/volume/drivers/netapp/test_common.py
+++ b/cinder/tests/unit/volume/drivers/netapp/test_common.py
@@ -85,8 +85,11 @@ class NetAppDriverFactoryTestCase(test.TestCase):
         def get_full_class_name(obj):
             return obj.__module__ + '.' + obj.__class__.__name__
 
-        kwargs = {'configuration': na_fakes.create_configuration(),
-                  'app_version': 'fake_info'}
+        kwargs = {
+            'configuration': na_fakes.create_configuration(),
+            'app_version': 'fake_info',
+            'host': 'fakehost@fakebackend',
+        }
 
         registry = na_common.NETAPP_UNIFIED_DRIVER_REGISTRY
 
@@ -98,8 +101,11 @@ class NetAppDriverFactoryTestCase(test.TestCase):
 
     def test_create_driver_case_insensitive(self):
 
-        kwargs = {'configuration': na_fakes.create_configuration(),
-                  'app_version': 'fake_info'}
+        kwargs = {
+            'configuration': na_fakes.create_configuration(),
+            'app_version': 'fake_info',
+            'host': 'fakehost@fakebackend',
+        }
 
         driver = na_common.NetAppDriver.create_driver('ONTAP_CLUSTER', 'FC',
                                                       **kwargs)
@@ -108,8 +114,11 @@ class NetAppDriverFactoryTestCase(test.TestCase):
 
     def test_create_driver_invalid_family(self):
 
-        kwargs = {'configuration': na_fakes.create_configuration(),
-                  'app_version': 'fake_info'}
+        kwargs = {
+            'configuration': na_fakes.create_configuration(),
+            'app_version': 'fake_info',
+            'host': 'fakehost@fakebackend',
+        }
 
         self.assertRaises(exception.InvalidInput,
                           na_common.NetAppDriver.create_driver,
@@ -117,8 +126,11 @@ class NetAppDriverFactoryTestCase(test.TestCase):
 
     def test_create_driver_invalid_protocol(self):
 
-        kwargs = {'configuration': na_fakes.create_configuration(),
-                  'app_version': 'fake_info'}
+        kwargs = {
+            'configuration': na_fakes.create_configuration(),
+            'app_version': 'fake_info',
+            'host': 'fakehost@fakebackend',
+        }
 
         self.assertRaises(exception.InvalidInput,
                           na_common.NetAppDriver.create_driver,
diff --git a/cinder/volume/drivers/netapp/dataontap/block_base.py b/cinder/volume/drivers/netapp/dataontap/block_base.py
index 318127dba91..b736c9b1883 100644
--- a/cinder/volume/drivers/netapp/dataontap/block_base.py
+++ b/cinder/volume/drivers/netapp/dataontap/block_base.py
@@ -32,6 +32,7 @@ import uuid
 
 from oslo_log import log as logging
 from oslo_log import versionutils
+from oslo_service import loopingcall
 from oslo_utils import excutils
 from oslo_utils import units
 import six
@@ -46,6 +47,7 @@ from cinder.volume import utils as volume_utils
 from cinder.zonemanager import utils as fczm_utils
 
 LOG = logging.getLogger(__name__)
+HOUSEKEEPING_INTERVAL_SECONDS = 600  # ten minutes
 
 
 class NetAppLun(object):
@@ -103,6 +105,8 @@ class NetAppBlockStorageLibrary(object):
         self.lun_space_reservation = 'true'
         self.lookup_service = fczm_utils.create_lookup_service()
         self.app_version = kwargs.get("app_version", "unknown")
+        self.host = kwargs.get('host')
+        self.backend_name = self.host.split('@')[1]
 
         self.configuration = kwargs['configuration']
         self.configuration.append_config_values(na_opts.netapp_connection_opts)
@@ -167,6 +171,21 @@ class NetAppBlockStorageLibrary(object):
         self._extract_and_populate_luns(lun_list)
         LOG.debug("Success getting list of LUNs from server.")
 
+        self._start_periodic_tasks()
+
+    def _start_periodic_tasks(self):
+        """Start recurring tasks common to all Data ONTAP block drivers."""
+
+        # Start the task that runs other housekeeping tasks, such as deletion
+        # of previously soft-deleted storage artifacts.
+        housekeeping_periodic_task = loopingcall.FixedIntervalLoopingCall(
+            self._handle_housekeeping_tasks)
+        housekeeping_periodic_task.start(
+            interval=HOUSEKEEPING_INTERVAL_SECONDS, initial_delay=0)
+
+    def _handle_housekeeping_tasks(self):
+        """Handle various cleanup activities."""
+
     def get_pool(self, volume):
         """Return pool name where volume resides.
 
diff --git a/cinder/volume/drivers/netapp/dataontap/block_cmode.py b/cinder/volume/drivers/netapp/dataontap/block_cmode.py
index 3be46c59f7b..acad15497b8 100644
--- a/cinder/volume/drivers/netapp/dataontap/block_cmode.py
+++ b/cinder/volume/drivers/netapp/dataontap/block_cmode.py
@@ -33,20 +33,21 @@ from cinder import exception
 from cinder.i18n import _
 from cinder import utils
 from cinder.volume.drivers.netapp.dataontap import block_base
-from cinder.volume.drivers.netapp.dataontap.client import client_cmode
 from cinder.volume.drivers.netapp.dataontap.performance import perf_cmode
 from cinder.volume.drivers.netapp.dataontap.utils import capabilities
+from cinder.volume.drivers.netapp.dataontap.utils import data_motion
+from cinder.volume.drivers.netapp.dataontap.utils import utils as cmode_utils
 from cinder.volume.drivers.netapp import options as na_opts
 from cinder.volume.drivers.netapp import utils as na_utils
 
 
 LOG = logging.getLogger(__name__)
-QOS_CLEANUP_INTERVAL_SECONDS = 60
 SSC_UPDATE_INTERVAL_SECONDS = 3600  # hourly
 
 
 @six.add_metaclass(utils.TraceWrapperMetaclass)
-class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
+class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary,
+                                     data_motion.DataMotionMixin):
     """NetApp block storage library for Data ONTAP (Cluster-mode)."""
 
     REQUIRED_CMODE_FLAGS = ['netapp_vserver']
@@ -57,27 +58,42 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
                                                              **kwargs)
         self.configuration.append_config_values(na_opts.netapp_cluster_opts)
         self.driver_mode = 'cluster'
+        self.failed_over_backend_name = kwargs.get('active_backend_id')
+        self.failed_over = self.failed_over_backend_name is not None
+        self.replication_enabled = (
+            True if self.get_replication_backend_names(
+                self.configuration) else False)
 
     def do_setup(self, context):
         super(NetAppBlockStorageCmodeLibrary, self).do_setup(context)
         na_utils.check_flags(self.REQUIRED_CMODE_FLAGS, self.configuration)
 
-        self.vserver = self.configuration.netapp_vserver
-
-        self.zapi_client = client_cmode.Client(
-            transport_type=self.configuration.netapp_transport_type,
-            username=self.configuration.netapp_login,
-            password=self.configuration.netapp_password,
-            hostname=self.configuration.netapp_server_hostname,
-            port=self.configuration.netapp_server_port,
-            vserver=self.vserver)
+        # cDOT API client
+        self.zapi_client = cmode_utils.get_client_for_backend(
+            self.failed_over_backend_name or self.backend_name)
+        self.vserver = self.zapi_client.vserver
 
+        # Performance monitoring library
         self.perf_library = perf_cmode.PerformanceCmodeLibrary(
             self.zapi_client)
+
+        # Storage service catalog
         self.ssc_library = capabilities.CapabilitiesLibrary(
             self.driver_protocol, self.vserver, self.zapi_client,
             self.configuration)
 
+    def _update_zapi_client(self, backend_name):
+        """Set cDOT API client for the specified config backend stanza name."""
+
+        self.zapi_client = cmode_utils.get_client_for_backend(backend_name)
+        self.vserver = self.zapi_client.vserver
+        self.ssc_library._update_for_failover(self.zapi_client,
+                                              self._get_flexvol_to_pool_map())
+        ssc = self.ssc_library.get_ssc()
+        self.perf_library._update_for_failover(self.zapi_client, ssc)
+        # Clear LUN table cache
+        self.lun_table = {}
+
     def check_for_setup_error(self):
         """Check that the driver is working and can communicate."""
         self.ssc_library.check_api_permissions()
@@ -89,9 +105,9 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
             raise exception.NetAppDriverException(msg)
 
         super(NetAppBlockStorageCmodeLibrary, self).check_for_setup_error()
-        self._start_periodic_tasks()
 
     def _start_periodic_tasks(self):
+        """Start recurring tasks for NetApp cDOT block drivers."""
 
         # Note(cknight): Run the task once in the current thread to prevent a
         # race with the first invocation of _update_volume_stats.
@@ -104,12 +120,32 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
             interval=SSC_UPDATE_INTERVAL_SECONDS,
             initial_delay=SSC_UPDATE_INTERVAL_SECONDS)
 
-        # Start the task that harvests soft-deleted QoS policy groups.
-        harvest_qos_periodic_task = loopingcall.FixedIntervalLoopingCall(
-            self.zapi_client.remove_unused_qos_policy_groups)
-        harvest_qos_periodic_task.start(
-            interval=QOS_CLEANUP_INTERVAL_SECONDS,
-            initial_delay=QOS_CLEANUP_INTERVAL_SECONDS)
+        super(NetAppBlockStorageCmodeLibrary, self)._start_periodic_tasks()
+
+    def _handle_housekeeping_tasks(self):
+        """Handle various cleanup activities."""
+        (super(NetAppBlockStorageCmodeLibrary, self).
+         _handle_housekeeping_tasks())
+
+        # Harvest soft-deleted QoS policy groups
+        self.zapi_client.remove_unused_qos_policy_groups()
+
+        active_backend = self.failed_over_backend_name or self.backend_name
+
+        LOG.debug("Current service state: Replication enabled: %("
+                  "replication)s. Failed-Over: %(failed)s. Active Backend "
+                  "ID: %(active)s",
+                  {
+                      'replication': self.replication_enabled,
+                      'failed': self.failed_over,
+                      'active': active_backend,
+                  })
+
+        # Create pool mirrors if whole-backend replication configured
+        if self.replication_enabled and not self.failed_over:
+            self.ensure_snapmirrors(
+                self.configuration, self.backend_name,
+                self.ssc_library.get_ssc_flexvol_names())
 
     def _create_lun(self, volume_name, lun_name, size,
                     metadata, qos_policy_group_name=None):
@@ -118,8 +154,9 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
         self.zapi_client.create_lun(
             volume_name, lun_name, size, metadata, qos_policy_group_name)
 
-    def _create_lun_handle(self, metadata):
+    def _create_lun_handle(self, metadata, vserver=None):
         """Returns LUN handle based on filer type."""
+        vserver = vserver or self.vserver
         return '%s:%s' % (self.vserver, metadata['Path'])
 
     def _find_mapped_lun_igroup(self, path, initiator_list):
@@ -186,7 +223,7 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
 
     def _update_volume_stats(self, filter_function=None,
                              goodness_function=None):
-        """Retrieve stats info from vserver."""
+        """Retrieve backend stats."""
 
         LOG.debug('Updating volume stats')
         data = {}
@@ -199,6 +236,7 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
             filter_function=filter_function,
             goodness_function=goodness_function)
         data['sparse_copy_volume'] = True
+        data.update(self.get_replication_backend_stats(self.configuration))
 
         self.zapi_client.provide_ems(self, self.driver_name, self.app_version)
         self._stats = data
@@ -368,3 +406,8 @@ class NetAppBlockStorageCmodeLibrary(block_base.NetAppBlockStorageLibrary):
             qos_policy_group_info = None
         self._mark_qos_policy_group_for_deletion(qos_policy_group_info)
         super(NetAppBlockStorageCmodeLibrary, self).unmanage(volume)
+
+    def failover_host(self, context, volumes, secondary_id=None):
+        """Failover a backend to a secondary replication target."""
+
+        return self._failover_host(volumes, secondary_id=secondary_id)
diff --git a/cinder/volume/drivers/netapp/dataontap/client/api.py b/cinder/volume/drivers/netapp/dataontap/client/api.py
index 8ce048cdd39..a4d2c2d6b22 100644
--- a/cinder/volume/drivers/netapp/dataontap/client/api.py
+++ b/cinder/volume/drivers/netapp/dataontap/client/api.py
@@ -37,10 +37,18 @@ from cinder import utils
 
 LOG = logging.getLogger(__name__)
 
+EAPIERROR = '13001'
 EAPIPRIVILEGE = '13003'
 EAPINOTFOUND = '13005'
-ESIS_CLONE_NOT_LICENSED = '14956'
 ESNAPSHOTNOTALLOWED = '13023'
+ESIS_CLONE_NOT_LICENSED = '14956'
+EOBJECTNOTFOUND = '15661'
+ESOURCE_IS_DIFFERENT = '17105'
+ERELATION_EXISTS = '17122'
+ERELATION_NOT_QUIESCED = '17127'
+ENOTRANSFER_IN_PROGRESS = '17130'
+EANOTHER_OP_ACTIVE = '17131'
+ETRANSFER_IN_PROGRESS = '17137'
 
 
 class NaServer(object):
diff --git a/cinder/volume/drivers/netapp/dataontap/client/client_base.py b/cinder/volume/drivers/netapp/dataontap/client/client_base.py
index 7435d12928f..797492b3074 100644
--- a/cinder/volume/drivers/netapp/dataontap/client/client_base.py
+++ b/cinder/volume/drivers/netapp/dataontap/client/client_base.py
@@ -73,6 +73,11 @@ class Client(object):
         minor = res.get_child_content('minor-version')
         return major, minor
 
+    def _strip_xml_namespace(self, string):
+        if string.startswith('{') and '}' in string:
+            return string.split('}', 1)[1]
+        return string
+
     def check_is_naelement(self, elem):
         """Checks if object is instance of NaElement."""
         if not isinstance(elem, netapp_api.NaElement):
diff --git a/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py b/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py
index d6f34c3368e..c2b1853853b 100644
--- a/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py
+++ b/cinder/volume/drivers/netapp/dataontap/client/client_cmode.py
@@ -20,6 +20,7 @@ import math
 import re
 
 from oslo_log import log as logging
+from oslo_utils import units
 import six
 
 from cinder import exception
@@ -61,6 +62,7 @@ class Client(client_base.Client):
         ontapi_1_30 = ontapi_version >= (1, 30)
         ontapi_1_100 = ontapi_version >= (1, 100)
 
+        self.features.add_feature('SNAPMIRROR_V2', supported=ontapi_1_20)
         self.features.add_feature('USER_CAPABILITY_LIST',
                                   supported=ontapi_1_20)
         self.features.add_feature('SYSTEM_METRICS', supported=ontapi_1_2x)
@@ -70,6 +72,7 @@ class Client(client_base.Client):
         self.features.add_feature('ADVANCED_DISK_PARTITIONING',
                                   supported=ontapi_1_30)
         self.features.add_feature('BACKUP_CLONE_PARAM', supported=ontapi_1_100)
+        self.features.add_feature('CLUSTER_PEER_POLICY', supported=ontapi_1_30)
 
     def _invoke_vserver_api(self, na_element, vserver):
         server = copy.copy(self.connection)
@@ -890,6 +893,7 @@ class Client(client_base.Client):
                         'owning-vserver-name': None,
                         'junction-path': None,
                         'containing-aggregate-name': None,
+                        'type': None,
                     },
                     'volume-mirror-attributes': {
                         'is-data-protection-mirror': None,
@@ -898,10 +902,18 @@ class Client(client_base.Client):
                     'volume-space-attributes': {
                         'is-space-guarantee-enabled': None,
                         'space-guarantee': None,
+                        'percentage-snapshot-reserve': None,
+                        'size': None,
                     },
                     'volume-qos-attributes': {
                         'policy-group-name': None,
-                    }
+                    },
+                    'volume-snapshot-attributes': {
+                        'snapshot-policy': None,
+                    },
+                    'volume-language-attributes': {
+                        'language-code': None,
+                    },
                 },
             },
         }
@@ -924,6 +936,10 @@ class Client(client_base.Client):
             'volume-space-attributes') or netapp_api.NaElement('none')
         volume_qos_attributes = volume_attributes.get_child_by_name(
             'volume-qos-attributes') or netapp_api.NaElement('none')
+        volume_snapshot_attributes = volume_attributes.get_child_by_name(
+            'volume-snapshot-attributes') or netapp_api.NaElement('none')
+        volume_language_attributes = volume_attributes.get_child_by_name(
+            'volume-language-attributes') or netapp_api.NaElement('none')
 
         volume = {
             'name': volume_id_attributes.get_child_content('name'),
@@ -933,13 +949,22 @@ class Client(client_base.Client):
                 'junction-path'),
             'aggregate': volume_id_attributes.get_child_content(
                 'containing-aggregate-name'),
+            'type': volume_id_attributes.get_child_content('type'),
             'space-guarantee-enabled': strutils.bool_from_string(
                 volume_space_attributes.get_child_content(
                     'is-space-guarantee-enabled')),
             'space-guarantee': volume_space_attributes.get_child_content(
                 'space-guarantee'),
+            'percentage-snapshot-reserve': (
+                volume_space_attributes.get_child_content(
+                    'percentage-snapshot-reserve')),
+            'size': volume_space_attributes.get_child_content('size'),
             'qos-policy-group': volume_qos_attributes.get_child_content(
-                'policy-group-name')
+                'policy-group-name'),
+            'snapshot-policy': volume_snapshot_attributes.get_child_content(
+                'snapshot-policy'),
+            'language': volume_language_attributes.get_child_content(
+                'language-code'),
         }
 
         return volume
@@ -1015,6 +1040,106 @@ class Client(client_base.Client):
 
         return True
 
+    def create_flexvol(self, flexvol_name, aggregate_name, size_gb,
+                       space_guarantee_type=None, snapshot_policy=None,
+                       language=None, dedupe_enabled=False,
+                       compression_enabled=False, snapshot_reserve=None,
+                       volume_type='rw'):
+
+        """Creates a volume."""
+        api_args = {
+            'containing-aggr-name': aggregate_name,
+            'size': six.text_type(size_gb) + 'g',
+            'volume': flexvol_name,
+            'volume-type': volume_type,
+        }
+        if volume_type == 'dp':
+            snapshot_policy = None
+        else:
+            api_args['junction-path'] = '/%s' % flexvol_name
+        if snapshot_policy is not None:
+            api_args['snapshot-policy'] = snapshot_policy
+        if space_guarantee_type:
+            api_args['space-reserve'] = space_guarantee_type
+        if language is not None:
+            api_args['language-code'] = language
+        if snapshot_reserve is not None:
+            api_args['percentage-snapshot-reserve'] = six.text_type(
+                snapshot_reserve)
+        self.send_request('volume-create', api_args)
+
+        # cDOT compression requires that deduplication be enabled.
+        if dedupe_enabled or compression_enabled:
+            self.enable_flexvol_dedupe(flexvol_name)
+        if compression_enabled:
+            self.enable_flexvol_compression(flexvol_name)
+
+    def flexvol_exists(self, volume_name):
+        """Checks if a flexvol exists on the storage array."""
+        LOG.debug('Checking if volume %s exists', volume_name)
+
+        api_args = {
+            'query': {
+                'volume-attributes': {
+                    'volume-id-attributes': {
+                        'name': volume_name,
+                    },
+                },
+            },
+            'desired-attributes': {
+                'volume-attributes': {
+                    'volume-id-attributes': {
+                        'name': None,
+                    },
+                },
+            },
+        }
+        result = self.send_iter_request('volume-get-iter', api_args)
+        return self._has_records(result)
+
+    def rename_flexvol(self, orig_flexvol_name, new_flexvol_name):
+        """Set flexvol name."""
+        api_args = {
+            'volume': orig_flexvol_name,
+            'new-volume-name': new_flexvol_name,
+        }
+        self.send_request('volume-rename', api_args)
+
+    def mount_flexvol(self, flexvol_name, junction_path=None):
+        """Mounts a volume on a junction path."""
+        api_args = {
+            'volume-name': flexvol_name,
+            'junction-path': (junction_path if junction_path
+                              else '/%s' % flexvol_name)
+        }
+        self.send_request('volume-mount', api_args)
+
+    def enable_flexvol_dedupe(self, flexvol_name):
+        """Enable deduplication on volume."""
+        api_args = {'path': '/vol/%s' % flexvol_name}
+        self.send_request('sis-enable', api_args)
+
+    def disable_flexvol_dedupe(self, flexvol_name):
+        """Disable deduplication on volume."""
+        api_args = {'path': '/vol/%s' % flexvol_name}
+        self.send_request('sis-disable', api_args)
+
+    def enable_flexvol_compression(self, flexvol_name):
+        """Enable compression on volume."""
+        api_args = {
+            'path': '/vol/%s' % flexvol_name,
+            'enable-compression': 'true'
+        }
+        self.send_request('sis-set-config', api_args)
+
+    def disable_flexvol_compression(self, flexvol_name):
+        """Disable compression on volume."""
+        api_args = {
+            'path': '/vol/%s' % flexvol_name,
+            'enable-compression': 'false'
+        }
+        self.send_request('sis-set-config', api_args)
+
     @utils.trace_method
     def delete_file(self, path_to_file):
         """Delete file at path."""
@@ -1400,3 +1525,492 @@ class Client(client_base.Client):
                     'volume %(vol)s.')
             msg_args = {'snap': snapshot_name, 'vol': volume_name}
             raise exception.VolumeBackendAPIException(data=msg % msg_args)
+
+    def create_cluster_peer(self, addresses, username=None, password=None,
+                            passphrase=None):
+        """Creates a cluster peer relationship."""
+
+        api_args = {
+            'peer-addresses': [
+                {'remote-inet-address': address} for address in addresses
+            ],
+        }
+        if username:
+            api_args['user-name'] = username
+        if password:
+            api_args['password'] = password
+        if passphrase:
+            api_args['passphrase'] = passphrase
+
+        self.send_request('cluster-peer-create', api_args)
+
+    def get_cluster_peers(self, remote_cluster_name=None):
+        """Gets one or more cluster peer relationships."""
+
+        api_args = {}
+        if remote_cluster_name:
+            api_args['query'] = {
+                'cluster-peer-info': {
+                    'remote-cluster-name': remote_cluster_name,
+                }
+            }
+
+        result = self.send_iter_request('cluster-peer-get-iter', api_args)
+        if not self._has_records(result):
+            return []
+
+        cluster_peers = []
+
+        for cluster_peer_info in result.get_child_by_name(
+                'attributes-list').get_children():
+
+            cluster_peer = {
+                'active-addresses': [],
+                'peer-addresses': []
+            }
+
+            active_addresses = cluster_peer_info.get_child_by_name(
+                'active-addresses') or netapp_api.NaElement('none')
+            for address in active_addresses.get_children():
+                cluster_peer['active-addresses'].append(address.get_content())
+
+            peer_addresses = cluster_peer_info.get_child_by_name(
+                'peer-addresses') or netapp_api.NaElement('none')
+            for address in peer_addresses.get_children():
+                cluster_peer['peer-addresses'].append(address.get_content())
+
+            cluster_peer['availability'] = cluster_peer_info.get_child_content(
+                'availability')
+            cluster_peer['cluster-name'] = cluster_peer_info.get_child_content(
+                'cluster-name')
+            cluster_peer['cluster-uuid'] = cluster_peer_info.get_child_content(
+                'cluster-uuid')
+            cluster_peer['remote-cluster-name'] = (
+                cluster_peer_info.get_child_content('remote-cluster-name'))
+            cluster_peer['serial-number'] = (
+                cluster_peer_info.get_child_content('serial-number'))
+            cluster_peer['timeout'] = cluster_peer_info.get_child_content(
+                'timeout')
+
+            cluster_peers.append(cluster_peer)
+
+        return cluster_peers
+
+    def delete_cluster_peer(self, cluster_name):
+        """Deletes a cluster peer relationship."""
+
+        api_args = {'cluster-name': cluster_name}
+        self.send_request('cluster-peer-delete', api_args)
+
+    def get_cluster_peer_policy(self):
+        """Gets the cluster peering policy configuration."""
+
+        if not self.features.CLUSTER_PEER_POLICY:
+            return {}
+
+        result = self.send_request('cluster-peer-policy-get')
+
+        attributes = result.get_child_by_name(
+            'attributes') or netapp_api.NaElement('none')
+        cluster_peer_policy = attributes.get_child_by_name(
+            'cluster-peer-policy') or netapp_api.NaElement('none')
+
+        policy = {
+            'is-unauthenticated-access-permitted':
+            cluster_peer_policy.get_child_content(
+                'is-unauthenticated-access-permitted'),
+            'passphrase-minimum-length':
+            cluster_peer_policy.get_child_content(
+                'passphrase-minimum-length'),
+        }
+
+        if policy['is-unauthenticated-access-permitted'] is not None:
+            policy['is-unauthenticated-access-permitted'] = (
+                strutils.bool_from_string(
+                    policy['is-unauthenticated-access-permitted']))
+        if policy['passphrase-minimum-length'] is not None:
+            policy['passphrase-minimum-length'] = int(
+                policy['passphrase-minimum-length'])
+
+        return policy
+
+    def set_cluster_peer_policy(self, is_unauthenticated_access_permitted=None,
+                                passphrase_minimum_length=None):
+        """Modifies the cluster peering policy configuration."""
+
+        if not self.features.CLUSTER_PEER_POLICY:
+            return
+
+        if (is_unauthenticated_access_permitted is None and
+                passphrase_minimum_length is None):
+            return
+
+        api_args = {}
+        if is_unauthenticated_access_permitted is not None:
+            api_args['is-unauthenticated-access-permitted'] = (
+                'true' if strutils.bool_from_string(
+                    is_unauthenticated_access_permitted) else 'false')
+        if passphrase_minimum_length is not None:
+            api_args['passphrase-minlength'] = six.text_type(
+                passphrase_minimum_length)
+
+        self.send_request('cluster-peer-policy-modify', api_args)
+
+    def create_vserver_peer(self, vserver_name, peer_vserver_name):
+        """Creates a Vserver peer relationship for SnapMirrors."""
+        api_args = {
+            'vserver': vserver_name,
+            'peer-vserver': peer_vserver_name,
+            'applications': [
+                {'vserver-peer-application': 'snapmirror'},
+            ],
+        }
+        self.send_request('vserver-peer-create', api_args)
+
+    def delete_vserver_peer(self, vserver_name, peer_vserver_name):
+        """Deletes a Vserver peer relationship."""
+
+        api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name}
+        self.send_request('vserver-peer-delete', api_args)
+
+    def accept_vserver_peer(self, vserver_name, peer_vserver_name):
+        """Accepts a pending Vserver peer relationship."""
+
+        api_args = {'vserver': vserver_name, 'peer-vserver': peer_vserver_name}
+        self.send_request('vserver-peer-accept', api_args)
+
+    def get_vserver_peers(self, vserver_name=None, peer_vserver_name=None):
+        """Gets one or more Vserver peer relationships."""
+
+        api_args = None
+        if vserver_name or peer_vserver_name:
+            api_args = {'query': {'vserver-peer-info': {}}}
+            if vserver_name:
+                api_args['query']['vserver-peer-info']['vserver'] = (
+                    vserver_name)
+            if peer_vserver_name:
+                api_args['query']['vserver-peer-info']['peer-vserver'] = (
+                    peer_vserver_name)
+
+        result = self.send_iter_request('vserver-peer-get-iter', api_args)
+        if not self._has_records(result):
+            return []
+
+        vserver_peers = []
+
+        for vserver_peer_info in result.get_child_by_name(
+                'attributes-list').get_children():
+
+            vserver_peer = {
+                'vserver': vserver_peer_info.get_child_content('vserver'),
+                'peer-vserver':
+                vserver_peer_info.get_child_content('peer-vserver'),
+                'peer-state':
+                vserver_peer_info.get_child_content('peer-state'),
+                'peer-cluster':
+                vserver_peer_info.get_child_content('peer-cluster'),
+            }
+            vserver_peers.append(vserver_peer)
+
+        return vserver_peers
+
+    def _ensure_snapmirror_v2(self):
+        """Verify support for SnapMirror control plane v2."""
+        if not self.features.SNAPMIRROR_V2:
+            msg = _('SnapMirror features require Data ONTAP 8.2 or later.')
+            raise exception.NetAppDriverException(msg)
+
+    def create_snapmirror(self, source_vserver, source_volume,
+                          destination_vserver, destination_volume,
+                          schedule=None, policy=None,
+                          relationship_type='data_protection'):
+        """Creates a SnapMirror relationship (cDOT 8.2 or later only)."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'source-volume': source_volume,
+            'source-vserver': source_vserver,
+            'destination-volume': destination_volume,
+            'destination-vserver': destination_vserver,
+            'relationship-type': relationship_type,
+        }
+        if schedule:
+            api_args['schedule'] = schedule
+        if policy:
+            api_args['policy'] = policy
+
+        try:
+            self.send_request('snapmirror-create', api_args)
+        except netapp_api.NaApiError as e:
+            if e.code != netapp_api.ERELATION_EXISTS:
+                raise
+
+    def initialize_snapmirror(self, source_vserver, source_volume,
+                              destination_vserver, destination_volume,
+                              source_snapshot=None, transfer_priority=None):
+        """Initializes a SnapMirror relationship (cDOT 8.2 or later only)."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'source-volume': source_volume,
+            'source-vserver': source_vserver,
+            'destination-volume': destination_volume,
+            'destination-vserver': destination_vserver,
+        }
+        if source_snapshot:
+            api_args['source-snapshot'] = source_snapshot
+        if transfer_priority:
+            api_args['transfer-priority'] = transfer_priority
+
+        result = self.send_request('snapmirror-initialize', api_args)
+
+        result_info = {}
+        result_info['operation-id'] = result.get_child_content(
+            'result-operation-id')
+        result_info['status'] = result.get_child_content('result-status')
+        result_info['jobid'] = result.get_child_content('result-jobid')
+        result_info['error-code'] = result.get_child_content(
+            'result-error-code')
+        result_info['error-message'] = result.get_child_content(
+            'result-error-message')
+
+        return result_info
+
+    def release_snapmirror(self, source_vserver, source_volume,
+                           destination_vserver, destination_volume,
+                           relationship_info_only=False):
+        """Removes a SnapMirror relationship on the source endpoint."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'query': {
+                'snapmirror-destination-info': {
+                    'source-volume': source_volume,
+                    'source-vserver': source_vserver,
+                    'destination-volume': destination_volume,
+                    'destination-vserver': destination_vserver,
+                    'relationship-info-only': ('true' if relationship_info_only
+                                               else 'false'),
+                }
+            }
+        }
+        self.send_request('snapmirror-release-iter', api_args)
+
+    def quiesce_snapmirror(self, source_vserver, source_volume,
+                           destination_vserver, destination_volume):
+        """Disables future transfers to a SnapMirror destination."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'source-volume': source_volume,
+            'source-vserver': source_vserver,
+            'destination-volume': destination_volume,
+            'destination-vserver': destination_vserver,
+        }
+        self.send_request('snapmirror-quiesce', api_args)
+
+    def abort_snapmirror(self, source_vserver, source_volume,
+                         destination_vserver, destination_volume,
+                         clear_checkpoint=False):
+        """Stops ongoing transfers for a SnapMirror relationship."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'source-volume': source_volume,
+            'source-vserver': source_vserver,
+            'destination-volume': destination_volume,
+            'destination-vserver': destination_vserver,
+            'clear-checkpoint': 'true' if clear_checkpoint else 'false',
+        }
+        try:
+            self.send_request('snapmirror-abort', api_args)
+        except netapp_api.NaApiError as e:
+            if e.code != netapp_api.ENOTRANSFER_IN_PROGRESS:
+                raise
+
+    def break_snapmirror(self, source_vserver, source_volume,
+                         destination_vserver, destination_volume):
+        """Breaks a data protection SnapMirror relationship."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'source-volume': source_volume,
+            'source-vserver': source_vserver,
+            'destination-volume': destination_volume,
+            'destination-vserver': destination_vserver,
+        }
+        self.send_request('snapmirror-break', api_args)
+
+    def modify_snapmirror(self, source_vserver, source_volume,
+                          destination_vserver, destination_volume,
+                          schedule=None, policy=None, tries=None,
+                          max_transfer_rate=None):
+        """Modifies a SnapMirror relationship."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'source-volume': source_volume,
+            'source-vserver': source_vserver,
+            'destination-volume': destination_volume,
+            'destination-vserver': destination_vserver,
+        }
+        if schedule:
+            api_args['schedule'] = schedule
+        if policy:
+            api_args['policy'] = policy
+        if tries is not None:
+            api_args['tries'] = tries
+        if max_transfer_rate is not None:
+            api_args['max-transfer-rate'] = max_transfer_rate
+
+        self.send_request('snapmirror-modify', api_args)
+
+    def delete_snapmirror(self, source_vserver, source_volume,
+                          destination_vserver, destination_volume):
+        """Destroys a SnapMirror relationship."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'query': {
+                'snapmirror-info': {
+                    'source-volume': source_volume,
+                    'source-vserver': source_vserver,
+                    'destination-volume': destination_volume,
+                    'destination-vserver': destination_vserver,
+                }
+            }
+        }
+        self.send_request('snapmirror-destroy-iter', api_args)
+
+    def update_snapmirror(self, source_vserver, source_volume,
+                          destination_vserver, destination_volume):
+        """Schedules a SnapMirror update."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'source-volume': source_volume,
+            'source-vserver': source_vserver,
+            'destination-volume': destination_volume,
+            'destination-vserver': destination_vserver,
+        }
+        try:
+            self.send_request('snapmirror-update', api_args)
+        except netapp_api.NaApiError as e:
+            if (e.code != netapp_api.ETRANSFER_IN_PROGRESS and
+                    e.code != netapp_api.EANOTHER_OP_ACTIVE):
+                raise
+
+    def resume_snapmirror(self, source_vserver, source_volume,
+                          destination_vserver, destination_volume):
+        """Resume a SnapMirror relationship if it is quiesced."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'source-volume': source_volume,
+            'source-vserver': source_vserver,
+            'destination-volume': destination_volume,
+            'destination-vserver': destination_vserver,
+        }
+        try:
+            self.send_request('snapmirror-resume', api_args)
+        except netapp_api.NaApiError as e:
+            if e.code != netapp_api.ERELATION_NOT_QUIESCED:
+                raise
+
+    def resync_snapmirror(self, source_vserver, source_volume,
+                          destination_vserver, destination_volume):
+        """Resync a SnapMirror relationship."""
+        self._ensure_snapmirror_v2()
+
+        api_args = {
+            'source-volume': source_volume,
+            'source-vserver': source_vserver,
+            'destination-volume': destination_volume,
+            'destination-vserver': destination_vserver,
+        }
+        self.send_request('snapmirror-resync', api_args)
+
+    def _get_snapmirrors(self, source_vserver=None, source_volume=None,
+                         destination_vserver=None, destination_volume=None,
+                         desired_attributes=None):
+
+        query = None
+        if (source_vserver or source_volume or destination_vserver or
+                destination_volume):
+            query = {'snapmirror-info': {}}
+            if source_volume:
+                query['snapmirror-info']['source-volume'] = source_volume
+            if destination_volume:
+                query['snapmirror-info']['destination-volume'] = (
+                    destination_volume)
+            if source_vserver:
+                query['snapmirror-info']['source-vserver'] = source_vserver
+            if destination_vserver:
+                query['snapmirror-info']['destination-vserver'] = (
+                    destination_vserver)
+
+        api_args = {}
+        if query:
+            api_args['query'] = query
+        if desired_attributes:
+            api_args['desired-attributes'] = desired_attributes
+
+        result = self.send_iter_request('snapmirror-get-iter', api_args)
+        if not self._has_records(result):
+            return []
+        else:
+            return result.get_child_by_name('attributes-list').get_children()
+
+    def get_snapmirrors(self, source_vserver, source_volume,
+                        destination_vserver, destination_volume,
+                        desired_attributes=None):
+        """Gets one or more SnapMirror relationships.
+
+        Either the source or destination info may be omitted.
+        Desired attributes should be a flat list of attribute names.
+        """
+        self._ensure_snapmirror_v2()
+
+        if desired_attributes is not None:
+            desired_attributes = {
+                'snapmirror-info': {attr: None for attr in desired_attributes},
+            }
+
+        result = self._get_snapmirrors(
+            source_vserver=source_vserver,
+            source_volume=source_volume,
+            destination_vserver=destination_vserver,
+            destination_volume=destination_volume,
+            desired_attributes=desired_attributes)
+
+        snapmirrors = []
+
+        for snapmirror_info in result:
+            snapmirror = {}
+            for child in snapmirror_info.get_children():
+                name = self._strip_xml_namespace(child.get_name())
+                snapmirror[name] = child.get_content()
+            snapmirrors.append(snapmirror)
+
+        return snapmirrors
+
+    def get_provisioning_options_from_flexvol(self, flexvol_name):
+        """Get a dict of provisioning options matching existing flexvol."""
+
+        flexvol_info = self.get_flexvol(flexvol_name=flexvol_name)
+        dedupe_info = self.get_flexvol_dedupe_info(flexvol_name)
+
+        provisioning_opts = {
+            'aggregate': flexvol_info['aggregate'],
+            # space-guarantee can be 'none', 'file', 'volume'
+            'space_guarantee_type': flexvol_info.get('space-guarantee'),
+            'snapshot_policy': flexvol_info['snapshot-policy'],
+            'language': flexvol_info['language'],
+            'dedupe_enabled': dedupe_info['dedupe'],
+            'compression_enabled': dedupe_info['compression'],
+            'snapshot_reserve': flexvol_info['percentage-snapshot-reserve'],
+            'volume_type': flexvol_info['type'],
+            'size': int(math.ceil(float(flexvol_info['size']) / units.Gi)),
+        }
+
+        return provisioning_opts
diff --git a/cinder/volume/drivers/netapp/dataontap/fc_7mode.py b/cinder/volume/drivers/netapp/dataontap/fc_7mode.py
index bd5113c8624..9691e384955 100644
--- a/cinder/volume/drivers/netapp/dataontap/fc_7mode.py
+++ b/cinder/volume/drivers/netapp/dataontap/fc_7mode.py
@@ -129,3 +129,6 @@ class NetApp7modeFibreChannelDriver(driver.BaseVD,
         return self.library.create_consistencygroup_from_src(
             group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots,
             source_cg=source_cg, source_vols=source_vols)
+
+    def failover_host(self, context, volumes, secondary_id=None):
+        raise NotImplementedError()
diff --git a/cinder/volume/drivers/netapp/dataontap/fc_cmode.py b/cinder/volume/drivers/netapp/dataontap/fc_cmode.py
index 391f4222339..fa610d6735b 100644
--- a/cinder/volume/drivers/netapp/dataontap/fc_cmode.py
+++ b/cinder/volume/drivers/netapp/dataontap/fc_cmode.py
@@ -129,3 +129,7 @@ class NetAppCmodeFibreChannelDriver(driver.BaseVD,
         return self.library.create_consistencygroup_from_src(
             group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots,
             source_cg=source_cg, source_vols=source_vols)
+
+    def failover_host(self, context, volumes, secondary_id=None):
+        return self.library.failover_host(
+            context, volumes, secondary_id=secondary_id)
diff --git a/cinder/volume/drivers/netapp/dataontap/iscsi_7mode.py b/cinder/volume/drivers/netapp/dataontap/iscsi_7mode.py
index f523cb5fe1d..442d329cace 100644
--- a/cinder/volume/drivers/netapp/dataontap/iscsi_7mode.py
+++ b/cinder/volume/drivers/netapp/dataontap/iscsi_7mode.py
@@ -126,3 +126,6 @@ class NetApp7modeISCSIDriver(driver.BaseVD,
         return self.library.create_consistencygroup_from_src(
             group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots,
             source_cg=source_cg, source_vols=source_vols)
+
+    def failover_host(self, context, volumes, secondary_id=None):
+        raise NotImplementedError()
diff --git a/cinder/volume/drivers/netapp/dataontap/iscsi_cmode.py b/cinder/volume/drivers/netapp/dataontap/iscsi_cmode.py
index 29e8d25d9df..2076caca513 100644
--- a/cinder/volume/drivers/netapp/dataontap/iscsi_cmode.py
+++ b/cinder/volume/drivers/netapp/dataontap/iscsi_cmode.py
@@ -126,3 +126,7 @@ class NetAppCmodeISCSIDriver(driver.BaseVD,
         return self.library.create_consistencygroup_from_src(
             group, volumes, cgsnapshot=cgsnapshot, snapshots=snapshots,
             source_cg=source_cg, source_vols=source_vols)
+
+    def failover_host(self, context, volumes, secondary_id=None):
+        return self.library.failover_host(
+            context, volumes, secondary_id=secondary_id)
diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_base.py b/cinder/volume/drivers/netapp/dataontap/nfs_base.py
index 6d4c361791e..3653348138e 100644
--- a/cinder/volume/drivers/netapp/dataontap/nfs_base.py
+++ b/cinder/volume/drivers/netapp/dataontap/nfs_base.py
@@ -32,6 +32,7 @@ import time
 from oslo_concurrency import processutils
 from oslo_config import cfg
 from oslo_log import log as logging
+from oslo_service import loopingcall
 from oslo_utils import units
 import six
 from six.moves import urllib
@@ -49,6 +50,7 @@ from cinder.volume import utils as volume_utils
 
 LOG = logging.getLogger(__name__)
 CONF = cfg.CONF
+HOUSEKEEPING_INTERVAL_SECONDS = 600  # ten minutes
 
 
 @six.add_metaclass(utils.TraceWrapperWithABCMetaclass)
@@ -76,6 +78,7 @@ class NetAppNfsDriver(driver.ManageableVD,
         self.configuration.append_config_values(na_opts.netapp_transport_opts)
         self.configuration.append_config_values(na_opts.netapp_img_cache_opts)
         self.configuration.append_config_values(na_opts.netapp_nfs_extra_opts)
+        self.backend_name = self.host.split('@')[1]
 
     def do_setup(self, context):
         super(NetAppNfsDriver, self).do_setup(context)
@@ -86,6 +89,20 @@ class NetAppNfsDriver(driver.ManageableVD,
     def check_for_setup_error(self):
         """Returns an error if prerequisites aren't met."""
         super(NetAppNfsDriver, self).check_for_setup_error()
+        self._start_periodic_tasks()
+
+    def _start_periodic_tasks(self):
+        """Start recurring tasks common to all Data ONTAP NFS drivers."""
+
+        # Start the task that runs other housekeeping tasks, such as deletion
+        # of previously soft-deleted storage artifacts.
+        housekeeping_periodic_task = loopingcall.FixedIntervalLoopingCall(
+            self._handle_housekeeping_tasks)
+        housekeeping_periodic_task.start(
+            interval=HOUSEKEEPING_INTERVAL_SECONDS, initial_delay=0)
+
+    def _handle_housekeeping_tasks(self):
+        """Handle various cleanup activities."""
 
     def get_pool(self, volume):
         """Return pool name where volume resides.
diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py
index 001eee42d36..75c9a7c0b46 100644
--- a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py
+++ b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py
@@ -34,23 +34,24 @@ from cinder.i18n import _, _LE, _LI, _LW
 from cinder.image import image_utils
 from cinder import interface
 from cinder import utils
-from cinder.volume.drivers.netapp.dataontap.client import client_cmode
 from cinder.volume.drivers.netapp.dataontap import nfs_base
 from cinder.volume.drivers.netapp.dataontap.performance import perf_cmode
 from cinder.volume.drivers.netapp.dataontap.utils import capabilities
+from cinder.volume.drivers.netapp.dataontap.utils import data_motion
+from cinder.volume.drivers.netapp.dataontap.utils import utils as cmode_utils
 from cinder.volume.drivers.netapp import options as na_opts
 from cinder.volume.drivers.netapp import utils as na_utils
 from cinder.volume import utils as volume_utils
 
 
 LOG = logging.getLogger(__name__)
-QOS_CLEANUP_INTERVAL_SECONDS = 60
 SSC_UPDATE_INTERVAL_SECONDS = 3600  # hourly
 
 
 @interface.volumedriver
 @six.add_metaclass(utils.TraceWrapperWithABCMetaclass)
-class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
+class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver,
+                           data_motion.DataMotionMixin):
     """NetApp NFS driver for Data ONTAP (Cluster-mode)."""
 
     REQUIRED_CMODE_FLAGS = ['netapp_vserver']
@@ -58,34 +59,48 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
     def __init__(self, *args, **kwargs):
         super(NetAppCmodeNfsDriver, self).__init__(*args, **kwargs)
         self.configuration.append_config_values(na_opts.netapp_cluster_opts)
+        self.failed_over_backend_name = kwargs.get('active_backend_id')
+        self.failed_over = self.failed_over_backend_name is not None
+        self.replication_enabled = (
+            True if self.get_replication_backend_names(
+                self.configuration) else False)
 
     def do_setup(self, context):
         """Do the customized set up on client for cluster mode."""
         super(NetAppCmodeNfsDriver, self).do_setup(context)
         na_utils.check_flags(self.REQUIRED_CMODE_FLAGS, self.configuration)
 
-        self.vserver = self.configuration.netapp_vserver
-
-        self.zapi_client = client_cmode.Client(
-            transport_type=self.configuration.netapp_transport_type,
-            username=self.configuration.netapp_login,
-            password=self.configuration.netapp_password,
-            hostname=self.configuration.netapp_server_hostname,
-            port=self.configuration.netapp_server_port,
-            vserver=self.vserver)
+        # cDOT API client
+        self.zapi_client = cmode_utils.get_client_for_backend(
+            self.failed_over_backend_name or self.backend_name)
+        self.vserver = self.zapi_client.vserver
 
+        # Performance monitoring library
         self.perf_library = perf_cmode.PerformanceCmodeLibrary(
             self.zapi_client)
+
+        # Storage service catalog
         self.ssc_library = capabilities.CapabilitiesLibrary(
             'nfs', self.vserver, self.zapi_client, self.configuration)
 
+    def _update_zapi_client(self, backend_name):
+        """Set cDOT API client for the specified config backend stanza name."""
+
+        self.zapi_client = cmode_utils.get_client_for_backend(backend_name)
+        self.vserver = self.zapi_client.vserver
+        self.ssc_library._update_for_failover(self.zapi_client,
+                                              self._get_flexvol_to_pool_map())
+        ssc = self.ssc_library.get_ssc()
+        self.perf_library._update_for_failover(self.zapi_client, ssc)
+
+    @utils.trace_method
     def check_for_setup_error(self):
         """Check that the driver is working and can communicate."""
         super(NetAppCmodeNfsDriver, self).check_for_setup_error()
         self.ssc_library.check_api_permissions()
-        self._start_periodic_tasks()
 
     def _start_periodic_tasks(self):
+        """Start recurring tasks for NetApp cDOT NFS driver."""
 
         # Note(cknight): Run the task once in the current thread to prevent a
         # race with the first invocation of _update_volume_stats.
@@ -98,12 +113,31 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
             interval=SSC_UPDATE_INTERVAL_SECONDS,
             initial_delay=SSC_UPDATE_INTERVAL_SECONDS)
 
-        # Start the task that harvests soft-deleted QoS policy groups.
-        harvest_qos_periodic_task = loopingcall.FixedIntervalLoopingCall(
-            self.zapi_client.remove_unused_qos_policy_groups)
-        harvest_qos_periodic_task.start(
-            interval=QOS_CLEANUP_INTERVAL_SECONDS,
-            initial_delay=QOS_CLEANUP_INTERVAL_SECONDS)
+        super(NetAppCmodeNfsDriver, self)._start_periodic_tasks()
+
+    def _handle_housekeeping_tasks(self):
+        """Handle various cleanup activities."""
+        super(NetAppCmodeNfsDriver, self)._handle_housekeeping_tasks()
+
+        # Harvest soft-deleted QoS policy groups
+        self.zapi_client.remove_unused_qos_policy_groups()
+
+        active_backend = self.failed_over_backend_name or self.backend_name
+
+        LOG.debug("Current service state: Replication enabled: %("
+                  "replication)s. Failed-Over: %(failed)s. Active Backend "
+                  "ID: %(active)s",
+                  {
+                      'replication': self.replication_enabled,
+                      'failed': self.failed_over,
+                      'active': active_backend,
+                  })
+
+        # Create pool mirrors if whole-backend replication configured
+        if self.replication_enabled and not self.failed_over:
+            self.ensure_snapmirrors(
+                self.configuration, self.backend_name,
+                self.ssc_library.get_ssc_flexvol_names())
 
     def _do_qos_for_volume(self, volume, extra_specs, cleanup=True):
         try:
@@ -166,6 +200,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
             filter_function=self.get_filter_function(),
             goodness_function=self.get_goodness_function())
         data['sparse_copy_volume'] = True
+        data.update(self.get_replication_backend_stats(self.configuration))
 
         self._spawn_clean_cache_job()
         self.zapi_client.provide_ems(self, netapp_backend, self._app_version)
@@ -638,3 +673,8 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver):
             pass
 
         super(NetAppCmodeNfsDriver, self).unmanage(volume)
+
+    def failover_host(self, context, volumes, secondary_id=None):
+        """Failover a backend to a secondary replication target."""
+
+        return self._failover_host(volumes, secondary_id=secondary_id)
diff --git a/cinder/volume/drivers/netapp/dataontap/performance/perf_cmode.py b/cinder/volume/drivers/netapp/dataontap/performance/perf_cmode.py
index e85c83d1169..d086019f025 100644
--- a/cinder/volume/drivers/netapp/dataontap/performance/perf_cmode.py
+++ b/cinder/volume/drivers/netapp/dataontap/performance/perf_cmode.py
@@ -113,6 +113,10 @@ class PerformanceCmodeLibrary(perf_base.PerformanceLibrary):
         return self.pool_utilization.get(pool_name,
                                          perf_base.DEFAULT_UTILIZATION)
 
+    def _update_for_failover(self, zapi_client, ssc_pools):
+        self.zapi_client = zapi_client
+        self.update_performance_cache(ssc_pools)
+
     def _get_aggregates_for_pools(self, ssc_pools):
         """Get the set of aggregates that contain the specified pools."""
 
diff --git a/cinder/volume/drivers/netapp/dataontap/utils/capabilities.py b/cinder/volume/drivers/netapp/dataontap/utils/capabilities.py
index a5fcb796250..8447b8dc9a4 100644
--- a/cinder/volume/drivers/netapp/dataontap/utils/capabilities.py
+++ b/cinder/volume/drivers/netapp/dataontap/utils/capabilities.py
@@ -88,6 +88,11 @@ class CapabilitiesLibrary(object):
 
         return copy.deepcopy(self.ssc)
 
+    def get_ssc_flexvol_names(self):
+        """Get the names of the FlexVols in the Storage Service Catalog."""
+        ssc = self.get_ssc()
+        return ssc.keys()
+
     def get_ssc_for_flexvol(self, flexvol_name):
         """Get map of Storage Service Catalog entries for a single flexvol."""
 
@@ -133,6 +138,11 @@ class CapabilitiesLibrary(object):
 
         self.ssc = ssc
 
+    def _update_for_failover(self, zapi_client, flexvol_map):
+
+        self.zapi_client = zapi_client
+        self.update_ssc(flexvol_map)
+
     def _get_ssc_flexvol_info(self, flexvol_name):
         """Gather flexvol info and recast into SSC-style volume stats."""
 
diff --git a/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py b/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py
new file mode 100644
index 00000000000..066435c1a5f
--- /dev/null
+++ b/cinder/volume/drivers/netapp/dataontap/utils/data_motion.py
@@ -0,0 +1,640 @@
+# Copyright (c) 2016 Alex Meade.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""
+NetApp Data ONTAP data motion library.
+
+This library handles transferring data from a source to a destination. Its
+responsibility is to handle this as efficiently as possible given the
+location of the data's source and destination. This includes cloning,
+SnapMirror, and copy-offload as improvements to brute force data transfer.
+"""
+
+from oslo_config import cfg
+from oslo_log import log
+from oslo_utils import excutils
+
+from cinder import exception
+from cinder import utils
+from cinder.i18n import _, _LE, _LI
+from cinder.objects import fields
+from cinder.volume.drivers.netapp.dataontap.client import api as netapp_api
+from cinder.volume.drivers.netapp.dataontap.utils import utils as config_utils
+from cinder.volume import utils as volume_utils
+
+LOG = log.getLogger(__name__)
+CONF = cfg.CONF
+ENTRY_DOES_NOT_EXIST = "(entry doesn't exist)"
+QUIESCE_RETRY_INTERVAL = 5
+
+
+class DataMotionMixin(object):
+
+    def get_replication_backend_names(self, config):
+        """Get the backend names for all configured replication targets."""
+
+        backend_names = []
+
+        replication_devices = config.safe_get('replication_device')
+        if replication_devices:
+            for replication_device in replication_devices:
+                backend_id = replication_device.get('backend_id')
+                if backend_id:
+                    backend_names.append(backend_id)
+
+        return backend_names
+
+    def get_replication_backend_stats(self, config):
+        """Get the driver replication info for merging into volume stats."""
+
+        backend_names = self.get_replication_backend_names(config)
+
+        if len(backend_names) > 0:
+            stats = {
+                'replication_enabled': True,
+                'replication_count': len(backend_names),
+                'replication_targets': backend_names,
+                'replication_type': 'async',
+            }
+        else:
+            stats = {'replication_enabled': False}
+
+        return stats
+
+    def _get_replication_aggregate_map(self, src_backend_name,
+                                       target_backend_name):
+        """Get the aggregate mapping config between src and destination."""
+
+        aggregate_map = {}
+
+        config = config_utils.get_backend_configuration(src_backend_name)
+
+        all_replication_aggregate_maps = config.safe_get(
+            'netapp_replication_aggregate_map')
+        if all_replication_aggregate_maps:
+            for replication_aggregate_map in all_replication_aggregate_maps:
+                if (replication_aggregate_map.get('backend_id') ==
+                        target_backend_name):
+                    replication_aggregate_map.pop('backend_id')
+                    aggregate_map = replication_aggregate_map
+                    break
+
+        return aggregate_map
+
+    def get_snapmirrors(self, src_backend_name, dest_backend_name,
+                        src_flexvol_name=None, dest_flexvol_name=None):
+        """Get info regarding SnapMirror relationship/s for given params."""
+        dest_backend_config = config_utils.get_backend_configuration(
+            dest_backend_name)
+        dest_vserver = dest_backend_config.netapp_vserver
+        dest_client = config_utils.get_client_for_backend(
+            dest_backend_name, vserver_name=dest_vserver)
+
+        src_backend_config = config_utils.get_backend_configuration(
+            src_backend_name)
+        src_vserver = src_backend_config.netapp_vserver
+
+        snapmirrors = dest_client.get_snapmirrors(
+            src_vserver, src_flexvol_name,
+            dest_vserver, dest_flexvol_name,
+            desired_attributes=[
+                'relationship-status',
+                'mirror-state',
+                'source-vserver',
+                'source-volume',
+                'destination-vserver',
+                'destination-volume',
+                'last-transfer-end-timestamp',
+                'lag-time',
+            ])
+        return snapmirrors
+
+    def create_snapmirror(self, src_backend_name, dest_backend_name,
+                          src_flexvol_name, dest_flexvol_name):
+        """Set up a SnapMirror relationship b/w two FlexVols (cinder pools)
+
+        1. Create SnapMirror relationship
+        2. Initialize data transfer asynchronously
+
+        If a SnapMirror relationship already exists and is broken off or
+        quiesced, resume and re-sync the mirror.
+        """
+        dest_backend_config = config_utils.get_backend_configuration(
+            dest_backend_name)
+        dest_vserver = dest_backend_config.netapp_vserver
+        dest_client = config_utils.get_client_for_backend(
+            dest_backend_name, vserver_name=dest_vserver)
+
+        source_backend_config = config_utils.get_backend_configuration(
+            src_backend_name)
+        src_vserver = source_backend_config.netapp_vserver
+
+        # 1. Create destination 'dp' FlexVol if it doesn't exist
+        if not dest_client.flexvol_exists(dest_flexvol_name):
+            self.create_destination_flexvol(src_backend_name,
+                                            dest_backend_name,
+                                            src_flexvol_name,
+                                            dest_flexvol_name)
+
+        # 2. Check if SnapMirror relationship exists
+        existing_mirrors = dest_client.get_snapmirrors(
+            src_vserver, src_flexvol_name, dest_vserver, dest_flexvol_name)
+
+        msg_payload = {
+            'src_vserver': src_vserver,
+            'src_volume': src_flexvol_name,
+            'dest_vserver': dest_vserver,
+            'dest_volume': dest_flexvol_name,
+        }
+
+        # 3. Create and initialize SnapMirror if it doesn't already exist
+        if not existing_mirrors:
+            # TODO(gouthamr): Change the schedule from hourly to a config value
+            msg = ("Creating a SnapMirror relationship between "
+                   "%(src_vserver)s:%(src_volume)s and %(dest_vserver)s:"
+                   "%(dest_volume)s.")
+            LOG.debug(msg, msg_payload)
+
+            dest_client.create_snapmirror(src_vserver,
+                                          src_flexvol_name,
+                                          dest_vserver,
+                                          dest_flexvol_name,
+                                          schedule='hourly')
+
+            msg = ("Initializing SnapMirror transfers between "
+                   "%(src_vserver)s:%(src_volume)s and %(dest_vserver)s:"
+                   "%(dest_volume)s.")
+            LOG.debug(msg, msg_payload)
+
+            # Initialize async transfer of the initial data
+            dest_client.initialize_snapmirror(src_vserver,
+                                              src_flexvol_name,
+                                              dest_vserver,
+                                              dest_flexvol_name)
+
+        # 4. Try to repair SnapMirror if existing
+        else:
+            snapmirror = existing_mirrors[0]
+            if snapmirror.get('mirror-state') != 'snapmirrored':
+                try:
+                    msg = ("SnapMirror between %(src_vserver)s:%(src_volume)s "
+                           "and %(dest_vserver)s:%(dest_volume)s is in "
+                           "'%(state)s' state. Attempting to repair it.")
+                    msg_payload['state'] = snapmirror.get('mirror-state')
+                    LOG.debug(msg, msg_payload)
+                    dest_client.resume_snapmirror(src_vserver,
+                                                  src_flexvol_name,
+                                                  dest_vserver,
+                                                  dest_flexvol_name)
+                    dest_client.resync_snapmirror(src_vserver,
+                                                  src_flexvol_name,
+                                                  dest_vserver,
+                                                  dest_flexvol_name)
+                except netapp_api.NaApiError:
+                    LOG.exception(_LE("Could not re-sync SnapMirror."))
+
+    def delete_snapmirror(self, src_backend_name, dest_backend_name,
+                          src_flexvol_name, dest_flexvol_name, release=True):
+        """Ensure all information about a SnapMirror relationship is removed.
+
+        1. Abort SnapMirror
+        2. Delete the SnapMirror
+        3. Release SnapMirror to cleanup SnapMirror metadata and snapshots
+        """
+        dest_backend_config = config_utils.get_backend_configuration(
+            dest_backend_name)
+        dest_vserver = dest_backend_config.netapp_vserver
+        dest_client = config_utils.get_client_for_backend(
+            dest_backend_name, vserver_name=dest_vserver)
+
+        source_backend_config = config_utils.get_backend_configuration(
+            src_backend_name)
+        src_vserver = source_backend_config.netapp_vserver
+
+        # 1. Abort any ongoing transfers
+        try:
+            dest_client.abort_snapmirror(src_vserver,
+                                         src_flexvol_name,
+                                         dest_vserver,
+                                         dest_flexvol_name,
+                                         clear_checkpoint=False)
+        except netapp_api.NaApiError:
+            # Snapmirror is already deleted
+            pass
+
+        # 2. Delete SnapMirror Relationship and cleanup destination snapshots
+        try:
+            dest_client.delete_snapmirror(src_vserver,
+                                          src_flexvol_name,
+                                          dest_vserver,
+                                          dest_flexvol_name)
+        except netapp_api.NaApiError as e:
+            with excutils.save_and_reraise_exception() as exc_context:
+                if (e.code == netapp_api.EOBJECTNOTFOUND or
+                        e.code == netapp_api.ESOURCE_IS_DIFFERENT or
+                        ENTRY_DOES_NOT_EXIST in e.message):
+                    LOG.info(_LI('No SnapMirror relationship to delete.'))
+                    exc_context.reraise = False
+
+        if release:
+            # If the source is unreachable, do not perform the release
+            try:
+                src_client = config_utils.get_client_for_backend(
+                    src_backend_name, vserver_name=src_vserver)
+            except Exception:
+                src_client = None
+            # 3. Cleanup SnapMirror relationship on source
+            try:
+                if src_client:
+                    src_client.release_snapmirror(src_vserver,
+                                                  src_flexvol_name,
+                                                  dest_vserver,
+                                                  dest_flexvol_name)
+            except netapp_api.NaApiError as e:
+                with excutils.save_and_reraise_exception() as exc_context:
+                    if (e.code == netapp_api.EOBJECTNOTFOUND or
+                            e.code == netapp_api.ESOURCE_IS_DIFFERENT or
+                            ENTRY_DOES_NOT_EXIST in e.message):
+                        # Handle the case where the SnapMirror is already
+                        # cleaned up
+                        exc_context.reraise = False
+
+    def update_snapmirror(self, src_backend_name, dest_backend_name,
+                          src_flexvol_name, dest_flexvol_name):
+        """Schedule a SnapMirror update on the backend."""
+        dest_backend_config = config_utils.get_backend_configuration(
+            dest_backend_name)
+        dest_vserver = dest_backend_config.netapp_vserver
+        dest_client = config_utils.get_client_for_backend(
+            dest_backend_name, vserver_name=dest_vserver)
+
+        source_backend_config = config_utils.get_backend_configuration(
+            src_backend_name)
+        src_vserver = source_backend_config.netapp_vserver
+
+        # Update SnapMirror
+        dest_client.update_snapmirror(src_vserver,
+                                      src_flexvol_name,
+                                      dest_vserver,
+                                      dest_flexvol_name)
+
+    def quiesce_then_abort(self, src_backend_name, dest_backend_name,
+                           src_flexvol_name, dest_flexvol_name):
+        """Quiesce a SnapMirror and wait with retries before aborting."""
+        dest_backend_config = config_utils.get_backend_configuration(
+            dest_backend_name)
+        dest_vserver = dest_backend_config.netapp_vserver
+        dest_client = config_utils.get_client_for_backend(
+            dest_backend_name, vserver_name=dest_vserver)
+
+        source_backend_config = config_utils.get_backend_configuration(
+            src_backend_name)
+        src_vserver = source_backend_config.netapp_vserver
+
+        # 1. Attempt to quiesce, then abort
+        dest_client.quiesce_snapmirror(src_vserver,
+                                       src_flexvol_name,
+                                       dest_vserver,
+                                       dest_flexvol_name)
+
+        retries = (source_backend_config.netapp_snapmirror_quiesce_timeout /
+                   QUIESCE_RETRY_INTERVAL)
+
+        @utils.retry(exception.NetAppDriverException,
+                     interval=QUIESCE_RETRY_INTERVAL,
+                     retries=retries, backoff_rate=1)
+        def wait_for_quiesced():
+            snapmirror = dest_client.get_snapmirrors(
+                src_vserver, src_flexvol_name, dest_vserver,
+                dest_flexvol_name,
+                desired_attributes=['relationship-status', 'mirror-state'])[0]
+            if snapmirror.get('relationship-status') != 'quiesced':
+                msg = _("SnapMirror relationship is not quiesced.")
+                raise exception.NetAppDriverException(reason=msg)
+
+        try:
+            wait_for_quiesced()
+        except exception.NetAppDriverException:
+            dest_client.abort_snapmirror(src_vserver,
+                                         src_flexvol_name,
+                                         dest_vserver,
+                                         dest_flexvol_name,
+                                         clear_checkpoint=False)
+
+    def break_snapmirror(self, src_backend_name, dest_backend_name,
+                         src_flexvol_name, dest_flexvol_name):
+        """Break SnapMirror relationship.
+
+        1. Quiesce any ongoing SnapMirror transfers
+        2. Wait until SnapMirror finishes transfers and enters quiesced state
+        3. Break SnapMirror
+        4. Mount the destination volume so it is given a junction path
+        """
+        dest_backend_config = config_utils.get_backend_configuration(
+            dest_backend_name)
+        dest_vserver = dest_backend_config.netapp_vserver
+        dest_client = config_utils.get_client_for_backend(
+            dest_backend_name, vserver_name=dest_vserver)
+
+        source_backend_config = config_utils.get_backend_configuration(
+            src_backend_name)
+        src_vserver = source_backend_config.netapp_vserver
+
+        # 1. Attempt to quiesce, then abort
+        self.quiesce_then_abort(src_backend_name, dest_backend_name,
+                                src_flexvol_name, dest_flexvol_name)
+
+        # 2. Break SnapMirror
+        dest_client.break_snapmirror(src_vserver,
+                                     src_flexvol_name,
+                                     dest_vserver,
+                                     dest_flexvol_name)
+
+        # 3. Mount the destination volume and create a junction path
+        dest_client.mount_flexvol(dest_flexvol_name)
+
+    def resync_snapmirror(self, src_backend_name, dest_backend_name,
+                          src_flexvol_name, dest_flexvol_name):
+        """Re-sync (repair / re-establish) SnapMirror relationship."""
+        dest_backend_config = config_utils.get_backend_configuration(
+            dest_backend_name)
+        dest_vserver = dest_backend_config.netapp_vserver
+        dest_client = config_utils.get_client_for_backend(
+            dest_backend_name, vserver_name=dest_vserver)
+
+        source_backend_config = config_utils.get_backend_configuration(
+            src_backend_name)
+        src_vserver = source_backend_config.netapp_vserver
+
+        dest_client.resync_snapmirror(src_vserver,
+                                      src_flexvol_name,
+                                      dest_vserver,
+                                      dest_flexvol_name)
+
+    def resume_snapmirror(self, src_backend_name, dest_backend_name,
+                          src_flexvol_name, dest_flexvol_name):
+        """Resume SnapMirror relationship from a quiesced state."""
+        dest_backend_config = config_utils.get_backend_configuration(
+            dest_backend_name)
+        dest_vserver = dest_backend_config.netapp_vserver
+        dest_client = config_utils.get_client_for_backend(
+            dest_backend_name, vserver_name=dest_vserver)
+
+        source_backend_config = config_utils.get_backend_configuration(
+            src_backend_name)
+        src_vserver = source_backend_config.netapp_vserver
+
+        dest_client.resume_snapmirror(src_vserver,
+                                      src_flexvol_name,
+                                      dest_vserver,
+                                      dest_flexvol_name)
+
+    def create_destination_flexvol(self, src_backend_name, dest_backend_name,
+                                   src_flexvol_name, dest_flexvol_name):
+        """Create a SnapMirror mirror target FlexVol for a given source."""
+        dest_backend_config = config_utils.get_backend_configuration(
+            dest_backend_name)
+        dest_vserver = dest_backend_config.netapp_vserver
+        dest_client = config_utils.get_client_for_backend(
+            dest_backend_name, vserver_name=dest_vserver)
+
+        source_backend_config = config_utils.get_backend_configuration(
+            src_backend_name)
+        src_vserver = source_backend_config.netapp_vserver
+        src_client = config_utils.get_client_for_backend(
+            src_backend_name, vserver_name=src_vserver)
+
+        provisioning_options = (
+            src_client.get_provisioning_options_from_flexvol(
+                src_flexvol_name)
+        )
+
+        # Remove size and volume_type
+        size = provisioning_options.pop('size', None)
+        if not size:
+            msg = _("Unable to read the size of the source FlexVol (%s) "
+                    "to create a SnapMirror destination.")
+            raise exception.NetAppDriverException(msg % src_flexvol_name)
+        provisioning_options.pop('volume_type', None)
+
+        source_aggregate = provisioning_options.pop('aggregate')
+        aggregate_map = self._get_replication_aggregate_map(
+            src_backend_name, dest_backend_name)
+
+        if not aggregate_map.get(source_aggregate):
+            msg = _("Unable to find configuration matching the source "
+                    "aggregate (%s) and the destination aggregate. Option "
+                    "netapp_replication_aggregate_map may be incorrect.")
+            raise exception.NetAppDriverException(
+                message=msg % source_aggregate)
+
+        destination_aggregate = aggregate_map[source_aggregate]
+
+        # NOTE(gouthamr): The volume is intentionally created as a Data
+        # Protection volume; junction-path will be added on breaking
+        # the mirror.
+        dest_client.create_flexvol(dest_flexvol_name,
+                                   destination_aggregate,
+                                   size,
+                                   volume_type='dp',
+                                   **provisioning_options)
+
+    def ensure_snapmirrors(self, config, src_backend_name, src_flexvol_names):
+        """Ensure all the SnapMirrors needed for whole-backend replication."""
+        backend_names = self.get_replication_backend_names(config)
+        for dest_backend_name in backend_names:
+            for src_flexvol_name in src_flexvol_names:
+
+                dest_flexvol_name = src_flexvol_name
+
+                self.create_snapmirror(src_backend_name,
+                                       dest_backend_name,
+                                       src_flexvol_name,
+                                       dest_flexvol_name)
+
+    def break_snapmirrors(self, config, src_backend_name, src_flexvol_names,
+                          chosen_target):
+        """Break all existing SnapMirror relationships for a given back end."""
+        failed_to_break = []
+        backend_names = self.get_replication_backend_names(config)
+        for dest_backend_name in backend_names:
+            for src_flexvol_name in src_flexvol_names:
+
+                dest_flexvol_name = src_flexvol_name
+                try:
+                    self.break_snapmirror(src_backend_name,
+                                          dest_backend_name,
+                                          src_flexvol_name,
+                                          dest_flexvol_name)
+                except netapp_api.NaApiError:
+                    msg = _("Unable to break SnapMirror between FlexVol "
+                            "%(src)s and Flexvol %(dest)s. Associated volumes "
+                            "will have their replication state set to error.")
+                    payload = {
+                        'src': ':'.join([src_backend_name, src_flexvol_name]),
+                        'dest': ':'.join([dest_backend_name,
+                                         dest_flexvol_name]),
+                    }
+                    if dest_backend_name == chosen_target:
+                        failed_to_break.append(src_flexvol_name)
+                    LOG.exception(msg, payload)
+
+        return failed_to_break
+
+    def update_snapmirrors(self, config, src_backend_name, src_flexvol_names):
+        """Update all existing SnapMirror relationships on a given back end."""
+        backend_names = self.get_replication_backend_names(config)
+        for dest_backend_name in backend_names:
+            for src_flexvol_name in src_flexvol_names:
+
+                dest_flexvol_name = src_flexvol_name
+                try:
+                    self.update_snapmirror(src_backend_name,
+                                           dest_backend_name,
+                                           src_flexvol_name,
+                                           dest_flexvol_name)
+                except netapp_api.NaApiError:
+                    # Ignore any errors since the current source may be
+                    # unreachable
+                    pass
+
+    def _choose_failover_target(self, backend_name, flexvols,
+                                replication_targets):
+        target_lag_times = []
+
+        for target in replication_targets:
+            all_target_mirrors = self.get_snapmirrors(
+                backend_name, target, None, None)
+            flexvol_mirrors = self._filter_and_sort_mirrors(
+                all_target_mirrors, flexvols)
+
+            if not flexvol_mirrors:
+                msg = ("Ignoring replication target %(target)s because no "
+                       "SnapMirrors were found for any of the flexvols "
+                       "in (%(flexvols)s).")
+                payload = {
+                    'flexvols': ', '.join(flexvols),
+                    'target': target,
+                }
+                LOG.debug(msg, payload)
+                continue
+
+            target_lag_times.append(
+                {
+                    'target': target,
+                    'highest-lag-time': flexvol_mirrors[0]['lag-time'],
+                }
+            )
+
+        # The best target is one with the least 'worst' lag time.
+        best_target = (sorted(target_lag_times,
+                              key=lambda x: int(x['highest-lag-time']))[0]
+                       if len(target_lag_times) > 0 else {})
+
+        return best_target.get('target')
+
+    def _filter_and_sort_mirrors(self, mirrors, flexvols):
+        """Return mirrors reverse-sorted by lag time.
+
+        The 'slowest' mirror determines the best update that occurred on a
+        given replication target.
+        """
+        filtered_mirrors = list(filter(lambda x: x.get('destination-volume')
+                                in flexvols, mirrors))
+        sorted_mirrors = sorted(filtered_mirrors,
+                                key=lambda x: int(x.get('lag-time')),
+                                reverse=True)
+
+        return sorted_mirrors
+
+    def _complete_failover(self, source_backend_name, replication_targets,
+                           flexvols, volumes, failover_target=None):
+        """Failover a backend to a secondary replication target."""
+        volume_updates = []
+
+        active_backend_name = failover_target or self._choose_failover_target(
+            source_backend_name, flexvols, replication_targets)
+
+        if active_backend_name is None:
+            msg = _("No suitable host was found to failover.")
+            raise exception.NetAppDriverException(msg)
+
+        source_backend_config = config_utils.get_backend_configuration(
+            source_backend_name)
+
+        # 1. Start an update to try to get a last minute transfer before we
+        # quiesce and break
+        self.update_snapmirrors(source_backend_config, source_backend_name,
+                                flexvols)
+        # 2. Break SnapMirrors
+        failed_to_break = self.break_snapmirrors(source_backend_config,
+                                                 source_backend_name,
+                                                 flexvols, active_backend_name)
+
+        # 3. Update cinder volumes within this host
+        for volume in volumes:
+            replication_status = fields.ReplicationStatus.FAILED_OVER
+            volume_pool = volume_utils.extract_host(volume['host'],
+                                                    level='pool')
+            if volume_pool in failed_to_break:
+                replication_status = 'error'
+
+            volume_update = {
+                'volume_id': volume['id'],
+                'updates': {
+                    'replication_status': replication_status,
+                },
+            }
+            volume_updates.append(volume_update)
+
+        return active_backend_name, volume_updates
+
+    def _failover_host(self, volumes, secondary_id=None):
+
+        if secondary_id == self.backend_name:
+            msg = _("Cannot failover to the same host as the primary.")
+            raise exception.InvalidReplicationTarget(reason=msg)
+
+        replication_targets = self.get_replication_backend_names(
+            self.configuration)
+
+        if not replication_targets:
+            msg = _("No replication targets configured for backend "
+                    "%s. Cannot failover.")
+            raise exception.InvalidReplicationTarget(reason=msg % self.host)
+        elif secondary_id and secondary_id not in replication_targets:
+            msg = _("%(target)s is not among replication targets configured "
+                    "for back end %(host)s. Cannot failover.")
+            payload = {
+                'target': secondary_id,
+                'host': self.host,
+            }
+            raise exception.InvalidReplicationTarget(reason=msg % payload)
+
+        flexvols = self.ssc_library.get_ssc_flexvol_names()
+
+        try:
+            active_backend_name, volume_updates = self._complete_failover(
+                self.backend_name, replication_targets, flexvols, volumes,
+                failover_target=secondary_id)
+        except exception.NetAppDriverException as e:
+            msg = _("Could not complete failover: %s") % e
+            raise exception.UnableToFailOver(reason=msg)
+
+        # Update the ZAPI client to the backend we failed over to
+        self._update_zapi_client(active_backend_name)
+
+        self.failed_over = True
+        self.failed_over_backend_name = active_backend_name
+
+        return active_backend_name, volume_updates
diff --git a/cinder/volume/drivers/netapp/dataontap/utils/utils.py b/cinder/volume/drivers/netapp/dataontap/utils/utils.py
new file mode 100644
index 00000000000..b79b92c193b
--- /dev/null
+++ b/cinder/volume/drivers/netapp/dataontap/utils/utils.py
@@ -0,0 +1,74 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+"""
+Utilities for NetApp FAS drivers.
+
+This module contains common utilities to be used by one or more
+NetApp FAS drivers to achieve the desired functionality.
+"""
+
+from oslo_config import cfg
+from oslo_log import log
+
+from cinder import exception
+from cinder.i18n import _
+from cinder import utils
+from cinder.volume import configuration
+from cinder.volume import driver
+from cinder.volume.drivers.netapp.dataontap.client import client_cmode
+from cinder.volume.drivers.netapp import options as na_opts
+
+LOG = log.getLogger(__name__)
+CONF = cfg.CONF
+
+
+def get_backend_configuration(backend_name):
+    """Get a cDOT configuration object for a specific backend."""
+
+    config_stanzas = CONF.list_all_sections()
+    if backend_name not in config_stanzas:
+        msg = _("Could not find backend stanza %(backend_name)s in "
+                "configuration. Available stanzas are %(stanzas)s")
+        params = {
+            "stanzas": config_stanzas,
+            "backend_name": backend_name,
+        }
+        raise exception.ConfigNotFound(message=msg % params)
+
+    config = configuration.Configuration(driver.volume_opts,
+                                         config_group=backend_name)
+    config.append_config_values(na_opts.netapp_proxy_opts)
+    config.append_config_values(na_opts.netapp_connection_opts)
+    config.append_config_values(na_opts.netapp_transport_opts)
+    config.append_config_values(na_opts.netapp_basicauth_opts)
+    config.append_config_values(na_opts.netapp_provisioning_opts)
+    config.append_config_values(na_opts.netapp_cluster_opts)
+    config.append_config_values(na_opts.netapp_san_opts)
+    config.append_config_values(na_opts.netapp_replication_opts)
+
+    return config
+
+
+def get_client_for_backend(backend_name, vserver_name=None):
+    """Get a cDOT API client for a specific backend."""
+
+    config = get_backend_configuration(backend_name)
+    client = client_cmode.Client(
+        transport_type=config.netapp_transport_type,
+        username=config.netapp_login,
+        password=config.netapp_password,
+        hostname=config.netapp_server_hostname,
+        port=config.netapp_server_port,
+        vserver=vserver_name or config.netapp_vserver,
+        trace=utils.TRACE_API)
+
+    return client
diff --git a/cinder/volume/drivers/netapp/options.py b/cinder/volume/drivers/netapp/options.py
index 0967b1827b5..4d3564424cf 100644
--- a/cinder/volume/drivers/netapp/options.py
+++ b/cinder/volume/drivers/netapp/options.py
@@ -25,6 +25,7 @@ place to ensure re usability and better management of configuration options.
 """
 
 from oslo_config import cfg
+from oslo_config import types
 
 NETAPP_SIZE_MULTIPLIER_DEFAULT = 1.2
 
@@ -187,6 +188,30 @@ netapp_san_opts = [
                      'is only utilized when the storage protocol is '
                      'configured to use iSCSI or FC.')), ]
 
+netapp_replication_opts = [
+    cfg.MultiOpt('netapp_replication_aggregate_map',
+                 item_type=types.Dict(),
+                 help="Multi opt of dictionaries to represent the aggregate "
+                      "mapping between source and destination back ends when "
+                      "using whole back end replication. For every "
+                      "source aggregate associated with a cinder pool (NetApp "
+                      "FlexVol), you would need to specify the destination "
+                      "aggregate on the replication target device. A "
+                      "replication target device is configured with the "
+                      "configuration option replication_device. Specify this "
+                      "option as many times as you have replication devices. "
+                      "Each entry takes the standard dict config form: "
+                      "netapp_replication_aggregate_map = "
+                      "backend_id:<name_of_replication_device_section>,"
+                      "src_aggr_name1:dest_aggr_name1,"
+                      "src_aggr_name2:dest_aggr_name2,..."),
+    cfg.IntOpt('netapp_snapmirror_quiesce_timeout',
+               min=0,
+               default=3600,  # One Hour
+               help='The maximum time in seconds to wait for existing '
+                    'SnapMirror transfers to complete before aborting '
+                    'during a failover.'), ]
+
 CONF = cfg.CONF
 CONF.register_opts(netapp_proxy_opts)
 CONF.register_opts(netapp_connection_opts)
@@ -199,3 +224,4 @@ CONF.register_opts(netapp_img_cache_opts)
 CONF.register_opts(netapp_eseries_opts)
 CONF.register_opts(netapp_nfs_extra_opts)
 CONF.register_opts(netapp_san_opts)
+CONF.register_opts(netapp_replication_opts)
diff --git a/releasenotes/notes/netapp-cDOT-whole-backend-replication-support-59d7537fe3d0eb05.yaml b/releasenotes/notes/netapp-cDOT-whole-backend-replication-support-59d7537fe3d0eb05.yaml
new file mode 100644
index 00000000000..16864456d2e
--- /dev/null
+++ b/releasenotes/notes/netapp-cDOT-whole-backend-replication-support-59d7537fe3d0eb05.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - Added host-level (whole back end replication - v2.1) replication support
+    to the NetApp cDOT drivers (iSCSI, FC, NFS).
+upgrade:
+  - While configuring NetApp cDOT back ends, new configuration options
+    ('replication_device' and 'netapp_replication_aggregate_map') must be
+    added in order to use the host-level failover feature.