Add mountable snapshots support to HNAS driver

This patch adds Mountable Snapshots support in HNAS driver. Also,
updates feature table in share_back_ends_feature_support_mapping.rst
adding mountable snapshot column.

Implements: bp hnas-mountable-snapshots
Change-Id: I5e2294d218595a7ef28261333ab34d8bcde94ff1
This commit is contained in:
tpsilva 2016-12-12 15:21:23 -02:00 committed by Alyson Rosa
parent dbd8f5dc95
commit 1f1932eb92
9 changed files with 647 additions and 259 deletions

View File

@ -30,61 +30,61 @@ Column value "-" means that this feature is not currently supported.
Mapping of share drivers and share features support Mapping of share drivers and share features support
--------------------------------------------------- ---------------------------------------------------
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Driver name | create delete share | manage unmanage share | extend share | shrink share | create delete snapshot | create share from snapshot | manage unmanage snapshot | revert to snapshot | | Driver name | create delete share | manage unmanage share | extend share | shrink share | create delete snapshot | create share from snapshot | manage unmanage snapshot | revert to snapshot | mountable snapshot |
+========================================+=======================+=======================+==============+==============+========================+============================+==========================+====================+ +========================================+=======================+=======================+==============+==============+========================+============================+==========================+====================+====================+
| ZFSonLinux | M | N | M | M | M | M | N | \- | | ZFSonLinux | M | N | M | M | M | M | N | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Container | N | \- | N | \- | \- | \- | \- | \- | | Container | N | \- | N | \- | \- | \- | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Generic (Cinder as back-end) | J | K | L | L | J | J | M | \- | | Generic (Cinder as back-end) | J | K | L | L | J | J | M | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| NetApp Clustered Data ONTAP | J | L | L | L | J | J | N | O | | NetApp Clustered Data ONTAP | J | L | L | L | J | J | N | O | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| EMC VMAX | O | \- | O | \- | O | O | \- | \- | | EMC VMAX | O | \- | O | \- | O | O | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| EMC VNX | J | \- | \- | \- | J | J | \- | \- | | EMC VNX | J | \- | \- | \- | J | J | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| EMC Unity | N | \- | N | \- | N | N | \- | \- | | EMC Unity | N | \- | N | \- | N | N | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| EMC Isilon | K | \- | M | \- | K | K | \- | \- | | EMC Isilon | K | \- | M | \- | K | K | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Red Hat GlusterFS | J | \- | \- | \- | volume layout (L) | volume layout (L) | \- | \- | | Red Hat GlusterFS | J | \- | \- | \- | volume layout (L) | volume layout (L) | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Red Hat GlusterFS-Native | J | \- | \- | \- | K | L | \- | \- | | Red Hat GlusterFS-Native | J | \- | \- | \- | K | L | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| HDFS | K | \- | M | \- | K | K | \- | \- | | HDFS | K | \- | M | \- | K | K | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Hitachi HNAS | L | L | L | M | L | L | O | O | | Hitachi HNAS | L | L | L | M | L | L | O | O | O |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Hitachi HSP | N | N | N | N | \- | \- | \- | \- | | Hitachi HSP | N | N | N | N | \- | \- | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| HPE 3PAR | K | \- | \- | \- | K | K | \- | \- | | HPE 3PAR | K | \- | \- | \- | K | K | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Huawei | K | L | L | L | K | M | \- | \- | | Huawei | K | L | L | L | K | M | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| IBM GPFS | K | O | L | \- | K | K | \- | \- | | IBM GPFS | K | O | L | \- | K | K | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| LVM | M | \- | M | \- | M | M | \- | O | | LVM | M | \- | M | \- | M | M | \- | O | O |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Quobyte | K | \- | M | M | \- | \- | \- | \- | | Quobyte | K | \- | M | M | \- | \- | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Windows SMB | L | L | L | L | L | L | \- | \- | | Windows SMB | L | L | L | L | L | L | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Oracle ZFSSA | K | N | M | M | K | K | \- | \- | | Oracle ZFSSA | K | N | M | M | K | K | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| CephFS Native | M | \- | M | M | M | \- | \- | \- | | CephFS Native | M | \- | M | M | M | \- | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| Tegile | M | \- | M | M | M | M | \- | \- | | Tegile | M | \- | M | M | M | M | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| NexentaStor4 | N | \- | N | \- | N | N | \- | \- | | NexentaStor4 | N | \- | N | \- | N | N | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| NexentaStor5 | N | \- | N | N | N | N | \- | \- | | NexentaStor5 | N | \- | N | N | N | N | \- | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| MapRFS | O | O | O | O | O | O | O | \- | | MapRFS | O | O | O | O | O | O | O | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
| QNAP | O | O | O | \- | O | O | O | \- | | QNAP | O | O | O | \- | O | O | O | \- | \- |
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+ +----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+--------------------+
Mapping of share drivers and share access rules support Mapping of share drivers and share access rules support
------------------------------------------------------- -------------------------------------------------------
@ -211,61 +211,61 @@ Mapping of share drivers and common capabilities
More information: :ref:`capabilities_and_extra_specs` More information: :ref:`capabilities_and_extra_specs`
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Driver name | DHSS=True | DHSS=False | dedupe | compression | thin_provisioning | thick_provisioning | qos | create share from snapshot | revert to snapshot | | Driver name | DHSS=True | DHSS=False | dedupe | compression | thin_provisioning | thick_provisioning | qos | create share from snapshot | revert to snapshot | mountable snapshot |
+========================================+===========+============+========+=============+===================+====================+=====+============================+====================+ +========================================+===========+============+========+=============+===================+====================+=====+============================+====================+====================+
| ZFSonLinux | \- | M | M | M | M | \- | \- | M | \- | | ZFSonLinux | \- | M | M | M | M | \- | \- | M | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Container | N | \- | \- | \- | \- | N | \- | \- | \- | | Container | N | \- | \- | \- | \- | N | \- | \- | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J | \- | | Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| NetApp Clustered Data ONTAP | J | K | M | M | M | L | \- | J | O | | NetApp Clustered Data ONTAP | J | K | M | M | M | L | \- | J | O | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| EMC VMAX | O | \- | \- | \- | \- | O | \- | O | \- | | EMC VMAX | O | \- | \- | \- | \- | O | \- | O | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- | | EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| EMC Unity | N | \- | \- | \- | N | \- | \- | N | \- | | EMC Unity | N | \- | \- | \- | N | \- | \- | N | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| EMC Isilon | \- | K | \- | \- | \- | L | \- | K | \- | | EMC Isilon | \- | K | \- | \- | \- | L | \- | K | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Red Hat GlusterFS | \- | J | \- | \- | \- | L | \- | volume layout (L) | \- | | Red Hat GlusterFS | \- | J | \- | \- | \- | L | \- | volume layout (L) | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Red Hat GlusterFS-Native | \- | J | \- | \- | \- | L | \- | L | \- | | Red Hat GlusterFS-Native | \- | J | \- | \- | \- | L | \- | L | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| HDFS | \- | K | \- | \- | \- | L | \- | K | \- | | HDFS | \- | K | \- | \- | \- | L | \- | K | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Hitachi HNAS | \- | L | N | \- | L | \- | \- | L | O | | Hitachi HNAS | \- | L | N | \- | L | \- | \- | L | O | O |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Hitachi HSP | \- | N | \- | \- | N | \- | \- | \- | \- | | Hitachi HSP | \- | N | \- | \- | N | \- | \- | \- | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| HPE 3PAR | L | K | L | \- | L | L | \- | K | \- | | HPE 3PAR | L | K | L | \- | L | L | \- | K | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Huawei | M | K | L | L | L | L | M | M | \- | | Huawei | M | K | L | L | L | L | M | M | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| LVM | \- | M | \- | \- | \- | M | \- | K | O | | LVM | \- | M | \- | \- | \- | M | \- | K | O | O |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Quobyte | \- | K | \- | \- | \- | L | \- | M | \- | | Quobyte | \- | K | \- | \- | \- | L | \- | M | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Windows SMB | L | L | \- | \- | \- | L | \- | \- | \- | | Windows SMB | L | L | \- | \- | \- | L | \- | \- | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| IBM GPFS | \- | K | \- | \- | \- | L | \- | L | \- | | IBM GPFS | \- | K | \- | \- | \- | L | \- | L | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Oracle ZFSSA | \- | K | \- | \- | \- | L | \- | K | \- | | Oracle ZFSSA | \- | K | \- | \- | \- | L | \- | K | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| CephFS Native | \- | M | \- | \- | \- | M | \- | \- | \- | | CephFS Native | \- | M | \- | \- | \- | M | \- | \- | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| Tegile | \- | M | M | M | M | \- | \- | M | \- | | Tegile | \- | M | M | M | M | \- | \- | M | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| NexentaStor4 | \- | N | N | N | N | N | \- | N | \- | | NexentaStor4 | \- | N | N | N | N | N | \- | N | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| NexentaStor5 | \- | N | N | N | N | N | \- | N | \- | | NexentaStor5 | \- | N | N | N | N | N | \- | N | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| MapRFS | \- | N | \- | \- | \- | N | \- | O | \- | | MapRFS | \- | N | \- | \- | \- | N | \- | O | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
| QNAP | \- | O | \- | \- | O | \- | \- | O | \- | | QNAP | \- | O | \- | \- | O | \- | \- | O | \- | \- |
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+ +----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+
.. note:: .. note::

