Adds a new Manila driver for Dell PowerFlex storage backend
Adds a new Manila driver to support Dell PowerFlex storage backend. It will include the minimum set of Manila features. Implements: blueprint dell-powerflex-manila-driver Change-Id: I4dc81bf75135b32f1971ca21eee298bca33441cf
This commit is contained in:
parent
16af92c694
commit
ac8a9a2380
@ -86,6 +86,7 @@ each back end.
|
||||
emc_vnx_driver
|
||||
../configuration/shared-file-systems/drivers/dell-emc-unity-driver
|
||||
../configuration/shared-file-systems/drivers/dell-emc-powerstore-driver
|
||||
../configuration/shared-file-systems/drivers/dell-emc-powerflex-driver
|
||||
generic_driver
|
||||
glusterfs_driver
|
||||
glusterfs_native_driver
|
||||
|
@ -53,6 +53,8 @@ Mapping of share drivers and share features support
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------------------+--------------------------+------------------------+-----------------------------------+--------------------------+--------------------+--------------------+
|
||||
| Dell EMC PowerStore | B | \- | B | B | B | B | \- | B | \- |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------------------+--------------------------+------------------------+-----------------------------------+--------------------------+--------------------+--------------------+
|
||||
| Dell EMC PowerFlex | B | \- | B | \- | B | \- | \- | \- | \- |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------------------+--------------------------+------------------------+-----------------------------------+--------------------------+--------------------+--------------------+
|
||||
| GlusterFS | J | \- | directory layout (T) | directory layout (T) | volume layout (L) | volume layout (L) | \- | \- | \- |
|
||||
+----------------------------------------+-----------------------+-----------------------+--------------------------+--------------------------+------------------------+-----------------------------------+--------------------------+--------------------+--------------------+
|
||||
| GlusterFS-Native | J | \- | \- | \- | K | L | \- | \- | \- |
|
||||
@ -128,6 +130,8 @@ Mapping of share drivers and share access rules support
|
||||
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
|
||||
| Dell EMC PowerStore | NFS (B) | \- | CIFS (B) | \- | \- | NFS (B) | \- | CIFS (B) | \- | \- |
|
||||
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
|
||||
| Dell EMC PowerFlex | NFS (B) | \- | \- | \- | \- | NFS (B) | \- | \- | \- | \- |
|
||||
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
|
||||
| GlusterFS | NFS (J) | \- | \- | \- | \- | \- | \- | \- | \- | \- |
|
||||
+----------------------------------------+--------------+--------------+----------------+------------+--------------+--------------+--------------+----------------+------------+------------+
|
||||
| GlusterFS-Native | \- | \- | \- | J | \- | \- | \- | \- | \- | \- |
|
||||
@ -201,6 +205,8 @@ Mapping of share drivers and security services support
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Dell EMC PowerStore | B | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| Dell EMC PowerFlex | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| GlusterFS | \- | \- | \- |
|
||||
+----------------------------------------+------------------+-----------------+------------------+
|
||||
| GlusterFS-Native | \- | \- | \- |
|
||||
@ -276,6 +282,8 @@ More information: :ref:`capabilities_and_extra_specs`
|
||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+--------------------+--------------+--------------+-------------------------+
|
||||
| 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 | \- | \- |
|
||||
|
@ -12,6 +12,7 @@ Share drivers
|
||||
|
||||
drivers/generic-driver.rst
|
||||
drivers/cephfs-native-driver.rst
|
||||
drivers/dell-emc-powerflex-driver.rst
|
||||
drivers/dell-emc-powermax-driver.rst
|
||||
drivers/dell-emc-unity-driver.rst
|
||||
drivers/dell-emc-vnx-driver.rst
|
||||
|
@ -0,0 +1,160 @@
|
||||
=========================
|
||||
Dell EMC PowerFlex driver
|
||||
=========================
|
||||
|
||||
The Dell EMC Shared File Systems service driver framework (EMCShareDriver)
|
||||
utilizes the Dell EMC storage products to provide the shared file systems to
|
||||
OpenStack. The Dell EMC driver is a plug-in based driver which is designed to
|
||||
use different plug-ins to manage different Dell EMC storage products.
|
||||
|
||||
The PowerFlex SDNAS plug-in manages the PowerFlex system to provide shared filesystems.
|
||||
The Dell EMC driver framework with the PowerFlex SDNAS plug-in is referred to as the
|
||||
PowerFlex SDNAS driver in this document.
|
||||
|
||||
The PowerFlex SDNAS driver can be used to provide functions such as share and
|
||||
snapshot for instances.
|
||||
|
||||
The PowerFlex SDNAS driver enables the PowerFlex 4.x storage system to provide
|
||||
file system management through REST API operations to OpenStack.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
- PowerFlex 4.x storage system
|
||||
- SDNAS cluster registrated with SDNAS Gateway.
|
||||
|
||||
|
||||
Supported shared filesystems and operations
|
||||
-------------------------------------------
|
||||
|
||||
The driver suppors NFS shares only.
|
||||
|
||||
The following operations are supported:
|
||||
|
||||
* Create a share.
|
||||
* Delete a share.
|
||||
* Allow share access.
|
||||
* Deny share access.
|
||||
* Extend a share.
|
||||
* Create a snapshot.
|
||||
* Delete a snapshot.
|
||||
|
||||
|
||||
Driver configuration
|
||||
--------------------
|
||||
|
||||
Edit the ``manila.conf`` file, which is usually located under the following
|
||||
path ``/etc/manila/manila.conf``.
|
||||
|
||||
* Add a section for the PowerFlex SDNAS driver backend.
|
||||
|
||||
* Under the ``[DEFAULT]`` section, set the ``enabled_share_backends`` parameter
|
||||
with the name of the new backend section.
|
||||
|
||||
* Configure the driver backend section with the parameters below.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
share_driver = manila.share.drivers.dell_emc.driver.EMCShareDriver
|
||||
emc_share_backend = powerflex
|
||||
dell_nas_backend_host = <Management IP of the PowerFlex system>
|
||||
dell_nas_backend_port = <Port number used for secured connection>
|
||||
dell_nas_server = <Name of the NAS server within the PowerFlex system>
|
||||
dell_nas_login = <user with administrator privilege>
|
||||
dell_nas_password = <password>
|
||||
powerflex_storage_pool = <Name of the storage pool>
|
||||
powerflex_protection_domain = <Name of the protection domain>
|
||||
share_backend_name = powerflex
|
||||
dell_ssl_cert_verify = <True|False>
|
||||
dell_ssl_certificate_path = <Path to SSL certificates>
|
||||
|
||||
Where:
|
||||
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| **Parameter** | **Description** |
|
||||
+=================================+====================================================+
|
||||
| ``share_driver`` | Full path of the EMCShareDriver used to enable |
|
||||
| | the plugin. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``emc_share_backend`` | The plugin name. Set it to `powerflex` to |
|
||||
| | enable the PowerFlex SDNAS driver. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``dell_nas_backend_host`` | The management IP of the PowerFlex system. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``dell_nas_backend_port`` | The port number used for secured connection. |
|
||||
| | 443 by default if not provided. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``dell_nas_server`` | The name of the NAS server within the |
|
||||
| | PowerFlex system. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``dell_nas_login`` | The login to use to connect to the PowerFlex |
|
||||
| | system. It must have administrator privileges. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``dell_nas_password`` | The password associated with the login. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``powerflex_storage_pool`` | The name of the storage pool within the |
|
||||
| | PowerFlex system. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``powerflex_protection_domain`` | The name of the protection domain within the |
|
||||
| | PowerFlex system. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``share_backend_name`` | The name of the backend which provides shares. |
|
||||
| | Must be set to powerflex |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``dell_ssl_cert_verify`` | Boolean to enable the usage of SSL certificates. |
|
||||
| | False is the default value. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
| ``dell_ssl_certificate_path`` | Full path to SSL certificates. |
|
||||
| | Applies only when the usage of SSL certificate is |
|
||||
| | enabled. |
|
||||
+---------------------------------+----------------------------------------------------+
|
||||
|
||||
Restart of manila-share service is needed for the configuration
|
||||
changes to take effect.
|
||||
|
||||
Required operations prior to any usage
|
||||
--------------------------------------
|
||||
|
||||
A new share type needs to be created before going further.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack share type create powerflex False
|
||||
|
||||
Map this share type to the backend section configured in Manila
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack share type set --extra_specs share_backend_name=powerflex powerflex
|
||||
|
||||
|
||||
Specific configuration for Snapshot support
|
||||
-------------------------------------------
|
||||
|
||||
The following extra specifications need to be configured with share type.
|
||||
|
||||
- snapshot_support = True
|
||||
|
||||
For new share type, these extra specifications can be set directly when
|
||||
creating share type:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack share type create --extra_specs snapshot_support=True ${share_type_name} False
|
||||
|
||||
Or you can update already existing share type with command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack share type set --extra_specs snapshot_support=True ${share_type_name}
|
||||
|
||||
|
||||
Known restrictions
|
||||
------------------
|
||||
|
||||
The PowerFlex SDNAS driver has the following restrictions.
|
||||
|
||||
- Minimum size 3GiB.
|
||||
- Only NFS protocol is supported.
|
||||
- Only DHSS=False is supported
|
@ -42,7 +42,7 @@ EMC_NAS_OPTS = [
|
||||
cfg.StrOpt('emc_share_backend',
|
||||
ignore_case=True,
|
||||
choices=['isilon', 'vnx', 'unity', 'vmax', 'powermax',
|
||||
'powerstore'],
|
||||
'powerstore', 'powerflex'],
|
||||
help='Share backend.'),
|
||||
cfg.StrOpt('emc_nas_root_dir',
|
||||
help='The root directory where shares will be located.'),
|
||||
@ -82,9 +82,11 @@ class EMCShareDriver(driver.ShareDriver):
|
||||
"OpenStack. After that, only "
|
||||
"'emc_share_backend=powermax' will be excepted.")
|
||||
self.backend_name = 'powermax'
|
||||
LOG.info("BACKEND IS: %s", self.backend_name)
|
||||
self.plugin = self.plugin_manager.load_plugin(
|
||||
self.backend_name,
|
||||
configuration=self.configuration)
|
||||
LOG.info(f"PLUGIN HAS: {self.plugin.__dict__}")
|
||||
super(EMCShareDriver, self).__init__(
|
||||
self.plugin.driver_handles_share_servers, *args, **kwargs)
|
||||
|
||||
@ -284,6 +286,7 @@ class EMCShareDriver(driver.ShareDriver):
|
||||
revert_to_snapshot_support=self.revert_to_snap_support)
|
||||
self.plugin.update_share_stats(data)
|
||||
super(EMCShareDriver, self)._update_share_stats(data)
|
||||
LOG.info(f"Updated share stats: {self._stats}")
|
||||
|
||||
def get_network_allocations_number(self):
|
||||
"""Returns number of network allocations for creating VIFs."""
|
||||
|
390
manila/share/drivers/dell_emc/plugins/powerflex/connection.py
Normal file
390
manila/share/drivers/dell_emc/plugins/powerflex/connection.py
Normal file
@ -0,0 +1,390 @@
|
||||
# Copyright (c) 2023 Dell Inc. or its subsidiaries.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
PowerFlex specific NAS backend plugin.
|
||||
"""
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import units
|
||||
|
||||
from manila.common import constants as const
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.share.drivers.dell_emc.plugins import base as driver
|
||||
from manila.share.drivers.dell_emc.plugins.powerflex import (
|
||||
object_manager as manager)
|
||||
|
||||
"""Version history:
|
||||
1.0 - Initial version
|
||||
"""
|
||||
|
||||
VERSION = "1.0"
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
POWERFLEX_OPTS = [
|
||||
cfg.StrOpt('powerflex_storage_pool',
|
||||
help='Storage pool used to provision NAS.'),
|
||||
cfg.StrOpt('powerflex_protection_domain',
|
||||
help='Protection domain to use.'),
|
||||
cfg.StrOpt('dell_nas_backend_host',
|
||||
help='Dell NAS backend hostname or IP address.'),
|
||||
cfg.StrOpt('dell_nas_backend_port',
|
||||
help='Port number to use with the Dell NAS backend.'),
|
||||
cfg.StrOpt('dell_nas_server',
|
||||
help='Root directory or NAS server which owns the shares.'),
|
||||
cfg.StrOpt('dell_nas_login',
|
||||
help='User name for the Dell NAS backend.'),
|
||||
cfg.StrOpt('dell_nas_password',
|
||||
secret=True,
|
||||
help='Password for the Dell NAS backend.')
|
||||
|
||||
]
|
||||
|
||||
|
||||
class PowerFlexStorageConnection(driver.StorageConnection):
|
||||
"""Implements PowerFlex specific functionality for Dell Manila driver."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Do initialization"""
|
||||
|
||||
LOG.debug('Invoking base constructor for Manila \
|
||||
Dell PowerFlex SDNAS Driver.')
|
||||
super(PowerFlexStorageConnection,
|
||||
self).__init__(*args, **kwargs)
|
||||
|
||||
LOG.debug('Setting up attributes for Manila \
|
||||
Dell PowerFlex SDNAS Driver.')
|
||||
if 'configuration' in kwargs:
|
||||
kwargs['configuration'].append_config_values(POWERFLEX_OPTS)
|
||||
|
||||
self.manager = None
|
||||
self.server = None
|
||||
self._username = None
|
||||
self._password = None
|
||||
self._server_url = None
|
||||
self._root_dir = None
|
||||
self._verify_ssl_cert = None
|
||||
self._shares = {}
|
||||
self.verify_certificate = None
|
||||
self.certificate_path = None
|
||||
self.export_path = None
|
||||
|
||||
self.driver_handles_share_servers = False
|
||||
|
||||
self.reserved_percentage = None
|
||||
self.reserved_snapshot_percentage = None
|
||||
self.reserved_share_extend_percentage = None
|
||||
self.max_over_subscription_ratio = None
|
||||
|
||||
def connect(self, dell_share_driver, context):
|
||||
"""Connects to Dell PowerFlex SDNAS server."""
|
||||
LOG.debug('Reading configuration parameters for Manila \
|
||||
Dell PowerFlex SDNAS Driver.')
|
||||
config = dell_share_driver.configuration
|
||||
get_config_value = config.safe_get
|
||||
self.verify_certificate = get_config_value("dell_ssl_cert_verify")
|
||||
self.rest_ip = get_config_value("dell_nas_backend_host")
|
||||
self.rest_port = (int(get_config_value("dell_nas_backend_port")) or
|
||||
443)
|
||||
self.nas_server = get_config_value("dell_nas_server")
|
||||
self.storage_pool = get_config_value("powerflex_storage_pool")
|
||||
self.protection_domain = get_config_value(
|
||||
"powerflex_protection_domain")
|
||||
self.rest_username = get_config_value("dell_nas_login")
|
||||
self.rest_password = get_config_value("dell_nas_password")
|
||||
if self.verify_certificate:
|
||||
self.certificate_path = get_config_value(
|
||||
"dell_ssl_certificate_path")
|
||||
if not all([self.rest_ip,
|
||||
self.rest_username,
|
||||
self.rest_password]):
|
||||
message = _("REST server IP, username and password"
|
||||
" must be specified.")
|
||||
raise exception.BadConfigurationException(reason=message)
|
||||
# validate certificate settings
|
||||
if self.verify_certificate and not self.certificate_path:
|
||||
message = _("Path to REST server's certificate must be specified.")
|
||||
raise exception.BadConfigurationException(reason=message)
|
||||
|
||||
LOG.debug('Initializing Dell PowerFlex SDNAS Layer.')
|
||||
self.host_url = ("https://%(server_ip)s:%(server_port)s" %
|
||||
{
|
||||
"server_ip": self.rest_ip,
|
||||
"server_port": self.rest_port})
|
||||
LOG.info("REST server IP: %(ip)s, port: %(port)s, "
|
||||
"username: %(user)s. Verify server's certificate: "
|
||||
"%(verify_cert)s.",
|
||||
{
|
||||
"ip": self.rest_ip,
|
||||
"port": self.rest_port,
|
||||
"user": self.rest_username,
|
||||
"verify_cert": self.verify_certificate,
|
||||
})
|
||||
|
||||
self.manager = manager.StorageObjectManager(self.host_url,
|
||||
self.rest_username,
|
||||
self.rest_password,
|
||||
self.export_path,
|
||||
self.certificate_path,
|
||||
self.verify_certificate)
|
||||
|
||||
# configuration for share status update
|
||||
self.reserved_percentage = config.safe_get(
|
||||
'reserved_share_percentage')
|
||||
if self.reserved_percentage is None:
|
||||
self.reserved_percentage = 0
|
||||
|
||||
self.reserved_snapshot_percentage = config.safe_get(
|
||||
'reserved_share_from_snapshot_percentage')
|
||||
if self.reserved_snapshot_percentage is None:
|
||||
self.reserved_snapshot_percentage = self.reserved_percentage
|
||||
|
||||
self.reserved_share_extend_percentage = config.safe_get(
|
||||
'reserved_share_extend_percentage')
|
||||
if self.reserved_share_extend_percentage is None:
|
||||
self.reserved_share_extend_percentage = self.reserved_percentage
|
||||
|
||||
self.max_over_subscription_ratio = config.safe_get(
|
||||
'max_over_subscription_ratio')
|
||||
|
||||
def create_share(self, context, share, share_server):
|
||||
"""Is called to create a share."""
|
||||
LOG.debug(f'Creating {share["share_proto"]} share.')
|
||||
location = self._create_nfs_share(share)
|
||||
|
||||
return location
|
||||
|
||||
def create_share_from_snapshot(self, context, share, snapshot,
|
||||
share_server=None, parent_share=None):
|
||||
"""Is called to create a share from an existing snapshot."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def allow_access(self, context, share, access, share_server):
|
||||
"""Is called to allow access to a share."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Is called to check for setup error."""
|
||||
|
||||
def update_access(self, context, share, access_rules, add_rules,
|
||||
delete_rules, share_server=None):
|
||||
"""Is called to update share access."""
|
||||
LOG.debug(f'Updating access to {share["share_proto"]} share.')
|
||||
return self._update_nfs_access(share, access_rules)
|
||||
|
||||
def create_snapshot(self, context, snapshot, share_server):
|
||||
"""Is called to create snapshot."""
|
||||
export_name = snapshot['share_name']
|
||||
LOG.debug(f'Retrieving filesystem ID for share {export_name}')
|
||||
filesystem_id = self.manager.get_fsid_from_export_name(export_name)
|
||||
LOG.debug(f'Retrieving snapshot ID for filesystem {filesystem_id}')
|
||||
snapshot_id = self.manager.create_snapshot(snapshot['name'],
|
||||
filesystem_id)
|
||||
if snapshot_id:
|
||||
LOG.info("Snapshot %(id)s successfully created.",
|
||||
{'id': snapshot['id']})
|
||||
|
||||
def delete_snapshot(self, context, snapshot, share_server):
|
||||
"""Is called to delete snapshot."""
|
||||
snapshot_name = snapshot['name']
|
||||
filesystem_id = self.manager.get_fsid_from_snapshot_name(snapshot_name)
|
||||
LOG.debug(f'Retrieving filesystem ID for snapshot {snapshot_name}')
|
||||
snapshot_deleted = self.manager.delete_filesystem(filesystem_id)
|
||||
if not snapshot_deleted:
|
||||
message = (
|
||||
_('Failed to delete snapshot "%(snapshot)s".') %
|
||||
{'snapshot': snapshot['name']})
|
||||
LOG.error(message)
|
||||
raise exception.ShareBackendException(msg=message)
|
||||
else:
|
||||
LOG.info("Snapshot %(id)s successfully deleted.",
|
||||
{'id': snapshot['id']})
|
||||
|
||||
def delete_share(self, context, share, share_server):
|
||||
"""Is called to delete a share."""
|
||||
LOG.debug(f'Deleting {share["share_proto"]} share.')
|
||||
self._delete_nfs_share(share)
|
||||
|
||||
def deny_access(self, context, share, access, share_server):
|
||||
"""Is called to deny access to a share."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def ensure_share(self, context, share, share_server):
|
||||
"""Is called to ensure a share is exported."""
|
||||
|
||||
def extend_share(self, share, new_size, share_server=None):
|
||||
"""Is called to extend a share."""
|
||||
# Converts the size from GiB to Bytes
|
||||
new_size_in_bytes = new_size * units.Gi
|
||||
LOG.debug(f"Extending {share['name']} to {new_size}GiB")
|
||||
filesystem_id = self.manager.get_filesystem_id(share['name'])
|
||||
self.manager.extend_export(filesystem_id,
|
||||
new_size_in_bytes)
|
||||
|
||||
def setup_server(self, network_info, metadata=None):
|
||||
"""Is called to set up a share server.
|
||||
|
||||
Requires driver_handles_share_servers to be True.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def teardown_server(self, server_details, security_services=None):
|
||||
"""Is called to teardown a share server.
|
||||
|
||||
Requires driver_handles_share_servers to be True.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _create_nfs_share(self, share):
|
||||
"""Creates an NFS share.
|
||||
|
||||
In PowerFlex, an export (share) belongs to a filesystem.
|
||||
This function creates a filesystem and an export.
|
||||
"""
|
||||
LOG.debug(f'Retrieving Storage Pool ID for {self.storage_pool}')
|
||||
storage_pool_id = self.manager.get_storage_pool_id(
|
||||
self.protection_domain,
|
||||
self.storage_pool)
|
||||
nas_server_id = self.manager.get_nas_server_id(self.nas_server)
|
||||
LOG.debug(f"Creating filesystem {share['name']}")
|
||||
size_in_bytes = share['size'] * units.Gi
|
||||
filesystem_id = self.manager.create_filesystem(storage_pool_id,
|
||||
self.nas_server,
|
||||
share['name'],
|
||||
size_in_bytes)
|
||||
if not filesystem_id:
|
||||
message = {
|
||||
_('The requested NFS export "%(export)s"'
|
||||
' was not created.') %
|
||||
{'export': share['name']}}
|
||||
LOG.error(message)
|
||||
raise exception.ShareBackendException(msg=message)
|
||||
|
||||
LOG.debug(f"Creating export {share['name']}")
|
||||
export_id = self.manager.create_nfs_export(filesystem_id,
|
||||
share['name'])
|
||||
if not export_id:
|
||||
message = (
|
||||
_('The requested NFS export "%(export)s"'
|
||||
' was not created.') %
|
||||
{'export': share['name']})
|
||||
LOG.error(message)
|
||||
raise exception.ShareBackendException(msg=message)
|
||||
file_interfaces = self.manager.get_nas_server_interfaces(
|
||||
nas_server_id)
|
||||
export_path = self.manager.get_nfs_export_name(export_id)
|
||||
locations = self._get_nfs_location(file_interfaces,
|
||||
export_path)
|
||||
return locations
|
||||
|
||||
def _delete_nfs_share(self, share):
|
||||
"""Deletes a filesystem and its associated export."""
|
||||
filesystem_id = self.manager.get_filesystem_id(share['name'])
|
||||
LOG.debug(f"Retrieving filesystem ID for filesystem {share['name']}")
|
||||
if filesystem_id is None:
|
||||
message = ('Attempted to delete NFS export "%s",'
|
||||
' but the export does not appear to exist.')
|
||||
LOG.warning(message, share['name'])
|
||||
else:
|
||||
LOG.debug(f"Deleting filesystem ID {filesystem_id}")
|
||||
share_deleted = self.manager.delete_filesystem(filesystem_id)
|
||||
if not share_deleted:
|
||||
message = (
|
||||
_('Failed to delete NFS export "%(export)s".') %
|
||||
{'export': share['name']})
|
||||
LOG.error(message)
|
||||
raise exception.ShareBackendException(msg=message)
|
||||
|
||||
def _update_nfs_access(self, share, access_rules):
|
||||
"""Updates access rules for NFS share type."""
|
||||
nfs_rw_ips = set()
|
||||
nfs_ro_ips = set()
|
||||
access_updates = {}
|
||||
|
||||
for rule in access_rules:
|
||||
if rule['access_type'].lower() != 'ip':
|
||||
message = (_("Only IP access type currently supported for "
|
||||
"NFS. Share provided %(share)s with rule type "
|
||||
"%(type)s") % {'share': share['display_name'],
|
||||
'type': rule['access_type']})
|
||||
LOG.error(message)
|
||||
access_updates.update({rule['access_id']: {'state': 'error'}})
|
||||
|
||||
else:
|
||||
if rule['access_level'] == const.ACCESS_LEVEL_RW:
|
||||
nfs_rw_ips.add(rule['access_to'])
|
||||
elif rule['access_level'] == const.ACCESS_LEVEL_RO:
|
||||
nfs_ro_ips.add(rule['access_to'])
|
||||
access_updates.update({rule['access_id']: {'state': 'active'}})
|
||||
|
||||
share_id = self.manager.get_nfs_export_id(share['name'])
|
||||
share_updated = self.manager.set_export_access(share_id,
|
||||
nfs_rw_ips,
|
||||
nfs_ro_ips)
|
||||
if not share_updated:
|
||||
message = (
|
||||
_('Failed to update NFS access rules for "%(export)s".') %
|
||||
{'export': share['display_name']})
|
||||
LOG.error(message)
|
||||
raise exception.ShareBackendException(msg=message)
|
||||
return access_updates
|
||||
|
||||
def update_share_stats(self, stats_dict):
|
||||
"""Retrieve stats info from share."""
|
||||
stats_dict['driver_version'] = VERSION
|
||||
stats_dict['storage_protocol'] = 'NFS'
|
||||
stats_dict['create_share_from_snapshot_support'] = False
|
||||
stats_dict['pools'] = []
|
||||
storage_pool_id = self.manager.get_storage_pool_id(
|
||||
self.protection_domain,
|
||||
self.storage_pool
|
||||
)
|
||||
statistic = self.manager.get_storage_pool_statistic(storage_pool_id)
|
||||
if statistic:
|
||||
total = statistic.get('maxCapacityInKb') // units.Mi
|
||||
free = statistic.get('netUnusedCapacityInKb') // units.Mi
|
||||
used = statistic.get('capacityInUseInKb') // units.Mi
|
||||
provisioned = statistic.get('primaryVacInKb') // units.Mi
|
||||
pool_stat = {
|
||||
'pool_name': self.storage_pool,
|
||||
'thin_provisioning': True,
|
||||
'total_capacity_gb': total,
|
||||
'free_capacity_gb': free,
|
||||
'allocated_capacity_gb': used,
|
||||
'provisioned_capacity_gb': provisioned,
|
||||
'qos': False,
|
||||
'reserved_percentage': self.reserved_percentage,
|
||||
'reserved_snapshot_percentage':
|
||||
self.reserved_snapshot_percentage,
|
||||
'reserved_share_extend_percentage':
|
||||
self.reserved_share_extend_percentage,
|
||||
'max_over_subscription_ratio':
|
||||
self.max_over_subscription_ratio
|
||||
}
|
||||
stats_dict['pools'].append(pool_stat)
|
||||
|
||||
def _get_nfs_location(self, file_interfaces, export_path):
|
||||
export_locations = []
|
||||
for interface in file_interfaces:
|
||||
export_locations.append(
|
||||
{'path': f"{interface}:/{export_path}"})
|
||||
return export_locations
|
||||
|
||||
def get_default_filter_function(self):
|
||||
return 'share.size >= 3'
|
@ -0,0 +1,409 @@
|
||||
# Copyright (c) 2023 Dell Inc. or its subsidiaries.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from http import client as http_client
|
||||
import json
|
||||
|
||||
from oslo_log import log as logging
|
||||
import requests
|
||||
|
||||
from manila import exception
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StorageObjectManager(object):
|
||||
|
||||
def __init__(self,
|
||||
host_url,
|
||||
username,
|
||||
password,
|
||||
export_path,
|
||||
certificate_path=None,
|
||||
verify_ssl_cert=False):
|
||||
self.host_url = host_url
|
||||
self.base_url = host_url + '/rest'
|
||||
self.rest_username = username
|
||||
self.rest_password = password
|
||||
self.rest_token = None
|
||||
self.got_token = False
|
||||
self.export_path = export_path
|
||||
self.verify_certificate = verify_ssl_cert
|
||||
self.certificate_path = certificate_path
|
||||
|
||||
def _get_headers(self):
|
||||
if self.got_token:
|
||||
return {"Content-type": "application/json",
|
||||
"Accept": "application/json",
|
||||
"Authorization": "Bearer " + self.rest_token}
|
||||
else:
|
||||
return {"Content-type": "application/json",
|
||||
"Accept": "application/json"}
|
||||
|
||||
def execute_powerflex_get_request(self, url, **url_params):
|
||||
request = url % url_params
|
||||
res = requests.get(request,
|
||||
headers=self._get_headers(),
|
||||
verify=self._get_verify_cert())
|
||||
res = self._check_response(res, request, "GET")
|
||||
response = res.json()
|
||||
|
||||
return res, response
|
||||
|
||||
def execute_powerflex_post_request(self, url, params=None, **url_params):
|
||||
if not params:
|
||||
params = {}
|
||||
request = url % url_params
|
||||
res = requests.post(request,
|
||||
data=json.dumps(params),
|
||||
headers=self._get_headers(),
|
||||
verify=self._get_verify_cert())
|
||||
res = self._check_response(res, request, "POST", params)
|
||||
response = None
|
||||
try:
|
||||
response = res.json()
|
||||
except ValueError:
|
||||
# Particular case for get_storage_pool_id which is not
|
||||
# a json object but a string
|
||||
response = res
|
||||
return res, response
|
||||
|
||||
def execute_powerflex_delete_request(self, url, **url_params):
|
||||
request = url % url_params
|
||||
res = requests.delete(request,
|
||||
headers=self._get_headers(),
|
||||
verify=self._get_verify_cert())
|
||||
res = self._check_response(res, request, "DELETE")
|
||||
return res
|
||||
|
||||
def execute_powerflex_patch_request(self, url, params=None, **url_params):
|
||||
if not params:
|
||||
params = {}
|
||||
request = url % url_params
|
||||
res = requests.patch(request,
|
||||
data=json.dumps(params),
|
||||
headers=self._get_headers(),
|
||||
verify=self._get_verify_cert())
|
||||
res = self._check_response(res, request, "PATCH")
|
||||
return res
|
||||
|
||||
def _check_response(self,
|
||||
response,
|
||||
request,
|
||||
request_type,
|
||||
params=None):
|
||||
login_url = "/auth/login"
|
||||
|
||||
if (response.status_code == http_client.UNAUTHORIZED or
|
||||
response.status_code == http_client.FORBIDDEN):
|
||||
LOG.info("Dell PowerFlex token is invalid, going to re-login "
|
||||
"and get a new one.")
|
||||
login_request = self.base_url + login_url
|
||||
verify_cert = self._get_verify_cert()
|
||||
self.got_token = False
|
||||
payload = json.dumps({"username": self.rest_username,
|
||||
"password": self.rest_password})
|
||||
res = requests.post(login_request,
|
||||
headers=self._get_headers(),
|
||||
data=payload,
|
||||
verify=verify_cert)
|
||||
if (res.status_code == http_client.UNAUTHORIZED or
|
||||
res.status_code == http_client.FORBIDDEN):
|
||||
message = ("PowerFlex REST API access is still forbidden or "
|
||||
"unauthorized, there might be an issue with your "
|
||||
"credentials.")
|
||||
LOG.error(message)
|
||||
raise exception.NotAuthorized()
|
||||
else:
|
||||
token = res.json()["access_token"]
|
||||
self.rest_token = token
|
||||
self.got_token = True
|
||||
LOG.info("Going to perform request again %s with valid token.",
|
||||
request)
|
||||
if (request_type == "GET"):
|
||||
response = requests.get(request,
|
||||
headers=self._get_headers(),
|
||||
verify=verify_cert)
|
||||
elif (request_type == "POST"):
|
||||
response = requests.post(request,
|
||||
headers=self._get_headers(),
|
||||
data=json.dumps(params),
|
||||
verify=verify_cert)
|
||||
elif (request_type == "DELETE"):
|
||||
response = requests.delete(request,
|
||||
headers=self._get_headers(),
|
||||
verify=verify_cert)
|
||||
elif (request_type == "PATCH"):
|
||||
response = requests.patch(request,
|
||||
headers=self._get_headers(),
|
||||
data=json.dumps(params),
|
||||
verify=verify_cert)
|
||||
level = logging.DEBUG
|
||||
if response.status_code != http_client.OK:
|
||||
level = logging.ERROR
|
||||
LOG.log(level,
|
||||
"REST REQUEST: %s with params %s",
|
||||
request,
|
||||
json.dumps(params))
|
||||
LOG.log(level,
|
||||
"REST RESPONSE: %s with params %s",
|
||||
response.status_code,
|
||||
response.text)
|
||||
return response
|
||||
|
||||
def _get_verify_cert(self):
|
||||
verify_cert = False
|
||||
if self.verify_certificate:
|
||||
verify_cert = self.certificate_path
|
||||
return verify_cert
|
||||
|
||||
def create_filesystem(self, storage_pool_id, nas_server, name, size):
|
||||
"""Creates a filesystem.
|
||||
|
||||
:param nas_server: name of the nas_server
|
||||
:param name: name of the filesystem
|
||||
:param size: size in GiB
|
||||
:return: ID of the filesystem if created successfully
|
||||
"""
|
||||
nas_server_id = self.get_nas_server_id(nas_server)
|
||||
params = {
|
||||
"name": name,
|
||||
"size_total": size,
|
||||
"storage_pool_id": storage_pool_id,
|
||||
"nas_server_id": nas_server_id
|
||||
}
|
||||
url = self.base_url + '/v1/file-systems'
|
||||
res, response = self.execute_powerflex_post_request(url, params)
|
||||
if res.status_code == 201:
|
||||
return response["id"]
|
||||
|
||||
def create_nfs_export(self, filesystem_id, name):
|
||||
"""Creates an NFS export.
|
||||
|
||||
:param filesystem_id: ID of the filesystem on which
|
||||
the export will be created
|
||||
:param name: name of the NFS export
|
||||
:return: ID of the export if created successfully
|
||||
"""
|
||||
params = {
|
||||
"file_system_id": filesystem_id,
|
||||
"path": "/" + str(name),
|
||||
"name": name
|
||||
}
|
||||
url = self.base_url + '/v1/nfs-exports'
|
||||
res, response = self.execute_powerflex_post_request(url, params)
|
||||
if res.status_code == 201:
|
||||
return response["id"]
|
||||
|
||||
def delete_filesystem(self, filesystem_id):
|
||||
"""Deletes a filesystem and all associated export.
|
||||
|
||||
:param filesystem_id: ID of the filesystem to delete
|
||||
:return: True if deleted successfully
|
||||
"""
|
||||
url = self.base_url + \
|
||||
'/v1/file-systems/' + \
|
||||
filesystem_id
|
||||
res = self.execute_powerflex_delete_request(url)
|
||||
return res.status_code == 204
|
||||
|
||||
def create_snapshot(self, name, filesystem_id):
|
||||
"""Creates a snapshot of a filesystem.
|
||||
|
||||
:param name: name of the snapshot
|
||||
:param filesystem_id: ID of the filesystem
|
||||
:return: ID of the snapshot if created successfully
|
||||
"""
|
||||
params = {
|
||||
"name": name
|
||||
}
|
||||
url = self.base_url + \
|
||||
'/v1/file-systems/' + \
|
||||
filesystem_id + \
|
||||
'/snapshot'
|
||||
res, response = self.execute_powerflex_post_request(url, params)
|
||||
return res.status_code == 201
|
||||
|
||||
def get_nas_server_id(self, nas_server):
|
||||
"""Retrieves the NAS server ID.
|
||||
|
||||
:param nas_server: NAS server name
|
||||
:return: ID of the NAS server if success
|
||||
"""
|
||||
url = self.base_url + \
|
||||
'/v1/nas-servers?select=id&name=eq.' + \
|
||||
nas_server
|
||||
res, response = self.execute_powerflex_get_request(url)
|
||||
if res.status_code == 200:
|
||||
return response[0]['id']
|
||||
|
||||
def get_nfs_export_name(self, export_id):
|
||||
"""Retrieves NFS Export name.
|
||||
|
||||
:param export_id: ID of the NFS export
|
||||
:return: path of the NFS export if success
|
||||
"""
|
||||
url = self.base_url + '/v1/nfs-exports/' + export_id + '?select=*'
|
||||
res, response = self.execute_powerflex_get_request(url)
|
||||
if res.status_code == 200:
|
||||
return response["name"]
|
||||
|
||||
def get_filesystem_id(self, name):
|
||||
"""Retrieves an ID for a filesystem.
|
||||
|
||||
:param name: name of the filesystem
|
||||
:return: ID of the filesystem if success
|
||||
"""
|
||||
url = self.base_url + \
|
||||
'/v1/file-systems?select=id&name=eq.' + \
|
||||
name
|
||||
res, response = self.execute_powerflex_get_request(url)
|
||||
if res.status_code == 200:
|
||||
return response[0]['id']
|
||||
|
||||
def get_nfs_export_id(self, name):
|
||||
"""Retrieves NFS Export ID.
|
||||
|
||||
:param name: name of the NFS export
|
||||
:return: id of the NFS export if success
|
||||
"""
|
||||
url = self.base_url + \
|
||||
'/v1/nfs-exports?select=id&name=eq.' + \
|
||||
name
|
||||
res, response = self.execute_powerflex_get_request(url)
|
||||
if res.status_code == 200:
|
||||
return response[0]['id']
|
||||
|
||||
def get_storage_pool_id(self, protection_domain, storage_pool):
|
||||
"""Retrieves the Storage Pool ID.
|
||||
|
||||
:param protection_domain: protection domain name
|
||||
:param storage_pool: storage pool name
|
||||
:return: ID of the storage pool if success
|
||||
"""
|
||||
params = {
|
||||
"protectionDomainName": protection_domain,
|
||||
"name": storage_pool
|
||||
}
|
||||
url = self.host_url + \
|
||||
'/api/types/StoragePool/instances/action/queryIdByKey'
|
||||
res, response = self.execute_powerflex_post_request(url, params)
|
||||
if res.status_code == 200:
|
||||
return response
|
||||
|
||||
def set_export_access(self, export_id, rw_hosts, ro_hosts):
|
||||
"""Sets the authorization access on the export.
|
||||
|
||||
:param export_id: NFS export ID
|
||||
:param rw_hosts: a set of RW hosts
|
||||
:param ro_hosts: a set of RO hosts
|
||||
:return: True if operation succeeded
|
||||
"""
|
||||
params = {
|
||||
"read_only_hosts": list(ro_hosts),
|
||||
"read_write_root_hosts": list(rw_hosts)
|
||||
}
|
||||
url = self.base_url + \
|
||||
'/v1/nfs-exports/' + \
|
||||
export_id
|
||||
res = self.execute_powerflex_patch_request(url, params)
|
||||
return res.status_code == 204
|
||||
|
||||
def extend_export(self, export_id, new_size):
|
||||
"""Extends the size of a share to a new size.
|
||||
|
||||
:param export_id: ID of the NFS export
|
||||
:param new_size: new size to allocate in bytes
|
||||
:return: True if extended successfully
|
||||
"""
|
||||
params = {
|
||||
"size_total": new_size
|
||||
}
|
||||
url = self.base_url + \
|
||||
'/v1/file-systems/' + \
|
||||
export_id
|
||||
res = self.execute_powerflex_patch_request(url, params)
|
||||
return res.status_code == 204
|
||||
|
||||
def get_fsid_from_export_name(self, name):
|
||||
"""Retieves the Filesystem ID used by an export.
|
||||
|
||||
:param name: name of the export
|
||||
:return: ID of the Filesystem which owns the export
|
||||
"""
|
||||
url = self.base_url + \
|
||||
'/v1/nfs-exports?select=file_system_id&name=eq.' + \
|
||||
name
|
||||
res, response = self.execute_powerflex_get_request(url)
|
||||
if res.status_code == 200:
|
||||
return response[0]['file_system_id']
|
||||
|
||||
def get_fsid_from_snapshot_name(self, snapshot_name):
|
||||
"""Retrieves the Filesystem ID used by a snapshot.
|
||||
|
||||
:param snapshot_name: Name of the snapshot
|
||||
:return: ID of the parent filesystem of the snapshot
|
||||
"""
|
||||
url = self.base_url + \
|
||||
'/v1/file-systems?select=id&name=eq.' + \
|
||||
snapshot_name
|
||||
res, response = self.execute_powerflex_get_request(url)
|
||||
if res.status_code == 200:
|
||||
return response[0]['id']
|
||||
|
||||
def get_storage_pool_spare_percentage(self, storage_pool_id):
|
||||
"""Retrieves the spare capacity percentage of the storage pool.
|
||||
|
||||
:param storage_pool_id: ID of the storage pool
|
||||
:return: Spare capacity percentage of the storage pool
|
||||
"""
|
||||
url = self.host_url + \
|
||||
'/api/instances/StoragePool::' + \
|
||||
storage_pool_id
|
||||
res, response = self.execute_powerflex_get_request(url)
|
||||
if res.status_code == 200:
|
||||
return response['sparePercentage']
|
||||
|
||||
def get_storage_pool_statistic(self, storage_pool_id):
|
||||
"""Retrieves the spare capacity percentage of the storage pool.
|
||||
|
||||
:param storage_pool_id: ID of the storage pool
|
||||
:return: Statistics of the storage pool
|
||||
"""
|
||||
url = self.host_url + \
|
||||
'/api/instances/StoragePool::' + \
|
||||
storage_pool_id + '/relationships/Statistics'
|
||||
res, response = self.execute_powerflex_get_request(url)
|
||||
if res.status_code == 200:
|
||||
statistics = {
|
||||
"maxCapacityInKb": response['maxCapacityInKb'],
|
||||
"capacityInUseInKb": response['capacityInUseInKb'],
|
||||
"netUnusedCapacityInKb": response['netUnusedCapacityInKb'],
|
||||
"primaryVacInKb": response['primaryVacInKb'],
|
||||
}
|
||||
return statistics
|
||||
|
||||
def get_nas_server_interfaces(self, nas_server_id):
|
||||
"""Retrieves the file interfaces for a given na_server.
|
||||
|
||||
:param nas_server_id: ID of the NAS server
|
||||
:return: file interfaces of the NAS server
|
||||
"""
|
||||
url = self.base_url + \
|
||||
'/v1/file-interfaces?select=ip_address&nas_server_id=eq.' + \
|
||||
nas_server_id
|
||||
res, response = self.execute_powerflex_get_request(url)
|
||||
if res.status_code == 200:
|
||||
return [i['ip_address'] for i in response]
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"id": "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"id": "6433a2b2-6d60-f737-9f3b-2a50fb1ccff3"
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"id": "6433b635-6c1f-878e-6467-2a50fb1ccff3"
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"id": "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
}
|
||||
]
|
@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"file_system_id": "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
}
|
||||
]
|
@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"id": "6433b635-6c1f-878e-6467-2a50fb1ccff3"
|
||||
}
|
||||
]
|
@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"id": "64132f37-d33e-9d4a-89ba-d625520a4779"
|
||||
}
|
||||
]
|
@ -0,0 +1,5 @@
|
||||
[
|
||||
{
|
||||
"id": "6433a2b2-6d60-f737-9f3b-2a50fb1ccff3"
|
||||
}
|
||||
]
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"id": "6433a2b2-6d60-f737-9f3b-2a50fb1ccff3",
|
||||
"file_system_id": "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3",
|
||||
"name": "Manila-UT-filesystem",
|
||||
"path": "/Manila-UT-filesystem",
|
||||
"description": null,
|
||||
"default_access": "NO_ACCESS",
|
||||
"min_security": "SYS",
|
||||
"nfs_owner_username": "root",
|
||||
"no_access_hosts": [],
|
||||
"read_only_hosts": [],
|
||||
"read_only_root_hosts": [],
|
||||
"read_write_hosts": [],
|
||||
"read_write_root_hosts": [],
|
||||
"anonymous_UID": -2,
|
||||
"anonymous_GID": -2,
|
||||
"is_no_SUID": false,
|
||||
"default_access_l10n": null,
|
||||
"min_security_l10n": null
|
||||
}
|
@ -0,0 +1 @@
|
||||
"28515fee00000000"
|
@ -0,0 +1,98 @@
|
||||
{
|
||||
"name": "Env8-SP-SW_SSD-1",
|
||||
"rebuildIoPriorityPolicy": "limitNumOfConcurrentIos",
|
||||
"rebalanceIoPriorityPolicy": "favorAppIos",
|
||||
"vtreeMigrationIoPriorityPolicy": "favorAppIos",
|
||||
"protectedMaintenanceModeIoPriorityPolicy": "limitNumOfConcurrentIos",
|
||||
"rebuildIoPriorityNumOfConcurrentIosPerDevice": 1,
|
||||
"rebalanceIoPriorityNumOfConcurrentIosPerDevice": 1,
|
||||
"vtreeMigrationIoPriorityNumOfConcurrentIosPerDevice": 1,
|
||||
"protectedMaintenanceModeIoPriorityNumOfConcurrentIosPerDevice": 1,
|
||||
"rebuildIoPriorityBwLimitPerDeviceInKbps": 10240,
|
||||
"rebalanceIoPriorityBwLimitPerDeviceInKbps": 10240,
|
||||
"vtreeMigrationIoPriorityBwLimitPerDeviceInKbps": 10240,
|
||||
"protectedMaintenanceModeIoPriorityBwLimitPerDeviceInKbps": 10240,
|
||||
"rebuildIoPriorityAppIopsPerDeviceThreshold": null,
|
||||
"rebalanceIoPriorityAppIopsPerDeviceThreshold": null,
|
||||
"vtreeMigrationIoPriorityAppIopsPerDeviceThreshold": null,
|
||||
"protectedMaintenanceModeIoPriorityAppIopsPerDeviceThreshold": null,
|
||||
"rebuildIoPriorityAppBwPerDeviceThresholdInKbps": null,
|
||||
"rebalanceIoPriorityAppBwPerDeviceThresholdInKbps": null,
|
||||
"zeroPaddingEnabled": true,
|
||||
"vtreeMigrationIoPriorityAppBwPerDeviceThresholdInKbps": null,
|
||||
"protectedMaintenanceModeIoPriorityAppBwPerDeviceThresholdInKbps": null,
|
||||
"rebuildIoPriorityQuietPeriodInMsec": null,
|
||||
"rebalanceIoPriorityQuietPeriodInMsec": null,
|
||||
"vtreeMigrationIoPriorityQuietPeriodInMsec": null,
|
||||
"protectedMaintenanceModeIoPriorityQuietPeriodInMsec": null,
|
||||
"useRmcache": false,
|
||||
"backgroundScannerMode": "DataComparison",
|
||||
"backgroundScannerBWLimitKBps": 3072,
|
||||
"fglAccpId": null,
|
||||
"fglMetadataSizeXx100": null,
|
||||
"fglNvdimmWriteCacheSizeInMb": null,
|
||||
"fglNvdimmMetadataAmortizationX100": null,
|
||||
"mediaType": "SSD",
|
||||
"rmcacheWriteHandlingMode": "Cached",
|
||||
"checksumEnabled": false,
|
||||
"rebalanceEnabled": true,
|
||||
"fragmentationEnabled": true,
|
||||
"numOfParallelRebuildRebalanceJobsPerDevice": 2,
|
||||
"bgScannerCompareErrorAction": "ReportAndFix",
|
||||
"bgScannerReadErrorAction": "ReportAndFix",
|
||||
"externalAccelerationType": "None",
|
||||
"compressionMethod": "Invalid",
|
||||
"fglExtraCapacity": null,
|
||||
"fglOverProvisioningFactor": null,
|
||||
"fglWriteAtomicitySize": null,
|
||||
"fglMaxCompressionRatio": null,
|
||||
"fglPerfProfile": null,
|
||||
"replicationCapacityMaxRatio": 35,
|
||||
"persistentChecksumEnabled": true,
|
||||
"persistentChecksumState": "Protected",
|
||||
"persistentChecksumBuilderLimitKb": 3072,
|
||||
"protectionDomainId": "95c5a8b100000000",
|
||||
"rebuildEnabled": true,
|
||||
"dataLayout": "MediumGranularity",
|
||||
"persistentChecksumValidateOnRead": false,
|
||||
"spClass": "Nas",
|
||||
"addressSpaceUsage": "Normal",
|
||||
"useRfcache": false,
|
||||
"sparePercentage": 34,
|
||||
"capacityAlertHighThreshold": 66,
|
||||
"capacityAlertCriticalThreshold": 83,
|
||||
"capacityUsageState": "Normal",
|
||||
"addressSpaceUsageType": "DeviceCapacityLimit",
|
||||
"capacityUsageType": "NetCapacity",
|
||||
"id": "28515fee00000000",
|
||||
"links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"href": "/api/instances/StoragePool::28515fee00000000"
|
||||
},
|
||||
{
|
||||
"rel": "/api/StoragePool/relationship/Statistics",
|
||||
"href": "/api/instances/StoragePool::28515fee00000000/relationships/Statistics"
|
||||
},
|
||||
{
|
||||
"rel": "/api/StoragePool/relationship/SpSds",
|
||||
"href": "/api/instances/StoragePool::28515fee00000000/relationships/SpSds"
|
||||
},
|
||||
{
|
||||
"rel": "/api/StoragePool/relationship/Volume",
|
||||
"href": "/api/instances/StoragePool::28515fee00000000/relationships/Volume"
|
||||
},
|
||||
{
|
||||
"rel": "/api/StoragePool/relationship/Device",
|
||||
"href": "/api/instances/StoragePool::28515fee00000000/relationships/Device"
|
||||
},
|
||||
{
|
||||
"rel": "/api/StoragePool/relationship/VTree",
|
||||
"href": "/api/instances/StoragePool::28515fee00000000/relationships/VTree"
|
||||
},
|
||||
{
|
||||
"rel": "/api/parent/relationship/protectionDomainId",
|
||||
"href": "/api/instances/ProtectionDomain::95c5a8b100000000"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,381 @@
|
||||
{
|
||||
"backgroundScanFixedReadErrorCount": 0,
|
||||
"pendingMovingOutBckRebuildJobs": 0,
|
||||
"degradedHealthyCapacityInKb": 0,
|
||||
"activeMovingOutFwdRebuildJobs": 0,
|
||||
"bckRebuildWriteBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"netFglUncompressedDataSizeInKb": 0,
|
||||
"primaryReadFromDevBwc": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 1188,
|
||||
"numOccured": 18
|
||||
},
|
||||
"BackgroundScannedInMB": 233566,
|
||||
"volumeIds": [
|
||||
"2488c46c00000003",
|
||||
"2488c46d00000004",
|
||||
"2488c46e00000005",
|
||||
"2488c46f00000006",
|
||||
"2488c47000000007",
|
||||
"2488c47100000008",
|
||||
"2488c47200000009",
|
||||
"2488c4730000000a",
|
||||
"2488eb7e00000001"
|
||||
],
|
||||
"maxUserDataCapacityInKb": 3185378304,
|
||||
"persistentChecksumBuilderProgress": 100,
|
||||
"rfcacheReadsSkippedAlignedSizeTooLarge": 0,
|
||||
"pendingMovingInRebalanceJobs": 0,
|
||||
"rfcacheWritesSkippedHeavyLoad": 0,
|
||||
"unusedCapacityInKb": 3132161024,
|
||||
"userDataSdcReadLatency": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 31971,
|
||||
"numOccured": 45
|
||||
},
|
||||
"totalReadBwc": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 4644,
|
||||
"numOccured": 45
|
||||
},
|
||||
"numOfDeviceAtFaultRebuilds": 0,
|
||||
"totalWriteBwc": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 76,
|
||||
"numOccured": 19
|
||||
},
|
||||
"persistentChecksumCapacityInKb": 2359296,
|
||||
"rmPendingAllocatedInKb": 0,
|
||||
"numOfVolumes": 12,
|
||||
"rfcacheIosOutstanding": 0,
|
||||
"numOfMappedToAllVolumes": 0,
|
||||
"capacityAvailableForVolumeAllocationInKb": 1551892480,
|
||||
"netThinUserDataCapacityInKb": 26608640,
|
||||
"backgroundScanFixedCompareErrorCount": 0,
|
||||
"volMigrationWriteBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"thinAndSnapshotRatio": 6.9356937,
|
||||
"pendingMovingInEnterProtectedMaintenanceModeJobs": 0,
|
||||
"fglUserDataCapacityInKb": 0,
|
||||
"activeMovingInNormRebuildJobs": 0,
|
||||
"aggregateCompressionLevel": "Uncompressed",
|
||||
"targetOtherLatency": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"netUserDataCapacityInKb": 26608640,
|
||||
"pendingMovingOutExitProtectedMaintenanceModeJobs": 0,
|
||||
"overallUsageRatio": 6.9356937,
|
||||
"volMigrationReadBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"rfcacheReadsSkippedInternalError": 0,
|
||||
"netCapacityInUseNoOverheadInKb": 26608640,
|
||||
"pendingMovingInBckRebuildJobs": 0,
|
||||
"activeBckRebuildCapacityInKb": 0,
|
||||
"rebalanceCapacityInKb": 0,
|
||||
"pendingMovingInExitProtectedMaintenanceModeJobs": 0,
|
||||
"rfcacheReadsSkippedLowResources": 0,
|
||||
"rplJournalCapAllowed": 1115684864,
|
||||
"userDataSdcTrimLatency": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"thinCapacityInUseInKb": 0,
|
||||
"activeMovingInEnterProtectedMaintenanceModeJobs": 0,
|
||||
"rfcacheWritesSkippedInternalError": 0,
|
||||
"netUserDataCapacityNoTrimInKb": 26608640,
|
||||
"rfcacheWritesSkippedCacheMiss": 0,
|
||||
"degradedFailedCapacityInKb": 0,
|
||||
"activeNormRebuildCapacityInKb": 0,
|
||||
"numOfMigratingVolumes": 0,
|
||||
"fglSparesInKb": 0,
|
||||
"snapCapacityInUseInKb": 0,
|
||||
"compressionRatio": 1,
|
||||
"rfcacheWriteMiss": 0,
|
||||
"primaryReadFromRmcacheBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"migratingVtreeIds": [],
|
||||
"numOfVtrees": 12,
|
||||
"userDataCapacityNoTrimInKb": 53217280,
|
||||
"rfacheReadHit": 0,
|
||||
"rplUsedJournalCap": 21504,
|
||||
"compressedDataCompressionRatio": 0,
|
||||
"pendingMovingCapacityInKb": 0,
|
||||
"numOfSnapshots": 0,
|
||||
"pendingFwdRebuildCapacityInKb": 0,
|
||||
"tempCapacityInKb": 0,
|
||||
"totalFglMigrationSizeInKb": 0,
|
||||
"normRebuildCapacityInKb": 0,
|
||||
"logWrittenBlocksInKb": 0,
|
||||
"numOfThickBaseVolumes": 0,
|
||||
"primaryWriteBwc": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 36,
|
||||
"numOccured": 9
|
||||
},
|
||||
"enterProtectedMaintenanceModeReadBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"activeRebalanceCapacityInKb": 0,
|
||||
"numOfReplicationJournalVolumes": 3,
|
||||
"rfcacheReadsSkippedLockIos": 0,
|
||||
"unreachableUnusedCapacityInKb": 0,
|
||||
"netProvisionedAddressesInKb": 26608640,
|
||||
"trimmedUserDataCapacityInKb": 0,
|
||||
"provisionedAddressesInKb": 53217280,
|
||||
"numOfVolumesInDeletion": 0,
|
||||
"maxCapacityInKb": 4826330112,
|
||||
"pendingMovingOutFwdRebuildJobs": 0,
|
||||
"rmPendingThickInKb": 0,
|
||||
"protectedCapacityInKb": 53217280,
|
||||
"secondaryWriteBwc": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 40,
|
||||
"numOccured": 10
|
||||
},
|
||||
"normRebuildReadBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"thinCapacityAllocatedInKb": 369098752,
|
||||
"netFglUserDataCapacityInKb": 0,
|
||||
"metadataOverheadInKb": 0,
|
||||
"thinCapacityAllocatedInKm": 369098752,
|
||||
"rebalanceWriteBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"primaryVacInKb": 184549376,
|
||||
"deviceIds": [
|
||||
"fbdbf0a700000000",
|
||||
"fbdef0a800010000",
|
||||
"fbdff0a600020000"
|
||||
],
|
||||
"secondaryVacInKb": 184549376,
|
||||
"netSnapshotCapacityInKb": 0,
|
||||
"numOfDevices": 3,
|
||||
"rplTotalJournalCap": 25165824,
|
||||
"failedCapacityInKb": 0,
|
||||
"netMetadataOverheadInKb": 0,
|
||||
"activeMovingOutBckRebuildJobs": 0,
|
||||
"rfcacheReadsFromCache": 0,
|
||||
"pendingMovingInNormRebuildJobs": 0,
|
||||
"enterProtectedMaintenanceModeCapacityInKb": 0,
|
||||
"activeMovingOutEnterProtectedMaintenanceModeJobs": 0,
|
||||
"primaryReadBwc": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 4644,
|
||||
"numOccured": 45
|
||||
},
|
||||
"failedVacInKb": 0,
|
||||
"fglCompressedDataSizeInKb": 0,
|
||||
"fglUncompressedDataSizeInKb": 0,
|
||||
"pendingRebalanceCapacityInKb": 0,
|
||||
"rfcacheAvgReadTime": 0,
|
||||
"semiProtectedCapacityInKb": 0,
|
||||
"pendingMovingOutEnterProtectedMaintenanceModeJobs": 0,
|
||||
"mgUserDdataCcapacityInKb": 53217280,
|
||||
"netMgUserDataCapacityInKb": 26608640,
|
||||
"snapshotCapacityInKb": 0,
|
||||
"fwdRebuildReadBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"rfcacheWritesReceived": 0,
|
||||
"netUnusedCapacityInKb": 1566080512,
|
||||
"thinUserDataCapacityInKb": 53217280,
|
||||
"protectedVacInKb": 369098752,
|
||||
"bckRebuildCapacityInKb": 0,
|
||||
"activeMovingInFwdRebuildJobs": 0,
|
||||
"activeMovingRebalanceJobs": 0,
|
||||
"netTrimmedUserDataCapacityInKb": 0,
|
||||
"pendingMovingRebalanceJobs": 0,
|
||||
"numOfMarkedVolumesForReplication": 1,
|
||||
"degradedHealthyVacInKb": 0,
|
||||
"semiProtectedVacInKb": 0,
|
||||
"userDataReadBwc": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 4644,
|
||||
"numOccured": 45
|
||||
},
|
||||
"pendingBckRebuildCapacityInKb": 0,
|
||||
"capacityLimitInKb": 4826330112,
|
||||
"vtreeIds": [
|
||||
"3ad4906800000003",
|
||||
"3ad4906900000004",
|
||||
"3ad4906a00000005",
|
||||
"3ad4906b00000006",
|
||||
"3ad4906c00000007",
|
||||
"3ad4906d00000008",
|
||||
"3ad4906e00000009",
|
||||
"3ad4906f0000000a",
|
||||
"3ad4b77700000002",
|
||||
"3ad4b7780000000b",
|
||||
"3ad4b7790000000c",
|
||||
"3ad4b77a00000000"
|
||||
],
|
||||
"activeMovingCapacityInKb": 0,
|
||||
"targetWriteLatency": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 11392,
|
||||
"numOccured": 9
|
||||
},
|
||||
"pendingExitProtectedMaintenanceModeCapacityInKb": 0,
|
||||
"rfcacheIosSkipped": 0,
|
||||
"userDataWriteBwc": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 40,
|
||||
"numOccured": 10
|
||||
},
|
||||
"inMaintenanceVacInKb": 0,
|
||||
"exitProtectedMaintenanceModeReadBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"netFglSparesInKb": 0,
|
||||
"rfcacheReadsSkipped": 0,
|
||||
"activeExitProtectedMaintenanceModeCapacityInKb": 0,
|
||||
"activeMovingOutExitProtectedMaintenanceModeJobs": 0,
|
||||
"numOfUnmappedVolumes": 4,
|
||||
"tempCapacityVacInKb": 0,
|
||||
"volumeAddressSpaceInKb": 184549376,
|
||||
"currentFglMigrationSizeInKb": 0,
|
||||
"rfcacheWritesSkippedMaxIoSize": 0,
|
||||
"netMaxUserDataCapacityInKb": 1592689152,
|
||||
"numOfMigratingVtrees": 0,
|
||||
"atRestCapacityInKb": 26608640,
|
||||
"rfacheWriteHit": 0,
|
||||
"bckRebuildReadBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"rfcacheSourceDeviceWrites": 0,
|
||||
"spareCapacityInKb": 1640951808,
|
||||
"enterProtectedMaintenanceModeWriteBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"rfcacheIoErrors": 0,
|
||||
"normRebuildWriteBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"inaccessibleCapacityInKb": 0,
|
||||
"capacityInUseInKb": 53217280,
|
||||
"rebalanceReadBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"rfcacheReadsSkippedMaxIoSize": 0,
|
||||
"activeMovingInExitProtectedMaintenanceModeJobs": 0,
|
||||
"secondaryReadFromDevBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"secondaryReadBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"rfcacheWritesSkippedStuckIo": 0,
|
||||
"secondaryReadFromRmcacheBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"inMaintenanceCapacityInKb": 0,
|
||||
"exposedCapacityInKb": 0,
|
||||
"netFglCompressedDataSizeInKb": 0,
|
||||
"userDataSdcWriteLatency": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 17356,
|
||||
"numOccured": 10
|
||||
},
|
||||
"inUseVacInKb": 369098752,
|
||||
"fwdRebuildCapacityInKb": 0,
|
||||
"thickCapacityInUseInKb": 0,
|
||||
"backgroundScanReadErrorCount": 0,
|
||||
"activeMovingInRebalanceJobs": 0,
|
||||
"migratingVolumeIds": [],
|
||||
"rfcacheWritesSkippedLowResources": 0,
|
||||
"capacityInUseNoOverheadInKb": 53217280,
|
||||
"exitProtectedMaintenanceModeWriteBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"rfcacheSkippedUnlinedWrite": 0,
|
||||
"netCapacityInUseInKb": 26608640,
|
||||
"numOfOutgoingMigrations": 0,
|
||||
"rfcacheAvgWriteTime": 0,
|
||||
"pendingNormRebuildCapacityInKb": 0,
|
||||
"pendingMovingOutNormrebuildJobs": 0,
|
||||
"rfcacheSourceDeviceReads": 0,
|
||||
"rfcacheReadsPending": 0,
|
||||
"volumeAllocationLimitInKb": 15745417216,
|
||||
"fwdRebuildWriteBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"rfcacheReadsSkippedHeavyLoad": 0,
|
||||
"rfcacheReadMiss": 0,
|
||||
"targetReadLatency": {
|
||||
"numSeconds": 5,
|
||||
"totalWeightInKb": 8488,
|
||||
"numOccured": 18
|
||||
},
|
||||
"userDataCapacityInKb": 53217280,
|
||||
"activeMovingInBckRebuildJobs": 0,
|
||||
"movingCapacityInKb": 0,
|
||||
"activeEnterProtectedMaintenanceModeCapacityInKb": 0,
|
||||
"backgroundScanCompareErrorCount": 0,
|
||||
"pendingMovingInFwdRebuildJobs": 0,
|
||||
"rfcacheReadsReceived": 0,
|
||||
"spSdsIds": [
|
||||
"ebb7772100020000",
|
||||
"ebb7772200000000",
|
||||
"ebb6772300010000"
|
||||
],
|
||||
"pendingEnterProtectedMaintenanceModeCapacityInKb": 0,
|
||||
"vtreeAddresSpaceInKb": 184549376,
|
||||
"snapCapacityInUseOccupiedInKb": 0,
|
||||
"activeFwdRebuildCapacityInKb": 0,
|
||||
"rfcacheReadsSkippedStuckIo": 0,
|
||||
"activeMovingOutNormRebuildJobs": 0,
|
||||
"rfcacheWritePending": 0,
|
||||
"numOfThinBaseVolumes": 12,
|
||||
"degradedFailedVacInKb": 0,
|
||||
"userDataTrimBwc": {
|
||||
"numSeconds": 0,
|
||||
"totalWeightInKb": 0,
|
||||
"numOccured": 0
|
||||
},
|
||||
"numOfIncomingVtreeMigrations": 0
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"scope": "openid profile email",
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI5QTNCYXpXRGRvdEdQcTM3TkQyVHNDSmhiVXYwOXprb2hMWG9tNE94bXVRIn0.eyJleHAiOjE2ODExMTcwNDIsImlhdCI6MTY4MTExNjc0MiwianRpIjoiMGZiMjk4MmUtMjJmZC00MDhjLWI4MmMtYTMwNTEyNDk4NGQ4IiwiaXNzIjoiaHR0cHM6Ly9wZmxleDRlbnY4LnBpZS5sYWIuZW1jLmNvbS9hdXRoL3JlYWxtcy9wb3dlcmZsZXgiLCJhdWQiOlsiUG93ZXJmbGV4U2VydmljZXMiLCJhY2NvdW50Il0sInN1YiI6IjEzMzRhMDAxLWU2MmItNDdhYy1iZGNlLWIyNmVlZThiMjAyZiIsInR5cCI6IkJlYXJlciIsImF6cCI6InBvd2VyZmxleFJlc3QiLCJzZXNzaW9uX3N0YXRlIjoiMzU0MDhiNTQtNWE0ZS00ODNlLTkwYTUtZDA2M2ZjMDFkY2JlIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJTdXBlclVzZXIiXX0sInJlc291cmNlX2FjY2VzcyI6eyJQb3dlcmZsZXhTZXJ2aWNlcyI6eyJyb2xlcyI6WyJzdGFuZGFyZCIsIlJlYWRPbmx5IiwiQWRtaW5pc3RyYXRvciIsIm9wZXJhdG9yIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwic2lkIjoiMzU0MDhiNTQtNWE0ZS00ODNlLTkwYTUtZDA2M2ZjMDFkY2JlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBvd2VyZmxleCI6eyJwZXJtaXNzaW9ucyI6eyJTdXBlclVzZXIiOlsiR0xCOkdMQiJdfX0sIm5hbWUiOiJhZG1pbiBhZG1pbiIsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIiwiZ2l2ZW5fbmFtZSI6ImFkbWluIiwiZmFtaWx5X25hbWUiOiJhZG1pbiIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20ifQ.D78oxRxnf6hE238Wd9rVlm7L7ZpA_qqsHH_igqyA_ELtX-I3k0VMvOAKdpTOci5qEcMQYTgwQQ09ADUApw12wOxhgU_WCbSGdq07Emqfnb9Yw2vD1m6_sNNMrHOfgWXlpjZq6tS7ew7MGlnymzZXuUMRdPoI4QYZ8XDyIaqprHmJ3P1W4am9PAOWcciRMgwJo9t0LhJl2yP8fQKVgRXxnTAUVja1TYk_U8huKv9oqQR3dYLVJrGuBv8-YOvnS_RXNhUcZQUf0AGJzEG9Vjfk8MpuhuvAqjbiTQYei5rxosfxje3eVCEifEezxkZzdr_BFs1XQ-Df_Ll6m_psoxL7bA",
|
||||
"expires_in": 300,
|
||||
"refresh_expires_in": 1800,
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjZmU2NGYxNi05ZGRmLTQyYmUtYmVkMi04ZjMyZWNjM2RkYzAifQ.eyJleHAiOjE2ODExMTg1NDIsImlhdCI6MTY4MTExNjc0MiwianRpIjoiNzMxNGViZDgtNWU4Yy00N2MxLTg5OGMtYjkyZTFhYTg0ZGZlIiwiaXNzIjoiaHR0cHM6Ly9wZmxleDRlbnY4LnBpZS5sYWIuZW1jLmNvbS9hdXRoL3JlYWxtcy9wb3dlcmZsZXgiLCJhdWQiOiJodHRwczovL3BmbGV4NGVudjgucGllLmxhYi5lbWMuY29tL2F1dGgvcmVhbG1zL3Bvd2VyZmxleCIsInN1YiI6IjEzMzRhMDAxLWU2MmItNDdhYy1iZGNlLWIyNmVlZThiMjAyZiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJwb3dlcmZsZXhSZXN0Iiwic2Vzc2lvbl9zdGF0ZSI6IjM1NDA4YjU0LTVhNGUtNDgzZS05MGE1LWQwNjNmYzAxZGNiZSIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJzaWQiOiIzNTQwOGI1NC01YTRlLTQ4M2UtOTBhNS1kMDYzZmMwMWRjYmUifQ.12EA6mujHEmsC49adECuqWrqhsfCnQHv5aGo_hipSsw",
|
||||
"token_type": "Bearer",
|
||||
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI5QTNCYXpXRGRvdEdQcTM3TkQyVHNDSmhiVXYwOXprb2hMWG9tNE94bXVRIn0.eyJleHAiOjE2ODExMTcwNDIsImlhdCI6MTY4MTExNjc0MiwiYXV0aF90aW1lIjowLCJqdGkiOiI5YTg0ZDM4OC1iNTc5LTQ2ZGEtYjBkNC1mZjdlYzQ1MzA0MmMiLCJpc3MiOiJodHRwczovL3BmbGV4NGVudjgucGllLmxhYi5lbWMuY29tL2F1dGgvcmVhbG1zL3Bvd2VyZmxleCIsImF1ZCI6WyJwb3dlcmZsZXhSZXN0IiwiUG93ZXJmbGV4U2VydmljZXMiXSwic3ViIjoiMTMzNGEwMDEtZTYyYi00N2FjLWJkY2UtYjI2ZWVlOGIyMDJmIiwidHlwIjoiSUQiLCJhenAiOiJwb3dlcmZsZXhSZXN0Iiwic2Vzc2lvbl9zdGF0ZSI6IjM1NDA4YjU0LTVhNGUtNDgzZS05MGE1LWQwNjNmYzAxZGNiZSIsImF0X2hhc2giOiJielhNLVVrN0dHNl9lQlpVNTVXUVd3IiwiYWNyIjoiMSIsInNpZCI6IjM1NDA4YjU0LTVhNGUtNDgzZS05MGE1LWQwNjNmYzAxZGNiZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJwb3dlcmZsZXgiOnsicGVybWlzc2lvbnMiOnsiU3VwZXJVc2VyIjpbIkdMQjpHTEIiXX19LCJuYW1lIjoiYWRtaW4gYWRtaW4iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImdpdmVuX25hbWUiOiJhZG1pbiIsImZhbWlseV9uYW1lIjoiYWRtaW4iLCJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIn0.MVOfN10vq7VD75HMV4N2SYiGpVtnGRpXGFu3WLFPBrQjZrwFkKFb6gmtijw0Onz3xBcg7Eq7asd8lKcBaQ03LY_ru0DXpoStAlCd8z1Vfs2J5boYwn41QHrzwLn0VJK4w6zyHWbRXpK33gTNKjyX0L_JM_o2ZaCJZX8Hxvhb96-LAanbOBtwl1KR-umBHWh6FQOt43YRXAwQSo4Qz425taTmrb2U-LUu1hVZz8GjUmi2dakor6tRgT1ysxM7-9lsNXrFpgZk0XynKpxPg3yDxCdSEkIyoCGB8RH617kN4P1sGicWIk_swDZekwR23LNUiG9tjedaHTriuNAkQZ5a3w",
|
||||
"session_state": "35408b54-5a4e-483e-90a5-d063fc01dcbe"
|
||||
}
|
@ -0,0 +1,558 @@
|
||||
# Copyright (c) 2023 EMC Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import ddt
|
||||
from oslo_log import log
|
||||
from oslo_utils import units
|
||||
|
||||
from manila.common import constants as const
|
||||
from manila import exception
|
||||
from manila.share.drivers.dell_emc.plugins.powerflex import connection
|
||||
from manila import test
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class PowerFlexTest(test.TestCase):
|
||||
"""Integration test for the PowerFlex Manila driver."""
|
||||
|
||||
POWERFLEX_ADDR = "192.168.0.110"
|
||||
SHARE_NAME = "Manila-UT-filesystem"
|
||||
STORAGE_POOL_ID = "28515fee00000000"
|
||||
FILESYSTEM_ID = "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
NFS_EXPORT_ID = "6433a2b2-6d60-f737-9f3b-2a50fb1ccff3"
|
||||
NFS_EXPORT_NAME = "Manila-UT-filesystem"
|
||||
SNAPSHOT_NAME = "Manila-UT-filesystem-snap"
|
||||
SNAPSHOT_PATH = "Manila-UT-filesystem"
|
||||
SNAPSHOT_ID = "75758d63-2946-4c07-9118-9a6c6027d5e7"
|
||||
NAS_SERVER_IP = "192.168.11.23"
|
||||
|
||||
class MockConfig(object):
|
||||
def safe_get(self, value):
|
||||
if value == "dell_nas_backend_host":
|
||||
return "192.168.0.110"
|
||||
elif value == "dell_nas_backend_port":
|
||||
return "443"
|
||||
elif value == "dell_nas_login":
|
||||
return "admin"
|
||||
elif value == "dell_nas_password":
|
||||
return "pwd"
|
||||
elif value == "powerflex_storage_pool":
|
||||
return "Env8-SP-SW_SSD-1"
|
||||
elif value == "powerflex_protection_domain":
|
||||
return "Env8-PD-1"
|
||||
elif value == "dell_nas_server":
|
||||
return "env8nasserver"
|
||||
else:
|
||||
return None
|
||||
|
||||
@mock.patch(
|
||||
"manila.share.drivers.dell_emc.plugins.powerflex.object_manager."
|
||||
"StorageObjectManager",
|
||||
autospec=True,
|
||||
)
|
||||
def setUp(self, mock_powerflex_manager):
|
||||
super(PowerFlexTest, self).setUp()
|
||||
|
||||
self._mock_powerflex_manager = mock_powerflex_manager.return_value
|
||||
self.storage_connection = connection.PowerFlexStorageConnection(LOG)
|
||||
|
||||
self.mock_context = mock.Mock("Context")
|
||||
self.mock_emc_driver = mock.Mock("EmcDriver")
|
||||
|
||||
self._mock_config = self.MockConfig()
|
||||
self.mock_emc_driver.attach_mock(self._mock_config, "configuration")
|
||||
self.storage_connection.connect(
|
||||
self.mock_emc_driver, self.mock_context
|
||||
)
|
||||
|
||||
@mock.patch(
|
||||
"manila.share.drivers.dell_emc.plugins.powerflex.object_manager."
|
||||
"StorageObjectManager",
|
||||
autospec=True,
|
||||
)
|
||||
def test_connect(self, mock_powerflex_manager):
|
||||
storage_connection = connection.PowerFlexStorageConnection(LOG)
|
||||
|
||||
# execute method under test
|
||||
storage_connection.connect(self.mock_emc_driver, self.mock_context)
|
||||
|
||||
# verify connect sets driver params appropriately
|
||||
mock_config = self.MockConfig()
|
||||
server_addr = mock_config.safe_get("dell_nas_backend_host")
|
||||
self.assertEqual(server_addr, storage_connection.rest_ip)
|
||||
expected_port = int(mock_config.safe_get("dell_nas_backend_port"))
|
||||
self.assertEqual(expected_port, storage_connection.rest_port)
|
||||
self.assertEqual(
|
||||
"https://{0}:{1}".format(server_addr, expected_port),
|
||||
storage_connection.host_url,
|
||||
)
|
||||
expected_username = mock_config.safe_get("dell_nas_login")
|
||||
self.assertEqual(expected_username, storage_connection.rest_username)
|
||||
expected_password = mock_config.safe_get("dell_nas_password")
|
||||
self.assertEqual(expected_password, storage_connection.rest_password)
|
||||
expected_erify_certificates = mock_config.safe_get(
|
||||
"dell_ssl_cert_verify"
|
||||
)
|
||||
self.assertEqual(
|
||||
expected_erify_certificates, storage_connection.verify_certificate
|
||||
)
|
||||
|
||||
def test_create_share_nfs(self):
|
||||
self._mock_powerflex_manager.get_storage_pool_id.return_value = (
|
||||
self.STORAGE_POOL_ID
|
||||
)
|
||||
self._mock_powerflex_manager.create_filesystem.return_value = (
|
||||
self.FILESYSTEM_ID
|
||||
)
|
||||
self._mock_powerflex_manager.create_nfs_export.return_value = (
|
||||
self.NFS_EXPORT_ID
|
||||
)
|
||||
self._mock_powerflex_manager.get_nfs_export_name.return_value = (
|
||||
self.NFS_EXPORT_NAME
|
||||
)
|
||||
self._mock_powerflex_manager.get_nas_server_interfaces.return_value = (
|
||||
[self.NAS_SERVER_IP]
|
||||
)
|
||||
|
||||
self.assertFalse(
|
||||
self._mock_powerflex_manager.get_storage_pool_id.called
|
||||
)
|
||||
self.assertFalse(self._mock_powerflex_manager.create_filesystem.called)
|
||||
self.assertFalse(self._mock_powerflex_manager.create_nfs_export.called)
|
||||
self.assertFalse(
|
||||
self._mock_powerflex_manager.get_nfs_export_name.called
|
||||
)
|
||||
|
||||
# create the share
|
||||
share = {"name": self.SHARE_NAME, "share_proto": "NFS", "size": 8}
|
||||
locations = self.storage_connection.create_share(
|
||||
self.mock_context, share, None
|
||||
)
|
||||
|
||||
# verify location and API call made
|
||||
expected_locations = [{"path": "%s:/%s" % (
|
||||
self.NAS_SERVER_IP,
|
||||
self.SHARE_NAME,
|
||||
)}]
|
||||
self.assertEqual(expected_locations, locations)
|
||||
self._mock_powerflex_manager.get_storage_pool_id.assert_called_with(
|
||||
self._mock_config.safe_get("powerflex_protection_domain"),
|
||||
self._mock_config.safe_get("powerflex_storage_pool"),
|
||||
)
|
||||
self._mock_powerflex_manager.create_filesystem.assert_called_with(
|
||||
self.STORAGE_POOL_ID,
|
||||
self._mock_config.safe_get("dell_nas_server"),
|
||||
self.SHARE_NAME,
|
||||
8 * units.Gi,
|
||||
)
|
||||
self._mock_powerflex_manager.create_nfs_export.assert_called_with(
|
||||
self.FILESYSTEM_ID, self.SHARE_NAME
|
||||
)
|
||||
self._mock_powerflex_manager.get_nfs_export_name.assert_called_with(
|
||||
self.NFS_EXPORT_ID
|
||||
)
|
||||
|
||||
def test_create_share_nfs_filesystem_id_not_found(self):
|
||||
share = {"name": self.SHARE_NAME, "share_proto": "NFS", "size": 8}
|
||||
self._mock_powerflex_manager.create_filesystem.return_value = None
|
||||
|
||||
self.assertRaises(
|
||||
exception.ShareBackendException,
|
||||
self.storage_connection.create_share,
|
||||
self.mock_context,
|
||||
share,
|
||||
share_server=None,
|
||||
)
|
||||
|
||||
def test_create_share_nfs_backend_failure(self):
|
||||
share = {"name": self.SHARE_NAME, "share_proto": "NFS", "size": 8}
|
||||
self._mock_powerflex_manager.create_nfs_export.return_value = False
|
||||
|
||||
self.assertRaises(
|
||||
exception.ShareBackendException,
|
||||
self.storage_connection.create_share,
|
||||
self.mock_context,
|
||||
share,
|
||||
share_server=None,
|
||||
)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
self._mock_powerflex_manager.get_fsid_from_export_name.return_value = (
|
||||
self.FILESYSTEM_ID
|
||||
)
|
||||
self._mock_powerflex_manager.create_snapshot.return_value = True
|
||||
|
||||
snapshot = {
|
||||
"name": self.SNAPSHOT_NAME,
|
||||
"share_name": self.SNAPSHOT_PATH,
|
||||
"id": self.SNAPSHOT_ID,
|
||||
}
|
||||
self.storage_connection.create_snapshot(
|
||||
self.mock_context, snapshot, None
|
||||
)
|
||||
|
||||
# verify the create snapshot API call is executed
|
||||
self._mock_powerflex_manager.get_fsid_from_export_name. \
|
||||
assert_called_with(
|
||||
self.SNAPSHOT_PATH
|
||||
)
|
||||
self._mock_powerflex_manager.create_snapshot.assert_called_with(
|
||||
self.SNAPSHOT_NAME, self.FILESYSTEM_ID
|
||||
)
|
||||
|
||||
def test_create_snapshot_failure(self):
|
||||
self._mock_powerflex_manager.get_fsid_from_export_name.return_value = (
|
||||
self.FILESYSTEM_ID
|
||||
)
|
||||
self._mock_powerflex_manager.create_snapshot.return_value = False
|
||||
|
||||
snapshot = {
|
||||
"name": self.SNAPSHOT_NAME,
|
||||
"share_name": self.SNAPSHOT_PATH,
|
||||
"id": self.SNAPSHOT_ID,
|
||||
}
|
||||
self.storage_connection.create_snapshot(
|
||||
self.mock_context, snapshot, None
|
||||
)
|
||||
|
||||
def test_delete_share_nfs(self):
|
||||
share = {"name": self.SHARE_NAME, "share_proto": "NFS"}
|
||||
|
||||
self._mock_powerflex_manager.get_filesystem_id.return_value = (
|
||||
self.FILESYSTEM_ID
|
||||
)
|
||||
|
||||
self.assertFalse(self._mock_powerflex_manager.get_filesystem_id.called)
|
||||
self.assertFalse(self._mock_powerflex_manager.delete_filesystem.called)
|
||||
|
||||
# delete the share
|
||||
self.storage_connection.delete_share(self.mock_context, share, None)
|
||||
|
||||
# verify share delete
|
||||
self._mock_powerflex_manager.get_filesystem_id.assert_called_with(
|
||||
self.SHARE_NAME
|
||||
)
|
||||
self._mock_powerflex_manager.delete_filesystem.assert_called_with(
|
||||
self.FILESYSTEM_ID
|
||||
)
|
||||
|
||||
def test_delete_nfs_share_backend_failure(self):
|
||||
share = {"name": self.SHARE_NAME, "share_proto": "NFS"}
|
||||
|
||||
self._mock_powerflex_manager.delete_filesystem.return_value = False
|
||||
self.assertRaises(
|
||||
exception.ShareBackendException,
|
||||
self.storage_connection.delete_share,
|
||||
self.mock_context,
|
||||
share,
|
||||
None,
|
||||
)
|
||||
|
||||
def test_delete_nfs_share_share_does_not_exist(self):
|
||||
self._mock_powerflex_manager.get_filesystem_id.return_value = None
|
||||
share = {"name": self.SHARE_NAME, "share_proto": "NFS"}
|
||||
|
||||
# verify the calling delete on a non-existent share returns and does
|
||||
# not throw exception
|
||||
self.storage_connection.delete_share(self.mock_context, share, None)
|
||||
self.assertTrue(self._mock_powerflex_manager.get_filesystem_id.called)
|
||||
self.assertFalse(self._mock_powerflex_manager.delete_filesystem.called)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
self._mock_powerflex_manager.get_fsid_from_snapshot_name. \
|
||||
return_value = (
|
||||
self.FILESYSTEM_ID
|
||||
)
|
||||
|
||||
self.assertFalse(
|
||||
self._mock_powerflex_manager.get_fsid_from_snapshot_name.called
|
||||
)
|
||||
self.assertFalse(self._mock_powerflex_manager.delete_filesystem.called)
|
||||
|
||||
# delete the created snapshot
|
||||
snapshot = {
|
||||
"name": self.SNAPSHOT_NAME,
|
||||
"share_name": self.SNAPSHOT_PATH,
|
||||
"id": self.SNAPSHOT_ID,
|
||||
}
|
||||
self.storage_connection.delete_snapshot(
|
||||
self.mock_context, snapshot, None
|
||||
)
|
||||
|
||||
# verify the API call was made to delete the snapshot
|
||||
self._mock_powerflex_manager.get_fsid_from_snapshot_name. \
|
||||
assert_called_with(
|
||||
self.SNAPSHOT_NAME
|
||||
)
|
||||
self._mock_powerflex_manager.delete_filesystem.assert_called_with(
|
||||
self.FILESYSTEM_ID
|
||||
)
|
||||
|
||||
def test_delete_snapshot_backend_failure(self):
|
||||
self._mock_powerflex_manager.get_fsid_from_snapshot_name. \
|
||||
return_value = (
|
||||
self.FILESYSTEM_ID
|
||||
)
|
||||
self._mock_powerflex_manager.delete_filesystem.return_value = False
|
||||
|
||||
self.assertFalse(
|
||||
self._mock_powerflex_manager.get_fsid_from_snapshot_name.called
|
||||
)
|
||||
self.assertFalse(self._mock_powerflex_manager.delete_filesystem.called)
|
||||
|
||||
snapshot = {
|
||||
"name": self.SNAPSHOT_NAME,
|
||||
"share_name": self.SNAPSHOT_PATH,
|
||||
"id": self.SNAPSHOT_ID,
|
||||
}
|
||||
# verify the API call was made to delete the snapshot
|
||||
self.assertRaises(
|
||||
exception.ShareBackendException,
|
||||
self.storage_connection.delete_snapshot,
|
||||
self.mock_context,
|
||||
snapshot,
|
||||
None,
|
||||
)
|
||||
self._mock_powerflex_manager.get_fsid_from_snapshot_name. \
|
||||
assert_called_with(
|
||||
self.SNAPSHOT_NAME
|
||||
)
|
||||
self._mock_powerflex_manager.delete_filesystem.assert_called_with(
|
||||
self.FILESYSTEM_ID
|
||||
)
|
||||
|
||||
def test_extend_share(self):
|
||||
new_share_size = 20
|
||||
share = {
|
||||
"name": self.SHARE_NAME,
|
||||
"share_proto": "NFS",
|
||||
"size": new_share_size,
|
||||
}
|
||||
self._mock_powerflex_manager.get_filesystem_id.return_value = (
|
||||
self.FILESYSTEM_ID
|
||||
)
|
||||
self.assertFalse(self._mock_powerflex_manager.get_filesystem_id.called)
|
||||
|
||||
self.storage_connection.extend_share(share, new_share_size)
|
||||
|
||||
self._mock_powerflex_manager.get_filesystem_id.assert_called_with(
|
||||
self.SHARE_NAME
|
||||
)
|
||||
expected_quota_size = new_share_size * units.Gi
|
||||
self._mock_powerflex_manager.extend_export.assert_called_once_with(
|
||||
self.FILESYSTEM_ID, expected_quota_size
|
||||
)
|
||||
|
||||
def test_update_access_add_nfs(self):
|
||||
share = {"name": self.SHARE_NAME, "share_proto": "NFS"}
|
||||
|
||||
self._mock_powerflex_manager.get_nfs_export_id.return_value = (
|
||||
self.NFS_EXPORT_ID
|
||||
)
|
||||
self._mock_powerflex_manager.set_export_access.return_value = True
|
||||
|
||||
self.assertFalse(self._mock_powerflex_manager.get_nfs_export_id.called)
|
||||
self.assertFalse(self._mock_powerflex_manager.set_export_access.called)
|
||||
|
||||
nfs_rw_ip = "192.168.0.10"
|
||||
nfs_ro_ip = "192.168.0.11"
|
||||
nfs_access_rw = {
|
||||
"access_type": "ip",
|
||||
"access_to": nfs_rw_ip,
|
||||
"access_level": const.ACCESS_LEVEL_RW,
|
||||
"access_id": "09960614-8574-4e03-89cf-7cf267b0bd08",
|
||||
}
|
||||
nfs_access_ro = {
|
||||
"access_type": "ip",
|
||||
"access_to": nfs_ro_ip,
|
||||
"access_level": const.ACCESS_LEVEL_RO,
|
||||
"access_id": "09960614-8574-4e03-89cf-7cf267b0bd08",
|
||||
}
|
||||
access_rules = [nfs_access_rw, nfs_access_ro]
|
||||
|
||||
self.storage_connection.update_access(
|
||||
self.mock_context,
|
||||
share,
|
||||
access_rules,
|
||||
add_rules=None,
|
||||
delete_rules=None,
|
||||
share_server=None,
|
||||
)
|
||||
|
||||
self._mock_powerflex_manager.get_nfs_export_id.assert_called_once_with(
|
||||
self.SHARE_NAME
|
||||
)
|
||||
self._mock_powerflex_manager.set_export_access.assert_called_once_with(
|
||||
self.NFS_EXPORT_ID, {nfs_rw_ip}, {nfs_ro_ip}
|
||||
)
|
||||
|
||||
def test_update_access_add_nfs_invalid_acess_type(self):
|
||||
share = {
|
||||
"name": self.SHARE_NAME,
|
||||
"share_proto": "NFS",
|
||||
"display_name": "foo_display_name",
|
||||
}
|
||||
|
||||
nfs_rw_ip = "192.168.0.10"
|
||||
nfs_ro_ip = "192.168.0.11"
|
||||
nfs_access_rw = {
|
||||
"access_type": "invalid_type",
|
||||
"access_to": nfs_rw_ip,
|
||||
"access_level": const.ACCESS_LEVEL_RW,
|
||||
"access_id": "09960614-8574-4e03-89cf-7cf267b0bd08",
|
||||
}
|
||||
nfs_access_ro = {
|
||||
"access_type": "invalid_type",
|
||||
"access_to": nfs_ro_ip,
|
||||
"access_level": const.ACCESS_LEVEL_RO,
|
||||
"access_id": "09960614-8574-4e03-89cf-7cf267b0bd09",
|
||||
}
|
||||
access_rules = [nfs_access_rw, nfs_access_ro]
|
||||
|
||||
self._mock_powerflex_manager.get_nfs_export_id.return_value = (
|
||||
self.NFS_EXPORT_ID
|
||||
)
|
||||
|
||||
access_updates = self.storage_connection.update_access(
|
||||
self.mock_context,
|
||||
share,
|
||||
access_rules,
|
||||
add_rules=None,
|
||||
delete_rules=None,
|
||||
share_server=None,
|
||||
)
|
||||
|
||||
self._mock_powerflex_manager.set_export_access.assert_called_once_with(
|
||||
self.NFS_EXPORT_ID, set(), set()
|
||||
)
|
||||
|
||||
self.assertIsNotNone(access_updates)
|
||||
|
||||
def test_update_access_add_nfs_backend_failure(self):
|
||||
share = {
|
||||
"name": self.SHARE_NAME,
|
||||
"share_proto": "NFS",
|
||||
"display_name": "foo_display_name",
|
||||
}
|
||||
|
||||
self._mock_powerflex_manager.get_nfs_export_id.return_value = (
|
||||
self.NFS_EXPORT_ID
|
||||
)
|
||||
self._mock_powerflex_manager.set_export_access.return_value = False
|
||||
|
||||
self.assertFalse(self._mock_powerflex_manager.get_nfs_export_id.called)
|
||||
self.assertFalse(self._mock_powerflex_manager.set_export_access.called)
|
||||
|
||||
nfs_rw_ip = "192.168.0.10"
|
||||
nfs_ro_ip = "192.168.0.11"
|
||||
nfs_access_rw = {
|
||||
"access_type": "ip",
|
||||
"access_to": nfs_rw_ip,
|
||||
"access_level": const.ACCESS_LEVEL_RW,
|
||||
"access_id": "09960614-8574-4e03-89cf-7cf267b0bd08",
|
||||
}
|
||||
nfs_access_ro = {
|
||||
"access_type": "ip",
|
||||
"access_to": nfs_ro_ip,
|
||||
"access_level": const.ACCESS_LEVEL_RO,
|
||||
"access_id": "09960614-8574-4e03-89cf-7cf267b0bd08",
|
||||
}
|
||||
access_rules = [nfs_access_rw, nfs_access_ro]
|
||||
|
||||
self.assertRaises(
|
||||
exception.ShareBackendException,
|
||||
self.storage_connection.update_access,
|
||||
self.mock_context,
|
||||
share,
|
||||
access_rules,
|
||||
add_rules=None,
|
||||
delete_rules=None,
|
||||
share_server=None,
|
||||
)
|
||||
|
||||
def test_update_share_stats(self):
|
||||
data = dict(
|
||||
share_backend_name='powerflex',
|
||||
vendor_name='Dell EMC',
|
||||
storage_protocol='NFS_CIFS',
|
||||
snapshot_support=True,
|
||||
create_share_from_snapshot_support=True)
|
||||
stats = dict(
|
||||
maxCapacityInKb=4826330112,
|
||||
capacityInUseInKb=53217280,
|
||||
netUnusedCapacityInKb=1566080512,
|
||||
primaryVacInKb=184549376)
|
||||
|
||||
self._mock_powerflex_manager.get_storage_pool_id.return_value = (
|
||||
self.STORAGE_POOL_ID
|
||||
)
|
||||
self._mock_powerflex_manager.get_storage_pool_statistic. \
|
||||
return_value = stats
|
||||
self.storage_connection.update_share_stats(data)
|
||||
self.assertEqual(data['storage_protocol'], 'NFS')
|
||||
self.assertEqual(data['create_share_from_snapshot_support'], False)
|
||||
self.assertEqual(data['driver_version'], connection.VERSION)
|
||||
self.assertIsNotNone(data['pools'])
|
||||
|
||||
def test_get_default_filter_function(self):
|
||||
filter = self.storage_connection.get_default_filter_function()
|
||||
self.assertEqual(filter, "share.size >= 3")
|
||||
|
||||
def test_create_share_from_snapshot(self):
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.storage_connection.create_share_from_snapshot,
|
||||
self.mock_context,
|
||||
share=None,
|
||||
snapshot=None,
|
||||
)
|
||||
|
||||
def test_allow_access(self):
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.storage_connection.allow_access,
|
||||
self.mock_context,
|
||||
share=None,
|
||||
access=None,
|
||||
share_server=None,
|
||||
)
|
||||
|
||||
def test_deny_access(self):
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.storage_connection.deny_access,
|
||||
self.mock_context,
|
||||
share=None,
|
||||
access=None,
|
||||
share_server=None,
|
||||
)
|
||||
|
||||
def test_setup_server(self):
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.storage_connection.setup_server,
|
||||
network_info=None,
|
||||
)
|
||||
|
||||
def test_teardown_server(self):
|
||||
self.assertRaises(
|
||||
NotImplementedError,
|
||||
self.storage_connection.teardown_server,
|
||||
server_details=None,
|
||||
)
|
@ -0,0 +1,458 @@
|
||||
# Copyright (c) 2023 Dell Inc. or its subsidiaries.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from http import client as http_client
|
||||
import json
|
||||
import pathlib
|
||||
|
||||
import ddt
|
||||
import requests_mock
|
||||
|
||||
from manila import exception
|
||||
from manila.share.drivers.dell_emc.plugins.powerflex import (
|
||||
object_manager as manager
|
||||
)
|
||||
from manila import test
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class StorageObjectManagerTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(StorageObjectManagerTestCase, self).setUp()
|
||||
|
||||
self._mock_url = "https://192.168.0.110:443"
|
||||
self.manager = manager.StorageObjectManager(
|
||||
self._mock_url, username="admin", password="pwd", export_path=None
|
||||
)
|
||||
self.mockup_file_base = (
|
||||
str(pathlib.Path.cwd())
|
||||
+ "/manila/tests/share/drivers/dell_emc/plugins/powerflex/mockup/"
|
||||
)
|
||||
|
||||
@ddt.data(False, True)
|
||||
def test__get_headers(self, got_token):
|
||||
self.manager.got_token = got_token
|
||||
self.manager.rest_token = "token_str"
|
||||
self.assertEqual(
|
||||
self.manager._get_headers().get("Authorization") is not None,
|
||||
got_token,
|
||||
)
|
||||
|
||||
def _getJsonFile(self, filename):
|
||||
f = open(self.mockup_file_base + filename)
|
||||
data = json.load(f)
|
||||
f.close()
|
||||
return data
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_nas_server_id(self, m):
|
||||
nas_server = "env8nasserver"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_nas_server_id_response(
|
||||
m, nas_server, self._getJsonFile("get_nas_server_id_response.json")
|
||||
)
|
||||
id = self.manager.get_nas_server_id(nas_server)
|
||||
self.assertEqual(id, "64132f37-d33e-9d4a-89ba-d625520a4779")
|
||||
|
||||
def _add_get_nas_server_id_response(self, m, nas_server, json_str):
|
||||
url = "{0}/rest/v1/nas-servers?select=id&name=eq.{1}".format(
|
||||
self._mock_url, nas_server
|
||||
)
|
||||
m.get(url, status_code=200, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_create_filesystem(self, m):
|
||||
nas_server = "env8nasserver"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_nas_server_id_response(
|
||||
m, nas_server, self._getJsonFile("get_nas_server_id_response.json")
|
||||
)
|
||||
storage_pool_id = "8515fee00000000"
|
||||
self._add_create_filesystem_response(
|
||||
m, self._getJsonFile("create_filesystem_response.json")
|
||||
)
|
||||
id = self.manager.create_filesystem(
|
||||
storage_pool_id,
|
||||
nas_server,
|
||||
name="Manila-filesystem",
|
||||
size=3221225472,
|
||||
)
|
||||
self.assertEqual(id, "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3")
|
||||
|
||||
def _add_create_filesystem_response(self, m, json_str):
|
||||
url = "{0}/rest/v1/file-systems".format(self._mock_url)
|
||||
m.post(url, status_code=201, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_create_nfs_export(self, m):
|
||||
filesystem_id = "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
name = "Manila-UT-filesystem"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_create_nfs_export_response(
|
||||
m, self._getJsonFile("create_nfs_export_response.json")
|
||||
)
|
||||
id = self.manager.create_nfs_export(filesystem_id, name)
|
||||
self.assertEqual(id, "6433a2b2-6d60-f737-9f3b-2a50fb1ccff3")
|
||||
|
||||
def _add_create_nfs_export_response(self, m, json_str):
|
||||
url = "{0}/rest/v1/nfs-exports".format(self._mock_url)
|
||||
m.post(url, status_code=201, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_delete_filesystem(self, m):
|
||||
filesystem_id = "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_delete_filesystem_response(m, filesystem_id)
|
||||
result = self.manager.delete_filesystem(filesystem_id)
|
||||
self.assertEqual(result, True)
|
||||
|
||||
def _add_delete_filesystem_response(self, m, filesystem_id):
|
||||
url = "{0}/rest/v1/file-systems/{1}".format(
|
||||
self._mock_url, filesystem_id
|
||||
)
|
||||
m.delete(url, status_code=204)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_create_snapshot(self, m):
|
||||
name = "Manila-UT-filesystem-snap"
|
||||
filesystem_id = "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_create_snapshot_response(
|
||||
m,
|
||||
filesystem_id,
|
||||
self._getJsonFile("create_nfs_snapshot_response.json"),
|
||||
)
|
||||
result = self.manager.create_snapshot(name, filesystem_id)
|
||||
self.assertEqual(result, True)
|
||||
|
||||
def _add_create_snapshot_response(self, m, filesystem_id, json_str):
|
||||
url = "{0}/rest/v1/file-systems/{1}/snapshot".format(
|
||||
self._mock_url, filesystem_id
|
||||
)
|
||||
m.post(url, status_code=201, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_nfs_export_name(self, m):
|
||||
export_id = "6433a2b2-6d60-f737-9f3b-2a50fb1ccff3"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_nfs_export_name_response(
|
||||
m,
|
||||
export_id,
|
||||
self._getJsonFile("get_nfs_export_name_response.json"),
|
||||
)
|
||||
name = self.manager.get_nfs_export_name(export_id)
|
||||
self.assertEqual(name, "Manila-UT-filesystem")
|
||||
|
||||
def _add_get_nfs_export_name_response(self, m, export_id, json_str):
|
||||
url = "{0}/rest/v1/nfs-exports/{1}?select=*".format(
|
||||
self._mock_url, export_id
|
||||
)
|
||||
m.get(url, status_code=200, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_filesystem_id(self, m):
|
||||
name = "Manila-UT-filesystem"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_filesystem_id_response(
|
||||
m, name, self._getJsonFile("get_fileystem_id_response.json")
|
||||
)
|
||||
id = self.manager.get_filesystem_id(name)
|
||||
self.assertEqual(id, "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3")
|
||||
|
||||
def _add_get_filesystem_id_response(self, m, name, json_str):
|
||||
url = "{0}/rest/v1/file-systems?select=id&name=eq.{1}".format(
|
||||
self._mock_url, name
|
||||
)
|
||||
m.get(url, status_code=200, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_nfs_export_id(self, m):
|
||||
name = "Manila-UT-filesystem"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_nfs_export_id_response(
|
||||
m, name, self._getJsonFile("get_nfs_export_id_response.json")
|
||||
)
|
||||
id = self.manager.get_nfs_export_id(name)
|
||||
self.assertEqual(id, "6433a2b2-6d60-f737-9f3b-2a50fb1ccff3")
|
||||
|
||||
def _add_get_nfs_export_id_response(self, m, name, json_str):
|
||||
url = "{0}/rest/v1/nfs-exports?select=id&name=eq.{1}".format(
|
||||
self._mock_url, name
|
||||
)
|
||||
m.get(url, status_code=200, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_storage_pool_id(self, m):
|
||||
protection_domain_name = "Env8-PD-1"
|
||||
storage_pool_name = "Env8-SP-SW_SSD-1"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_storage_pool_id_response(
|
||||
m, self._getJsonFile("get_storage_pool_id_response.json")
|
||||
)
|
||||
id = self.manager.get_storage_pool_id(
|
||||
protection_domain_name, storage_pool_name
|
||||
)
|
||||
self.assertEqual(id, "28515fee00000000")
|
||||
|
||||
def _add_get_storage_pool_id_response(self, m, json_str):
|
||||
url = "{0}/api/types/StoragePool/instances/action/queryIdByKey".format(
|
||||
self._mock_url
|
||||
)
|
||||
m.post(url, status_code=200, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_set_export_access(self, m):
|
||||
export_id = "6433a2b2-6d60-f737-9f3b-2a50fb1ccff3"
|
||||
rw_hosts = "192.168.1.110"
|
||||
ro_hosts = "192.168.1.111"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_set_export_access_response(m, export_id)
|
||||
result = self.manager.set_export_access(export_id, rw_hosts, ro_hosts)
|
||||
self.assertEqual(result, True)
|
||||
|
||||
def _add_set_export_access_response(self, m, export_id):
|
||||
url = "{0}/rest/v1/nfs-exports/{1}".format(self._mock_url, export_id)
|
||||
m.patch(url, status_code=204)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_extend_export(self, m):
|
||||
filesystem_id = "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
new_size = 6441225472
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_extend_export_response(m, filesystem_id)
|
||||
result = self.manager.extend_export(filesystem_id, new_size)
|
||||
self.assertEqual(result, True)
|
||||
|
||||
def _add_extend_export_response(self, m, filesystem_id):
|
||||
url = "{0}/rest/v1/file-systems/{1}".format(
|
||||
self._mock_url, filesystem_id
|
||||
)
|
||||
m.patch(url, status_code=204)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_fsid_from_export_name(self, m):
|
||||
name = "Manila-UT-filesystem"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_fsid_from_export_name_response(
|
||||
m,
|
||||
name,
|
||||
self._getJsonFile("get_fsid_from_export_name_response.json"),
|
||||
)
|
||||
id = self.manager.get_fsid_from_export_name(name)
|
||||
self.assertEqual(id, "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3")
|
||||
|
||||
def _add_get_fsid_from_export_name_response(self, m, name, json_str):
|
||||
url = (
|
||||
"{0}/rest/v1/nfs-exports?select=file_system_id&name=eq.{1}".format(
|
||||
self._mock_url, name
|
||||
)
|
||||
)
|
||||
m.get(url, status_code=200, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_fsid_from_snapshot_name(self, m):
|
||||
snapshot_name = "Manila-UT-filesystem-snap"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_fsid_from_snapshot_name_response(
|
||||
m,
|
||||
snapshot_name,
|
||||
self._getJsonFile("get_fsid_from_snapshot_name_response.json"),
|
||||
)
|
||||
id = self.manager.get_fsid_from_snapshot_name(snapshot_name)
|
||||
self.assertEqual(id, "6433b635-6c1f-878e-6467-2a50fb1ccff3")
|
||||
|
||||
def _add_get_fsid_from_snapshot_name_response(
|
||||
self, m, snapshot_name, json_str
|
||||
):
|
||||
url = "{0}/rest/v1/file-systems?select=id&name=eq.{1}".format(
|
||||
self._mock_url, snapshot_name
|
||||
)
|
||||
m.get(url, status_code=200, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_check_response_with_login_get(self, m):
|
||||
nas_server = "env8nasserver"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_nas_server_id_response_list(m, nas_server)
|
||||
self._add_login_success_response(m)
|
||||
id = self.manager.get_nas_server_id(nas_server)
|
||||
self.assertEqual(id, "64132f37-d33e-9d4a-89ba-d625520a4779")
|
||||
|
||||
def _add_get_nas_server_id_response_list(self, m, nas_server):
|
||||
url = "{0}/rest/v1/nas-servers?select=id&name=eq.{1}".format(
|
||||
self._mock_url, nas_server
|
||||
)
|
||||
m.get(
|
||||
url,
|
||||
[
|
||||
{"status_code": http_client.UNAUTHORIZED},
|
||||
{
|
||||
"status_code": 200,
|
||||
"json": self._getJsonFile(
|
||||
"get_nas_server_id_response.json"
|
||||
),
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
def _add_login_success_response(self, m):
|
||||
url = "{0}/rest/auth/login".format(self._mock_url)
|
||||
m.post(
|
||||
url, status_code=200, json=self._getJsonFile("login_response.json")
|
||||
)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_check_response_with_login_post(self, m):
|
||||
filesystem_id = "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
name = "Manila-UT-filesystem"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_create_nfs_export_response_list(m)
|
||||
self._add_login_success_response(m)
|
||||
id = self.manager.create_nfs_export(filesystem_id, name)
|
||||
self.assertEqual(id, "6433a2b2-6d60-f737-9f3b-2a50fb1ccff3")
|
||||
|
||||
def _add_create_nfs_export_response_list(self, m):
|
||||
url = "{0}/rest/v1/nfs-exports".format(self._mock_url)
|
||||
m.post(
|
||||
url,
|
||||
[
|
||||
{"status_code": http_client.UNAUTHORIZED},
|
||||
{
|
||||
"status_code": 201,
|
||||
"json": self._getJsonFile(
|
||||
"create_nfs_export_response.json"
|
||||
),
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_check_response_with_login_delete(self, m):
|
||||
filesystem_id = "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_delete_filesystem_response_list(m, filesystem_id)
|
||||
self._add_login_success_response(m)
|
||||
result = self.manager.delete_filesystem(filesystem_id)
|
||||
self.assertEqual(result, True)
|
||||
|
||||
def _add_delete_filesystem_response_list(self, m, filesystem_id):
|
||||
url = "{0}/rest/v1/file-systems/{1}".format(
|
||||
self._mock_url, filesystem_id
|
||||
)
|
||||
m.delete(
|
||||
url,
|
||||
[{"status_code": http_client.UNAUTHORIZED}, {"status_code": 204}],
|
||||
)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_check_response_with_login_patch(self, m):
|
||||
filesystem_id = "6432b79e-1cc3-0414-3ffd-2a50fb1ccff3"
|
||||
new_size = 6441225472
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_extend_export_response_list(m, filesystem_id)
|
||||
self._add_login_success_response(m)
|
||||
result = self.manager.extend_export(filesystem_id, new_size)
|
||||
self.assertEqual(result, True)
|
||||
|
||||
def _add_extend_export_response_list(self, m, filesystem_id):
|
||||
url = "{0}/rest/v1/file-systems/{1}".format(
|
||||
self._mock_url, filesystem_id
|
||||
)
|
||||
m.patch(
|
||||
url,
|
||||
[{"status_code": http_client.UNAUTHORIZED}, {"status_code": 204}],
|
||||
)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_check_response_with_invalid_credential(self, m):
|
||||
nas_server = "env8nasserver"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_nas_server_id_unauthorized_response(m, nas_server)
|
||||
self._add_login_fail_response(m)
|
||||
self.assertRaises(
|
||||
exception.NotAuthorized, self.manager.get_nas_server_id, nas_server
|
||||
)
|
||||
|
||||
def _add_get_nas_server_id_unauthorized_response(self, m, nas_server):
|
||||
url = "{0}/rest/v1/nas-servers?select=id&name=eq.{1}".format(
|
||||
self._mock_url, nas_server
|
||||
)
|
||||
m.get(url, status_code=http_client.UNAUTHORIZED)
|
||||
|
||||
def _add_login_fail_response(self, m):
|
||||
url = "{0}/rest/auth/login".format(self._mock_url)
|
||||
m.post(url, status_code=http_client.UNAUTHORIZED)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_execute_powerflex_post_request_with_no_param(self, m):
|
||||
url = self._mock_url + "/fake_url"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
m.post(url, status_code=201)
|
||||
res, response = self.manager.execute_powerflex_post_request(url)
|
||||
self.assertEqual(res.status_code, 201)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_execute_powerflex_patch_request_with_no_param(self, m):
|
||||
url = self._mock_url + "/fake_url"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
m.patch(url, status_code=204)
|
||||
res = self.manager.execute_powerflex_patch_request(url)
|
||||
self.assertEqual(res.status_code, 204)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_storage_pool_spare_percentage(self, m):
|
||||
storage_pool_id = "28515fee00000000"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_storage_pool_spare_percentage(
|
||||
m,
|
||||
storage_pool_id,
|
||||
self._getJsonFile("get_storage_pool_spare_percentage.json"),
|
||||
)
|
||||
spare = self.manager.get_storage_pool_spare_percentage(storage_pool_id)
|
||||
self.assertEqual(spare, 34)
|
||||
|
||||
def _add_get_storage_pool_spare_percentage(self, m, storage_pool_id,
|
||||
json_str):
|
||||
url = (
|
||||
"{0}/api/instances/StoragePool::{1}".format(
|
||||
self._mock_url, storage_pool_id
|
||||
)
|
||||
)
|
||||
m.get(url, status_code=200, json=json_str)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_get_storage_pool_statistic(self, m):
|
||||
storage_pool_id = "28515fee00000000"
|
||||
self.assertEqual(0, len(m.request_history))
|
||||
self._add_get_storage_pool_statistic(
|
||||
m,
|
||||
storage_pool_id,
|
||||
self._getJsonFile("get_storage_pool_statistic.json"),
|
||||
)
|
||||
statistic = self.manager.get_storage_pool_statistic(storage_pool_id)
|
||||
self.assertEqual(statistic['maxCapacityInKb'], 4826330112)
|
||||
self.assertEqual(statistic['capacityInUseInKb'], 53217280)
|
||||
self.assertEqual(statistic['netUnusedCapacityInKb'], 1566080512)
|
||||
self.assertEqual(statistic['primaryVacInKb'], 184549376)
|
||||
|
||||
def _add_get_storage_pool_statistic(self, m, storage_pool_id,
|
||||
json_str):
|
||||
url = (
|
||||
("{0}/api/instances/StoragePool::{1}/relationships/" +
|
||||
"Statistics").format(
|
||||
self._mock_url, storage_pool_id
|
||||
)
|
||||
)
|
||||
m.get(url, status_code=200, json=json_str)
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added a new Manila driver to support Dell PowerFlex storage backend.
|
||||
It supports the minimum set of Manila features.
|
@ -84,6 +84,7 @@ manila.share.drivers.dell_emc.plugins =
|
||||
isilon = manila.share.drivers.dell_emc.plugins.isilon.isilon:IsilonStorageConnection
|
||||
powermax = manila.share.drivers.dell_emc.plugins.powermax.connection:PowerMaxStorageConnection
|
||||
powerstore = manila.share.drivers.dell_emc.plugins.powerstore.connection:PowerStoreStorageConnection
|
||||
powerflex = manila.share.drivers.dell_emc.plugins.powerflex.connection:PowerFlexStorageConnection
|
||||
manila.tests.scheduler.fakes =
|
||||
FakeWeigher1 = manila.tests.scheduler.fakes:FakeWeigher1
|
||||
FakeWeigher2 = manila.tests.scheduler.fakes:FakeWeigher2
|
||||
|
Loading…
x
Reference in New Issue
Block a user