]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add dedup provisioning to 3PAR drivers
authorKurt Martin <kurt.f.martin@hp.com>
Fri, 30 Jan 2015 21:59:56 +0000 (13:59 -0800)
committerKurt Martin <kurt.f.martin@hp.com>
Thu, 12 Feb 2015 01:19:05 +0000 (17:19 -0800)
3PAR now supports thin duplication provisioning. This review is for
adding the dedup provisioning support to the 3PAR provisioning extra
specs type as well as supporting volume retype between provisioning
types (thin<->dedup, full<->dedup).

The OpenStack Configuration Reference Guide will need to updated
to include dedup as a valid hp3par:provisioning value. A note
should also be added that this feature requires SSD disks and
3PAR firmware version 3.2.1 MU1 or greater.

DocImpact
Implements: blueprint 3par-dedup

Change-Id: I6ebad3682d5d53a9b1a6329d666a1d7fb06eba0a

cinder/tests/test_hp3par.py
cinder/volume/drivers/san/hp/hp_3par_common.py

index 8019b767c24af4393ee10e587de6773139ab9066..af022965435f363ee5501403c5956a562cbe1e4d 100644 (file)
@@ -69,6 +69,7 @@ class HP3PARBaseDriver(object):
 
     VOLUME_ID = 'd03338a9-9115-48a3-8dfc-35cdfcdc15a7'
     CLONE_ID = 'd03338a9-9115-48a3-8dfc-000000000000'
+    VOLUME_TYPE_ID_DEDUP = 'd03338a9-9115-48a3-8dfc-11111111111'
     VOLUME_NAME = 'volume-' + VOLUME_ID
     VOLUME_NAME_3PAR = 'osv-0DM4qZEVSKON-DXN-NwVpw'
     SNAPSHOT_ID = '2f823bdc-e36e-4dc8-bd15-de1c7a28ff31'
@@ -112,6 +113,14 @@ class HP3PARBaseDriver(object):
               'volume_type': None,
               'volume_type_id': None}
 
+    volume_dedup = {'name': VOLUME_NAME,
+                    'id': VOLUME_ID,
+                    'display_name': 'Foo Volume',
+                    'size': 2,
+                    'host': FAKE_HOST,
+                    'volume_type': 'dedup',
+                    'volume_type_id': VOLUME_TYPE_ID_DEDUP}
+
     volume_pool = {'name': VOLUME_NAME,
                    'id': VOLUME_ID,
                    'display_name': 'Foo Volume',
@@ -161,6 +170,14 @@ class HP3PARBaseDriver(object):
                    'deleted_at': None,
                    'id': 'gold'}
 
+    volume_type_dedup = {'name': 'dedup',
+                         'deleted': False,
+                         'updated_at': None,
+                         'extra_specs': {'cpg': HP3PAR_CPG2,
+                                         'provisioning': 'dedup'},
+                         'deleted_at': None,
+                         'id': VOLUME_TYPE_ID_DEDUP}
+
     cpgs = [
         {'SAGrowth': {'LDLayout': {'diskPatterns': [{'diskType': 2}]},
                       'incrementMiB': 8192},
@@ -250,6 +267,7 @@ class HP3PARBaseDriver(object):
             'vvs': RETYPE_VVS_NAME,
             'qos': RETYPE_QOS_SPECS,
             'tpvv': True,
+            'tdvv': False,
             'volume_type': volume_type
         }
     }
@@ -263,6 +281,7 @@ class HP3PARBaseDriver(object):
             'vvs': VVS_NAME,
             'qos': QOS,
             'tpvv': True,
+            'tdvv': False,
             'volume_type': volume_type
         }
     }
@@ -276,10 +295,24 @@ class HP3PARBaseDriver(object):
             'vvs': RETYPE_VVS_NAME,
             'qos': RETYPE_QOS_SPECS,
             'tpvv': True,
+            'tdvv': False,
             'volume_type': volume_type
         }
     }
 