View File

@ -450,6 +450,10 @@ class ShareSnapshotAccessExists(InvalidInput):
message = _("Share snapshot access %(access_type)s:%(access)s exists.") message = _("Share snapshot access %(access_type)s:%(access)s exists.")
class InvalidSnapshotAccess(Invalid):
message = _("Invalid access rule: %(reason)s")
class InvalidShareAccess(Invalid): class InvalidShareAccess(Invalid):
message = _("Invalid access rule: %(reason)s") message = _("Invalid access rule: %(reason)s")

View File

@ -25,6 +25,7 @@ from manila.common import constants
from manila import exception from manila import exception
from manila.i18n import _, _LE, _LI, _LW from manila.i18n import _, _LE, _LI, _LW
from manila.share import driver from manila.share import driver
from manila.share import utils
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -215,7 +216,7 @@ class HitachiHNASDriver(driver.ShareDriver):
host_list.append(rule['access_to'] + '(' + host_list.append(rule['access_to'] + '(' +
rule['access_level'] + ')') rule['access_level'] + ')')
self.hnas.update_nfs_access_rule(hnas_share_id, host_list) self.hnas.update_nfs_access_rule(host_list, share_id=hnas_share_id)
if host_list: if host_list:
LOG.debug("Share %(share)s has the rules: %(rules)s", LOG.debug("Share %(share)s has the rules: %(rules)s",
@ -223,14 +224,25 @@ class HitachiHNASDriver(driver.ShareDriver):
else: else:
LOG.debug("Share %(share)s has no rules.", {'share': share['id']}) LOG.debug("Share %(share)s has no rules.", {'share': share['id']})
def _cifs_allow_access(self, share, hnas_share_id, add_rules): def _cifs_allow_access(self, share_or_snapshot, hnas_id, add_rules,
is_snapshot=False):
entity_type = "share"
if is_snapshot:
entity_type = "snapshot"
for rule in add_rules: for rule in add_rules:
if rule['access_type'].lower() != 'user': if rule['access_type'].lower() != 'user':
msg = _("Only USER access type currently supported for CIFS. " msg = _("Only USER access type currently supported for CIFS. "
"Share provided %(share)s with rule %(r_id)s type " "%(entity_type)s provided %(share)s with "
"%(type)s allowing permission to %(to)s.") % { "rule %(r_id)s type %(type)s allowing permission "
'share': share['id'], 'type': rule['access_type'], "to %(to)s.") % {
'r_id': rule['id'], 'to': rule['access_to']} 'entity_type': entity_type.capitalize(),
'share': share_or_snapshot['id'],
'type': rule['access_type'],
'r_id': rule['id'],
'to': rule['access_to'],
}
raise exception.InvalidShareAccess(reason=msg) raise exception.InvalidShareAccess(reason=msg)
if rule['access_level'] == constants.ACCESS_LEVEL_RW: if rule['access_level'] == constants.ACCESS_LEVEL_RW:
@ -242,38 +254,54 @@ class HitachiHNASDriver(driver.ShareDriver):
formatted_user = rule['access_to'].replace('\\', '\\\\') formatted_user = rule['access_to'].replace('\\', '\\\\')
self.hnas.cifs_allow_access(hnas_share_id, formatted_user, self.hnas.cifs_allow_access(hnas_id, formatted_user,
permission) permission, is_snapshot=is_snapshot)
LOG.debug("Added %(rule)s rule for user/group %(user)s to share " LOG.debug("Added %(rule)s rule for user/group %(user)s "
"%(share)s.", {'rule': rule['access_level'], "to %(entity_type)s %(share)s.",
{'rule': rule['access_level'],
'user': rule['access_to'], 'user': rule['access_to'],
'share': share['id']}) 'entity_type': entity_type,
'share': share_or_snapshot['id']})
def _cifs_deny_access(self, share_or_snapshot, hnas_id, delete_rules,
is_snapshot=False):
if is_snapshot:
entity_type = "snapshot"
share_proto = share_or_snapshot['share']['share_proto']
else:
entity_type = "share"
share_proto = share_or_snapshot['share_proto']
def _cifs_deny_access(self, share, hnas_share_id, delete_rules):
for rule in delete_rules: for rule in delete_rules:
if rule['access_type'].lower() != 'user': if rule['access_type'].lower() != 'user':
LOG.warning(_LW('Only USER access type is allowed for ' LOG.warning(_LW('Only USER access type is allowed for '
'CIFS shares. Share provided %(share)s with ' 'CIFS. %(entity_type)s '
'provided %(share)s with '
'protocol %(proto)s.'), 'protocol %(proto)s.'),
{'share': share['id'], {'entity_type': entity_type.capitalize(),
'proto': share['share_proto']}) 'share': share_or_snapshot['id'],
'proto': share_proto})
continue continue
formatted_user = rule['access_to'].replace('\\', '\\\\') formatted_user = rule['access_to'].replace('\\', '\\\\')
self.hnas.cifs_deny_access(hnas_share_id, formatted_user) self.hnas.cifs_deny_access(hnas_id, formatted_user,
is_snapshot=is_snapshot)
LOG.debug("Access denied for user/group %(user)s to share " LOG.debug("Access denied for user/group %(user)s "
"%(share)s.", {'user': rule['access_to'], "to %(entity_type)s %(share)s.",
'share': share['id']}) {'user': rule['access_to'],
'entity_type': entity_type,
'share': share_or_snapshot['id']})
def _clean_cifs_access_list(self, hnas_share_id): def _clean_cifs_access_list(self, hnas_id, is_snapshot=False):
permission_list = self.hnas.list_cifs_permissions(hnas_share_id) permission_list = self.hnas.list_cifs_permissions(hnas_id)
for permission in permission_list: for permission in permission_list:
formatted_user = r'"\{1}{0}\{1}"'.format(permission[0], '"') formatted_user = r'"\{1}{0}\{1}"'.format(permission[0], '"')
self.hnas.cifs_deny_access(hnas_share_id, formatted_user) self.hnas.cifs_deny_access(hnas_id, formatted_user,
is_snapshot=is_snapshot)
def create_share(self, context, share, share_server=None): def create_share(self, context, share, share_server=None):
"""Creates share. """Creates share.
@ -363,12 +391,15 @@ class HitachiHNASDriver(driver.ShareDriver):
{'snap_share_id': snapshot['share_id'], {'snap_share_id': snapshot['share_id'],
'snap_id': snapshot['id']}) 'snap_id': snapshot['id']})
self._create_snapshot(hnas_share_id, snapshot) export_locations = self._create_snapshot(hnas_share_id, snapshot)
LOG.info(_LI("Snapshot %(id)s successfully created."), LOG.info(_LI("Snapshot %(id)s successfully created."),
{'id': snapshot['id']}) {'id': snapshot['id']})
return {'provider_location': os.path.join('/snapshots', hnas_share_id, return {
snapshot['id'])} 'provider_location': os.path.join('/snapshots', hnas_share_id,
snapshot['id']),
'export_locations': export_locations,
}
def delete_snapshot(self, context, snapshot, share_server=None): def delete_snapshot(self, context, snapshot, share_server=None):
"""Deletes snapshot. """Deletes snapshot.
@ -386,7 +417,8 @@ class HitachiHNASDriver(driver.ShareDriver):
{'snap_id': snapshot['id'], {'snap_id': snapshot['id'],
'snap_share_id': snapshot['share_id']}) 'snap_share_id': snapshot['share_id']})
self._delete_snapshot(hnas_share_id, hnas_snapshot_id) self._delete_snapshot(snapshot['share']['share_proto'],
hnas_share_id, hnas_snapshot_id)
LOG.info(_LI("Snapshot %(id)s successfully deleted."), LOG.info(_LI("Snapshot %(id)s successfully deleted."),
{'id': snapshot['id']}) {'id': snapshot['id']})
@ -553,6 +585,7 @@ class HitachiHNASDriver(driver.ShareDriver):
'thin_provisioning': True, 'thin_provisioning': True,
'dedupe': dedupe, 'dedupe': dedupe,
'revert_to_snapshot_support': True, 'revert_to_snapshot_support': True,
'mount_snapshot_support': True,
} }
LOG.info(_LI("HNAS Capabilities: %(data)s."), LOG.info(_LI("HNAS Capabilities: %(data)s."),
@ -783,27 +816,30 @@ class HitachiHNASDriver(driver.ShareDriver):
LOG.debug("Share created with id %(shr)s, size %(size)sG.", LOG.debug("Share created with id %(shr)s, size %(size)sG.",
{'shr': share_id, 'size': share_size}) {'shr': share_id, 'size': share_size})
self._create_export(share_id, share_proto)
export_list = self._get_export_locations(share_proto, share_id)
return export_list
def _create_export(self, share_id, share_proto, snapshot_id=None):
try: try:
if share_proto.lower() == 'nfs': if share_proto.lower() == 'nfs':
# Create NFS export # Create NFS export
self.hnas.nfs_export_add(share_id) self.hnas.nfs_export_add(share_id, snapshot_id=snapshot_id)
LOG.debug("NFS Export created to %(shr)s.", LOG.debug("NFS Export created to %(shr)s.",
{'shr': share_id}) {'shr': share_id})
else: else:
# Create CIFS share with vvol path # Create CIFS share with vvol path
self.hnas.cifs_share_add(share_id) self.hnas.cifs_share_add(share_id, snapshot_id=snapshot_id)
LOG.debug("CIFS share created to %(shr)s.", LOG.debug("CIFS share created to %(shr)s.",
{'shr': share_id}) {'shr': share_id})
except exception.HNASBackendException as e: except exception.HNASBackendException as e:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
if snapshot_id is None:
self.hnas.vvol_delete(share_id) self.hnas.vvol_delete(share_id)
msg = six.text_type(e) msg = six.text_type(e)
LOG.exception(msg) LOG.exception(msg)
export_list = self._get_export_locations(share_proto, share_id)
return export_list
def _check_fs_mounted(self): def _check_fs_mounted(self):
mounted = self.hnas.check_fs_mounted() mounted = self.hnas.check_fs_mounted()
if not mounted: if not mounted:
@ -926,15 +962,15 @@ class HitachiHNASDriver(driver.ShareDriver):
self._ensure_share(snapshot['share'], hnas_share_id) self._ensure_share(snapshot['share'], hnas_share_id)
saved_list = [] saved_list = []
self._check_protocol(snapshot['share_id'], share_proto = snapshot['share']['share_proto']
snapshot['share']['share_proto']) self._check_protocol(snapshot['share_id'], share_proto)
if snapshot['share']['share_proto'].lower() == 'nfs': if share_proto.lower() == 'nfs':
saved_list = self.hnas.get_nfs_host_list(hnas_share_id) saved_list = self.hnas.get_nfs_host_list(hnas_share_id)
new_list = [] new_list = []
for access in saved_list: for access in saved_list:
new_list.append(access.replace('(rw)', '(ro)')) new_list.append(access.replace('(rw)', '(ro)'))
self.hnas.update_nfs_access_rule(hnas_share_id, new_list) self.hnas.update_nfs_access_rule(new_list, share_id=hnas_share_id)
else: # CIFS else: # CIFS
if (self.hnas.is_cifs_in_use(hnas_share_id) and if (self.hnas.is_cifs_in_use(hnas_share_id) and
not self.cifs_snapshot): not self.cifs_snapshot):
@ -952,10 +988,18 @@ class HitachiHNASDriver(driver.ShareDriver):
"directory.")) "directory."))
self.hnas.create_directory(dest_path) self.hnas.create_directory(dest_path)
finally: finally:
if snapshot['share']['share_proto'].lower() == 'nfs': if share_proto.lower() == 'nfs':
self.hnas.update_nfs_access_rule(hnas_share_id, saved_list) self.hnas.update_nfs_access_rule(saved_list,
share_id=hnas_share_id)
def _delete_snapshot(self, hnas_share_id, snapshot_id): self._create_export(hnas_share_id, share_proto,
snapshot_id=snapshot['id'])
export_locations = self._get_export_locations(share_proto,
snapshot['id'],
is_snapshot=True)
return export_locations
def _delete_snapshot(self, share_proto, hnas_share_id, snapshot_id):
"""Deletes snapshot. """Deletes snapshot.
It receives the hnas_share_id only to join the path for snapshot. It receives the hnas_share_id only to join the path for snapshot.
@ -964,6 +1008,11 @@ class HitachiHNASDriver(driver.ShareDriver):
""" """
self._check_fs_mounted() self._check_fs_mounted()
if share_proto.lower() == 'nfs':
self.hnas.nfs_export_del(snapshot_id=snapshot_id)
elif share_proto.lower() == 'cifs':
self.hnas.cifs_share_del(snapshot_id)
path = os.path.join('/snapshots', hnas_share_id, snapshot_id) path = os.path.join('/snapshots', hnas_share_id, snapshot_id)
self.hnas.tree_delete(path) self.hnas.tree_delete(path)
path = os.path.join('/snapshots', hnas_share_id) path = os.path.join('/snapshots', hnas_share_id)
@ -1029,11 +1078,12 @@ class HitachiHNASDriver(driver.ShareDriver):
'proto': protocol} 'proto': protocol}
raise exception.ShareBackendException(msg=msg) raise exception.ShareBackendException(msg=msg)
def _get_export_locations(self, share_proto, hnas_share_id): def _get_export_locations(self, share_proto, hnas_id, is_snapshot=False):
export_list = [] export_list = []
for ip in (self.hnas_evs_ip, self.hnas_admin_network_ip): for ip in (self.hnas_evs_ip, self.hnas_admin_network_ip):
if ip: if ip:
path = self._get_export_path(ip, share_proto, hnas_share_id) path = self._get_export_path(ip, share_proto, hnas_id,
is_snapshot)
export_list.append({ export_list.append({
"path": path, "path": path,
"is_admin_only": ip == self.hnas_admin_network_ip, "is_admin_only": ip == self.hnas_admin_network_ip,
@ -1041,12 +1091,30 @@ class HitachiHNASDriver(driver.ShareDriver):
}) })
return export_list return export_list
def _get_export_path(self, ip, share_proto, hnas_share_id): def _get_export_path(self, ip, share_proto, hnas_id, is_snapshot):
"""Gets and returns export path.
:param ip: IP from HNAS EVS configured.
:param share_proto: Share or snapshot protocol (NFS or CIFS).
:param hnas_id: Entity ID in HNAS, it can be the ID from a share or
a snapshot.
:param is_snapshot: Boolean to determine if export is related to a
share or a snapshot.
:return: Complete export path, for example:
- In NFS:
SHARE: 172.24.44.10:/shares/id
SNAPSHOT: 172.24.44.10:/snapshots/id
- In CIFS:
SHARE and SNAPSHOT: \\172.24.44.10\id
"""
if share_proto.lower() == 'nfs': if share_proto.lower() == 'nfs':
path = os.path.join('/shares', hnas_share_id) if is_snapshot:
path = os.path.join('/snapshots', hnas_id)
else:
path = os.path.join('/shares', hnas_id)
export = ':'.join((ip, path)) export = ':'.join((ip, path))
else: else:
export = r'\\%s\%s' % (ip, hnas_share_id) export = r'\\%s\%s' % (ip, hnas_id)
return export return export
def manage_existing_snapshot(self, snapshot, driver_options): def manage_existing_snapshot(self, snapshot, driver_options):
@ -1111,7 +1179,11 @@ class HitachiHNASDriver(driver.ShareDriver):
{'snap_path': snapshot['provider_location'], {'snap_path': snapshot['provider_location'],
'shr_id': snapshot['share_id'], 'snap_id': snapshot['id']}) 'shr_id': snapshot['share_id'], 'snap_id': snapshot['id']})
return {'size': snapshot_size} export_locations = self._get_export_locations(
snapshot['share']['share_proto'], hnas_snapshot_id,
is_snapshot=True)
return {'size': snapshot_size, 'export_locations': export_locations}
def unmanage_snapshot(self, snapshot): def unmanage_snapshot(self, snapshot):
"""Unmanage a share snapshot """Unmanage a share snapshot
@ -1123,3 +1195,72 @@ class HitachiHNASDriver(driver.ShareDriver):
"However, it is not deleted and can be found in HNAS."), "However, it is not deleted and can be found in HNAS."),
{'snap_id': snapshot['id'], {'snap_id': snapshot['id'],
'share_id': snapshot['share_id']}) 'share_id': snapshot['share_id']})
def snapshot_update_access(self, context, snapshot, access_rules,
add_rules, delete_rules, share_server=None):
"""Update access rules for given snapshot.
Drivers should support 2 different cases in this method:
1. Recovery after error - 'access_rules' contains all access rules,
'add_rules' and 'delete_rules' shall be empty. Driver should clear any
existent access rules and apply all access rules for given snapshot.
This recovery is made at driver start up.
2. Adding/Deleting of several access rules - 'access_rules' contains
all access rules, 'add_rules' and 'delete_rules' contain rules which
should be added/deleted. Driver can ignore rules in 'access_rules' and
apply only rules from 'add_rules' and 'delete_rules'. All snapshots
rules should be read only.
:param context: Current context
:param snapshot: Snapshot model with snapshot data.
:param access_rules: All access rules for given snapshot
:param add_rules: Empty List or List of access rules which should be
added. access_rules already contains these rules.
:param delete_rules: Empty List or List of access rules which should be
removed. access_rules doesn't contain these rules.
:param share_server: None or Share server model
"""
hnas_snapshot_id = self._get_hnas_snapshot_id(snapshot)
self._check_protocol(snapshot['share']['id'],
snapshot['share']['share_proto'])
access_rules, add_rules, delete_rules = utils.change_rules_to_readonly(
access_rules, add_rules, delete_rules)
if snapshot['share']['share_proto'].lower() == 'nfs':
host_list = []
for rule in access_rules:
if rule['access_type'].lower() != 'ip':
msg = _("Only IP access type currently supported for NFS. "
"Snapshot provided %(snapshot)s with rule type "
"%(type)s.") % {'snapshot': snapshot['id'],
'type': rule['access_type']}
raise exception.InvalidSnapshotAccess(reason=msg)
host_list.append(rule['access_to'] + '(ro)')
self.hnas.update_nfs_access_rule(host_list,
snapshot_id=hnas_snapshot_id)
if host_list:
LOG.debug("Snapshot %(snapshot)s has the rules: %(rules)s",
{'snapshot': snapshot['id'],
'rules': ', '.join(host_list)})
else:
LOG.debug("Snapshot %(snapshot)s has no rules.",
{'snapshot': snapshot['id']})
else:
if not (add_rules or delete_rules):
# cifs recovery mode
self._clean_cifs_access_list(hnas_snapshot_id,
is_snapshot=True)
self._cifs_allow_access(snapshot, hnas_snapshot_id,
access_rules, is_snapshot=True)
else:
self._cifs_deny_access(snapshot, hnas_snapshot_id,
delete_rules, is_snapshot=True)
self._cifs_allow_access(snapshot, hnas_snapshot_id,
add_rules, is_snapshot=True)

