From ea1ac5f4482121712c7a481e6d5ddb98b181b66e Mon Sep 17 00:00:00 2001 From: "jayaanand.borra@netapp.com" Date: Tue, 13 Feb 2024 17:17:34 +0530 Subject: [PATCH] Human readable export location core implementation Export locations are usually too difficult to memo rize.Currently, there is no way to determine the export location before the share is created, so users wait until the share creation request gets completed, and then they check the export locations to mount the share. The generated export locations are often not human readable and it is hard to memorize and control them. Implements: bp/human-readable-export-locations Change-Id: I72ac7e24ddd4330d76cafd5e7f78bac2b0174883 --- .../admin/capabilities_and_extra_specs.rst | 18 +++ ...hare_back_ends_feature_support_mapping.rst | 142 +++++++++--------- manila/api/openstack/api_version_request.py | 3 +- .../openstack/rest_api_version_history.rst | 4 + manila/api/v1/shares.py | 4 + manila/common/constants.py | 6 +- ...add_mount_point_name_to_share_instances.py | 47 ++++++ manila/db/sqlalchemy/api.py | 1 + manila/db/sqlalchemy/models.py | 2 +- manila/scheduler/filters/capabilities.py | 1 + manila/scheduler/host_manager.py | 1 + manila/scheduler/utils.py | 4 +- manila/share/api.py | 68 ++++++++- manila/share/driver.py | 1 + manila/share/drivers/container/driver.py | 1 + manila/share/drivers/lvm.py | 37 +++-- manila/share/share_types.py | 8 + manila/tests/api/v1/test_shares.py | 78 ++++++++++ manila/tests/api/v2/test_share_transfer.py | 70 +++++++-- manila/tests/api/v2/test_share_types.py | 1 + manila/tests/db_utils.py | 6 +- manila/tests/fake_share.py | 4 +- manila/tests/scheduler/fakes.py | 45 ++++-- .../scheduler/filters/test_capabilities.py | 18 +++ manila/tests/scheduler/test_host_manager.py | 11 ++ .../share/drivers/container/test_driver.py | 2 + .../share/drivers/dell_emc/test_driver.py | 1 + manila/tests/share/drivers/dummy.py | 4 + .../glusterfs/test_glusterfs_native.py | 1 + .../share/drivers/hpe/test_hpe_3par_driver.py | 3 + .../share/drivers/huawei/test_huawei_nas.py | 1 + manila/tests/share/drivers/test_lvm.py | 44 ++++++ .../share/drivers/veritas/test_veritas_isa.py | 1 + .../share/drivers/zfsonlinux/test_driver.py | 1 + manila/tests/share/test_api.py | 126 +++++++++++++++- manila/tests/share/test_driver.py | 1 + manila/tests/share/test_share_types.py | 20 ++- manila/transfer/api.py | 11 ++ ...cation-share-support-a72cd2f0e92c41c7.yaml | 12 ++ 39 files changed, 689 insertions(+), 120 deletions(-) create mode 100644 manila/db/migrations/alembic/versions/40d1f2374e89_add_mount_point_name_to_share_instances.py create mode 100644 releasenotes/notes/human-readable-export-location-share-support-a72cd2f0e92c41c7.yaml diff --git a/doc/source/admin/capabilities_and_extra_specs.rst b/doc/source/admin/capabilities_and_extra_specs.rst index f62c926aed..67980b2c37 100644 --- a/doc/source/admin/capabilities_and_extra_specs.rst +++ b/doc/source/admin/capabilities_and_extra_specs.rst @@ -196,6 +196,20 @@ Share type common capability extra-specs that are visible to end users: this extra-spec, the share type is assumed to be serviceable in all availability zones known to the Shared File Systems service. +* **mount_point_name_support** whether a custom export location could + be specified during share creation. To enable users to specify a custom + mount point for their shares, administrators must set this + extra-specification in the share type to True. They must also provide + an extra-spec named ``provisioning:mount_point_prefix``. The service will + use this prefix in conjunction with the mount point name provided by end + users during share creation. When ``provisioning:mount_point_prefix`` is not + set on a share type, but ``mount_point_name_support`` is enabled, the + share's export location will be prefixed with the ``project_id``. + However, shares created with a ``project_id`` prefix are not eligible + for transfer. For these shares to be transferred to a different project, + the admin will need to manually unmount them from the current project + and mount them to the target project. + Share type common capability extra-specs that are not visible to end users: --------------------------------------------------------------------------- @@ -274,3 +288,7 @@ Share type common capability extra-specs that are not visible to end users: the share type can not be greater than the specified value. This capability is ignored for regular users and the "provisioning:max_share_size" is the only effective limit. + +* **provisioning:mount_point_prefix** can set prefix for human readable + mount_point_name, the value must be a string containing ASCII alphabets + and optionally, the underscore character. diff --git a/doc/source/admin/share_back_ends_feature_support_mapping.rst b/doc/source/admin/share_back_ends_feature_support_mapping.rst index 0f5f097008..d0fa00cc5e 100644 --- a/doc/source/admin/share_back_ends_feature_support_mapping.rst +++ b/doc/source/admin/share_back_ends_feature_support_mapping.rst @@ -261,77 +261,77 @@ Mapping of share drivers and common capabilities 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 | mountable snapshot | ipv4_support | ipv6_support | multiple subnets per AZ | -+========================================+===========+============+========+=============+===================+====================+=====+============================+====================+====================+==============+==============+=========================+ -| ZFSonLinux | \- | M | M | M | M | \- | \- | M | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Container | N | \- | \- | \- | \- | N | \- | \- | \- | \- | P | \- | Y | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| NetApp Clustered Data ONTAP | J | K | M | M | M | L | P | J | O | \- | P | Q | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Dell EMC PowerMax | O | \- | \- | \- | \- | \- | \- | O | \- | \- | P | R | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- | \- | P | Q | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| EMC Unity | N | T | \- | \- | N | \- | \- | N | S | \- | P | Q | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| EMC Isilon | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Dell EMC PowerStore | \- | B | \- | \- | B | \- | \- | B | B | \- | B | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Dell EMC PowerFlex | \- | B | \- | \- | B | \- | \- | \- | \- | \- | B | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| GlusterFS | \- | J | \- | \- | \- | L | \- | volume layout (L) | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| GlusterFS-Native | \- | J | \- | \- | \- | L | \- | L | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| HDFS | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Hitachi HNAS | \- | L | N | \- | L | \- | \- | L | O | O | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Hitachi HSP | \- | N | \- | \- | N | \- | \- | \- | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| HPE 3PAR | L | K | L | \- | L | L | \- | K | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Huawei | M | K | L | L | L | L | M | M | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| INFINIDAT | \- | Q | \- | \- | Q | Q | \- | Q | Q | Q | Q | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Infortrend | \- | T | \- | \- | \- | \- | \- | \- | \- | \- | T | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| LVM | \- | M | \- | \- | \- | M | \- | K | O | O | P | P | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Macrosan | \- | Z | \- | \- | \- | Z | \- | \- | \- | \- | Z | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Quobyte | \- | K | \- | \- | \- | L | \- | M | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Windows SMB | L | L | \- | \- | \- | L | \- | \- | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| IBM GPFS | \- | K | \- | \- | \- | L | \- | L | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Oracle ZFSSA | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| CephFS | \- | M | \- | \- | \- | M | \- | \- | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Tegile | \- | M | M | M | M | \- | \- | M | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| NexentaStor4 | \- | N | N | N | N | N | \- | N | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| NexentaStor5 | \- | N | \- | N | N | N | \- | N | T | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| MapRFS | \- | N | \- | \- | \- | N | \- | O | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| QNAP | \- | O | Q | Q | O | Q | \- | O | \- | \- | P | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| INSPUR AS13000 | \- | R | \- | \- | R | \- | \- | R | \- | \- | R | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| INSPUR InStorage | \- | T | \- | \- | \- | T | \- | \- | \- | \- | T | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ -| Pure Storage FlashBlade | \- | X | \- | \- | X | \- | \- | \- | X | \- | X | \- | \- | -+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+ ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Driver name | DHSS=True | DHSS=False | dedupe | compression | thin_provisioning | thick_provisioning | qos | create share from snapshot | revert to snapshot | mountable snapshot | ipv4_support | ipv6_support | multiple subnets per AZ | mount point name support | ++========================================+===========+============+========+=============+===================+====================+=====+============================+====================+====================+==============+==============+=========================+==========================+ +| ZFSonLinux | \- | M | M | M | M | \- | \- | M | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Container | N | \- | \- | \- | \- | N | \- | \- | \- | \- | P | \- | Y | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| NetApp Clustered Data ONTAP | J | K | M | M | M | L | P | J | O | \- | P | Q | \- | Y | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Dell EMC PowerMax | O | \- | \- | \- | \- | \- | \- | O | \- | \- | P | R | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- | \- | P | Q | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| EMC Unity | N | T | \- | \- | N | \- | \- | N | S | \- | P | Q | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| EMC Isilon | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Dell EMC PowerStore | \- | B | \- | \- | B | \- | \- | B | B | \- | B | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Dell EMC PowerFlex | \- | B | \- | \- | B | \- | \- | \- | \- | \- | B | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| GlusterFS | \- | J | \- | \- | \- | L | \- | volume layout (L) | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| GlusterFS-Native | \- | J | \- | \- | \- | L | \- | L | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| HDFS | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Hitachi HNAS | \- | L | N | \- | L | \- | \- | L | O | O | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Hitachi HSP | \- | N | \- | \- | N | \- | \- | \- | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| HPE 3PAR | L | K | L | \- | L | L | \- | K | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Huawei | M | K | L | L | L | L | M | M | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| INFINIDAT | \- | Q | \- | \- | Q | Q | \- | Q | Q | Q | Q | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Infortrend | \- | T | \- | \- | \- | \- | \- | \- | \- | \- | T | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| LVM | \- | M | \- | \- | \- | M | \- | K | O | O | P | P | \- | Y | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Macrosan | \- | Z | \- | \- | \- | Z | \- | \- | \- | \- | Z | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Quobyte | \- | K | \- | \- | \- | L | \- | M | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Windows SMB | L | L | \- | \- | \- | L | \- | \- | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| IBM GPFS | \- | K | \- | \- | \- | L | \- | L | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Oracle ZFSSA | \- | K | \- | \- | \- | L | \- | K | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| CephFS | \- | M | \- | \- | \- | M | \- | \- | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Tegile | \- | M | M | M | M | \- | \- | M | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| NexentaStor4 | \- | N | N | N | N | N | \- | N | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| NexentaStor5 | \- | N | \- | N | N | N | \- | N | T | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| MapRFS | \- | N | \- | \- | \- | N | \- | O | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| QNAP | \- | O | Q | Q | O | Q | \- | O | \- | \- | P | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| INSPUR AS13000 | \- | R | \- | \- | R | \- | \- | R | \- | \- | R | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| INSPUR InStorage | \- | T | \- | \- | \- | T | \- | \- | \- | \- | T | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ +| Pure Storage FlashBlade | \- | X | \- | \- | X | \- | \- | \- | X | \- | X | \- | \- | \- | ++----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+--------------------------+ .. note:: diff --git a/manila/api/openstack/api_version_request.py b/manila/api/openstack/api_version_request.py index de9b034691..7507624b25 100644 --- a/manila/api/openstack/api_version_request.py +++ b/manila/api/openstack/api_version_request.py @@ -201,13 +201,14 @@ REST_API_VERSION_HISTORY = """ * 2.81 - Added API methods, endpoint /resource-locks. * 2.82 - Added lock and restriction to share access rules. * 2.83 - Added 'disabled_reason' field to services. + * 2.84 - Added mount_point_name to shares. """ # The minimum and maximum versions of the API supported # The default api version request is defined to be the # minimum version of the API supported. _MIN_API_VERSION = "2.0" -_MAX_API_VERSION = "2.83" +_MAX_API_VERSION = "2.84" DEFAULT_API_VERSION = _MIN_API_VERSION diff --git a/manila/api/openstack/rest_api_version_history.rst b/manila/api/openstack/rest_api_version_history.rst index 5484bb0a14..777ac37b2b 100644 --- a/manila/api/openstack/rest_api_version_history.rst +++ b/manila/api/openstack/rest_api_version_history.rst @@ -450,3 +450,7 @@ user documentation. The ``disabled_reason`` field was added to the service to mark the reason why the user disabled the service. ``disabled`` field will be replaced by ``status`` field. + +2.84 +---- + Added optional ``mount_point_name`` field to share. diff --git a/manila/api/v1/shares.py b/manila/api/v1/shares.py index 441849b01e..0394770f66 100644 --- a/manila/api/v1/shares.py +++ b/manila/api/v1/shares.py @@ -25,6 +25,7 @@ import webob from webob import exc from manila.api import common +from manila.api.openstack import api_version_request as api_version from manila.api.openstack import wsgi from manila.api.views import share_accesses as share_access_views from manila.api.views import shares as share_views @@ -445,6 +446,9 @@ class ShareMixin(object): kwargs['scheduler_hints'] = scheduler_hints + if req.api_version_request >= api_version.APIVersionRequest("2.84"): + kwargs['mount_point_name'] = share.pop('mount_point_name', None) + new_share = self.share_api.create(context, share_proto, size, diff --git a/manila/common/constants.py b/manila/common/constants.py index be3d27266b..45a750a091 100644 --- a/manila/common/constants.py +++ b/manila/common/constants.py @@ -300,10 +300,12 @@ class ExtraSpecs(object): CREATE_SHARE_FROM_SNAPSHOT_SUPPORT = "create_share_from_snapshot_support" REVERT_TO_SNAPSHOT_SUPPORT = "revert_to_snapshot_support" MOUNT_SNAPSHOT_SUPPORT = "mount_snapshot_support" + MOUNT_POINT_NAME_SUPPORT = "mount_point_name_support" AVAILABILITY_ZONES = "availability_zones" PROVISIONING_MAX_SHARE_SIZE = "provisioning:max_share_size" PROVISIONING_MIN_SHARE_SIZE = "provisioning:min_share_size" PROVISIONING_MAX_SHARE_EXTEND_SIZE = "provisioning:max_share_extend_size" + PROVISIONING_MOUNT_POINT_PREFIX = "provisioning:mount_point_prefix" # Extra specs containers REQUIRED = ( @@ -316,10 +318,12 @@ class ExtraSpecs(object): REVERT_TO_SNAPSHOT_SUPPORT, REPLICATION_TYPE_SPEC, MOUNT_SNAPSHOT_SUPPORT, + MOUNT_POINT_NAME_SUPPORT, AVAILABILITY_ZONES, PROVISIONING_MAX_SHARE_SIZE, PROVISIONING_MIN_SHARE_SIZE, - PROVISIONING_MAX_SHARE_EXTEND_SIZE + PROVISIONING_MAX_SHARE_EXTEND_SIZE, + PROVISIONING_MOUNT_POINT_PREFIX, ) # NOTE(cknight): Some extra specs are necessary parts of the Manila API and diff --git a/manila/db/migrations/alembic/versions/40d1f2374e89_add_mount_point_name_to_share_instances.py b/manila/db/migrations/alembic/versions/40d1f2374e89_add_mount_point_name_to_share_instances.py new file mode 100644 index 0000000000..177a75ebc6 --- /dev/null +++ b/manila/db/migrations/alembic/versions/40d1f2374e89_add_mount_point_name_to_share_instances.py @@ -0,0 +1,47 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""add mount_point_name to share_instances + +Revision ID: 6e32091979e0 +Revises: 99d328f0a3d2 +Create Date: 2024-01-26 22:08:22.412974 +""" +# revision identifiers, used by Alembic. +revision = '6e32091979e0' +down_revision = '99d328f0a3d2' + +from alembic import op +from oslo_log import log +import sqlalchemy as sa + +LOG = log.getLogger(__name__) +share_instances_table_name = 'share_instances' +column_name = "mount_point_name" + + +def upgrade(): + try: + op.add_column(share_instances_table_name, sa.Column(column_name, + sa.String(255), + nullable=True)) + except Exception: + LOG.error("Column mount_point_name not created!") + raise + + +def downgrade(): + try: + op.drop_column(share_instances_table_name, column_name) + except Exception: + LOG.error("Column mount_point_name not dropped!") + raise diff --git a/manila/db/sqlalchemy/api.py b/manila/db/sqlalchemy/api.py index 9466651758..c3f1c1ef3f 100644 --- a/manila/db/sqlalchemy/api.py +++ b/manila/db/sqlalchemy/api.py @@ -1619,6 +1619,7 @@ def _extract_share_instance_values(values): 'status', 'host', 'scheduled_at', 'launched_at', 'terminated_at', 'share_server_id', 'share_network_id', 'availability_zone_id', 'replica_state', 'share_type_id', 'share_type', 'access_rules_status', + 'mount_point_name', ] share_instance_values, share_values = ( _extract_subdict_by_fields(values, share_instance_model_fields) diff --git a/manila/db/sqlalchemy/models.py b/manila/db/sqlalchemy/models.py index 96ef205c85..4e3bf66d2c 100644 --- a/manila/db/sqlalchemy/models.py +++ b/manila/db/sqlalchemy/models.py @@ -371,7 +371,7 @@ class ShareInstance(BASE, ManilaBase): host = Column(String(255)) status = Column(String(255)) progress = Column(String(32)) - + mount_point_name = Column(String(255)) ACCESS_STATUS_PRIORITIES = { constants.STATUS_ACTIVE: 0, constants.SHARE_INSTANCE_RULES_SYNCING: 1, diff --git a/manila/scheduler/filters/capabilities.py b/manila/scheduler/filters/capabilities.py index 5b373dc93d..8ae470f6d0 100644 --- a/manila/scheduler/filters/capabilities.py +++ b/manila/scheduler/filters/capabilities.py @@ -39,6 +39,7 @@ class CapabilitiesFilter(base_host.BaseHostFilter): def host_passes(self, host_state, filter_properties): """Return a list of hosts that can create resource_type.""" resource_type = filter_properties.get('resource_type') + if not self._satisfies_extra_specs(host_state.capabilities, resource_type): LOG.debug("%(host_state)s fails resource_type extra_specs " diff --git a/manila/scheduler/host_manager.py b/manila/scheduler/host_manager.py index f805242e1c..4162f0fea4 100644 --- a/manila/scheduler/host_manager.py +++ b/manila/scheduler/host_manager.py @@ -162,6 +162,7 @@ class HostState(object): self.security_service_update_support = False self.network_allocation_update_support = False self.share_server_multiple_subnet_support = False + self.mount_point_name_support = False # PoolState for all pools self.pools = {} diff --git a/manila/scheduler/utils.py b/manila/scheduler/utils.py index 989be21850..e2fcf54982 100644 --- a/manila/scheduler/utils.py +++ b/manila/scheduler/utils.py @@ -66,7 +66,9 @@ def generate_stats(host_state, properties): 'network_allocation_update_support': ( host_state.network_allocation_update_support), 'share_server_multiple_subnet_support': ( - host_state.share_server_multiple_subnet_support) + host_state.share_server_multiple_subnet_support), + 'mount_point_name_support': ( + host_state.mount_point_name_support) } host_caps = host_state.capabilities diff --git a/manila/share/api.py b/manila/share/api.py index 2b70fb666f..bce1fbeb56 100644 --- a/manila/share/api.py +++ b/manila/share/api.py @@ -21,6 +21,7 @@ Handles all requests relating to shares. """ import functools import json +import re from oslo_config import cfg from oslo_log import log @@ -270,7 +271,8 @@ class API(base.Base): share_network_id=None, share_type=None, is_public=False, share_group_id=None, share_group_snapshot_member=None, availability_zones=None, scheduler_hints=None, - az_request_multiple_subnet_support_map=None): + az_request_multiple_subnet_support_map=None, + mount_point_name=None): """Create new share.""" api_common.check_metadata_properties(metadata) @@ -348,6 +350,18 @@ class API(base.Base): deltas = {'shares': 1, 'gigabytes': size} share_type_attributes = self.get_share_attributes_from_share_type( share_type) + + mount_point_name_support = share_type_attributes.get( + constants.ExtraSpecs.MOUNT_POINT_NAME_SUPPORT, None) + if mount_point_name is not None: + if not mount_point_name_support: + msg = _("Setting a mount point name is not supported" + " by the share type used: %s." % share_type_id) + raise exception.InvalidInput(reason=msg) + mount_point_name = self._prefix_mount_point_name( + share_type, context, mount_point_name + ) + share_type_supports_replication = share_type_attributes.get( 'replication_type', None) if share_type_supports_replication: @@ -482,7 +496,8 @@ class API(base.Base): share_type_id=share_type_id, availability_zones=availability_zones, snapshot_host=snapshot_host, scheduler_hints=scheduler_hints, az_request_multiple_subnet_support_map=( - az_request_multiple_subnet_support_map)) + az_request_multiple_subnet_support_map), + mount_point_name=mount_point_name) # Retrieve the share with instance details share = self.db.share_get(context, share['id']) @@ -505,6 +520,9 @@ class API(base.Base): constants.ExtraSpecs.REVERT_TO_SNAPSHOT_SUPPORT) mount_snapshot_support_key = ( constants.ExtraSpecs.MOUNT_SNAPSHOT_SUPPORT) + mount_point_name_support_key = ( + constants.ExtraSpecs.MOUNT_POINT_NAME_SUPPORT + ) snapshot_support_default = inferred_map.get(snapshot_support_key) create_share_from_snapshot_support_default = inferred_map.get( @@ -513,6 +531,7 @@ class API(base.Base): revert_to_snapshot_key) mount_snapshot_support_default = inferred_map.get( constants.ExtraSpecs.MOUNT_SNAPSHOT_SUPPORT) + mount_point_name_support_default = False if share_type: snapshot_support = share_types.parse_boolean_extra_spec( @@ -536,6 +555,11 @@ class API(base.Base): 'extra_specs', {}).get( mount_snapshot_support_key, mount_snapshot_support_default)) + mount_point_name_support = share_types.parse_boolean_extra_spec( + mount_point_name_support_key, share_type.get( + 'extra_specs', {}).get( + mount_point_name_support_key, + mount_point_name_support_default)) replication_type = share_type.get('extra_specs', {}).get( 'replication_type') else: @@ -544,6 +568,7 @@ class API(base.Base): create_share_from_snapshot_support_default) revert_to_snapshot_support = revert_to_snapshot_support_default mount_snapshot_support = mount_snapshot_support_default + mount_point_name_support = mount_point_name_support_default replication_type = None return { @@ -553,6 +578,7 @@ class API(base.Base): 'revert_to_snapshot_support': revert_to_snapshot_support, 'replication_type': replication_type, 'mount_snapshot_support': mount_snapshot_support, + 'mount_point_name_support': mount_point_name_support, } def create_instance(self, context, share, share_network_id=None, @@ -560,7 +586,8 @@ class API(base.Base): share_group=None, share_group_snapshot_member=None, share_type_id=None, availability_zones=None, snapshot_host=None, scheduler_hints=None, - az_request_multiple_subnet_support_map=None): + az_request_multiple_subnet_support_map=None, + mount_point_name=None): request_spec, share_instance = ( self.create_share_instance_and_get_request_spec( context, share, availability_zone=availability_zone, @@ -570,7 +597,8 @@ class API(base.Base): availability_zones=availability_zones, snapshot_host=snapshot_host, az_request_multiple_subnet_support_map=( - az_request_multiple_subnet_support_map))) + az_request_multiple_subnet_support_map), + mount_point_name=mount_point_name)) if share_group_snapshot_member: # Inherit properties from the share_group_snapshot_member @@ -613,7 +641,8 @@ class API(base.Base): share_group=None, host=None, share_network_id=None, share_type_id=None, cast_rules_to_readonly=False, availability_zones=None, snapshot_host=None, - az_request_multiple_subnet_support_map=None): + az_request_multiple_subnet_support_map=None, + mount_point_name=None): availability_zone_id = None if availability_zone: @@ -633,6 +662,7 @@ class API(base.Base): 'availability_zone_id': availability_zone_id, 'share_type_id': share_type_id, 'cast_rules_to_readonly': cast_rules_to_readonly, + 'mount_point_name': mount_point_name, } ) @@ -666,7 +696,7 @@ class API(base.Base): 'host': share_instance['host'], 'status': share_instance['status'], 'replica_state': share_instance['replica_state'], - 'share_type_id': share_instance['share_type_id'], + 'share_type_id': share_instance['share_type_id'] } share_type = None @@ -686,7 +716,7 @@ class API(base.Base): 'availability_zone_id': availability_zone_id, 'availability_zones': availability_zones, 'az_request_multiple_subnet_support_map': ( - az_request_multiple_subnet_support_map), + az_request_multiple_subnet_support_map) } return request_spec, share_instance @@ -1063,6 +1093,11 @@ class API(base.Base): share_type.get('extra_specs', {}).get( 'mount_snapshot_support') ), + 'mount_point_name_support': kwargs.get( + 'mount_point_name_support', + share_type.get('extra_specs', {}).get( + 'mount_point_name_support') + ), 'share_proto': kwargs.get('share_proto', share.get('share_proto')), 'share_type_id': share_type['id'], 'is_public': kwargs.get('is_public', share.get('is_public')), @@ -1094,6 +1129,25 @@ class API(base.Base): } return request_spec + def _prefix_mount_point_name(self, share_type, context, + mount_point_name=None): + prefix = share_type.get('extra_specs').get( + constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX) + prefix = prefix or context.project_id + prefix = prefix.format(context.to_dict()) + mount_point_name = f"{prefix}_{mount_point_name}" + + if mount_point_name and ( + not re.match( + r'^[a-zA-Z0-9_]*$', mount_point_name) + or len(mount_point_name) > 255 + ): + msg = _("Invalid mount_point_name: %s") + LOG.error(msg, mount_point_name) + raise exception.InvalidInput(msg % mount_point_name) + + return mount_point_name + @prevent_locked_action_on_share('delete') def unmanage(self, context, share): policy.check_policy(context, 'share', 'unmanage') diff --git a/manila/share/driver.py b/manila/share/driver.py index c8ece0235a..91574a8828 100644 --- a/manila/share/driver.py +++ b/manila/share/driver.py @@ -1356,6 +1356,7 @@ class ShareDriver(object): network_allocation_update_support=( self.network_allocation_update_support), share_server_multiple_subnet_support=False, + mount_point_name_support=False, ) if isinstance(data, dict): common.update(data) diff --git a/manila/share/drivers/container/driver.py b/manila/share/drivers/container/driver.py index efdfa963e6..dc9ebd4308 100644 --- a/manila/share/drivers/container/driver.py +++ b/manila/share/drivers/container/driver.py @@ -137,6 +137,7 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): 'pools': self.storage.get_share_server_pools(), 'security_service_update_support': True, 'share_server_multiple_subnet_support': True, + 'mount_point_name_support': False, } super(ContainerShareDriver, self)._update_share_stats(data) diff --git a/manila/share/drivers/lvm.py b/manila/share/drivers/lvm.py index 27bc602436..73bcf338bc 100644 --- a/manila/share/drivers/lvm.py +++ b/manila/share/drivers/lvm.py @@ -116,6 +116,9 @@ class LVMMixin(driver.ExecuteMixin): except processutils.ProcessExecutionError: raise + def _get_mount_point_name(self, share): + return share.get('mount_point_name') or share.get('name') + def _extend_container(self, share, device_name, size): privsep_common.execute_with_retries( privsep_lvm.lvextend, [device_name, size], @@ -267,14 +270,16 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver): 'reserved_percentage': 0, 'reserved_snapshot_percentage': 0, 'reserved_share_extend_percentage': 0, + 'mount_point_name_support': True, }, ] def create_share(self, context, share, share_server=None): self._allocate_container(share) # create file system device_name = self._get_local_path(share) + share_export_location = self._get_mount_point_name(share) location = self._get_helper(share).create_exports( - self.share_server, share['name']) + self.share_server, share_export_location) self._mount_device(share, device_name) return location @@ -287,8 +292,9 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver): self._set_random_uuid_to_device(share) self._copy_volume( snapshot_device_name, share_device_name, share['size']) + share_export_location = self._get_mount_point_name(share) location = self._get_helper(share).create_exports( - self.share_server, share['name']) + self.share_server, share_export_location) self._mount_device(share, share_device_name) return location @@ -342,14 +348,19 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver): """Ensure that storage are mounted and exported.""" device_name = self._get_local_path(share) self._mount_device(share, device_name) + share_export_location = self._get_mount_point_name(share) return self._get_helper(share).create_exports( - self.share_server, share['name'], recreate=True) + self.share_server, + share_export_location, + recreate=True + ) def _delete_share(self, ctx, share): + share_export_location = self._get_mount_point_name(share) """Delete a share.""" try: self._get_helper(share).remove_exports( - self.share_server, share['name']) + self.share_server, share_export_location) except exception.ProcessExecutionError: LOG.warning("Can't remove share %r", share['id']) except exception.InvalidShare as exc: @@ -379,8 +390,10 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver): removed. access_rules doesn't contain these rules. :param share_server: None or Share server model """ + share_export_location = self._get_mount_point_name(share) self._get_helper(share).update_access(self.share_server, - share['name'], access_rules, + share_export_location, + access_rules, add_rules=add_rules, delete_rules=delete_rules) @@ -434,11 +447,15 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver): def revert_to_snapshot(self, context, snapshot, share_access_rules, snapshot_access_rules, share_server=None): share = snapshot['share'] + snapshot_export_location = self._get_mount_point_name(snapshot) + share_export_location = self._get_mount_point_name(share) # Temporarily remove all access rules self._get_helper(share).update_access(self.share_server, - snapshot['name'], [], [], []) + snapshot_export_location, + [], [], []) self._get_helper(share).update_access(self.share_server, - share['name'], [], [], []) + share_export_location, + [], [], []) # Unmount the snapshot filesystem self._unmount_device(snapshot) # Unmount the share filesystem @@ -459,15 +476,17 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver): # Also remount the snapshot device_name = self._get_local_path(snapshot) self._mount_device(snapshot, device_name) + share_export_location = self._get_mount_point_name(share) + snapshot_export_location = self._get_mount_point_name(share) # Lastly we add all the access rules back self._get_helper(share).update_access(self.share_server, - share['name'], + share_export_location, share_access_rules, [], []) snapshot_access_rules, __, __ = share_utils.change_rules_to_readonly( snapshot_access_rules, [], []) self._get_helper(share).update_access(self.share_server, - snapshot['name'], + snapshot_export_location, snapshot_access_rules, [], []) diff --git a/manila/share/share_types.py b/manila/share/share_types.py index 29a8904933..891ad93afe 100644 --- a/manila/share/share_types.py +++ b/manila/share/share_types.py @@ -330,6 +330,10 @@ def is_valid_csv(extra_spec_value): return all([v.strip() for v in values]) +def is_valid_string(v): + return isinstance(v, str) and len(v) in range(1, 256) + + def sanitize_csv(csv_string): return ','.join(value.strip() for value in csv_string.split(',') if (csv_string and value)) @@ -356,8 +360,12 @@ def is_valid_optional_extra_spec(key, value): return value in constants.ExtraSpecs.REPLICATION_TYPES elif key == constants.ExtraSpecs.MOUNT_SNAPSHOT_SUPPORT: return parse_boolean_extra_spec(key, value) is not None + elif key == constants.ExtraSpecs.MOUNT_POINT_NAME_SUPPORT: + return parse_boolean_extra_spec(key, value) is not None elif key == constants.ExtraSpecs.AVAILABILITY_ZONES: return is_valid_csv(value) + elif key == constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX: + return is_valid_string(value) elif key in [constants.ExtraSpecs.PROVISIONING_MAX_SHARE_SIZE, constants.ExtraSpecs.PROVISIONING_MIN_SHARE_SIZE, constants.ExtraSpecs.PROVISIONING_MAX_SHARE_EXTEND_SIZE]: diff --git a/manila/tests/api/v1/test_shares.py b/manila/tests/api/v1/test_shares.py index 339fd3401d..aefdcf2bdb 100644 --- a/manila/tests/api/v1/test_shares.py +++ b/manila/tests/api/v1/test_shares.py @@ -261,6 +261,37 @@ class ShareAPITest(test.TestCase): self.assertEqual("fakenetid", create_mock.call_args[1]['share_network_id']) + def test_share_create_mount_point_name(self): + shr = { + "size": 100, + "name": "Share Test Name", + "description": "Share Test Desc", + "share_proto": "fakeproto", + "mount_point_name": "fake_mp" + } + fake_network = {'id': 'fakenetid'} + create_mock = mock.Mock(return_value=stubs.stub_share('1', + display_name=shr['name'], + display_description=shr['description'], + size=shr['size'], + share_proto=shr['share_proto'].upper(), + mount_point_name=shr['mount_point_name'])) + self.mock_object(share_api.API, 'create', create_mock) + self.mock_object(share_api.API, 'get_share_network', mock.Mock( + return_value=fake_network)) + self.mock_object(common, 'check_share_network_is_active', + mock.Mock(return_value=True)) + self.mock_object( + db, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value={'id': 'fakesubnetid'})) + + body = {"share": copy.deepcopy(shr)} + req = fakes.HTTPRequest.blank('/v1/fake/shares') + self.controller.create(req, body) + + self.mock_policy_check.assert_called_once_with( + req.environ['manila.context'], self.resource_name, 'create') + def test_share_create_with_share_net_not_active(self): shr = { "size": 100, @@ -459,6 +490,53 @@ class ShareAPITest(test.TestCase): self.mock_policy_check.assert_called_once_with( req.environ['manila.context'], self.resource_name, 'create') + def test_share_create_from_mount_point_name(self): + parent_share_net = 444 + shr = { + "size": 100, + "name": "Share Test Name", + "description": "Share Test Desc", + "share_proto": "fakeproto", + "availability_zone": "zone1:host1", + "snapshot_id": 333, + "share_network_id": parent_share_net, + "mount_point_name": "fake_mp" + } + fake_share_net = {'id': parent_share_net} + share_net_subnets = [db_utils.create_share_network_subnet( + id='fake_subnet_id', share_network_id=fake_share_net['id'])] + create_mock = mock.Mock(return_value=stubs.stub_share('1', + display_name=shr['name'], + display_description=shr['description'], + size=shr['size'], + share_proto=shr['share_proto'].upper(), + snapshot_id=shr['snapshot_id'], + mount_point_name=shr['mount_point_name'], + instance=dict( + availability_zone=shr['availability_zone'], + share_network_id=shr['share_network_id'], + ))) + self.mock_object(share_api.API, 'create', create_mock) + self.mock_object(share_api.API, 'get_snapshot', + stubs.stub_snapshot_get) + self.mock_object(common, 'check_share_network_is_active', + mock.Mock(return_value=True)) + parent_share = stubs.stub_share( + '1', instance={'share_network_id': parent_share_net}, + create_share_from_snapshot_support=True) + self.mock_object(share_api.API, 'get', mock.Mock( + return_value=parent_share)) + self.mock_object(share_api.API, 'get_share_network', mock.Mock( + return_value=fake_share_net)) + self.mock_object( + db, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=share_net_subnets)) + + body = {"share": copy.deepcopy(shr)} + req = fakes.HTTPRequest.blank('/v1/fake/shares', version='2.84') + res_dict = self.controller.create(req, body) + self.assertEqual(res_dict['share']['project_id'], 'fakeproject') + @ddt.data( {'name': 'name1', 'description': 'x' * 256}, {'name': 'x' * 256, 'description': 'description1'}, diff --git a/manila/tests/api/v2/test_share_transfer.py b/manila/tests/api/v2/test_share_transfer.py index c5e9c97e6a..c5ac8f3022 100644 --- a/manila/tests/api/v2/test_share_transfer.py +++ b/manila/tests/api/v2/test_share_transfer.py @@ -61,17 +61,40 @@ class ShareTransferAPITestCase(test.TestCase): size=1, project_id='fake_project_id', user_id='fake_user_id', - share_network_id=None): + share_network_id=None, + mount_point_name=None): """Create a share object.""" share_type = db_utils.create_share_type() - share = db_utils.create_share(display_name=display_name, - display_description=display_description, - status=status, size=size, - project_id=project_id, - user_id=user_id, - share_type_id=share_type['id'], - share_network_id=share_network_id - ) + if mount_point_name: + instance_list = [ + db_utils.create_share_instance( + status=status, + share_id='fake_id', + mount_point_name=mount_point_name + ) + ] + share = db_utils.create_share( + display_name=display_name, + display_description=display_description, + status=status, size=size, + project_id=project_id, + user_id=user_id, + share_type_id=share_type['id'], + share_network_id=share_network_id, + instances=instance_list + ) + else: + share = db_utils.create_share( + display_name=display_name, + display_description=display_description, + status=status, + size=size, + project_id=project_id, + user_id=user_id, + share_type_id=share_type['id'], + share_network_id=share_network_id, + mount_point_name=mount_point_name + ) share_id = share['id'] return share_id @@ -232,6 +255,35 @@ class ShareTransferAPITestCase(test.TestCase): self.assertRaises(webob.exc.HTTPBadRequest, self.v2_controller.create, req, body) + def test_create_transfer_with_invalid_mount_point_name(self): + share_id = self._create_share( + project_id='fake_pid', + mount_point_name='fake_pid_mount_point_name') + body = {"transfer": {"name": "transfer1", + "share_id": share_id}} + db.share_update(context.get_admin_context(), + share_id, {'status': 'error'}) + + path = '/v2/fake_project_id/share-transfers' + req = fakes.HTTPRequest.blank(path, version=self.microversion) + req.environ['manila.context'] = self.ctxt + req.method = 'POST' + req.headers['Content-Type'] = 'application/json' + req.body = jsonutils.dumps(body).encode("utf-8") + self.assertRaises(webob.exc.HTTPBadRequest, + self.v2_controller.create, req, body) + + def test_create_transfer_with_project_id_prefix_mount_point_name(self): + share_id = self._create_share(project_id='fake', + mount_point_name='fake_mp') + '''self.share_transfer_api.create(context.get_admin_context(), + share_id, + 'test_missing_share_type')''' + self.assertRaises(exception.Invalid, + self.share_transfer_api.create, + context.get_admin_context(), share_id, + 'test_missing_share_type') + def test_create_transfer_share_with_network_id(self): share_id = self._create_share(share_network_id='fake_id') body = {"transfer": {"name": "transfer1", diff --git a/manila/tests/api/v2/test_share_types.py b/manila/tests/api/v2/test_share_types.py index 6f49f22faa..d50aa3f9e6 100644 --- a/manila/tests/api/v2/test_share_types.py +++ b/manila/tests/api/v2/test_share_types.py @@ -393,6 +393,7 @@ class ShareTypesAPITest(test.TestCase): constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT: False, constants.ExtraSpecs.REVERT_TO_SNAPSHOT_SUPPORT: True, constants.ExtraSpecs.MOUNT_SNAPSHOT_SUPPORT: True, + constants.ExtraSpecs.MOUNT_POINT_NAME_SUPPORT: True, } now = timeutils.utcnow().isoformat() diff --git a/manila/tests/db_utils.py b/manila/tests/db_utils.py index 2e4e43ee9b..4566d5ffe4 100644 --- a/manila/tests/db_utils.py +++ b/manila/tests/db_utils.py @@ -94,7 +94,8 @@ def create_share(**kwargs): 'availability_zone': 'fake_availability_zone', 'status': constants.STATUS_CREATING, 'host': 'fake_host', - 'is_soft_deleted': False + 'is_soft_deleted': False, + 'mount_point_name': 'fake_mp', } return _create_db_row(db.share_create, share, kwargs) @@ -112,7 +113,8 @@ def create_share_without_instance(**kwargs): 'availability_zone': 'fake_availability_zone', 'status': constants.STATUS_CREATING, 'host': 'fake_host', - 'is_soft_deleted': False + 'is_soft_deleted': False, + 'mount_point_name': None, } share.update(copy.deepcopy(kwargs)) return db.share_create(context.get_admin_context(), share, False) diff --git a/manila/tests/fake_share.py b/manila/tests/fake_share.py index 78d5c52396..1b1d93ab44 100644 --- a/manila/tests/fake_share.py +++ b/manila/tests/fake_share.py @@ -64,6 +64,7 @@ def fake_share_instance(base_share=None, **kwargs): 'share_network_id': 'fakesharenetworkid', 'share_server_id': 'fakeshareserverid', 'share_type_id': '1', + 'mount_point_name': None, } for attr in models.ShareInstance._proxified_properties: @@ -82,7 +83,8 @@ def fake_share_type(**kwargs): 'is_public': False, 'extra_specs': { 'driver_handles_share_servers': 'False', - } + }, + 'mount_point_name_support': False } extra_specs = kwargs.pop('extra_specs', {}) diff --git a/manila/tests/scheduler/fakes.py b/manila/tests/scheduler/fakes.py index 2e1f7a2687..e0fbea22a0 100644 --- a/manila/tests/scheduler/fakes.py +++ b/manila/tests/scheduler/fakes.py @@ -55,7 +55,8 @@ SERVICE_STATES_NO_POOLS = { create_share_from_snapshot_support=False, revert_to_snapshot_support=True, mount_snapshot_support=True, - driver_handles_share_servers=False), + driver_handles_share_servers=False, + mount_point_name_support=False), 'host2@back1': dict(share_backend_name='BBB', total_capacity_gb=256, free_capacity_gb=100, timestamp=None, reserved_percentage=0, @@ -68,7 +69,8 @@ SERVICE_STATES_NO_POOLS = { create_share_from_snapshot_support=True, revert_to_snapshot_support=False, mount_snapshot_support=False, - driver_handles_share_servers=False), + driver_handles_share_servers=False, + mount_point_name_support=False), 'host2@back2': dict(share_backend_name='CCC', total_capacity_gb=10000, free_capacity_gb=700, timestamp=None, reserved_percentage=0, @@ -81,7 +83,8 @@ SERVICE_STATES_NO_POOLS = { create_share_from_snapshot_support=True, revert_to_snapshot_support=False, mount_snapshot_support=False, - driver_handles_share_servers=False), + driver_handles_share_servers=False, + mount_point_name_support=False), } SHARE_SERVICES_WITH_POOLS = [ @@ -124,7 +127,9 @@ SHARE_SERVICE_STATES_WITH_POOLS = { reserved_share_extend_percentage=0, provisioned_capacity_gb=10, max_over_subscription_ratio=1.0, - thin_provisioning=False)]), + thin_provisioning=False, + mount_point_name_support=False, + )]), 'host2@BBB': dict(share_backend_name='BBB', timestamp=None, reserved_percentage=0, reserved_snapshot_percentage=0, @@ -142,7 +147,9 @@ SHARE_SERVICE_STATES_WITH_POOLS = { reserved_share_extend_percentage=0, provisioned_capacity_gb=60, max_over_subscription_ratio=2.0, - thin_provisioning=True)]), + thin_provisioning=True, + mount_point_name_support=False, + )]), 'host3@CCC': dict(share_backend_name='CCC', timestamp=None, reserved_percentage=0, reserved_snapshot_percentage=0, @@ -160,7 +167,9 @@ SHARE_SERVICE_STATES_WITH_POOLS = { reserved_share_extend_percentage=0, provisioned_capacity_gb=100, max_over_subscription_ratio=20.0, - thin_provisioning=True)]), + thin_provisioning=True, + mount_point_name_support=False, + )]), 'host4@DDD': dict(share_backend_name='DDD', timestamp=None, reserved_percentage=0, reserved_snapshot_percentage=0, @@ -178,7 +187,9 @@ SHARE_SERVICE_STATES_WITH_POOLS = { reserved_share_extend_percentage=0, provisioned_capacity_gb=800, max_over_subscription_ratio=2.0, - thin_provisioning=True), + thin_provisioning=True, + mount_point_name_support=False, + ), dict(pool_name='pool4b', total_capacity_gb=542, free_capacity_gb=442, @@ -187,7 +198,9 @@ SHARE_SERVICE_STATES_WITH_POOLS = { reserved_share_extend_percentage=0, provisioned_capacity_gb=2000, max_over_subscription_ratio=10.0, - thin_provisioning=True)]), + thin_provisioning=True, + mount_point_name_support=False, + )]), 'host5@EEE': dict(share_backend_name='EEE', timestamp=None, reserved_percentage=0, reserved_snapshot_percentage=0, @@ -205,7 +218,9 @@ SHARE_SERVICE_STATES_WITH_POOLS = { reserved_share_extend_percentage=0, provisioned_capacity_gb=100, max_over_subscription_ratio=1.0, - thin_provisioning=False), + thin_provisioning=False, + mount_point_name_support=False, + ), dict(pool_name='pool5b', total_capacity_gb=552, free_capacity_gb=452, @@ -214,7 +229,9 @@ SHARE_SERVICE_STATES_WITH_POOLS = { reserved_share_extend_percentage=0, provisioned_capacity_gb=100, max_over_subscription_ratio=1.0, - thin_provisioning=False)]), + thin_provisioning=False, + mount_point_name_support=False, + )]), 'host6@FFF': dict(share_backend_name='FFF', timestamp=None, reserved_percentage=0, reserved_snapshot_percentage=0, @@ -232,7 +249,9 @@ SHARE_SERVICE_STATES_WITH_POOLS = { reserved_share_extend_percentage=0, provisioned_capacity_gb=100, max_over_subscription_ratio=1.0, - thin_provisioning=False), + thin_provisioning=False, + mount_point_name_support=False, + ), dict(pool_name='pool6b', total_capacity_gb='unknown', free_capacity_gb='unknown', @@ -241,7 +260,9 @@ SHARE_SERVICE_STATES_WITH_POOLS = { reserved_share_extend_percentage=0, provisioned_capacity_gb=100, max_over_subscription_ratio=1.0, - thin_provisioning=False)]), + thin_provisioning=False, + mount_point_name_support=False, + )]), } FAKE_ACTIVE_IQ_WEIGHER_LIST = [ diff --git a/manila/tests/scheduler/filters/test_capabilities.py b/manila/tests/scheduler/filters/test_capabilities.py index 21ebffd49e..b00299f231 100644 --- a/manila/tests/scheduler/filters/test_capabilities.py +++ b/manila/tests/scheduler/filters/test_capabilities.py @@ -45,6 +45,24 @@ class HostFiltersTestCase(test.TestCase): assertion = self.assertTrue if passes else self.assertFalse assertion(self.filter.host_passes(host, filter_properties)) + def test_mount_point_name_support_pass(self): + capabilities = {'mount_point_name_support': True} + service = {'disabled': False} + filter_properties = { + 'resource_type': { + 'request_spec': { + 'share_properties': { + 'mount_point_name': 'fake_mp', + } + } + } + } + host = fakes.FakeHostState('host1', + {'free_capacity_gb': 1024, + 'capabilities': capabilities, + 'service': service}) + self.assertTrue(self.filter.host_passes(host, filter_properties)) + def test_capability_filter_passes_extra_specs_simple(self): self._do_test_type_filter_extra_specs( ecaps={'opt1': '1', 'opt2': '2'}, diff --git a/manila/tests/scheduler/test_host_manager.py b/manila/tests/scheduler/test_host_manager.py index 6a4850c8a6..3970883a33 100644 --- a/manila/tests/scheduler/test_host_manager.py +++ b/manila/tests/scheduler/test_host_manager.py @@ -217,6 +217,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, { 'name': 'host2@back1#BBB', @@ -250,6 +251,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, { 'name': 'host2@back2#CCC', @@ -283,6 +285,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, ] @@ -338,6 +341,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, { 'name': 'host2@BBB#pool2', @@ -372,6 +376,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, { 'name': 'host3@CCC#pool3', @@ -406,6 +411,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, { 'name': 'host4@DDD#pool4a', @@ -440,6 +446,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, { 'name': 'host4@DDD#pool4b', @@ -474,6 +481,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, ] @@ -541,6 +549,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, { 'name': 'host2@back1#BBB', @@ -574,6 +583,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, ] @@ -635,6 +645,7 @@ class HostManagerTestCase(test.TestCase): 'security_service_update_support': False, 'network_allocation_update_support': False, 'share_server_multiple_subnet_support': False, + 'mount_point_name_support': False, }, }, ] diff --git a/manila/tests/share/drivers/container/test_driver.py b/manila/tests/share/drivers/container/test_driver.py index b162fc9a2f..e0f732b892 100644 --- a/manila/tests/share/drivers/container/test_driver.py +++ b/manila/tests/share/drivers/container/test_driver.py @@ -114,6 +114,8 @@ class ContainerShareDriverTestCase(test.TestCase): self.assertEqual('test-pool', self._driver._stats['pools']) self.assertTrue(self._driver._stats['ipv4_support']) self.assertFalse(self._driver._stats['ipv6_support']) + self.assertFalse(self._driver. + _stats['mount_point_name_support']) def test_create_share(self): diff --git a/manila/tests/share/drivers/dell_emc/test_driver.py b/manila/tests/share/drivers/dell_emc/test_driver.py index a10fc0caee..9a79ab5dde 100644 --- a/manila/tests/share/drivers/dell_emc/test_driver.py +++ b/manila/tests/share/drivers/dell_emc/test_driver.py @@ -166,6 +166,7 @@ class EMCShareFrameworkTestCase(test.TestCase): data['replication_domain'] = None data['filter_function'] = None data['goodness_function'] = None + data['mount_point_name_support'] = False data['snapshot_support'] = True data['create_share_from_snapshot_support'] = True data['ipv4_support'] = True diff --git a/manila/tests/share/drivers/dummy.py b/manila/tests/share/drivers/dummy.py index 94682c77cc..bec6094c00 100644 --- a/manila/tests/share/drivers/dummy.py +++ b/manila/tests/share/drivers/dummy.py @@ -181,6 +181,9 @@ class DummyDriver(driver.ShareDriver): )) def _get_share_name(self, share): + mount_point_name = share.get('mount_point_name') + if mount_point_name is not None: + return mount_point_name return "share_%(s_id)s_%(si_id)s" % { "s_id": share["share_id"].replace("-", "_"), "si_id": share["id"].replace("-", "_")} @@ -516,6 +519,7 @@ class DummyDriver(driver.ShareDriver): "consistent_snapshot_support": "pool", }, 'share_server_multiple_subnet_support': True, + 'mount_point_name_support': True, } if self.configuration.replication_domain: data["replication_type"] = "readable" diff --git a/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py b/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py index 07515c8505..d408a199a6 100644 --- a/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py +++ b/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py @@ -268,6 +268,7 @@ class GlusterfsNativeShareDriverTestCase(test.TestCase): 'replication_domain': None, 'filter_function': None, 'goodness_function': None, + 'mount_point_name_support': False, 'ipv4_support': True, 'ipv6_support': False, 'security_service_update_support': False, 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 018c5e491c..dbd22c6cd6 100644 --- a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py +++ b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py @@ -746,6 +746,7 @@ class HPE3ParDriverTestCase(test.TestCase): 'replication_domain': None, 'filter_function': None, 'goodness_function': None, + 'mount_point_name_support': False, 'ipv4_support': True, 'ipv6_support': False, 'max_share_server_size': -1, @@ -839,6 +840,7 @@ class HPE3ParDriverTestCase(test.TestCase): 'replication_domain': None, 'filter_function': None, 'goodness_function': None, + 'mount_point_name_support': False, 'ipv4_support': True, 'ipv6_support': False, } @@ -890,6 +892,7 @@ class HPE3ParDriverTestCase(test.TestCase): 'replication_domain': None, 'filter_function': None, 'goodness_function': None, + 'mount_point_name_support': False, 'ipv4_support': True, 'ipv6_support': False, } diff --git a/manila/tests/share/drivers/huawei/test_huawei_nas.py b/manila/tests/share/drivers/huawei/test_huawei_nas.py index 4b1673d05a..7b8bcc9112 100644 --- a/manila/tests/share/drivers/huawei/test_huawei_nas.py +++ b/manila/tests/share/drivers/huawei/test_huawei_nas.py @@ -2431,6 +2431,7 @@ class HuaweiShareDriverTestCase(test.TestCase): "replication_domain": None, "filter_function": None, "goodness_function": None, + 'mount_point_name_support': False, "pools": [], "share_group_stats": {"consistent_snapshot_support": None}, "ipv4_support": True, diff --git a/manila/tests/share/drivers/test_lvm.py b/manila/tests/share/drivers/test_lvm.py index 2501221518..e834762c75 100644 --- a/manila/tests/share/drivers/test_lvm.py +++ b/manila/tests/share/drivers/test_lvm.py @@ -186,6 +186,8 @@ class LVMShareDriverTestCase(test.TestCase): CONF.lvm_share_volume_group, 0, 0] self.mock_object(privsep_common, 'execute_with_retries') self.mock_object(filesystem, 'make_filesystem') + self.mock_object(self._driver, '_get_mount_point_name', + mock.Mock(return_value=self.share['name'])) ret = self._driver.create_share(self._context, self.share, self.share_server) @@ -222,6 +224,8 @@ class LVMShareDriverTestCase(test.TestCase): self.mock_object(filesystem, 'make_filesystem') self.mock_object(filesystem, 'e2fsck') self.mock_object(filesystem, 'tune2fs') + self.mock_object(self._driver, '_get_mount_point_name', + mock.Mock(return_value=self.share['name'])) self._driver.create_share_from_snapshot(self._context, self.share, @@ -256,6 +260,8 @@ class LVMShareDriverTestCase(test.TestCase): self._driver._mount_device = mock.Mock() self.mock_object(privsep_common, 'execute_with_retries') self.mock_object(filesystem, 'make_filesystem') + self.mock_object(self._driver, '_get_mount_point_name', + mock.Mock(return_value=self.share['name'])) ret = self._driver.create_share(self._context, share, self.share_server) @@ -402,6 +408,8 @@ class LVMShareDriverTestCase(test.TestCase): def test_ensure_share(self): device_name = '/dev/mapper/fakevg-fakename' + self.mock_object(self._driver, '_get_mount_point_name', + mock.Mock(return_value=self.share['name'])) with mock.patch.object(self._driver, '_mount_device', mock.Mock(return_value='fake_location')): @@ -413,6 +421,8 @@ class LVMShareDriverTestCase(test.TestCase): self.server, self.share['name'], recreate=True) def test_delete_share(self): + self.mock_object(self._driver, '_get_mount_point_name', + mock.Mock(return_value=self.share['name'])) mount_path = self._get_mount_path(self.share) self._helper_nfs.remove_export(mount_path, self.share['name']) self._driver._delete_share(self._context, self.share) @@ -437,6 +447,8 @@ class LVMShareDriverTestCase(test.TestCase): self.mock_object(self._driver, '_deallocate_container') self._driver._get_helper = mock.Mock( side_effect=exception.InvalidShare(reason='fake')) + self.mock_object(self._driver, '_get_mount_point_name', + mock.Mock(return_value=self.share['name'])) self._driver.delete_share(self._context, self.share, self.share_server) @@ -450,6 +462,8 @@ class LVMShareDriverTestCase(test.TestCase): self._helper_nfs, 'remove_export', mock.Mock(side_effect=exception.ProcessExecutionError)) + self.mock_object(self._driver, '_get_mount_point_name', + mock.Mock(return_value=self.share['name'])) self._driver._delete_share(self._context, self.share) self._helper_nfs.remove_exports.assert_called_once_with( @@ -464,6 +478,8 @@ class LVMShareDriverTestCase(test.TestCase): '2.2.2.2', access_level), ] delete_rules = [test_generic.get_fake_access_rule( '3.3.3.3', access_level), ] + self.mock_object(self._driver, '_get_mount_point_name', + mock.Mock(return_value=self.share['name'])) self._driver.update_access(self._context, self.share, access_rules, add_rules=add_rules, delete_rules=delete_rules, @@ -609,6 +625,7 @@ class LVMShareDriverTestCase(test.TestCase): 'reserved_percentage': 0, 'reserved_snapshot_percentage': 0, 'reserved_share_extend_percentage': 0, + 'mount_point_name_support': True, }, ] out, err = "VSize 33g VFree 22g", None self.mock_object( @@ -669,6 +686,8 @@ class LVMShareDriverTestCase(test.TestCase): mock_get_local_path = self.mock_object( self._driver, '_get_local_path', mock.Mock(side_effect=[share_local_path, snapshot_local_path])) + self.mock_object(self._driver, '_get_mount_point_name', + mock.Mock(return_value=self.snapshot['name'])) snapshot_parent_share = self.snapshot['share'] self._driver.revert_to_snapshot(self._context, self.snapshot, @@ -784,3 +803,28 @@ class LVMShareDriverTestCase(test.TestCase): {'export_ips': ','.join(self.server['public_addresses']), 'db_version': mock.ANY}, backend_info) + + def test_get_mount_point_name_with_mount_point_name(self): + share = {'mount_point_name': 'fake_mp_name', 'name': 'fakename'} + result = self._driver._get_mount_point_name(share) + self.assertEqual(result, 'fake_mp_name') + + def test_get_mount_point_name_without_mount_point_name(self): + share = {'name': 'fakename'} + result = self._driver._get_mount_point_name(share) + self.assertEqual(result, 'fakename') + + def test_get_mount_point_name_with_empty_mount_point_name(self): + share = {'mount_point_name': '', 'name': 'fakename'} + result = self._driver._get_mount_point_name(share) + self.assertEqual(result, 'fakename') + + def test_get_mount_point_name_with_none_mount_point_name(self): + share = {'mount_point_name': None, 'name': 'fakename'} + result = self._driver._get_mount_point_name(share) + self.assertEqual(result, 'fakename') + + def test_get_mount_point_name_without_name(self): + share = {'mount_point_name': 'fake_mp_name'} + result = self._driver._get_mount_point_name(share) + self.assertEqual(result, 'fake_mp_name') diff --git a/manila/tests/share/drivers/veritas/test_veritas_isa.py b/manila/tests/share/drivers/veritas/test_veritas_isa.py index 4b4134874c..08b0183c84 100644 --- a/manila/tests/share/drivers/veritas/test_veritas_isa.py +++ b/manila/tests/share/drivers/veritas/test_veritas_isa.py @@ -438,6 +438,7 @@ class ACCESSShareDriverTestCase(test.TestCase): 'driver_handles_share_servers': False, 'filter_function': 'Disable', 'goodness_function': 'Disable', + 'mount_point_name_support': False, 'ipv4_support': True, 'ipv6_support': False, 'mount_snapshot_support': False, diff --git a/manila/tests/share/drivers/zfsonlinux/test_driver.py b/manila/tests/share/drivers/zfsonlinux/test_driver.py index c48c8f615f..262360816b 100644 --- a/manila/tests/share/drivers/zfsonlinux/test_driver.py +++ b/manila/tests/share/drivers/zfsonlinux/test_driver.py @@ -376,6 +376,7 @@ class ZFSonLinuxShareDriverTestCase(test.TestCase): 'vendor_name': 'Open Source', 'filter_function': None, 'goodness_function': None, + 'mount_point_name_support': False, 'ipv4_support': True, 'ipv6_support': False, 'security_service_update_support': False, diff --git a/manila/tests/share/test_api.py b/manila/tests/share/test_api.py index c24932cb4d..25c6800176 100644 --- a/manila/tests/share/test_api.py +++ b/manila/tests/share/test_api.py @@ -166,7 +166,10 @@ class ShareAPITestCase(test.TestCase): share_instance = db_utils.create_share_instance( share_id=share['id'], share_type_id=share_type_id) - share_type = {'fake': 'fake'} + share_type = { + 'fake': 'fake', + 'mount_point_name_support': False + } self.mock_object(db_api, 'share_instance_create', mock.Mock(return_value=share_instance)) self.mock_object(db_api, 'share_type_get', @@ -801,7 +804,7 @@ class ShareAPITestCase(test.TestCase): availability_zones=expected_azs, az_request_multiple_subnet_support_map=compatible_azs_multiple, snapshot_host=None, - scheduler_hints=None + scheduler_hints=None, mount_point_name=None, ) db_api.share_get.assert_called_once() @@ -840,6 +843,85 @@ class ShareAPITestCase(test.TestCase): get_all_azs_sns.assert_called_once_with( self.context, fake_share_network_id) + def test_prefix_with_missing_extra_spec_mount_point_name_support(self): + share, share_data = self._setup_create_mocks(is_public=True) + az = share_data.pop('availability_zone') + extra_specs = {'replication_type': 'readable', + 'mount_point_name_support': False} + self.mock_object( + self.api, 'get_share_attributes_from_share_type', + mock.Mock(return_value=extra_specs)) + + self.assertRaises( + exception.InvalidInput, + self.api.create, + self.context, share_data['share_proto'], share_data['size'], + share_data['display_name'], share_data['display_description'], + availability_zones=az, + mount_point_name='fake_mp') + + def test_prefix_with_valid_mount_point_name(self): + share_type = { + 'extra_specs': { + constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX: 'prefix', + } + } + self.context.project_id = 'project_id' + mount_point_name = 'mount_point' + result = self.api._prefix_mount_point_name( + share_type, self.context, mount_point_name + ) + self.assertEqual(result, 'prefix_mount_point') + + def test_prefix_with_valid_missing_extra_spec_mount_point_name(self): + share_type = { + 'extra_specs': {}, + } + self.context.project_id = 'project_id' + mount_point_name = 'mount_point' + result = self.api._prefix_mount_point_name( + share_type, self.context, mount_point_name + ) + self.assertEqual(result, 'project_id_mount_point') + + def test_prefix_with_invalid_mount_point_name(self): + share_type = \ + { + 'extra_specs': + { + constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX: + 'prefix', + } + } + self.context.project_id = 'project_id' + mount_point_name = 'invalid*name' + self.assertRaises( + exception.InvalidInput, + self.api._prefix_mount_point_name, + share_type, + self.context, + mount_point_name + ) + + def test_prefix_with_too_long_mount_point_name(self): + share_type = \ + { + 'extra_specs': + { + constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX: + 'prefix', + } + } + self.context.project_id = 'project_id' + mount_point_name = 'a' * 256 + self.assertRaises( + exception.InvalidInput, + self.api._prefix_mount_point_name, + share_type, + self.context, + mount_point_name + ) + @ddt.data( None, '', 'fake', 'nfsfake', 'cifsfake', 'glusterfsfake', 'hdfsfake') def test_create_share_invalid_protocol(self, proto): @@ -984,6 +1066,7 @@ class ShareAPITestCase(test.TestCase): 'availability_zone_id': 'fake_id', 'share_type_id': 'fake_share_type', 'cast_rules_to_readonly': False, + 'mount_point_name': None, } ) db_api.share_type_get.assert_called_once_with( @@ -999,6 +1082,28 @@ class ShareAPITestCase(test.TestCase): self.assertFalse( self.api.scheduler_rpcapi.create_share_instance.called) + def test_create_share_instance_with_mount_point_name(self): + host, share, share_instance = self._setup_create_instance_mocks() + + self.api.create_instance(self.context, share, host=host, + availability_zone='fake', + share_type_id='fake_share_type', + mount_point_name='fake_mp') + + db_api.share_instance_create.assert_called_once_with( + self.context, share['id'], + { + 'share_network_id': None, + 'status': constants.STATUS_CREATING, + 'scheduled_at': self.dt_utc, + 'host': host, + 'availability_zone_id': 'fake_id', + 'share_type_id': 'fake_share_type', + 'cast_rules_to_readonly': False, + 'mount_point_name': 'fake_mp', + } + ) + def test_create_share_instance_without_host(self): _, share, share_instance = self._setup_create_instance_mocks() @@ -1072,6 +1177,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, + 'mount_point_name_support': False, 'replication_type': 'dr', } } @@ -1090,6 +1196,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, + 'mount_point_name_support': False, 'replication_type': None, } self.assertEqual(expected, result) @@ -1140,6 +1247,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, + 'mount_point_name_support': False, 'driver_handles_share_servers': dhss, }, } @@ -1176,6 +1284,8 @@ class ShareAPITestCase(test.TestCase): fake_type['extra_specs']['revert_to_snapshot_support'], 'mount_snapshot_support': fake_type['extra_specs']['mount_snapshot_support'], + 'mount_point_name_support': + fake_type['extra_specs']['mount_point_name_support'], 'replication_type': replication_type, }) @@ -1231,6 +1341,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, + 'mount_point_name_support': False, 'driver_handles_share_servers': dhss, }, } @@ -1278,6 +1389,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, + 'mount_point_name_support': False, 'driver_handles_share_servers': True, }, } @@ -1329,6 +1441,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, + 'mount_point_name_support': False, 'driver_handles_share_servers': True, }, } @@ -1412,6 +1525,9 @@ class ShareAPITestCase(test.TestCase): 'mount_snapshot_support': kwargs.get( 'mount_snapshot_support', share_type['extra_specs'].get('mount_snapshot_support')), + 'mount_point_name_support': kwargs.get( + 'mount_point_name_support', + share_type['extra_specs'].get('mount_point_name_support')), 'share_proto': kwargs.get('share_proto', share.get('share_proto')), 'share_type_id': share_type['id'], 'is_public': kwargs.get('is_public', share.get('is_public')), @@ -2356,7 +2472,7 @@ class ShareAPITestCase(test.TestCase): availability_zones=None, az_request_multiple_subnet_support_map=None, snapshot_host=snapshot['share']['instance']['host'], - scheduler_hints=None) + scheduler_hints=None, mount_point_name=None) share_api.policy.check_policy.assert_called_once_with( self.context, 'share_snapshot', 'get_snapshot') quota.QUOTAS.reserve.assert_called_once_with( @@ -3508,6 +3624,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, + 'mount_point_name_support': False, 'driver_handles_share_servers': dhss, }, } @@ -3520,6 +3637,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, + 'mount_point_name_support': False, 'driver_handles_share_servers': dhss, 'availability_zones': 'fake_az1,fake_az2', }, @@ -3603,6 +3721,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, + 'mount_point_name_support': False, 'driver_handles_share_servers': 'true', 'availability_zones': 'fake_az3' }, @@ -3615,6 +3734,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, + 'mount_point_name_support': False, 'driver_handles_share_servers': 'true', 'availability_zones': 'fake_az1,fake_az2', }, diff --git a/manila/tests/share/test_driver.py b/manila/tests/share/test_driver.py index 2713001471..e2a3c73a1c 100644 --- a/manila/tests/share/test_driver.py +++ b/manila/tests/share/test_driver.py @@ -144,6 +144,7 @@ class ShareDriverTestCase(test.TestCase): 'reserved_share_extend_percentage', 'vendor_name', 'storage_protocol', 'snapshot_support', 'mount_snapshot_support', + 'mount_point_name_support', ] share_driver = driver.ShareDriver(True, configuration=conf) fake_stats = {'fake_key': 'fake_value'} diff --git a/manila/tests/share/test_share_types.py b/manila/tests/share/test_share_types.py index 77285bdc1f..db0981bada 100644 --- a/manila/tests/share/test_share_types.py +++ b/manila/tests/share/test_share_types.py @@ -406,14 +406,18 @@ class ShareTypesTestCase(test.TestCase): (constants.ExtraSpecs.SNAPSHOT_SUPPORT, constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT, constants.ExtraSpecs.REVERT_TO_SNAPSHOT_SUPPORT, - constants.ExtraSpecs.MOUNT_SNAPSHOT_SUPPORT), + constants.ExtraSpecs.MOUNT_SNAPSHOT_SUPPORT, + constants.ExtraSpecs.MOUNT_POINT_NAME_SUPPORT), strutils.TRUE_STRINGS + strutils.FALSE_STRINGS)) + list(itertools.product( (constants.ExtraSpecs.REPLICATION_TYPE_SPEC,), constants.ExtraSpecs.REPLICATION_TYPES)) + [(constants.ExtraSpecs.AVAILABILITY_ZONES, 'zone a, zoneb$c'), (constants.ExtraSpecs.AVAILABILITY_ZONES, ' zonea, zoneb'), - (constants.ExtraSpecs.AVAILABILITY_ZONES, 'zone1')] + (constants.ExtraSpecs.AVAILABILITY_ZONES, 'zone1')] + + [(constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX, 'gold'), + (constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX, 'silver'), + (constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX, 'bronze')] )) @ddt.unpack def test_is_valid_optional_extra_spec_valid(self, key, value): @@ -422,6 +426,18 @@ class ShareTypesTestCase(test.TestCase): self.assertTrue(result) + def test_valid_string(self): + self.assertTrue(share_types.is_valid_string("This is a valid string")) + + def test_empty_string(self): + self.assertFalse(share_types.is_valid_string("")) + + def test_string_too_long(self): + self.assertFalse(share_types.is_valid_string("a" * 256)) + + def test_non_string_input(self): + self.assertFalse(share_types.is_valid_string(123)) + def test_is_valid_optional_extra_spec_valid_unknown_key(self): result = share_types.is_valid_optional_extra_spec('fake', 'fake') diff --git a/manila/transfer/api.py b/manila/transfer/api.py index 7cc6b42246..0abb107664 100644 --- a/manila/transfer/api.py +++ b/manila/transfer/api.py @@ -168,6 +168,17 @@ class API(base.Base): policy.check_policy(context, "share_transfer", "create", target_obj=share_ref) share_instance = share_ref['instance'] + + mount_point_name = share_instance['mount_point_name'] + if (mount_point_name and + mount_point_name.startswith(share_ref['project_id'])): + msg = _('Share %s has a custom mount_point_name %s.' + ' This has the project_id encoded in it.' + ' Transferring such' + ' a share isn\'t supported') % (share_ref['name'], + mount_point_name) + raise exception.Invalid(reason=msg) + if share_ref['status'] != "available": raise exception.InvalidShare(reason=_("Share's status must be " "available")) diff --git a/releasenotes/notes/human-readable-export-location-share-support-a72cd2f0e92c41c7.yaml b/releasenotes/notes/human-readable-export-location-share-support-a72cd2f0e92c41c7.yaml new file mode 100644 index 0000000000..18323c795f --- /dev/null +++ b/releasenotes/notes/human-readable-export-location-share-support-a72cd2f0e92c41c7.yaml @@ -0,0 +1,12 @@ +--- +features: + - A human readable ``mount_point_name`` can now be specified + while creating shares through the mount_point_name parameter. + Manila will prepend a prefix to the mount point name which + can be configured through the ``provisioning:mount_point_prefix`` + share type extra spec. In case this extra spec is not available + in the share type, Manila will prepend a project identification + to the mount point name. Project id will be added to this friendly + name ``provisioning:mount_point_prefix`` share type is not + provided during provisioning. The LVM driver now supports + human readable export locations. \ No newline at end of file