+    RETYPE_VOLUME_TYPE_3 = {
+        'name': 'purple',
+        'id': RETYPE_VOLUME_TYPE_ID,
+        'extra_specs': {
+            'cpg': HP3PAR_CPG_QOS,
+            'snap_cpg': HP3PAR_CPG_SNAP,
+            'vvs': RETYPE_VVS_NAME,
+            'qos': RETYPE_QOS_SPECS,
+            'tpvv': False,
+            'tdvv': True,
+            'volume_type': volume_type
+        }
+    }
     RETYPE_VOLUME_TYPE_BAD_PERSONA = {
         'name': 'bad_persona',
         'id': 'any_id',
@@ -339,6 +372,19 @@ class HP3PARBaseDriver(object):
         'comment': RETYPE_TEST_COMMENT
     }
 
+    RETYPE_TEST_COMMENT_2 = "{'retype_test': 'test comment 2'}"
+
+    RETYPE_VOLUME_INFO_2 = {
+        'name': VOLUME_NAME,
+        'id': VOLUME_ID,
+        'display_name': 'Retype Vol2',
+        'size': 1,
+        'host': RETYPE_HOST,
+        'userCPG': HP3PAR_CPG,
+        'snapCPG': HP3PAR_CPG_SNAP,
+        'provisioningType': 3,
+        'comment': RETYPE_TEST_COMMENT
+    }
     # Test for when we don't get a snapCPG.
     RETYPE_VOLUME_INFO_NO_SNAP = {
         'name': VOLUME_NAME,
@@ -365,6 +411,16 @@ class HP3PARBaseDriver(object):
     # intentionally removed to make _retype more usable for other use cases.
     RETYPE_DIFF = None
 
+    wsapi_version = {'major': 1,
+                     'build': 30201120,
+                     'minor': 4,
+                     'revision': 1}
+
+    wsapi_version_312 = {'major': 1,
+                         'build': 30102422,
+                         'minor': 3,
+                         'revision': 1}
+
     standard_login = [
         mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
         mock.call.setSSHOptions(
@@ -538,6 +594,7 @@ class HP3PARBaseDriver(object):
                     1907, {
                         'comment': comment,
                         'tpvv': True,
+                        'tdvv': False,
                         'snapCPG': HP3PAR_CPG_SNAP})]
 
             mock_client.assert_has_calls(
@@ -566,6 +623,7 @@ class HP3PARBaseDriver(object):
                     1907, {
                         'comment': comment,
                         'tpvv': True,
+                        'tdvv': False,
                         'snapCPG': HP3PAR_CPG_SNAP})]
 
             mock_client.assert_has_calls(
@@ -574,6 +632,30 @@ class HP3PARBaseDriver(object):
                 self.standard_logout)
             self.assertEqual(return_model, None)
 
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_unsupported_dedup_volume_type(self, _mock_volume_types):
+
+        mock_client = self.setup_driver_312()
+        _mock_volume_types.return_value = {
+            'name': 'dedup',
+            'extra_specs': {
+                'cpg': HP3PAR_CPG_QOS,
+                'snap_cpg': HP3PAR_CPG_SNAP,
+                'vvs_name': self.VVS_NAME,
+                'qos': self.QOS,
+                'provisioning': 'dedup',
+                'volume_type': self.volume_type_dedup}}
+
+        with mock.patch.object(hpcommon.HP3PARCommon,
+                               '_create_client') as mock_create_client:
+            mock_create_client.return_value = mock_client
+            common = self.driver._login()
+
+            self.assertRaises(exception.InvalidInput,
+                              common.get_volume_settings_from_type_id,
+                              self.VOLUME_TYPE_ID_DEDUP,
+                              "mock")
+
     @mock.patch.object(volume_types, 'get_volume_type')
     def test_get_snap_cpg_from_volume_type(self, _mock_volume_types):
 