View File

@ -20,6 +20,7 @@ from oslo_utils import units
import paramiko import paramiko
import six import six
import os
import time import time
from manila import exception from manila import exception
@ -61,10 +62,15 @@ class HNASSSHBackend(object):
available_space = fs.size - fs.used available_space = fs.size - fs.used
return fs.size, available_space, fs.dedupe return fs.size, available_space, fs.dedupe
def nfs_export_add(self, share_id): def nfs_export_add(self, share_id, snapshot_id=None):
path = '/shares/' + share_id if snapshot_id is not None:
path = os.path.join('/snapshots', share_id, snapshot_id)
name = os.path.join('/snapshots', snapshot_id)
else:
path = name = os.path.join('/shares', share_id)
command = ['nfs-export', 'add', '-S', 'disable', '-c', '127.0.0.1', command = ['nfs-export', 'add', '-S', 'disable', '-c', '127.0.0.1',
path, self.fs_name, path] name, self.fs_name, path]
try: try:
self._execute(command) self._execute(command)
except processutils.ProcessExecutionError: except processutils.ProcessExecutionError:
@ -72,24 +78,36 @@ class HNASSSHBackend(object):
LOG.exception(msg) LOG.exception(msg)
raise exception.HNASBackendException(msg=msg) raise exception.HNASBackendException(msg=msg)
def nfs_export_del(self, share_id): def nfs_export_del(self, share_id=None, snapshot_id=None):
path = '/shares/' + share_id if share_id is not None:
command = ['nfs-export', 'del', path] name = os.path.join('/shares', share_id)
elif snapshot_id is not None:
name = os.path.join('/snapshots', snapshot_id)
else:
msg = _("NFS export not specified to delete.")
raise exception.HNASBackendException(msg=msg)
command = ['nfs-export', 'del', name]
try: try:
self._execute(command) self._execute(command)
except processutils.ProcessExecutionError as e: except processutils.ProcessExecutionError as e:
if 'does not exist' in e.stderr: if 'does not exist' in e.stderr:
LOG.warning(_LW("Export %s does not exist on " LOG.warning(_LW("Export %s does not exist on "
"backend anymore."), path) "backend anymore."), name)
else: else:
msg = six.text_type(e) msg = six.text_type(e)
LOG.exception(msg) LOG.exception(msg)
raise exception.HNASBackendException(msg=msg) raise exception.HNASBackendException(msg=msg)
def cifs_share_add(self, share_id): def cifs_share_add(self, share_id, snapshot_id=None):
if snapshot_id is not None:
path = r'\\snapshots\\' + share_id + r'\\' + snapshot_id
name = snapshot_id
else:
path = r'\\shares\\' + share_id path = r'\\shares\\' + share_id
name = share_id
command = ['cifs-share', 'add', '-S', 'disable', '--enable-abe', command = ['cifs-share', 'add', '-S', 'disable', '--enable-abe',
'--nodefaultsaa', share_id, self.fs_name, path] '--nodefaultsaa', name, self.fs_name, path]
try: try:
self._execute(command) self._execute(command)
except processutils.ProcessExecutionError: except processutils.ProcessExecutionError:
@ -97,15 +115,15 @@ class HNASSSHBackend(object):
LOG.exception(msg) LOG.exception(msg)
raise exception.HNASBackendException(msg=msg) raise exception.HNASBackendException(msg=msg)
def cifs_share_del(self, share_id): def cifs_share_del(self, name):
command = ['cifs-share', 'del', '--target-label', self.fs_name, command = ['cifs-share', 'del', '--target-label', self.fs_name,
share_id] name]
try: try:
self._execute(command) self._execute(command)
except processutils.ProcessExecutionError as e: except processutils.ProcessExecutionError as e:
if e.exit_code == 1: if e.exit_code == 1:
LOG.warning(_LW("CIFS share %s does not exist on " LOG.warning(_LW("CIFS share %s does not exist on "
"backend anymore."), share_id) "backend anymore."), name)
else: else:
msg = six.text_type(e) msg = six.text_type(e)
LOG.exception(msg) LOG.exception(msg)
@ -115,7 +133,16 @@ class HNASSSHBackend(object):
export = self._get_share_export(share_id) export = self._get_share_export(share_id)
return export[0].export_configuration return export[0].export_configuration
def update_nfs_access_rule(self, share_id, host_list): def update_nfs_access_rule(self, host_list, share_id=None,
snapshot_id=None):
if share_id is not None:
name = os.path.join('/shares', share_id)
elif snapshot_id is not None:
name = os.path.join('/snapshots', snapshot_id)
else:
msg = _("No share/snapshot provided to update NFS rules.")
raise exception.HNASBackendException(msg=msg)
command = ['nfs-export', 'mod', '-c'] command = ['nfs-export', 'mod', '-c']
if len(host_list) == 0: if len(host_list) == 0:
@ -128,35 +155,47 @@ class HNASSSHBackend(object):
string_command += '"' string_command += '"'
command.append(string_command) command.append(string_command)
path = '/shares/' + share_id command.append(name)
command.append(path)
self._execute(command) self._execute(command)
def cifs_allow_access(self, hnas_share_id, user, permission): def cifs_allow_access(self, name, user, permission, is_snapshot=False):
command = ['cifs-saa', 'add', '--target-label', self.fs_name, command = ['cifs-saa', 'add', '--target-label', self.fs_name,
hnas_share_id, user, permission] name, user, permission]
entity_type = "share"
if is_snapshot:
entity_type = "snapshot"
try: try:
self._execute(command) self._execute(command)
except processutils.ProcessExecutionError as e: except processutils.ProcessExecutionError as e:
if 'already listed as a user' in e.stderr: if 'already listed as a user' in e.stderr:
LOG.debug('User %(user)s already allowed to access share ' LOG.debug('User %(user)s already allowed to access '
'%(share)s.', {'user': user, 'share': hnas_share_id}) '%(entity_type)s %(share)s.',
{'entity_type': entity_type, 'user': user,
'share': name})
else: else:
msg = six.text_type(e) msg = six.text_type(e)
LOG.exception(msg) LOG.exception(msg)
raise exception.InvalidShareAccess(reason=msg) raise exception.InvalidShareAccess(reason=msg)
def cifs_deny_access(self, hnas_share_id, user): def cifs_deny_access(self, name, user, is_snapshot=False):
command = ['cifs-saa', 'delete', '--target-label', self.fs_name, command = ['cifs-saa', 'delete', '--target-label', self.fs_name,
hnas_share_id, user] name, user]
entity_type = "share"
if is_snapshot:
entity_type = "snapshot"
try: try:
self._execute(command) self._execute(command)
except processutils.ProcessExecutionError as e: except processutils.ProcessExecutionError as e:
if ('not listed as a user' in e.stderr or if ('not listed as a user' in e.stderr or
'Could not delete user/group' in e.stderr): 'Could not delete user/group' in e.stderr):
LOG.warning(_LW('User %(user)s already not allowed to access ' LOG.warning(_LW('User %(user)s already not allowed to access '
'share %(share)s.'), '%(entity_type)s %(share)s.'),
{'user': user, 'share': hnas_share_id}) {'entity_type': entity_type, 'user': user,
'share': name})
else: else:
msg = six.text_type(e) msg = six.text_type(e)
LOG.exception(msg) LOG.exception(msg)
@ -438,6 +477,7 @@ class HNASSSHBackend(object):
def _get_share_export(self, share_id): def _get_share_export(self, share_id):
share_id = '/shares/' + share_id share_id = '/shares/' + share_id
command = ['nfs-export', 'list ', share_id] command = ['nfs-export', 'list ', share_id]
export_list = [] export_list = []
try: try:
@ -445,7 +485,7 @@ class HNASSSHBackend(object):
except processutils.ProcessExecutionError as e: except processutils.ProcessExecutionError as e:
if 'does not exist' in e.stderr: if 'does not exist' in e.stderr:
msg = _("Export %(share)s was not found in EVS " msg = _("Export %(share)s was not found in EVS "
"%(evs_id)s") % {'share': share_id, "%(evs_id)s.") % {'share': share_id,
'evs_id': self.evs_id} 'evs_id': self.evs_id}
raise exception.HNASItemNotFoundException(msg=msg) raise exception.HNASItemNotFoundException(msg=msg)
else: else:

