From 18e3fcda0e66d4f59bf32c3dede44e20ef8e10aa Mon Sep 17 00:00:00 2001
From: "mark.sturdevant" <mark.sturdevant@hpe.com>
Date: Tue, 5 Jul 2016 17:18:33 -0700
Subject: [PATCH] HPE3PAR make share from snapshot writable

Use share mounting and copying to allow the
3PAR to export writable shares created from
snapshots. This version works without the
data service driver data helper.

Implements: blueprint hpe3par-rw-snapshot-shares
Change-Id: I6a15db0dea09e72e9d1de3c817852e5165eec956
---
 doc/source/devref/hpe_3par_driver.rst         |   6 -
 manila/share/drivers/hpe/hpe_3par_driver.py   |  27 +-
 manila/share/drivers/hpe/hpe_3par_mediator.py | 389 +++++++++++++-----
 .../drivers/hpe/test_hpe_3par_constants.py    |   3 +
 .../share/drivers/hpe/test_hpe_3par_driver.py |  37 +-
 .../drivers/hpe/test_hpe_3par_mediator.py     | 284 +++++++++++--
 ...r-rw-snapshot-shares-f7c33b4bf528bf00.yaml |   3 +
 7 files changed, 571 insertions(+), 178 deletions(-)
 create mode 100644 releasenotes/notes/hpe3par-rw-snapshot-shares-f7c33b4bf528bf00.yaml

diff --git a/doc/source/devref/hpe_3par_driver.rst b/doc/source/devref/hpe_3par_driver.rst
index 1a3e530272..f92404b2fe 100644
--- a/doc/source/devref/hpe_3par_driver.rst
+++ b/doc/source/devref/hpe_3par_driver.rst
@@ -36,21 +36,15 @@ The following operations are supported with HPE 3PAR File Persona:
 - Allow/deny NFS share access
 
   * IP access rules are required for NFS share access
-  * Shares created from snapshots are always read-only
-  * Shares not created from snapshots are read-write (and subject to ACLs)
 
 - Allow/deny CIFS share access
 
   * CIFS shares require user access rules.
   * User access requires a 3PAR local user (LDAP and AD is not yet supported)
-  * Shares created from snapshots are always read-only
-  * Shares not created from snapshots are read-write (and subject to ACLs)
 
 - Create/delete snapshots
 - Create shares from snapshots
 
-  * Shares created from snapshots are always read-only
-
 Share networks are not supported. Shares are created directly on the 3PAR
 without the use of a share server or service VM. Network connectivity is
 setup outside of manila.
diff --git a/manila/share/drivers/hpe/hpe_3par_driver.py b/manila/share/drivers/hpe/hpe_3par_driver.py
index a5c5806cd4..6392f8bc08 100644
--- a/manila/share/drivers/hpe/hpe_3par_driver.py
+++ b/manila/share/drivers/hpe/hpe_3par_driver.py
@@ -269,28 +269,6 @@ class HPE3ParShareDriver(driver.ShareDriver):
         return share_server['backend_details'].get('ip') if share_server else (
             self.share_ip_address)
 
-    @staticmethod
-    def _build_export_location(protocol, ip, path):
-
-        if not ip:
-            message = _('Failed to build export location due to missing IP.')
-            raise exception.InvalidInput(message)
-
-        if not path:
-            message = _('Failed to build export location due to missing path.')
-            raise exception.InvalidInput(message)
-
-        if protocol == 'NFS':
-            location = ':'.join((ip, path))
-        elif protocol == 'CIFS':
-            location = '\\\\%s\%s' % (ip, path)
-        else:
-            message = _('Invalid protocol. Expected NFS or CIFS. '
-                        'Got %s.') % protocol
-            raise exception.InvalidInput(message)
-
-        return location
-
     @staticmethod
     def build_share_comment(share):
         """Create an informational only comment to help admins and testers."""
@@ -325,7 +303,7 @@ class HPE3ParShareDriver(driver.ShareDriver):
             comment=self.build_share_comment(share)
         )
 
-        return self._build_export_location(protocol, ip, path)
+        return self._hpe3par.build_export_location(protocol, ip, path)
 
     def create_share_from_snapshot(self, context, share, snapshot,
                                    share_server=None):
@@ -345,10 +323,11 @@ class HPE3ParShareDriver(driver.ShareDriver):
             snapshot['id'],
             self.fpg,
             self.vfs,
+            size=share['size'],
             comment=self.build_share_comment(share)
         )
 
-        return self._build_export_location(protocol, ip, path)
+        return self._hpe3par.build_export_location(protocol, ip, path)
 
     def delete_share(self, context, share, share_server=None):
         """Deletes share and its fstore."""
diff --git a/manila/share/drivers/hpe/hpe_3par_mediator.py b/manila/share/drivers/hpe/hpe_3par_mediator.py
index 3c86cf0e7c..41fb1f3975 100644
--- a/manila/share/drivers/hpe/hpe_3par_mediator.py
+++ b/manila/share/drivers/hpe/hpe_3par_mediator.py
@@ -23,9 +23,10 @@ from oslo_utils import importutils
 from oslo_utils import units
 import six
 
+from manila.data import utils as data_utils
 from manila import exception
 from manila import utils
-from manila.i18n import _, _LI, _LW
+from manila.i18n import _, _LI, _LW, _LE
 
 hpe3parclient = importutils.try_import("hpe3parclient")
 if hpe3parclient:
@@ -56,6 +57,7 @@ DOES_NOT_EXIST = 'does not exist, cannot'
 LOCAL_IP = '127.0.0.1'
 LOCAL_IP_RO = '127.0.0.2'
 SUPER_SHARE = 'OPENSTACK_SUPER_SHARE'
+TMP_RO_SNAP_EXPORT = "Temp RO snapshot export as source for creating RW share."
 
 
 class HPE3ParMediator(object):
@@ -73,10 +75,11 @@ class HPE3ParMediator(object):
         2.0.4 - Remove file tree on delete when using nested shares #1538800
         2.0.5 - Reduce the fsquota by share size
                 when a share is deleted #1582931