@@ -674,6 +756,7 @@ class HP3PARBaseDriver(object):
                 'vvs_name': self.VVS_NAME,
                 'qos': self.QOS,
                 'tpvv': True,
+                'tdvv': False,
                 'volume_type': self.volume_type}}
 
         with mock.patch.object(hpcommon.HP3PARCommon,
@@ -695,6 +778,55 @@ class HP3PARBaseDriver(object):
                     1907, {
                         'comment': comment,
                         'tpvv': True,
+                        'tdvv': False,
+                        'snapCPG': HP3PAR_CPG_SNAP})]
+
+            mock_client.assert_has_calls(
+                self.standard_login +
+                expected +
+                self.standard_logout)
+            self.assertEqual(return_model,
+                             {'host': volume_utils.append_host(
+                                 self.FAKE_HOST,
+                                 HP3PAR_CPG_QOS)})
+
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_create_volume_dedup(self, _mock_volume_types):
+        # setup_mock_client drive with default configuration
+        # and return the mock HTTP 3PAR client
+        mock_client = self.setup_driver()
+
+        _mock_volume_types.return_value = {
+            'name': 'dedup',
+            'extra_specs': {
+                'cpg': HP3PAR_CPG_QOS,
+                'snap_cpg': HP3PAR_CPG_SNAP,
+                'vvs_name': self.VVS_NAME,
+                'qos': self.QOS,
+                'provisioning': 'dedup',
+                'volume_type': self.volume_type_dedup}}
+
+        with mock.patch.object(hpcommon.HP3PARCommon,
+                               '_create_client') as mock_create_client:
+            mock_create_client.return_value = mock_client
+
+            return_model = self.driver.create_volume(self.volume_dedup)
+            comment = (
+                '{"volume_type_name": "dedup", "display_name": "Foo Volume"'
+                ', "name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7'
+                '", "volume_type_id": "d03338a9-9115-48a3-8dfc-11111111111"'
+                ', "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7"'
+                ', "qos": {}, "type": "OpenStack"}')
+
+            expected = [
+                mock.call.getCPG(HP3PAR_CPG_QOS),
+                mock.call.createVolume(
+                    self.VOLUME_3PAR_NAME,
+                    HP3PAR_CPG_QOS,
+                    1907, {
+                        'comment': comment,
+                        'tpvv': False,
+                        'tdvv': True,
                         'snapCPG': HP3PAR_CPG_SNAP})]
 
             mock_client.assert_has_calls(
@@ -1026,7 +1158,7 @@ class HP3PARBaseDriver(object):
                            "old_type", "old_type_id",
                            HP3PARBaseDriver.RETYPE_HOST,
                            None, cpg, cpg, snap_cpg, snap_cpg,
-                           True, True, None, None,
+                           True, False, False, True, None, None,
                            self.QOS_SPECS, self.RETYPE_QOS_SPECS,
                            "{}")
 
@@ -1044,6 +1176,35 @@ class HP3PARBaseDriver(object):
                     'osv-0DM4qZEVSKON-DXN-NwVpw')]
             mock_client.assert_has_calls(expected)
 
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_retype_dedup(self, _mock_volume_types):
+        _mock_volume_types.return_value = self.RETYPE_VOLUME_TYPE_3
+        mock_client = self.setup_driver(mock_conf=self.RETYPE_CONF)
+
+        cpg = "any_cpg"
+        snap_cpg = "any_cpg"
+        with mock.patch.object(hpcommon.HP3PARCommon,
+                               '_create_client') as mock_create_client:
+            mock_create_client.return_value = mock_client
+            common = self.driver._login()
+            common._retype(self.volume,
+                           HP3PARBaseDriver.VOLUME_3PAR_NAME,
+                           "old_type", "old_type_id",
+                           HP3PARBaseDriver.RETYPE_HOST,
+                           None, cpg, cpg, snap_cpg, snap_cpg,
+                           True, False, False, True, None, None,
+                           self.QOS_SPECS, self.RETYPE_QOS_SPECS,
+                           "{}")
+
+            expected = [
+                mock.call.modifyVolume('osv-0DM4qZEVSKON-DXN-NwVpw',
+                                       {'action': 6,
+                                        'userCPG': 'any_cpg',
+                                        'conversionOperation': 3,
+                                        'tuneOperation': 1}),
+                mock.call.getTask(1)]
+        mock_client.assert_has_calls(expected)
+
     def test_delete_volume(self):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