View File

@ -31,6 +31,7 @@ from manila import exception
from manila.i18n import _, _LE, _LI, _LW from manila.i18n import _, _LE, _LI, _LW
from manila.share import driver from manila.share import driver
from manila.share.drivers import generic from manila.share.drivers import generic
from manila.share import utils
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -428,27 +429,9 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver):
:param share_server: None or Share server model :param share_server: None or Share server model
""" """
helper = self._get_helper(snapshot['share']) helper = self._get_helper(snapshot['share'])
access_rules, add_rules, delete_rules = change_rules_to_readonly( access_rules, add_rules, delete_rules = utils.change_rules_to_readonly(
access_rules, add_rules, delete_rules) access_rules, add_rules, delete_rules)
helper.update_access(self.share_server, helper.update_access(self.share_server,
snapshot['name'], access_rules, snapshot['name'], access_rules,
add_rules=add_rules, delete_rules=delete_rules) add_rules=add_rules, delete_rules=delete_rules)
def change_rules_to_readonly(access_rules, add_rules, delete_rules):
dict_access_rules = cast_access_object_to_dict_in_readonly(access_rules)
dict_add_rules = cast_access_object_to_dict_in_readonly(add_rules)
dict_delete_rules = cast_access_object_to_dict_in_readonly(delete_rules)
return dict_access_rules, dict_add_rules, dict_delete_rules
def cast_access_object_to_dict_in_readonly(rules):
dict_rules = []
for rule in rules:
dict_rules.append({
'access_level': 'ro',
'access_type': rule['access_type'],
'access_to': rule['access_to']
})
return dict_rules