+        2.0.6 - Read-write share from snapshot (using driver mount and copy)
 
     """
 
-    VERSION = "2.0.5"
+    VERSION = "2.0.6"
 
     def __init__(self, **kwargs):
 
@@ -199,6 +202,25 @@ class HPE3ParMediator(object):
                               'err': six.text_type(e)})
             # don't raise exception on logout()
 
+    @staticmethod
+    def build_export_location(protocol, ip, path):
+
+        if not ip:
+            message = _('Failed to build export location due to missing IP.')
+            raise exception.InvalidInput(reason=message)
+
+        if not path:
+            message = _('Failed to build export location due to missing path.')
+            raise exception.InvalidInput(reason=message)
+
+        share_proto = HPE3ParMediator.ensure_supported_protocol(protocol)
+        if share_proto == 'nfs':
+            location = ':'.join((ip, path))
+        else:
+            location = r'\\%s\%s' % (ip, path)
+
+        return location
+
     def get_provisioned_gb(self, fpg):
         total_mb = 0
         try:
@@ -359,7 +381,8 @@ class HPE3ParMediator(object):
         return ','.join(options)
 
     def _build_createfshare_kwargs(self, protocol, fpg, fstore, readonly,
-                                   sharedir, extra_specs, comment):
+                                   sharedir, extra_specs, comment,
+                                   client_ip=None):
         createfshare_kwargs = dict(fpg=fpg,
                                    fstore=fstore,
                                    sharedir=sharedir,
@@ -371,21 +394,27 @@ class HPE3ParMediator(object):
             LOG.warning(msg)
 
         if protocol == 'nfs':
-            # New NFS shares needs seed IP to prevent "all" access.
-            # Readonly and readwrite NFS shares client IPs cannot overlap.
-            if readonly:
-                createfshare_kwargs['clientip'] = LOCAL_IP_RO
+            if client_ip:
+                createfshare_kwargs['clientip'] = client_ip
             else:
-                createfshare_kwargs['clientip'] = LOCAL_IP
+                # New NFS shares needs seed IP to prevent "all" access.
+                # Readonly and readwrite NFS shares client IPs cannot overlap.
+                if readonly:
+                    createfshare_kwargs['clientip'] = LOCAL_IP_RO
+                else:
+                    createfshare_kwargs['clientip'] = LOCAL_IP
             options = self._get_nfs_options(extra_specs, readonly)
             createfshare_kwargs['options'] = options
         else:
 
             # To keep the original (Kilo, Liberty) behavior where CIFS IP
             # access rules were required in addition to user rules enable
-            # this to use a local seed IP instead of the default (all allowed).
+            # this to use a seed IP instead of the default (all allowed).
             if self.hpe3par_require_cifs_ip:
-                createfshare_kwargs['allowip'] = LOCAL_IP
+                if client_ip:
+                    createfshare_kwargs['allowip'] = client_ip
+                else:
+                    createfshare_kwargs['allowip'] = LOCAL_IP
 
             smb_opts = (ACCESS_BASED_ENUM, CONTINUOUS_AVAIL, CACHE)
 
@@ -455,7 +484,8 @@ class HPE3ParMediator(object):
             raise exception.ShareBackendException(msg=msg)
 
     def _create_share(self, project_id, share_id, protocol, extra_specs,
-                      fpg, vfs, fstore, sharedir, readonly, size, comment):
+                      fpg, vfs, fstore, sharedir, readonly, size, comment,
+                      client_ip=None):
         share_name = self.ensure_prefix(share_id, readonly=readonly)
 
         if not (sharedir or self.hpe3par_fstore_per_share):
@@ -471,13 +501,15 @@ class HPE3ParMediator(object):
             else:
                 fstore = self.ensure_prefix(project_id, protocol)
 
-        createfshare_kwargs = self._build_createfshare_kwargs(protocol,
-                                                              fpg,
-                                                              fstore,
-                                                              readonly,
-                                                              sharedir,
-                                                              extra_specs,
-                                                              comment)
+        createfshare_kwargs = self._build_createfshare_kwargs(
+            protocol,
+            fpg,
+            fstore,
+            readonly,
+            sharedir,
+            extra_specs,
+            comment,
+            client_ip=client_ip)
 
         if not use_existing_fstore:
 
@@ -538,7 +570,8 @@ class HPE3ParMediator(object):
     def create_share(self, project_id, share_id, share_proto, extra_specs,
                      fpg, vfs,
                      fstore=None, sharedir=None, readonly=False, size=None,
-                     comment=OPEN_STACK_MANILA):
+                     comment=OPEN_STACK_MANILA,
+                     client_ip=None):
         """Create the share and return its path.
 
         This method can create a share when called by the driver or when
@@ -556,6 +589,8 @@ class HPE3ParMediator(object):
         :param sharedir: (optional) Share directory.
         :param readonly: (optional) Create share as read-only.
         :param size: (optional) Size limit for file store if creating one.