@@ -1086,7 +1247,7 @@ class HP3PARBaseDriver(object):
                     'osv-0DM4qZEVSKON-AAAAAAAAA',
                     HP3PAR_CPG2,
                     {'snapCPG': 'OpenStackCPGSnap', 'tpvv': True,
-                     'online': True})]
+                     'tdvv': False, 'online': True})]
 
             mock_client.assert_has_calls(
                 self.standard_login +
@@ -1122,7 +1283,7 @@ class HP3PARBaseDriver(object):
                     'osv-0DM4qZEVSKON-AAAAAAAAA',
                     expected_cpg,
                     {'snapCPG': 'OpenStackCPGSnap', 'tpvv': True,
-                     'online': True})]
+                     'tdvv': False, 'online': True})]
 
             mock_client.assert_has_calls(
                 self.standard_login +
@@ -1565,15 +1726,18 @@ class HP3PARBaseDriver(object):
         # and return the mock HTTP 3PAR client
         mock_client = self.setup_driver()
 
-        self.driver.create_snapshot(self.snapshot)
+        with mock.patch.object(hpcommon.HP3PARCommon,
+                               '_create_client') as mock_create_client:
+            mock_create_client.return_value = mock_client
+            self.driver.create_snapshot(self.snapshot)
 
-        try:
-            ex = hpexceptions.HTTPNotFound("not found")
-            mock_client.deleteVolume = mock.Mock(side_effect=ex)
-            self.driver.delete_snapshot(self.snapshot)
-        except Exception:
-            self.fail("Deleting a snapshot that is missing should act as if "
-                      "it worked.")
+            try:
+                ex = hpexceptions.HTTPNotFound("not found")
+                mock_client.deleteVolume = mock.Mock(side_effect=ex)
+                self.driver.delete_snapshot(self.snapshot)
+            except Exception:
+                self.fail("Deleting a snapshot that is missing should act "
+                          "as if it worked.")
 
     def test_create_volume_from_snapshot(self):
         # setup_mock_client drive with default configuration
@@ -1687,6 +1851,7 @@ class HP3PARBaseDriver(object):
                 'vvs_name': self.VVS_NAME,
                 'qos': self.QOS,
                 'tpvv': True,
+                'tdvv': False,
                 'volume_type': self.volume_type}}
 
         with mock.patch.object(hpcommon.HP3PARCommon,
@@ -1745,14 +1910,17 @@ class HP3PARBaseDriver(object):
             'getVolume.return_value': {}
         }
 
-        self.setup_driver(mock_conf=conf)
+        mock_client = self.setup_driver(mock_conf=conf)
 
-        volume = self.volume.copy()
-        volume['size'] = self.volume['size'] + 10
+        with mock.patch.object(hpcommon.HP3PARCommon,
+                               '_create_client') as mock_create_client:
+            mock_create_client.return_value = mock_client
+            volume = self.volume.copy()
+            volume['size'] = self.volume['size'] + 10
 
-        self.assertRaises(exception.CinderException,
-                          self.driver.create_volume_from_snapshot,
-                          volume, self.snapshot)
+            self.assertRaises(exception.CinderException,
+                              self.driver.create_volume_from_snapshot,
+                              volume, self.snapshot)
 
     @mock.patch.object(volume_types, 'get_volume_type')
     def test_create_volume_from_snapshot_qos(self, _mock_volume_types):
@@ -1770,6 +1938,7 @@ class HP3PARBaseDriver(object):
                     'vvs_name': self.VVS_NAME,
                     'qos': self.QOS,
                     'tpvv': True,
