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