+        :param comment: (optional) Comment to set on the share.
+        :param client_ip: (optional) IP address to give access to.
         :return: share path string
         """
 
@@ -570,7 +605,8 @@ class HPE3ParMediator(object):
                                    sharedir,
                                    readonly,
                                    size,
-                                   comment)
+                                   comment,
+                                   client_ip=client_ip)
 
         if protocol == 'nfs':
             return share['sharePath']
@@ -580,6 +616,7 @@ class HPE3ParMediator(object):
     def create_share_from_snapshot(self, share_id, share_proto, extra_specs,
                                    orig_project_id, orig_share_id,
                                    snapshot_id, fpg, vfs,
+                                   size=None,
                                    comment=OPEN_STACK_MANILA):
 
         protocol = self.ensure_supported_protocol(share_proto)
@@ -612,7 +649,28 @@ class HPE3ParMediator(object):
             sharedir = '.snapshot/%s/%s' % (snapshot['snapName'],
                                             orig_share_name)
 
-        return self.create_share(
+        if protocol == "smb" and (not self.hpe3par_cifs_admin_access_username
+           or not self.hpe3par_cifs_admin_access_password):
+            LOG.warning(_LW("hpe3par_cifs_admin_access_username and "
+                            "hpe3par_cifs_admin_access_password must be "
+                            "provided in order for CIFS shares created from "
+                            "snapshots to be writable."))
+            return self.create_share(
+                orig_project_id,
+                share_id,
+                protocol,
+                extra_specs,
+                fpg,
+                vfs,
+                fstore=fstore,
+                sharedir=sharedir,
+                readonly=True,
+                comment=comment,
+            )
+
+        # Export the snapshot as read-only to copy from.
+        temp = ' '.join((comment, TMP_RO_SNAP_EXPORT))
+        source_path = self.create_share(
             orig_project_id,
             share_id,
             protocol,
@@ -622,9 +680,120 @@ class HPE3ParMediator(object):
             fstore=fstore,
             sharedir=sharedir,
             readonly=True,
-            comment=comment,
+            comment=temp,
+            client_ip=self.my_ip
         )
 
+        try:
+            share_name = self.ensure_prefix(share_id)
+            dest_path = self.create_share(
+                orig_project_id,
+                share_id,
+                protocol,
+                extra_specs,
+                fpg,
+                vfs,
+                fstore=fstore,
+                readonly=False,
+                size=size,
+                comment=comment,
+                client_ip=','.join((self.my_ip, LOCAL_IP))
+            )
+
+            try:
+                if protocol == 'smb':
+                    self._grant_admin_smb_access(
+                        protocol, fpg, vfs, fstore, comment, share=share_name)
+
+                    ro_share_name = self.ensure_prefix(share_id, readonly=True)
+                    self._grant_admin_smb_access(
+                        protocol, fpg, vfs, fstore, temp, share=ro_share_name)
+
+                ip = self.hpe3par_share_ip_address
+                source_location = self.build_export_location(
+                    protocol, ip, source_path)
+                dest_location = self.build_export_location(
+                    protocol, ip, dest_path)
+
+                self._copy_share_data(
+                    share_id, source_location, dest_location,  protocol)
+
+                # Revoke the admin access that was needed to copy to the dest.
+                if protocol == 'nfs':
+                    self._change_access(DENY,
+                                        orig_project_id,
+                                        share_id,
+                                        protocol,
+                                        'ip',
+                                        self.my_ip,
+                                        'rw',
+                                        fpg,
+                                        vfs)
+                else:
+                    self._revoke_admin_smb_access(
+                        protocol, fpg, vfs, fstore, comment)
+
+            except Exception as e:
+                msg = _LE('Exception during mount and copy from RO snapshot '
+                          'to RW share: %s')
+                LOG.error(msg, e)
+                self._delete_share(share_name, protocol, fpg, vfs, fstore)
+                raise
+
+        finally:
+            self._delete_ro_share(
+                orig_project_id, share_id, protocol, fpg, vfs, fstore)
+
+        return dest_path
+
+    def _copy_share_data(self, dest_id, source_location, dest_location,
+                         protocol):
+
+        mount_location = "%s%s" % (self.hpe3par_share_mount_path, dest_id)
+        source_share_dir = '/'.join((mount_location, "source_snap"))
+        dest_share_dir = '/'.join((mount_location, "dest_share"))
+
+        dirs_to_remove = []
+        dirs_to_unmount = []
+        try:
+            utils.execute('mkdir', '-p', source_share_dir, run_as_root=True)
+            dirs_to_remove.append(source_share_dir)
+            self._mount_share(protocol, source_location, source_share_dir)
+            dirs_to_unmount.append(source_share_dir)
+
+            utils.execute('mkdir', dest_share_dir, run_as_root=True)
+            dirs_to_remove.append(dest_share_dir)
+            self._mount_share(protocol, dest_location, dest_share_dir)
+            dirs_to_unmount.append(dest_share_dir)
+
+            self._copy_data(source_share_dir, dest_share_dir)
+        finally:
+            for d in dirs_to_unmount:
+                self._unmount_share(d)
+
+            if dirs_to_remove:
+                dirs_to_remove.append(mount_location)
+                utils.execute('rmdir', *dirs_to_remove, run_as_root=True)
+
+    def _copy_data(self, source_share_dir, dest_share_dir):
+
+        err_msg = None
+        err_data = None
+        try:
+            copy = data_utils.Copy(source_share_dir, dest_share_dir, '')
+            copy.run()
+            progress = copy.get_progress()['total_progress']
+            if progress != 100:
+                err_msg = _("Failed to copy data, reason: "
+                            "Total progress %d != 100.")
+                err_data = progress
+        except Exception as err:
+            err_msg = _("Failed to copy data, reason: %s.")
+            err_data = six.text_type(err)
+
+        if err_msg:
+            raise exception.ShareBackendException(msg=err_msg % err_data)
+
     def _delete_share(self, share_name, protocol, fpg, vfs, fstore):
         try:
             self._client.removefshare(
@@ -636,6 +805,20 @@ class HPE3ParMediator(object):
             LOG.exception(msg)
             raise exception.ShareBackendException(msg=msg)
 
+    def _delete_ro_share(self, project_id, share_id, protocol,
+                         fpg, vfs, fstore):
+        share_name_ro = self.ensure_prefix(share_id, readonly=True)
+        if not fstore:
+            fstore = self._find_fstore(project_id,
+                                       share_name_ro,
+                                       protocol,
+                                       fpg,
+                                       vfs,
+                                       allow_cross_protocol=True)
+        if fstore:
+            self._delete_share(share_name_ro, protocol, fpg, vfs, fstore)
+        return fstore
+
     def delete_share(self, project_id, share_id, share_size, share_proto,
                      fpg, vfs):
 
@@ -653,46 +836,39 @@ class HPE3ParMediator(object):
             self._delete_share(share_name, protocol, fpg, vfs, fstore)
             removed_writable = True
 
-        share_name_ro = self.ensure_prefix(share_id, readonly=True)
-        if not fstore:
-            fstore = self._find_fstore(project_id,
-                                       share_name_ro,
-                                       protocol,
-                                       fpg,
-                                       vfs,
-                                       allow_cross_protocol=True)
-        if fstore:
-            self._delete_share(share_name_ro, protocol, fpg, vfs, fstore)
+        # Try to delete the read-only twin share, too.
+        fstore = self._delete_ro_share(
+            project_id, share_id, protocol, fpg, vfs, fstore)
 
-            if fstore == share_name:
-                try:
-                    self._client.removefstore(vfs, fstore, fpg=fpg)
-                except Exception as e:
-                    msg = (_('Failed to remove fstore %(fstore)s: %(e)s') %
-                           {'fstore': fstore, 'e': six.text_type(e)})
-                    LOG.exception(msg)
-                    raise exception.ShareBackendException(msg=msg)
+        if fstore == share_name:
+            try:
+                self._client.removefstore(vfs, fstore, fpg=fpg)
+            except Exception as e:
+                msg = (_('Failed to remove fstore %(fstore)s: %(e)s') %
+                       {'fstore': fstore, 'e': six.text_type(e)})
+                LOG.exception(msg)
+                raise exception.ShareBackendException(msg=msg)
 
-            elif removed_writable:
-                try:
-                    # Attempt to remove file tree on delete when using nested
-                    # shares. If the file tree cannot be removed for whatever
-                    # reason, we will not treat this as an error_deleting
-                    # issue. We will allow the delete to continue as requested.
-                    self._delete_file_tree(
-                        share_name, protocol, fpg, vfs, fstore)
-                    # reduce the fsquota by share size when a tree is deleted.
-                    self._update_capacity_quotas(
-                        fstore, 0, share_size, fpg, vfs)
-                except Exception as e:
-                    msg = _LW('Exception during cleanup of deleted '
-                              'share %(share)s in filestore %(fstore)s: %(e)s')
-                    data = {
-                        'fstore': fstore,
-                        'share': share_name,
-                        'e': six.text_type(e),
-                    }
-                    LOG.warning(msg, data)
+        elif removed_writable:
+            try:
+                # Attempt to remove file tree on delete when using nested
+                # shares. If the file tree cannot be removed for whatever
+                # reason, we will not treat this as an error_deleting
+                # issue. We will allow the delete to continue as requested.
+                self._delete_file_tree(
+                    share_name, protocol, fpg, vfs, fstore)
+                # reduce the fsquota by share size when a tree is deleted.
+                self._update_capacity_quotas(
+                    fstore, 0, share_size, fpg, vfs)
+            except Exception as e:
+                msg = _LW('Exception during cleanup of deleted '
+                          'share %(share)s in filestore %(fstore)s: %(e)s')
+                data = {
+                    'fstore': fstore,
+                    'share': share_name,
+                    'e': six.text_type(e),
+                }
+                LOG.warning(msg, data)
 
     def _delete_file_tree(self, share_name, protocol, fpg, vfs, fstore):
         # If the share protocol is CIFS, we need to make sure the admin
@@ -722,11 +898,43 @@ class HPE3ParMediator(object):
         self._delete_share_directory(share_dir)
 
         # Unmount the super share.
-        self._unmount_super_share(mount_location)
+        self._unmount_share(mount_location)
 
         # Delete the mount directory.
         self._delete_share_directory(mount_location)
 
+    def _grant_admin_smb_access(self, protocol, fpg, vfs, fstore, comment,
+                                share=SUPER_SHARE):
+        user = '+%s:fullcontrol' % self.hpe3par_cifs_admin_access_username
+        setfshare_kwargs = {
+            'fpg': fpg,
+            'fstore': fstore,
+            'comment': comment,
+            'allowperm': user,
+        }
+        try:
+            self._client.setfshare(
+                protocol, vfs, share, **setfshare_kwargs)
+        except Exception as err:
+            raise exception.ShareBackendException(
+                msg=_("There was an error adding permissions: %s") % err)
+
+    def _revoke_admin_smb_access(self, protocol, fpg, vfs, fstore, comment,
+                                 share=SUPER_SHARE):
+        user = '-%s:fullcontrol' % self.hpe3par_cifs_admin_access_username
+        setfshare_kwargs = {
+            'fpg': fpg,
+            'fstore': fstore,
+            'comment': comment,
+            'allowperm': user,
+        }
+        try:
+            self._client.setfshare(
+                protocol, vfs, share, **setfshare_kwargs)
+        except Exception as err:
+            raise exception.ShareBackendException(
+                msg=_("There was an error revoking permissions: %s") % err)
+
     def _create_super_share(self, protocol, fpg, vfs, fstore, readonly=False):
         sharedir = ''
         extra_specs = {}
@@ -761,20 +969,7 @@ class HPE3ParMediator(object):
 
         # If the share is CIFS, we need to grant access to the specified admin.
         if protocol == 'smb':
-            user = '+%s:fullcontrol' % self.hpe3par_cifs_admin_access_username
-            setfshare_kwargs = {
-                'fpg': fpg,
-                'fstore': fstore,
-                'comment': comment,
-                'allowperm': user,
-            }
-            try:
-                result = self._client.setfshare(
-                    protocol, vfs, SUPER_SHARE, **setfshare_kwargs)
-            except Exception as err:
-                message = (_("There was an error adding permissions: "
-                             "%s.") % six.text_type(err))
-                raise exception.ShareMountException(reason=message)
+            self._grant_admin_smb_access(protocol, fpg, vfs, fstore, comment)
 
     def _create_mount_directory(self, mount_location):
         try:
@@ -785,34 +980,42 @@ class HPE3ParMediator(object):
                        six.text_type(err))
             LOG.warning(message)
 
-    def _mount_super_share(self, protocol, mount_location, fpg, vfs, fstore):
+    def _mount_share(self, protocol, export_location, mount_dir):
+        if protocol == 'nfs':
+            cmd = ('mount', '-t', 'nfs', export_location, mount_dir)
+            utils.execute(*cmd, run_as_root=True)
+        else:
+            export_location = export_location.replace('\\', '/')
+            cred = ('username=' + self.hpe3par_cifs_admin_access_username +
+                    ',password=' +
+                    self.hpe3par_cifs_admin_access_password +
+                    ',domain=' + self.hpe3par_cifs_admin_access_domain)
+            cmd = ('mount', '-t', 'cifs', export_location, mount_dir,
+                   '-o', cred)
+            utils.execute(*cmd, run_as_root=True)
+
+    def _mount_super_share(self, protocol, mount_dir, fpg, vfs, fstore):
         try:
-            mount_path = self._generate_mount_path(protocol, fpg, vfs, fstore)
-            if protocol == 'nfs':
-                utils.execute('mount', '-t', 'nfs', mount_path, mount_location,
-                              run_as_root=True)
-                LOG.debug("Execute mount. mount_location: %s", mount_location)
-            else:
-                user = ('username=' + self.hpe3par_cifs_admin_access_username +
-                        ',password=' +
-                        self.hpe3par_cifs_admin_access_password +
-                        ',domain=' + self.hpe3par_cifs_admin_access_domain)
-                utils.execute('mount', '-t', 'cifs', mount_path,
-                              mount_location, '-o', user, run_as_root=True)
+            mount_location = self._generate_mount_path(
+                protocol, fpg, vfs, fstore)
+            self._mount_share(protocol, mount_location, mount_dir)
         except Exception as err:
             message = (_LW("There was an error mounting the super share: "
                            "%s. The nested file tree will not be deleted."),
                        six.text_type(err))
             LOG.warning(message)
 
-    def _unmount_super_share(self, mount_location):
+    def _unmount_share(self, mount_location):
         try:
             utils.execute('umount', mount_location, run_as_root=True)
         except Exception as err:
-            message = (_LW("There was an error unmounting the super share: "
-                           "%s. The nested file tree will not be deleted."),
-                       six.text_type(err))
-            LOG.warning(message)
+            message = _LW("There was an error unmounting the share at "
+                          "%(mount_location)s: %(error)s")
+            msg_data = {
+                'mount_location': mount_location,
+                'error': six.text_type(err),
+            }
+            LOG.warning(message, msg_data)
 
     def _delete_share_directory(self, directory):
         try:
diff --git a/manila/tests/share/drivers/hpe/test_hpe_3par_constants.py b/manila/tests/share/drivers/hpe/test_hpe_3par_constants.py
index 500dfbdd32..7de051c6d1 100644
--- a/manila/tests/share/drivers/hpe/test_hpe_3par_constants.py
+++ b/manila/tests/share/drivers/hpe/test_hpe_3par_constants.py
@@ -18,6 +18,8 @@ NFS_LOWER = 'nfs'
 IP = 'ip'
 USER = 'user'
 USERNAME = 'USERNAME_0'
+ADD_USERNAME = '+USERNAME_0:fullcontrol'
+DROP_USERNAME = '-USERNAME_0:fullcontrol'
 PASSWORD = 'PASSWORD_0'
 READ_WRITE = 'rw'
 READ_ONLY = 'ro'
@@ -32,6 +34,7 @@ CIDR_PREFIX = '24'
 # Constants to use with Mock and expect in results
 EXPECTED_IP_10203040 = '10.20.30.40'
 EXPECTED_IP_1234 = '1.2.3.4'
+EXPECTED_MY_IP = '9.8.7.6'
 EXPECTED_IP_127 = '127.0.0.1'
 EXPECTED_IP_127_2 = '127.0.0.2'
 EXPECTED_ACCESS_LEVEL = 'foo_access'
diff --git a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py
index 969000a3ba..5d78c24e3b 100644
--- a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py
+++ b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py
@@ -72,6 +72,11 @@ class HPE3ParDriverTestCase(test.TestCase):
         self.mock_object(hpe3parmediator, 'HPE3ParMediator')
         self.mock_mediator_constructor = hpe3parmediator.HPE3ParMediator
         self.mock_mediator = self.mock_mediator_constructor()
+        # restore needed static methods
+        self.mock_mediator.ensure_supported_protocol = (
+            self.real_hpe_3par_mediator.ensure_supported_protocol)
+        self.mock_mediator.build_export_location = (
+            self.real_hpe_3par_mediator.build_export_location)
 
         self.driver = hpe3pardriver.HPE3ParShareDriver(
             configuration=self.conf)
@@ -293,6 +298,8 @@ class HPE3ParDriverTestCase(test.TestCase):
         self.mock_mediator.create_share.return_value = (
             constants.EXPECTED_SHARE_NAME)
 
+        hpe3parmediator.HPE3ParMediator = self.real_hpe_3par_mediator
+
         location = self.do_create_share(constants.CIFS,
                                         constants.SHARE_TYPE_ID,
                                         constants.EXPECTED_PROJECT_ID,
@@ -319,6 +326,7 @@ class HPE3ParDriverTestCase(test.TestCase):
 
         self.mock_mediator.create_share.return_value = (
             constants.EXPECTED_SHARE_PATH)
+        hpe3parmediator.HPE3ParMediator = self.real_hpe_3par_mediator
 
         location = self.do_create_share(constants.NFS,
                                         constants.SHARE_TYPE_ID,
@@ -347,6 +355,7 @@ class HPE3ParDriverTestCase(test.TestCase):
 
         self.mock_mediator.create_share_from_snapshot.return_value = (
             constants.EXPECTED_SHARE_NAME)
+        hpe3parmediator.HPE3ParMediator = self.real_hpe_3par_mediator
 
         snapshot_instance = constants.SNAPSHOT_INSTANCE.copy()
         snapshot_instance['protocol'] = constants.CIFS
@@ -369,7 +378,8 @@ class HPE3ParDriverTestCase(test.TestCase):
                 constants.EXPECTED_SNAP_ID,
                 constants.EXPECTED_FPG,
                 constants.EXPECTED_VFS,
-                comment=mock.ANY),
+                comment=mock.ANY,
+                size=constants.EXPECTED_SIZE_2),
         ]
         self.mock_mediator.assert_has_calls(expected_calls)
 
@@ -381,6 +391,7 @@ class HPE3ParDriverTestCase(test.TestCase):
 
         self.mock_mediator.create_share_from_snapshot.return_value = (
             constants.EXPECTED_SHARE_PATH)
+        hpe3parmediator.HPE3ParMediator = self.real_hpe_3par_mediator
 
         location = self.do_create_share_from_snapshot(
             constants.NFS,
@@ -400,7 +411,8 @@ class HPE3ParDriverTestCase(test.TestCase):
                 constants.EXPECTED_SNAP_ID,
                 constants.EXPECTED_FPG,
                 constants.EXPECTED_VFS,
-                comment=mock.ANY),
+                comment=mock.ANY,
+                size=constants.EXPECTED_SIZE_1),
         ]
 
         self.mock_mediator.assert_has_calls(expected_calls)
@@ -700,27 +712,6 @@ class HPE3ParDriverTestCase(test.TestCase):
     def test_get_network_allocations_number(self):
         self.assertEqual(1, self.driver.get_network_allocations_number())
 
-    def test_build_export_location_bad_protocol(self):
-        self.assertRaises(exception.InvalidInput,
-                          self.driver._build_export_location,
-                          "BOGUS",
-                          constants.EXPECTED_IP_1234,
-                          constants.EXPECTED_SHARE_PATH)
-
-    def test_build_export_location_bad_ip(self):
-        self.assertRaises(exception.InvalidInput,
-                          self.driver._build_export_location,
-                          constants.NFS,
-                          None,
-                          None)
-
-    def test_build_export_location_bad_path(self):
-        self.assertRaises(exception.InvalidInput,
-                          self.driver._build_export_location,
-                          constants.NFS,
-                          constants.EXPECTED_IP_1234,
-                          None)
-
     def test_setup_server(self):
         """Setup server by creating a new FSIP."""
 
diff --git a/manila/tests/share/drivers/hpe/test_hpe_3par_mediator.py b/manila/tests/share/drivers/hpe/test_hpe_3par_mediator.py
index facedca59e..1e1645565c 100644
--- a/manila/tests/share/drivers/hpe/test_hpe_3par_mediator.py
+++ b/manila/tests/share/drivers/hpe/test_hpe_3par_mediator.py
@@ -19,6 +19,7 @@ import mock
 if 'hpe3parclient' not in sys.modules:
     sys.modules['hpe3parclient'] = mock.Mock()
 
+from manila.data import utils as data_utils
 from manila import exception
 from manila.share.drivers.hpe import hpe_3par_mediator as hpe3parmediator
 from manila import test
@@ -38,6 +39,21 @@ class HPE3ParMediatorTestCase(test.TestCase):
     def setUp(self):
         super(HPE3ParMediatorTestCase, self).setUp()
 
+        # Fake utils.execute
+        self.mock_object(utils, 'execute', mock.Mock(return_value={}))
+
+        # Fake data_utils.Copy
+        class FakeCopy(object):
+
+            def run(self):
+                pass
+
+            def get_progress(self):
+                return {'total_progress': 100}
+
+        self.mock_copy = self.mock_object(
+            data_utils, 'Copy', mock.Mock(return_value=FakeCopy()))
+
         # This is the fake client to use.
         self.mock_client = mock.Mock()
 
@@ -68,7 +84,8 @@ class HPE3ParMediatorTestCase(test.TestCase):
             hpe3par_cifs_admin_access_password=constants.PASSWORD,
             hpe3par_cifs_admin_access_domain=constants.EXPECTED_CIFS_DOMAIN,
             hpe3par_share_mount_path=constants.EXPECTED_MOUNT_PATH,
-            ssh_conn_timeout=constants.TIMEOUT)
+            ssh_conn_timeout=constants.TIMEOUT,
+            my_ip=constants.EXPECTED_MY_IP)
 
     def test_mediator_no_client(self):
         """Test missing hpe3parclient error."""
@@ -493,15 +510,22 @@ class HPE3ParMediatorTestCase(test.TestCase):
 
         self.assertEqual(constants.EXPECTED_SHARE_ID, location)
 
-        expected_kwargs = {
+        expected_kwargs_ro = {
             'comment': mock.ANY,
             'fpg': constants.EXPECTED_FPG,
             'fstore': constants.EXPECTED_FSTORE,
-            'sharedir': '.snapshot/%s/%s' % (constants.EXPECTED_SNAP_ID,
-                                             constants.EXPECTED_SHARE_ID),
         }
+        expected_kwargs_rw = expected_kwargs_ro.copy()
+
+        expected_kwargs_ro['sharedir'] = '.snapshot/%s/%s' % (
+            constants.EXPECTED_SNAP_ID, constants.EXPECTED_SHARE_ID)
+        expected_kwargs_rw['sharedir'] = constants.EXPECTED_SHARE_ID
+
         if require_cifs_ip:
-            expected_kwargs['allowip'] = constants.EXPECTED_IP_127
+            expected_kwargs_ro['allowip'] = constants.EXPECTED_MY_IP
+            expected_kwargs_rw['allowip'] = (
+                ','.join((constants.EXPECTED_MY_IP,
+                          constants.EXPECTED_IP_127)))
 
         expected_calls = [
             mock.call.getfsnap('*_%s' % constants.EXPECTED_SNAP_ID,
@@ -512,15 +536,94 @@ class HPE3ParMediatorTestCase(test.TestCase):
             mock.call.createfshare(constants.SMB_LOWER,
                                    constants.EXPECTED_VFS,
                                    constants.EXPECTED_SHARE_ID,
-                                   **expected_kwargs),
+                                   **expected_kwargs_ro),
             mock.call.getfshare(constants.SMB_LOWER,
                                 constants.EXPECTED_SHARE_ID,
                                 fpg=constants.EXPECTED_FPG,
                                 vfs=constants.EXPECTED_VFS,
-                                fstore=constants.EXPECTED_FSTORE)]
+                                fstore=constants.EXPECTED_FSTORE),
+            mock.call.createfshare(constants.SMB_LOWER,
+                                   constants.EXPECTED_VFS,
+                                   constants.EXPECTED_SHARE_ID,
+                                   **expected_kwargs_rw),
+            mock.call.getfshare(constants.SMB_LOWER,
+                                constants.EXPECTED_SHARE_ID,
+                                fpg=constants.EXPECTED_FPG,
+                                vfs=constants.EXPECTED_VFS,
+                                fstore=constants.EXPECTED_FSTORE),
+            mock.call.setfshare(constants.SMB_LOWER,
+                                constants.EXPECTED_VFS,
+                                constants.EXPECTED_SHARE_ID,
+                                allowperm=constants.ADD_USERNAME,
+                                comment=mock.ANY,
+                                fpg=constants.EXPECTED_FPG,
+                                fstore=constants.EXPECTED_FSTORE),
+            mock.call.setfshare(constants.SMB_LOWER,
+                                constants.EXPECTED_VFS,
+                                constants.EXPECTED_SHARE_ID,
+                                allowperm=constants.ADD_USERNAME,
+                                comment=mock.ANY,
+                                fpg=constants.EXPECTED_FPG,
+                                fstore=constants.EXPECTED_FSTORE),
+            mock.call.setfshare(constants.SMB_LOWER,
+                                constants.EXPECTED_VFS,
+                                constants.EXPECTED_SUPER_SHARE,
+                                allowperm=constants.DROP_USERNAME,
+                                comment=mock.ANY,
+                                fpg=constants.EXPECTED_FPG,
+                                fstore=constants.EXPECTED_FSTORE),
+            mock.call.removefshare(constants.SMB_LOWER,
+                                   constants.EXPECTED_VFS,
+                                   constants.EXPECTED_SHARE_ID,
+                                   fpg=constants.EXPECTED_FPG,
+                                   fstore=constants.EXPECTED_FSTORE),
+        ]
 
         self.mock_client.assert_has_calls(expected_calls)
 
+    def test_mediator_create_cifs_share_from_snapshot_ro(self):
+        self.init_mediator()
+
+        # RO because CIFS admin access username is not configured
+        self.mediator.hpe3par_cifs_admin_access_username = None
+
+        self.mock_client.getfsnap.return_value = {
+            'message': None,
+            'total': 1,
+            'members': [{'snapName': constants.EXPECTED_SNAP_ID,
+                         'fstoreName': constants.EXPECTED_FSTORE}]
+        }
+
+        location = self.mediator.create_share_from_snapshot(
+            constants.EXPECTED_SHARE_ID,
+            constants.CIFS,
+            constants.EXPECTED_EXTRA_SPECS,
+            constants.EXPECTED_PROJECT_ID,
+            constants.EXPECTED_SHARE_ID,
+            constants.EXPECTED_SNAP_ID,
+            constants.EXPECTED_FPG,
+            constants.EXPECTED_VFS,
+            comment=constants.EXPECTED_COMMENT)
+
+        self.assertEqual(constants.EXPECTED_SHARE_ID, location)
+
+        share_dir = '.snapshot/%s/%s' % (
+            constants.EXPECTED_SNAP_ID, constants.EXPECTED_SHARE_ID)
+
+        expected_kwargs_ro = {
+            'comment': constants.EXPECTED_COMMENT,
+            'fpg': constants.EXPECTED_FPG,
+            'fstore': constants.EXPECTED_FSTORE,
+            'sharedir': share_dir,
+        }
+
+        self.mock_client.createfshare.assert_called_once_with(
+            constants.SMB_LOWER,
+            constants.EXPECTED_VFS,
+            constants.EXPECTED_SHARE_ID,
+            **expected_kwargs_ro
+        )
+
     def test_mediator_create_nfs_share_from_snapshot(self):
         self.init_mediator()
 
@@ -558,16 +661,106 @@ class HPE3ParMediatorTestCase(test.TestCase):
                                             (constants.EXPECTED_SNAP_ID,
                                              constants.EXPECTED_SHARE_ID),
                                    fstore=constants.EXPECTED_FSTORE,
-                                   clientip=constants.EXPECTED_IP_127_2,
+                                   clientip=constants.EXPECTED_MY_IP,
                                    options='ro,no_root_squash,insecure'),
             mock.call.getfshare(constants.NFS_LOWER,
                                 constants.EXPECTED_SHARE_ID,
                                 fpg=constants.EXPECTED_FPG,
                                 vfs=constants.EXPECTED_VFS,
-                                fstore=constants.EXPECTED_FSTORE)]
+                                fstore=constants.EXPECTED_FSTORE),
+            mock.call.createfshare(constants.NFS_LOWER,
+                                   constants.EXPECTED_VFS,
+                                   constants.EXPECTED_SHARE_ID,
+                                   comment=mock.ANY,
+                                   fpg=constants.EXPECTED_FPG,
+                                   sharedir=constants.EXPECTED_SHARE_ID,
+                                   fstore=constants.EXPECTED_FSTORE,
+                                   clientip=','.join((
+                                       constants.EXPECTED_MY_IP,
+                                       constants.EXPECTED_IP_127)),
+                                   options='rw,no_root_squash,insecure'),
+            mock.call.getfshare(constants.NFS_LOWER,
+                                constants.EXPECTED_SHARE_ID,
+                                fpg=constants.EXPECTED_FPG,
+                                vfs=constants.EXPECTED_VFS,
+                                fstore=constants.EXPECTED_FSTORE),
+            mock.call.getfshare(constants.NFS_LOWER,
+                                constants.EXPECTED_SHARE_ID,
+                                fpg=constants.EXPECTED_FPG,
+                                vfs=constants.EXPECTED_VFS,
+                                fstore=constants.EXPECTED_FSTORE),
+            mock.call.setfshare(constants.NFS_LOWER,
+                                constants.EXPECTED_VFS,
+                                constants.EXPECTED_SHARE_ID,
+                                clientip=''.join(('-',
+                                                 constants.EXPECTED_MY_IP)),
+                                comment=mock.ANY,
+                                fpg=constants.EXPECTED_FPG,
+                                fstore=constants.EXPECTED_FSTORE),
+            mock.call.removefshare(constants.NFS_LOWER,
+                                   constants.EXPECTED_VFS,
+                                   constants.EXPECTED_SHARE_ID,
+                                   fpg=constants.EXPECTED_FPG,
+                                   fstore=constants.EXPECTED_FSTORE),
+        ]
 
         self.mock_client.assert_has_calls(expected_calls)
 
+    def test_mediator_create_share_from_snap_copy_incomplete(self):
+        self.init_mediator()
+
+        self.mock_client.getfsnap.return_value = {
+            'message': None,
+            'total': 1,
+            'members': [{'snapName': constants.EXPECTED_SNAP_ID,
+                         'fstoreName': constants.EXPECTED_FSTORE}]
+        }
+
+        mock_bad_copy = mock.Mock()
+        mock_bad_copy.get_progress.return_value = {'total_progress': 99}
+        self.mock_object(
+            data_utils, 'Copy', mock.Mock(return_value=mock_bad_copy))
+
+        self.assertRaises(exception.ShareBackendException,
+                          self.mediator.create_share_from_snapshot,
+                          constants.EXPECTED_SHARE_ID,
+                          constants.NFS,
+                          constants.EXPECTED_EXTRA_SPECS,
+                          constants.EXPECTED_PROJECT_ID,
+                          constants.EXPECTED_SHARE_ID,
+                          constants.EXPECTED_SNAP_ID,
+                          constants.EXPECTED_FPG,
+                          constants.EXPECTED_VFS)
+        self.assertTrue(mock_bad_copy.run.called)
+        self.assertTrue(mock_bad_copy.get_progress.called)
+
+    def test_mediator_create_share_from_snap_copy_exception(self):
+        self.init_mediator()
+
+        self.mock_client.getfsnap.return_value = {
+            'message': None,
+            'total': 1,
+            'members': [{'snapName': constants.EXPECTED_SNAP_ID,
+                         'fstoreName': constants.EXPECTED_FSTORE}]
+        }
+
+        mock_bad_copy = mock.Mock()
+        mock_bad_copy.run.side_effect = Exception('run exception')
+        self.mock_object(
+            data_utils, 'Copy', mock.Mock(return_value=mock_bad_copy))
+
+        self.assertRaises(exception.ShareBackendException,
+                          self.mediator.create_share_from_snapshot,
+                          constants.EXPECTED_SHARE_ID,
+                          constants.NFS,
+                          constants.EXPECTED_EXTRA_SPECS,
+                          constants.EXPECTED_PROJECT_ID,
+                          constants.EXPECTED_SHARE_ID,
+                          constants.EXPECTED_SNAP_ID,
+                          constants.EXPECTED_FPG,
+                          constants.EXPECTED_VFS)
+        self.assertTrue(mock_bad_copy.run.called)
+
     def test_mediator_create_share_from_snap_not_found(self):
         self.init_mediator()
 
@@ -793,7 +986,7 @@ class HPE3ParMediatorTestCase(test.TestCase):
                          '_delete_share_directory',
                          mock.Mock(return_value={}))
         self.mock_object(self.mediator,
-                         '_unmount_super_share',
+                         '_unmount_share',
                          mock.Mock(return_value={}))
         self.mock_object(self.mediator,
                          '_update_capacity_quotas',
@@ -815,7 +1008,7 @@ class HPE3ParMediatorTestCase(test.TestCase):
             mock.call.createfshare(constants.SMB_LOWER,
                                    constants.EXPECTED_VFS,
                                    constants.EXPECTED_SUPER_SHARE,
-                                   allowip=None,
+                                   allowip=constants.EXPECTED_MY_IP,
                                    comment=(
                                        constants.EXPECTED_SUPER_SHARE_COMMENT),
                                    fpg=constants.EXPECTED_FPG,
@@ -849,7 +1042,7 @@ class HPE3ParMediatorTestCase(test.TestCase):
             mock.call(expected_share_path),
             mock.call(expected_mount_path),
         ])
-        self.mediator._unmount_super_share.assert_called_once_with(
+        self.mediator._unmount_share.assert_called_once_with(
             expected_mount_path)
         self.mediator._update_capacity_quotas.assert_called_once_with(
             constants.EXPECTED_FSTORE,
@@ -910,7 +1103,7 @@ class HPE3ParMediatorTestCase(test.TestCase):
                          '_delete_share_directory',
                          mock.Mock(return_value={}))
         self.mock_object(self.mediator,
-                         '_unmount_super_share',
+                         '_unmount_share',
                          mock.Mock(return_value={}))
 
         self.mediator.delete_share(constants.EXPECTED_PROJECT_ID,
@@ -929,7 +1122,7 @@ class HPE3ParMediatorTestCase(test.TestCase):
             mock.call.createfshare(constants.SMB_LOWER,
                                    constants.EXPECTED_VFS,
                                    constants.EXPECTED_SUPER_SHARE,
-                                   allowip=None,
+                                   allowip=constants.EXPECTED_MY_IP,
                                    comment=(
                                        constants.EXPECTED_SUPER_SHARE_COMMENT),
                                    fpg=constants.EXPECTED_FPG,
@@ -963,7 +1156,7 @@ class HPE3ParMediatorTestCase(test.TestCase):
             constants.EXPECTED_VFS, constants.EXPECTED_FSTORE)
         self.mediator._delete_share_directory.assert_called_with(
             expected_mount_path)
-        self.mediator._unmount_super_share.assert_called_with(
+        self.mediator._unmount_share.assert_called_with(
             expected_mount_path)
 
     def test_mediator_create_snapshot(self):
@@ -2505,8 +2698,6 @@ class HPE3ParMediatorTestCase(test.TestCase):
     def test__create_mount_directory(self):
         self.init_mediator()
 
-        self.mock_object(utils, 'execute', mock.Mock(return_value={}))
-
         mount_location = '/mnt/foo'
         self.mediator._create_mount_directory(mount_location)
 
@@ -2531,8 +2722,6 @@ class HPE3ParMediatorTestCase(test.TestCase):
     def test__mount_super_share(self):
         self.init_mediator()
 
-        self.mock_object(utils, 'execute', mock.Mock(return_value={}))
-
         # Test mounting NFS share.
         protocol = 'nfs'
         mount_location = '/mnt/foo'
@@ -2582,8 +2771,6 @@ class HPE3ParMediatorTestCase(test.TestCase):
     def test__delete_share_directory(self):
         self.init_mediator()
 
-        self.mock_object(utils, 'execute', mock.Mock(return_value={}))
-
         mount_location = '/mnt/foo'
         self.mediator._delete_share_directory(mount_location)
 
@@ -2603,26 +2790,23 @@ class HPE3ParMediatorTestCase(test.TestCase):
         # Warning is logged (no exception thrown).
         self.assertTrue(mock_log.warning.called)
 
-    def test__unmount_super_share(self):
+    def test__unmount_share(self):
         self.init_mediator()
 
-        self.mock_object(utils, 'execute', mock.Mock(return_value={}))
+        mount_dir = '/mnt/foo'
+        self.mediator._unmount_share(mount_dir)
 
-        mount_location = '/mnt/foo'
-        self.mediator._unmount_super_share(mount_location)
+        utils.execute.assert_called_with('umount', mount_dir, run_as_root=True)
 
-        utils.execute.assert_called_with('umount', mount_location,
-                                         run_as_root=True)
-
-    def test__unmount_super_share_error(self):
+    def test__unmount_share_error(self):
         self.init_mediator()
 
         self.mock_object(utils, 'execute',
                          mock.Mock(side_effect=Exception('umount error.')))
         mock_log = self.mock_object(hpe3parmediator, 'LOG')
 
-        mount_location = '/mnt/foo'
-        self.mediator._unmount_super_share(mount_location)
+        mount_dir = '/mnt/foo'
+        self.mediator._unmount_share(mount_dir)
 
         # Warning is logged (no exception thrown).
         self.assertTrue(mock_log.warning.called)
@@ -2664,13 +2848,49 @@ class HPE3ParMediatorTestCase(test.TestCase):
             Exception("setfshare error."))
 
         self.assertRaises(
-            exception.ShareMountException,
+            exception.ShareBackendException,
             self.mediator._create_super_share,
             constants.SMB_LOWER,
             constants.EXPECTED_FPG,
             constants.EXPECTED_VFS,
             constants.EXPECTED_FSTORE)
 
+    def test__revoke_admin_smb_access_error(self):
+        self.init_mediator()
+
+        self.mock_client.setfshare.side_effect = (
+            Exception("setfshare error"))
+
+        self.assertRaises(
+            exception.ShareBackendException,
+            self.mediator._revoke_admin_smb_access,
+            constants.SMB_LOWER,
+            constants.EXPECTED_FPG,
+            constants.EXPECTED_VFS,
+            constants.EXPECTED_FSTORE,
+            constants.EXPECTED_COMMENT)
+
+    def test_build_export_location_bad_protocol(self):
+        self.assertRaises(exception.InvalidInput,
+                          self.mediator.build_export_location,
+                          "BOGUS",
+                          constants.EXPECTED_IP_1234,
+                          constants.EXPECTED_SHARE_PATH)
+
+    def test_build_export_location_bad_ip(self):
+        self.assertRaises(exception.InvalidInput,
+                          self.mediator.build_export_location,
+                          constants.NFS,
+                          None,
+                          None)
+
+    def test_build_export_location_bad_path(self):
+        self.assertRaises(exception.InvalidInput,
+                          self.mediator.build_export_location,
+                          constants.NFS,
+                          constants.EXPECTED_IP_1234,
+                          None)
+
 
 class OptionMatcher(object):
     """Options string order can vary. Compare as lists."""
diff --git a/releasenotes/notes/hpe3par-rw-snapshot-shares-f7c33b4bf528bf00.yaml b/releasenotes/notes/hpe3par-rw-snapshot-shares-f7c33b4bf528bf00.yaml
new file mode 100644
index 0000000000..4f4827f6d8
--- /dev/null
+++ b/releasenotes/notes/hpe3par-rw-snapshot-shares-f7c33b4bf528bf00.yaml
@@ -0,0 +1,3 @@
+---
+features:
+  - Add read-write functionality for HPE 3PAR shares from snapshots.