+                    'tdvv': False,
                     'volume_type': self.volume_type}}
             self.driver.create_volume_from_snapshot(
                 self.volume_qos,
@@ -2504,6 +2673,7 @@ class HP3PARBaseDriver(object):
                 'vvs_name': self.VVS_NAME,
                 'qos': self.QOS,
                 'tpvv': True,
+                'tdvv': False,
                 'volume_type': self.volume_type}}
 
         volume = {'display_name': None,
@@ -2683,6 +2853,28 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase):
             m_conf=mock_conf,
             driver=hpfcdriver.HP3PARFCDriver)
 
+        mock_client.getWsApiVersion.return_value = self.wsapi_version
+
+        expected = [
+            mock.call.getCPG(HP3PAR_CPG),
+            mock.call.getCPG(HP3PAR_CPG2)]
+        mock_client.assert_has_calls(
+            self.standard_login +
+            expected +
+            self.standard_logout)
+        mock_client.reset_mock()
+        return mock_client
+
+    def setup_driver_312(self, config=None, mock_conf=None):
+
+        self.ctxt = context.get_admin_context()
+        mock_client = self.setup_mock_client(
+            conf=config,
+            m_conf=mock_conf,
+            driver=hpfcdriver.HP3PARFCDriver)
+
+        mock_client.getWsApiVersion.return_value = self.wsapi_version_312
+
         expected = [
             mock.call.getCPG(HP3PAR_CPG),
             mock.call.getCPG(HP3PAR_CPG2)]
@@ -3326,6 +3518,34 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
             m_conf=mock_conf,
             driver=hpdriver.HP3PARISCSIDriver)
 
+        mock_client.getWsApiVersion.return_value = self.wsapi_version
+
+        expected_get_cpgs = [
+            mock.call.getCPG(HP3PAR_CPG),
+            mock.call.getCPG(HP3PAR_CPG2)]
+        expected_get_ports = [mock.call.getPorts()]
+        mock_client.assert_has_calls(
+            self.standard_login +
+            expected_get_cpgs +
+            self.standard_logout +
+            self.standard_login +
+            expected_get_ports +
+            self.standard_logout)
+        mock_client.reset_mock()
+
+        return mock_client
+
+    def setup_driver_312(self, config=None, mock_conf=None):
+
+        self.ctxt = context.get_admin_context()
+
+        mock_client = self.setup_mock_client(
+            conf=config,
+            m_conf=mock_conf,
+            driver=hpdriver.HP3PARISCSIDriver)
+
+        mock_client.getWsApiVersion.return_value = self.wsapi_version_312
+
         expected_get_cpgs = [
             mock.call.getCPG(HP3PAR_CPG),
             mock.call.getCPG(HP3PAR_CPG2)]
index 6326e1b86382967731edb8d46746b762e6dd9aa3..805cfe7522e1980c16c0092095d9e65505e6edab 100644 (file)
@@ -1,4 +1,4 @@
-#    (c) Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
+#    (c) Copyright 2012-2015 Hewlett-Packard Development Company, L.P.
 #    All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -69,6 +69,7 @@ from taskflow.patterns import linear_flow
 LOG = logging.getLogger(__name__)
 
 MIN_CLIENT_VERSION = '3.1.2'