View File

@ -86,3 +86,21 @@ def get_active_replica(replica_list):
for replica in replica_list: for replica in replica_list:
if replica['replica_state'] == constants.REPLICA_STATE_ACTIVE: if replica['replica_state'] == constants.REPLICA_STATE_ACTIVE:
return replica return replica
def change_rules_to_readonly(access_rules, add_rules, delete_rules):
dict_access_rules = cast_access_object_to_dict_in_readonly(access_rules)
dict_add_rules = cast_access_object_to_dict_in_readonly(add_rules)
dict_delete_rules = cast_access_object_to_dict_in_readonly(delete_rules)
return dict_access_rules, dict_add_rules, dict_delete_rules
def cast_access_object_to_dict_in_readonly(rules):
dict_rules = []
for rule in rules:
dict_rules.append({
'access_level': constants.ACCESS_LEVEL_RO,
'access_type': rule['access_type'],
'access_to': rule['access_to']
})
return dict_rules

View File

@ -228,10 +228,9 @@ class HitachiHNASTestCase(test.TestCase):
self._driver.update_access('context', share_nfs, access_list, [], []) self._driver.update_access('context', share_nfs, access_list, [], [])
ssh.HNASSSHBackend.update_nfs_access_rule.assert_called_once_with( ssh.HNASSSHBackend.update_nfs_access_rule.assert_called_once_with(
share_nfs['id'], [access1['access_to'] + '(' [access1['access_to'] + '(' + access1['access_level'] +
+ access1['access_level'] + ',norootsquash)', ',norootsquash)', access2['access_to'] + '(' +
access2['access_to'] + '(' access2['access_level'] + ')', ], share_id=share_nfs['id'])
+ access2['access_level'] + ')'])
self.assertTrue(self.mock_log.debug.called) self.assertTrue(self.mock_log.debug.called)
def test_update_access_ip_exception(self): def test_update_access_ip_exception(self):
@ -282,7 +281,7 @@ class HitachiHNASTestCase(test.TestCase):
access_list_allow, []) access_list_allow, [])
ssh.HNASSSHBackend.cifs_allow_access.assert_called_once_with( ssh.HNASSSHBackend.cifs_allow_access.assert_called_once_with(
share_cifs['id'], 'fake_user', permission) share_cifs['id'], 'fake_user', permission, is_snapshot=False)
self.assertTrue(self.mock_log.debug.called) self.assertTrue(self.mock_log.debug.called)
def test_allow_access_cifs_invalid_type(self): def test_allow_access_cifs_invalid_type(self):
@ -308,7 +307,7 @@ class HitachiHNASTestCase(test.TestCase):
access_list_deny) access_list_deny)
ssh.HNASSSHBackend.cifs_deny_access.assert_called_once_with( ssh.HNASSSHBackend.cifs_deny_access.assert_called_once_with(
share_cifs['id'], 'fake_user') share_cifs['id'], 'fake_user', is_snapshot=False)
self.assertTrue(self.mock_log.debug.called) self.assertTrue(self.mock_log.debug.called)
def test_deny_access_cifs_unsupported_type(self): def test_deny_access_cifs_unsupported_type(self):
@ -349,11 +348,16 @@ class HitachiHNASTestCase(test.TestCase):
share_cifs['id']) share_cifs['id'])
self.assertTrue(self.mock_log.debug.called) self.assertTrue(self.mock_log.debug.called)
def _get_export(self, share, ip, is_admin_only): def _get_export(self, id, share_proto, ip, is_admin_only,
if share['share_proto'].lower() == 'nfs': is_snapshot=False):
export = ':'.join((ip, '/shares/' + share['id'])) if share_proto.lower() == 'nfs':
if is_snapshot:
path = '/snapshots/' + id
else: else:
export = r'\\%s\%s' % (ip, share['id']) path = '/shares/' + id
export = ':'.join((ip, path))
else:
export = r'\\%s\%s' % (ip, id)
return { return {
"path": export, "path": export,
@ -379,17 +383,19 @@ class HitachiHNASTestCase(test.TestCase):
share['size']) share['size'])
expected = [ expected = [
self._get_export( self._get_export(
share, self._driver.hnas_evs_ip, False), share['id'], share['share_proto'], self._driver.hnas_evs_ip,
False),
self._get_export( self._get_export(
share, self._driver.hnas_admin_network_ip, True)] share['id'], share['share_proto'],
self._driver.hnas_admin_network_ip, True)]
if share['share_proto'].lower() == 'nfs': if share['share_proto'].lower() == 'nfs':
ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with( ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(
share_nfs['id']) share_nfs['id'], snapshot_id=None)
self.assertFalse(ssh.HNASSSHBackend.cifs_share_add.called) self.assertFalse(ssh.HNASSSHBackend.cifs_share_add.called)
else: else:
ssh.HNASSSHBackend.cifs_share_add.assert_called_once_with( ssh.HNASSSHBackend.cifs_share_add.assert_called_once_with(
share_cifs['id']) share_cifs['id'], snapshot_id=None)
self.assertFalse(ssh.HNASSSHBackend.nfs_export_add.called) self.assertFalse(ssh.HNASSSHBackend.nfs_export_add.called)
self.assertEqual(expected, result) self.assertEqual(expected, result)
@ -410,7 +416,7 @@ class HitachiHNASTestCase(test.TestCase):
ssh.HNASSSHBackend.quota_add.assert_called_once_with(share_nfs['id'], ssh.HNASSSHBackend.quota_add.assert_called_once_with(share_nfs['id'],
share_nfs['size']) share_nfs['size'])
ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with( ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(
share_nfs['id']) share_nfs['id'], snapshot_id=None)
ssh.HNASSSHBackend.vvol_delete.assert_called_once_with(share_nfs['id']) ssh.HNASSSHBackend.vvol_delete.assert_called_once_with(share_nfs['id'])
def test_create_share_invalid_share_protocol(self): def test_create_share_invalid_share_protocol(self):
@ -447,8 +453,18 @@ class HitachiHNASTestCase(test.TestCase):
@ddt.data(snapshot_nfs, snapshot_cifs) @ddt.data(snapshot_nfs, snapshot_cifs)
def test_create_snapshot(self, snapshot): def test_create_snapshot(self, snapshot):
hnas_id = snapshot['share_id'] hnas_id = snapshot['share_id']
p_location = {'provider_location': '/snapshots/' + hnas_id + '/' + export_locations = [
snapshot['id']} self._get_export(
snapshot['id'], snapshot['share']['share_proto'],
self._driver.hnas_evs_ip, False, is_snapshot=True),
self._get_export(
snapshot['id'], snapshot['share']['share_proto'],
self._driver.hnas_admin_network_ip, True, is_snapshot=True)]
expected = {
'provider_location': '/snapshots/' + hnas_id + '/' +
snapshot['id'],
'export_locations': export_locations,
}
self.mock_object(ssh.HNASSSHBackend, "get_nfs_host_list", mock.Mock( self.mock_object(ssh.HNASSSHBackend, "get_nfs_host_list", mock.Mock(
return_value=['172.24.44.200(rw)'])) return_value=['172.24.44.200(rw)']))
@ -457,21 +473,23 @@ class HitachiHNASTestCase(test.TestCase):
self.mock_object(ssh.HNASSSHBackend, "is_cifs_in_use", mock.Mock( self.mock_object(ssh.HNASSSHBackend, "is_cifs_in_use", mock.Mock(
return_value=False)) return_value=False))
self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "nfs_export_add")
self.mock_object(ssh.HNASSSHBackend, "cifs_share_add")
out = self._driver.create_snapshot('context', snapshot) out = self._driver.create_snapshot('context', snapshot)
ssh.HNASSSHBackend.tree_clone.assert_called_once_with( ssh.HNASSSHBackend.tree_clone.assert_called_once_with(
'/shares/' + hnas_id, '/snapshots/' + hnas_id + '/' + '/shares/' + hnas_id, '/snapshots/' + hnas_id + '/' +
snapshot['id']) snapshot['id'])
self.assertEqual(p_location, out) self.assertEqual(expected, out)
if snapshot['share']['share_proto'].lower() == 'nfs': if snapshot['share']['share_proto'].lower() == 'nfs':
ssh.HNASSSHBackend.get_nfs_host_list.assert_called_once_with( ssh.HNASSSHBackend.get_nfs_host_list.assert_called_once_with(
hnas_id) hnas_id)
ssh.HNASSSHBackend.update_nfs_access_rule.assert_any_call( ssh.HNASSSHBackend.update_nfs_access_rule.assert_any_call(
hnas_id, ['172.24.44.200(ro)']) ['172.24.44.200(ro)'], share_id=hnas_id)
ssh.HNASSSHBackend.update_nfs_access_rule.assert_any_call( ssh.HNASSSHBackend.update_nfs_access_rule.assert_any_call(
hnas_id, ['172.24.44.200(rw)']) ['172.24.44.200(rw)'], share_id=hnas_id)
else: else:
ssh.HNASSSHBackend.is_cifs_in_use.assert_called_once_with( ssh.HNASSSHBackend.is_cifs_in_use.assert_called_once_with(
hnas_id) hnas_id)
@ -506,39 +524,54 @@ class HitachiHNASTestCase(test.TestCase):
self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock( self.mock_object(ssh.HNASSSHBackend, "tree_clone", mock.Mock(
side_effect=exception.HNASNothingToCloneException('msg'))) side_effect=exception.HNASNothingToCloneException('msg')))
self.mock_object(ssh.HNASSSHBackend, "create_directory", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "create_directory", mock.Mock())
self.mock_object(ssh.HNASSSHBackend, "nfs_export_add")
self.mock_object(ssh.HNASSSHBackend, "cifs_share_add")
self._driver.create_snapshot('context', snapshot_nfs) self._driver.create_snapshot('context', snapshot_nfs)
self.assertTrue(self.mock_log.warning.called) self.assertTrue(self.mock_log.warning.called)
ssh.HNASSSHBackend.get_nfs_host_list.assert_called_once_with(hnas_id) ssh.HNASSSHBackend.get_nfs_host_list.assert_called_once_with(
hnas_id)
ssh.HNASSSHBackend.update_nfs_access_rule.assert_any_call( ssh.HNASSSHBackend.update_nfs_access_rule.assert_any_call(
hnas_id, ['172.24.44.200(ro)']) ['172.24.44.200(ro)'], share_id=hnas_id)
ssh.HNASSSHBackend.update_nfs_access_rule.assert_any_call( ssh.HNASSSHBackend.update_nfs_access_rule.assert_any_call(
hnas_id, ['172.24.44.200(rw)']) ['172.24.44.200(rw)'], share_id=hnas_id)
ssh.HNASSSHBackend.create_directory.assert_called_once_with( ssh.HNASSSHBackend.create_directory.assert_called_once_with(
'/snapshots/' + hnas_id + '/' + snapshot_nfs['id']) '/snapshots/' + hnas_id + '/' + snapshot_nfs['id'])
def test_delete_snapshot(self): @ddt.data(snapshot_nfs, snapshot_cifs)
hnas_id = snapshot_nfs['share_id'] def test_delete_snapshot(self, snapshot):
hnas_share_id = snapshot['share_id']
hnas_snapshot_id = snapshot['id']
self.mock_object(driver.HitachiHNASDriver, "_check_fs_mounted") self.mock_object(driver.HitachiHNASDriver, "_check_fs_mounted")
self.mock_object(ssh.HNASSSHBackend, "tree_delete", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "tree_delete")
self.mock_object(ssh.HNASSSHBackend, "delete_directory", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "delete_directory")
self.mock_object(ssh.HNASSSHBackend, "nfs_export_del")
self.mock_object(ssh.HNASSSHBackend, "cifs_share_del")
self._driver.delete_snapshot('context', snapshot_nfs) self._driver.delete_snapshot('context', snapshot)
self.assertTrue(self.mock_log.debug.called) self.assertTrue(self.mock_log.debug.called)
self.assertTrue(self.mock_log.info.called) self.assertTrue(self.mock_log.info.called)
driver.HitachiHNASDriver._check_fs_mounted.assert_called_once_with() driver.HitachiHNASDriver._check_fs_mounted.assert_called_once_with()
ssh.HNASSSHBackend.tree_delete.assert_called_once_with( ssh.HNASSSHBackend.tree_delete.assert_called_once_with(
'/snapshots/' + hnas_id + '/' + snapshot_nfs['id']) '/snapshots/' + hnas_share_id + '/' + snapshot['id'])
ssh.HNASSSHBackend.delete_directory.assert_called_once_with( ssh.HNASSSHBackend.delete_directory.assert_called_once_with(
'/snapshots/' + hnas_id) '/snapshots/' + hnas_share_id)
if snapshot['share']['share_proto'].lower() == 'nfs':
ssh.HNASSSHBackend.nfs_export_del.assert_called_once_with(
snapshot_id=hnas_snapshot_id)
else:
ssh.HNASSSHBackend.cifs_share_del.assert_called_once_with(
hnas_snapshot_id)
def test_delete_managed_snapshot(self): def test_delete_managed_snapshot(self):
hnas_id = manage_snapshot['share_id'] hnas_id = manage_snapshot['share_id']
self.mock_object(driver.HitachiHNASDriver, "_check_fs_mounted") self.mock_object(driver.HitachiHNASDriver, "_check_fs_mounted")
self.mock_object(ssh.HNASSSHBackend, "tree_delete") self.mock_object(ssh.HNASSSHBackend, "tree_delete")
self.mock_object(ssh.HNASSSHBackend, "delete_directory") self.mock_object(ssh.HNASSSHBackend, "delete_directory")
self.mock_object(ssh.HNASSSHBackend, "nfs_export_del")
self.mock_object(ssh.HNASSSHBackend, "cifs_share_del")
self._driver.delete_snapshot('context', manage_snapshot) self._driver.delete_snapshot('context', manage_snapshot)
@ -559,9 +592,11 @@ class HitachiHNASTestCase(test.TestCase):
expected = [ expected = [
self._get_export( self._get_export(
share, self._driver.hnas_evs_ip, False), share['id'], share['share_proto'], self._driver.hnas_evs_ip,
False),
self._get_export( self._get_export(
share, self._driver.hnas_admin_network_ip, True)] share['id'], share['share_proto'],
self._driver.hnas_admin_network_ip, True)]
if share['share_proto'].lower() == 'nfs': if share['share_proto'].lower() == 'nfs':
ssh.HNASSSHBackend.check_export.assert_called_once_with( ssh.HNASSSHBackend.check_export.assert_called_once_with(
@ -625,9 +660,11 @@ class HitachiHNASTestCase(test.TestCase):
expected_exports = [ expected_exports = [
self._get_export( self._get_export(
share, self._driver.hnas_evs_ip, False), share['id'], share['share_proto'], self._driver.hnas_evs_ip,
False),
self._get_export( self._get_export(
share, self._driver.hnas_admin_network_ip, True)] share['id'], share['share_proto'],
self._driver.hnas_admin_network_ip, True)]
expected_out = {'size': share['size'], expected_out = {'size': share['size'],
'export_locations': expected_exports} 'export_locations': expected_exports}
@ -724,9 +761,11 @@ class HitachiHNASTestCase(test.TestCase):
expected = [ expected = [
self._get_export( self._get_export(
share, self._driver.hnas_evs_ip, False), share['id'], share['share_proto'], self._driver.hnas_evs_ip,
False),
self._get_export( self._get_export(
share, self._driver.hnas_admin_network_ip, True)] share['id'], share['share_proto'],
self._driver.hnas_admin_network_ip, True)]
if share['share_proto'].lower() == 'nfs': if share['share_proto'].lower() == 'nfs':
ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with( ssh.HNASSSHBackend.nfs_export_add.assert_called_once_with(
@ -752,9 +791,11 @@ class HitachiHNASTestCase(test.TestCase):
snapshot_nfs) snapshot_nfs)
expected = [ expected = [
self._get_export( self._get_export(
share_nfs, self._driver.hnas_evs_ip, False), share_nfs['id'], share_nfs['share_proto'],
self._driver.hnas_evs_ip, False),
self._get_export( self._get_export(
share_nfs, self._driver.hnas_admin_network_ip, True)] share_nfs['id'], share_nfs['share_proto'],
self._driver.hnas_admin_network_ip, True)]
self.assertEqual(expected, result) self.assertEqual(expected, result)
self.assertTrue(self.mock_log.warning.called) self.assertTrue(self.mock_log.warning.called)
@ -837,6 +878,7 @@ class HitachiHNASTestCase(test.TestCase):
'thin_provisioning': True, 'thin_provisioning': True,
'dedupe': True, 'dedupe': True,
'revert_to_snapshot_support': True, 'revert_to_snapshot_support': True,
'mount_snapshot_support': True,
} }
self.mock_object(ssh.HNASSSHBackend, 'get_stats', mock.Mock( self.mock_object(ssh.HNASSSHBackend, 'get_stats', mock.Mock(
@ -933,3 +975,109 @@ class HitachiHNASTestCase(test.TestCase):
if exc: if exc:
self.assertTrue(self.mock_log.warning.called) self.assertTrue(self.mock_log.warning.called)
self.assertTrue(self.mock_log.info.called) self.assertTrue(self.mock_log.info.called)
def test_nfs_snapshot_update_access_allow(self):
access1 = {
'access_type': 'ip',
'access_to': '172.24.10.10',
}
access2 = {
'access_type': 'ip',
'access_to': '172.31.20.20',
}
access_list = [access1, access2]
self.mock_object(ssh.HNASSSHBackend, "update_nfs_access_rule")
self._driver.snapshot_update_access('ctxt', snapshot_nfs, access_list,
access_list, [])
ssh.HNASSSHBackend.update_nfs_access_rule.assert_called_once_with(
[access1['access_to'] + '(ro)', access2['access_to'] + '(ro)'],
snapshot_id=snapshot_nfs['id'])
self.assertTrue(self.mock_log.debug.called)
def test_nfs_snapshot_update_access_deny(self):
access1 = {
'access_type': 'ip',
'access_to': '172.24.10.10',
}
self.mock_object(ssh.HNASSSHBackend, "update_nfs_access_rule")
self._driver.snapshot_update_access('ctxt', snapshot_nfs, [],
[], [access1])
ssh.HNASSSHBackend.update_nfs_access_rule.assert_called_once_with(
[], snapshot_id=snapshot_nfs['id'])
self.assertTrue(self.mock_log.debug.called)
def test_nfs_snapshot_update_access_invalid_access_type(self):
access1 = {
'access_type': 'user',
'access_to': 'user1',
}
self.assertRaises(exception.InvalidSnapshotAccess,
self._driver.snapshot_update_access, 'ctxt',
snapshot_nfs, [access1], [], [])
def test_cifs_snapshot_update_access_allow(self):
access1 = {
'access_type': 'user',
'access_to': 'fake_user1',
}
self.mock_object(ssh.HNASSSHBackend, 'cifs_allow_access')
self._driver.snapshot_update_access('ctxt', snapshot_cifs, [access1],
[access1], [])
ssh.HNASSSHBackend.cifs_allow_access.assert_called_with(
snapshot_cifs['id'], access1['access_to'], 'ar', is_snapshot=True)
self.assertTrue(self.mock_log.debug.called)
def test_cifs_snapshot_update_access_deny(self):
access1 = {
'access_type': 'user',
'access_to': 'fake_user1',
}
self.mock_object(ssh.HNASSSHBackend, 'cifs_deny_access')
self._driver.snapshot_update_access('ctxt', snapshot_cifs, [], [],
[access1])
ssh.HNASSSHBackend.cifs_deny_access.assert_called_with(
snapshot_cifs['id'], access1['access_to'], is_snapshot=True)
self.assertTrue(self.mock_log.debug.called)
def test_cifs_snapshot_update_access_recovery_mode(self):
access1 = {
'access_type': 'user',
'access_to': 'fake_user1',
}
access2 = {
'access_type': 'user',
'access_to': 'HDS\\fake_user2',
}
access_list = [access1, access2]
permission_list = [('fake_user1', 'ar'), ('HDS\\fake_user2', 'ar')]
formatted_user = r'"\{1}{0}\{1}"'.format(access2['access_to'], '"')
self.mock_object(ssh.HNASSSHBackend, 'list_cifs_permissions',
mock.Mock(return_value=permission_list))
self.mock_object(ssh.HNASSSHBackend, 'cifs_deny_access')
self.mock_object(ssh.HNASSSHBackend, 'cifs_allow_access')
self._driver.snapshot_update_access('ctxt', snapshot_cifs, access_list,
[], [])
ssh.HNASSSHBackend.list_cifs_permissions.assert_called_once_with(
snapshot_cifs['id'])
ssh.HNASSSHBackend.cifs_deny_access.assert_called_with(
snapshot_cifs['id'], formatted_user, is_snapshot=True)
ssh.HNASSSHBackend.cifs_allow_access.assert_called_with(
snapshot_cifs['id'], access2['access_to'].replace('\\', '\\\\'),
'ar', is_snapshot=True)
self.assertTrue(self.mock_log.debug.called)

View File

@ -539,13 +539,24 @@ class HNASSSHTestCase(test.TestCase):
self.assertEqual(5120.0, free) self.assertEqual(5120.0, free)
self.assertTrue(dedupe) self.assertTrue(dedupe)
def test_nfs_export_add(self): @ddt.data(True, False)
def test_nfs_export_add(self, is_snapshot):
if is_snapshot:
name = '/snapshots/fake_snap'
path = '/snapshots/fake_share/fake_snap'
else:
name = path = '/shares/fake_share'
fake_nfs_command = ['nfs-export', 'add', '-S', 'disable', '-c', fake_nfs_command = ['nfs-export', 'add', '-S', 'disable', '-c',
'127.0.0.1', '/shares/vvol_test', self.fs_name, '127.0.0.1', name, self.fs_name,
'/shares/vvol_test'] path]
self.mock_object(ssh.HNASSSHBackend, '_execute', mock.Mock()) self.mock_object(ssh.HNASSSHBackend, '_execute', mock.Mock())
self._driver_ssh.nfs_export_add('vvol_test') if is_snapshot:
self._driver_ssh.nfs_export_add('fake_share',
snapshot_id='fake_snap')
else:
self._driver_ssh.nfs_export_add('fake_share')
self._driver_ssh._execute.assert_called_with(fake_nfs_command) self._driver_ssh._execute.assert_called_with(fake_nfs_command)
@ -557,11 +568,19 @@ class HNASSSHTestCase(test.TestCase):
self._driver_ssh.nfs_export_add, 'vvol_test') self._driver_ssh.nfs_export_add, 'vvol_test')
self.assertTrue(self.mock_log.exception.called) self.assertTrue(self.mock_log.exception.called)
def test_nfs_export_del(self): @ddt.data(True, False)
fake_nfs_command = ['nfs-export', 'del', '/shares/vvol_test'] def test_nfs_export_del(self, is_snapshot):
if is_snapshot:
name = '/snapshots/vvol_test'
args = {'snapshot_id': 'vvol_test'}
else:
name = '/shares/vvol_test'
args = {'share_id': 'vvol_test'}
fake_nfs_command = ['nfs-export', 'del', name]
self.mock_object(ssh.HNASSSHBackend, '_execute', mock.Mock()) self.mock_object(ssh.HNASSSHBackend, '_execute', mock.Mock())
self._driver_ssh.nfs_export_del('vvol_test') self._driver_ssh.nfs_export_del(**args)
self._driver_ssh._execute.assert_called_with(fake_nfs_command) self._driver_ssh._execute.assert_called_with(fake_nfs_command)
@ -574,7 +593,11 @@ class HNASSSHTestCase(test.TestCase):
self.assertTrue(self.mock_log.warning.called) self.assertTrue(self.mock_log.warning.called)
def test_nfs_export_del_error(self): def test_nfs_export_del_exception(self):
self.assertRaises(exception.HNASBackendException,
self._driver_ssh.nfs_export_del)
def test_nfs_export_del_execute_error(self):
self.mock_object(ssh.HNASSSHBackend, '_execute', mock.Mock( self.mock_object(ssh.HNASSSHBackend, '_execute', mock.Mock(
side_effect=[putils.ProcessExecutionError(stderr='')])) side_effect=[putils.ProcessExecutionError(stderr='')]))
@ -582,14 +605,26 @@ class HNASSSHTestCase(test.TestCase):
self._driver_ssh.nfs_export_del, 'vvol_test') self._driver_ssh.nfs_export_del, 'vvol_test')
self.assertTrue(self.mock_log.exception.called) self.assertTrue(self.mock_log.exception.called)
def test_cifs_share_add(self): @ddt.data(True, False)
def test_cifs_share_add(self, is_snapshot):
if is_snapshot:
name = 'fake_snap'
path = r'\\snapshots\\fake_share\\fake_snap'
else:
name = 'fake_share'
path = r'\\shares\\fake_share'
fake_cifs_add_command = ['cifs-share', 'add', '-S', 'disable', fake_cifs_add_command = ['cifs-share', 'add', '-S', 'disable',
'--enable-abe', '--nodefaultsaa', '--enable-abe', '--nodefaultsaa',
'vvol_test', self.fs_name, name, self.fs_name,
r'\\shares\\vvol_test'] path]
self.mock_object(ssh.HNASSSHBackend, '_execute', mock.Mock()) self.mock_object(ssh.HNASSSHBackend, '_execute', mock.Mock())
self._driver_ssh.cifs_share_add('vvol_test') if is_snapshot:
self._driver_ssh.cifs_share_add('fake_share',
snapshot_id='fake_snap')
else:
self._driver_ssh.cifs_share_add('fake_share')
self._driver_ssh._execute.assert_called_with(fake_cifs_add_command) self._driver_ssh._execute.assert_called_with(fake_cifs_add_command)
@ -642,10 +677,10 @@ class HNASSSHTestCase(test.TestCase):
def test_update_nfs_access_rule_empty_host_list(self): def test_update_nfs_access_rule_empty_host_list(self):
fake_export_command = ['nfs-export', 'mod', '-c', '127.0.0.1', fake_export_command = ['nfs-export', 'mod', '-c', '127.0.0.1',
'/shares/fake_id'] '/snapshots/fake_id']
self.mock_object(ssh.HNASSSHBackend, "_execute", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "_execute", mock.Mock())
self._driver_ssh.update_nfs_access_rule("fake_id", []) self._driver_ssh.update_nfs_access_rule([], snapshot_id="fake_id")
self._driver_ssh._execute.assert_called_with(fake_export_command) self._driver_ssh._execute.assert_called_with(fake_export_command)
@ -654,11 +689,16 @@ class HNASSSHTestCase(test.TestCase):
u'"127.0.0.1,127.0.0.2"', '/shares/fake_id'] u'"127.0.0.1,127.0.0.2"', '/shares/fake_id']
self.mock_object(ssh.HNASSSHBackend, "_execute", mock.Mock()) self.mock_object(ssh.HNASSSHBackend, "_execute", mock.Mock())
self._driver_ssh.update_nfs_access_rule("fake_id", ['127.0.0.1', self._driver_ssh.update_nfs_access_rule(['127.0.0.1', '127.0.0.2'],
'127.0.0.2']) share_id="fake_id")
self._driver_ssh._execute.assert_called_with(fake_export_command) self._driver_ssh._execute.assert_called_with(fake_export_command)
def test_update_nfs_access_rule_exception(self):
self.assertRaises(exception.HNASBackendException,
self._driver_ssh.update_nfs_access_rule,
['127.0.0.1'])
def test_cifs_allow_access(self): def test_cifs_allow_access(self):
fake_cifs_allow_command = ['cifs-saa', 'add', '--target-label', fake_cifs_allow_command = ['cifs-saa', 'add', '--target-label',
self.fs_name, 'vvol_test', self.fs_name, 'vvol_test',
@ -1163,15 +1203,20 @@ class HNASSSHTestCase(test.TestCase):
self.assertEqual(1024, self._driver_ssh.get_share_usage("vvol_test")) self.assertEqual(1024, self._driver_ssh.get_share_usage("vvol_test"))
def test__get_share_export(self): def test__get_share_export(self):
self.mock_object(ssh.HNASSSHBackend, '_execute', self.mock_object(ssh.HNASSSHBackend, '_execute', mock.Mock(
mock.Mock(return_value=[HNAS_RESULT_export_ip, ''])) return_value=[HNAS_RESULT_export_ip, '']))
export_list = self._driver_ssh._get_share_export('fake_id') export_list = self._driver_ssh._get_share_export(share_id='share_id')
path = '/shares/share_id'
command = ['nfs-export', 'list ', path]
self._driver_ssh._execute.assert_called_with(command)
self.assertEqual('vvol_test', export_list[0].export_name) self.assertEqual('vvol_test', export_list[0].export_name)
self.assertEqual('/vvol_test', export_list[0].export_path) self.assertEqual('/vvol_test', export_list[0].export_path)
self.assertEqual('fake_fs', export_list[0].file_system_label) self.assertEqual('fake_fs', export_list[0].file_system_label)
self.assertEqual('Yes', export_list[0].mounted) self.assertEqual('Yes', export_list[0].mounted)
self.assertIn('rw', export_list[0].export_configuration[0])
def test__get_share_export_exception_not_found(self): def test__get_share_export_exception_not_found(self):

View File

@ -0,0 +1,9 @@
---
features:
- Added Mountable Snapshots support to HNAS driver.
upgrade:
- If using existing share types with the HNAS back end,
set the 'mount_snapshot_support' extra-spec to allow
creating shares that support mountable snapshots.
This modification will not affect existing shares of
such types.