+DEDUP_API_VERSION = 30201120
 
 hp3par_opts = [
     cfg.StrOpt('hp3par_api_url',
@@ -164,10 +165,11 @@ class HP3PARCommon(object):
         2.0.33 - Fix host persona to match WSAPI mapping bug #1403997
         2.0.34 - Fix log messages to match guidelines. bug #1411370
         2.0.35 - Fix default snapCPG for manage_existing bug #1393609
+        2.0.36 - Added support for dedup provisioning
 
     """
 
-    VERSION = "2.0.35"
+    VERSION = "2.0.36"
 
     stats = {}
 
@@ -179,12 +181,14 @@ class HP3PARCommon(object):
     VLUN_TYPE_HOST_SET = 5
 
     THIN = 2
+    DEDUP = 6
     CONVERT_TO_THIN = 1
     CONVERT_TO_FULL = 2
+    CONVERT_TO_DEDUP = 3
 
     # Valid values for volume type extra specs
     # The first value in the list is the default value
-    valid_prov_values = ['thin', 'full']
+    valid_prov_values = ['thin', 'full', 'dedup']
     valid_persona_values = ['2 - Generic-ALUA',
                             '1 - Generic',
                             '3 - Generic-legacy',
@@ -266,6 +270,8 @@ class HP3PARCommon(object):
             raise exception.VolumeBackendAPIException(data=msg)
         try:
             self.client = self._create_client()
+            wsapi_version = self.client.getWsApiVersion()
+            self.API_VERSION = wsapi_version['build']
         except hpexceptions.UnsupportedVersion as ex:
             raise exception.InvalidInput(ex)
         LOG.info(_LI("HP3PARCommon %(common_ver)s, hp3parclient %(rest_ver)s"),
@@ -933,8 +939,8 @@ class HP3PARCommon(object):
         if persona_value not in self.valid_persona_values:
             err = (_("Must specify a valid persona %(valid)s,"
                      "value '%(persona)s' is invalid.") %
-                   ({'valid': self.valid_persona_values,
-                    'persona': persona_value}))
+                   {'valid': self.valid_persona_values,
+                   'persona': persona_value})
             LOG.error(err)
             raise exception.InvalidInput(reason=err)
         # persona is set by the id so remove the text and return the id
@@ -1016,21 +1022,34 @@ class HP3PARCommon(object):
                                          default_prov)
         # check for valid provisioning type
         if prov_value not in self.valid_prov_values:
-            err = _("Must specify a valid provisioning type %(valid)s, "
-                    "value '%(prov)s' is invalid.") % \
-                   ({'valid': self.valid_prov_values,
-                     'prov': prov_value})
+            err = (_("Must specify a valid provisioning type %(valid)s, "
+                     "value '%(prov)s' is invalid.") %
+                   {'valid': self.valid_prov_values,
+                    'prov': prov_value})
             LOG.error(err)
             raise exception.InvalidInput(reason=err)
 
         tpvv = True
+        tdvv = False
         if prov_value == "full":
             tpvv = False
+        elif prov_value == "dedup":
+            tpvv = False
+            tdvv = True
+
+        if tdvv and (self.API_VERSION < DEDUP_API_VERSION):
+            err = (_("Dedup is a valid provisioning type, "
+                     "but requires WSAPI version '%(dedup_version)s' "
+                     "version '%(version)s' is installed.") %
+                   {'dedup_version': DEDUP_API_VERSION,
+                    'version': self.API_VERSION})
+            LOG.error(err)
+            raise exception.InvalidInput(reason=err)
 
         return {'hp3par_keys': hp3par_keys,
                 'cpg': cpg, 'snap_cpg': snap_cpg,
                 'vvs_name': vvs_name, 'qos': qos,
-                'tpvv': tpvv, 'volume_type': volume_type}
+                'tpvv': tpvv, 'tdvv': tdvv, 'volume_type': volume_type}
 
     def get_volume_settings_from_type(self, volume, host=None):
         """Get 3PAR volume settings given a volume.
@@ -1083,6 +1102,7 @@ class HP3PARCommon(object):
             cpg = type_info['cpg']
             snap_cpg = type_info['snap_cpg']
             tpvv = type_info['tpvv']
+            tdvv = type_info['tdvv']
 
             type_id = volume.get('volume_type_id', None)
             if type_id is not None:
@@ -1097,6 +1117,10 @@ class HP3PARCommon(object):
                       'snapCPG': snap_cpg,
                       'tpvv': tpvv}
 
+            # Only set the dedup option if the backend supports it.
+            if self.API_VERSION >= DEDUP_API_VERSION:
+                extras['tdvv'] = tdvv
+
             capacity = self._capacity_from_size(volume['size'])
             volume_name = self._get_3par_vol_name(volume['id'])
             self.client.createVolume(volume_name, cpg, capacity, extras)
@@ -1129,7 +1153,7 @@ class HP3PARCommon(object):
         return self._get_model_update(volume['host'], cpg)
 
     def _copy_volume(self, src_name, dest_name, cpg, snap_cpg=None,
-                     tpvv=True):
+                     tpvv=True, tdvv=False):
         # Virtual volume sets are not supported with the -online option
         LOG.debug('Creating clone of a volume %(src)s to %(dest)s.',
                   {'src': src_name, 'dest': dest_name})
@@ -1138,6 +1162,9 @@ class HP3PARCommon(object):
         if snap_cpg is not None:
             optional['snapCPG'] = snap_cpg
 
+        if self.API_VERSION >= DEDUP_API_VERSION:
+            optional['tdvv'] = tdvv
+
         body = self.client.copyVolume(src_name, dest_name, cpg, optional)
         return body['taskid']
 
@@ -1194,7 +1221,8 @@ class HP3PARCommon(object):
             cpg = type_info['cpg']
             self._copy_volume(orig_name, vol_name, cpg=cpg,
                               snap_cpg=type_info['snap_cpg'],
-                              tpvv=type_info['tpvv'])
+                              tpvv=type_info['tpvv'],
+                              tdvv=type_info['tdvv'])
 
             return self._get_model_update(volume['host'], cpg)
 
@@ -1508,7 +1536,8 @@ class HP3PARCommon(object):
 
             # Create a physical copy of the volume
             task_id = self._copy_volume(volume_name, temp_vol_name,
-                                        cpg, cpg, type_info['tpvv'])
+                                        cpg, cpg, type_info['tpvv'],
+                                        type_info['tdvv'])
 
             LOG.debug('Copy volume scheduled: convert_to_base_volume: '
                       'id=%s.', volume['id'])
@@ -1659,7 +1688,8 @@ class HP3PARCommon(object):
         portPos['cardPort'] = int(split[2])
         return portPos
 
-    def tune_vv(self, old_tpvv, new_tpvv, old_cpg, new_cpg, volume_name):
+    def tune_vv(self, old_tpvv, new_tpvv, old_tdvv, new_tdvv,
+                old_cpg, new_cpg, volume_name):
         """Tune the volume to change the userCPG and/or provisioningType.
 
         The volume will be modified/tuned/converted to the new userCPG and
@@ -1670,7 +1700,7 @@ class HP3PARCommon(object):
         either be done or it is in a state that we need to treat as an error.
         """
 
-        if old_tpvv == new_tpvv:
+        if old_tpvv == new_tpvv and old_tdvv == new_tdvv:
             if new_cpg != old_cpg:
                 LOG.info(_LI("Modifying %(volume_name)s userCPG "
                              "from %(old_cpg)s"
@@ -1691,15 +1721,20 @@ class HP3PARCommon(object):
                            {'status': status, 'volume_name': volume_name})
                     raise exception.VolumeBackendAPIException(msg)
         else:
-            if old_tpvv:
-                cop = self.CONVERT_TO_FULL
-                LOG.info(_LI("Converting %(volume_name)s to full provisioning "
-                             "with userCPG=%(new_cpg)s"),
-                         {'volume_name': volume_name, 'new_cpg': new_cpg})
-            else:
+            if new_tpvv:
                 cop = self.CONVERT_TO_THIN
                 LOG.info(_LI("Converting %(volume_name)s to thin provisioning "
-                             "with userCPG=%(new_cpg)s"),
+                             "with userCPG=%(new_cpg)s") %
+                         {'volume_name': volume_name, 'new_cpg': new_cpg})
+            elif new_tdvv:
+                cop = self.CONVERT_TO_DEDUP
+                LOG.info(_LI("Converting %(volume_name)s to thin dedup "
+                             "provisioning with userCPG=%(new_cpg)s") %
+                         {'volume_name': volume_name, 'new_cpg': new_cpg})
+            else:
+                cop = self.CONVERT_TO_FULL
+                LOG.info(_LI("Converting %(volume_name)s to full provisioning "
+                             "with userCPG=%(new_cpg)s") %
                          {'volume_name': volume_name, 'new_cpg': new_cpg})
 
             try:
@@ -1772,8 +1807,8 @@ class HP3PARCommon(object):
 
     def _retype(self, volume, volume_name, new_type_name, new_type_id, host,
                 new_persona, old_cpg, new_cpg, old_snap_cpg, new_snap_cpg,
-                old_tpvv, new_tpvv, old_vvs, new_vvs, old_qos, new_qos,
-                old_comment):
+                old_tpvv, new_tpvv, old_tdvv, new_tdvv, old_vvs, new_vvs,
+                old_qos, new_qos, old_comment):
 
         action = "volume:retype"
 
@@ -1796,6 +1831,7 @@ class HP3PARCommon(object):
             store={'common': self,
                    'volume_name': volume_name, 'volume': volume,
                    'old_tpvv': old_tpvv, 'new_tpvv': new_tpvv,
+                   'old_tdvv': old_tdvv, 'new_tdvv': new_tdvv,
                    'old_cpg': old_cpg, 'new_cpg': new_cpg,
                    'old_snap_cpg': old_snap_cpg, 'new_snap_cpg': new_snap_cpg,
                    'old_vvs': old_vvs, 'new_vvs': new_vvs,
@@ -1836,6 +1872,7 @@ class HP3PARCommon(object):
         new_cpg = new_volume_settings['cpg']
         new_snap_cpg = new_volume_settings['snap_cpg']
         new_tpvv = new_volume_settings['tpvv']
+        new_tdvv = new_volume_settings['tdvv']
         new_qos = new_volume_settings['qos']
         new_vvs = new_volume_settings['vvs_name']
         new_persona = None
@@ -1850,6 +1887,7 @@ class HP3PARCommon(object):
         # same settings that were used with this volume.
         old_volume_info = self.client.getVolume(volume_name)
         old_tpvv = old_volume_info['provisioningType'] == self.THIN
+        old_tdvv = old_volume_info['provisioningType'] == self.DEDUP
         old_cpg = old_volume_info['userCPG']
         old_comment = old_volume_info['comment']
         old_snap_cpg = None
@@ -1863,7 +1901,8 @@ class HP3PARCommon(object):
         self._retype(volume, volume_name, new_type_name, new_type_id,
                      host, new_persona, old_cpg, new_cpg,
                      old_snap_cpg, new_snap_cpg, old_tpvv, new_tpvv,
-                     old_vvs, new_vvs, old_qos, new_qos, old_comment)
+                     old_tdvv, new_tdvv, old_vvs, new_vvs, old_qos,
+                     new_qos, old_comment)
 
         if host:
             return True, self._get_model_update(host['host'], new_cpg)
@@ -2037,9 +2076,10 @@ class TuneVolumeTask(flow_utils.CinderTask):
     def __init__(self, action, **kwargs):
         super(TuneVolumeTask, self).__init__(addons=[action])
 
-    def execute(self, common, old_tpvv, new_tpvv, old_cpg, new_cpg,
-                volume_name):
-        common.tune_vv(old_tpvv, new_tpvv, old_cpg, new_cpg, volume_name)
+    def execute(self, common, old_tpvv, new_tpvv, old_tdvv, new_tdvv,
+                old_cpg, new_cpg, volume_name):
+        common.tune_vv(old_tpvv, new_tpvv, old_tdvv, new_tdvv,
+                       old_cpg, new_cpg, volume_name)
 
 
 class ModifySpecsTask(flow_utils.CinderTask):