Implement share revert to snapshot
This commit adds the ability for Manila to revert a share to the latest available snapshot. The feature is implemented in the LVM driver, for testing purposes. APIImpact DocImpact Co-Authored-By: Ben Swartzlander <ben@swartzlander.org> Co-Authored-By: Andrew Kerr <andrew.kerr@netapp.com> Implements: blueprint manila-share-revert-to-snapshot Change-Id: Id497e13070e0003db2db951526a52de6c2182cca
This commit is contained in:
parent
8bdf0d476d
commit
d4a379d083
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"revert": {
|
||||||
|
"snapshot_id": "6020af24-a305-4155-9a29-55e20efcb0e8"
|
||||||
|
}
|
||||||
|
}
|
@ -326,3 +326,31 @@ Request example
|
|||||||
|
|
||||||
.. literalinclude:: samples/share-actions-unmanage-request.json
|
.. literalinclude:: samples/share-actions-unmanage-request.json
|
||||||
:language: javascript
|
:language: javascript
|
||||||
|
|
||||||
|
|
||||||
|
Revert share to snapshot
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. rest_method:: POST /v2/{tenant_id}/shares/{share_id}/action
|
||||||
|
|
||||||
|
Reverts a share to the specified snapshot, which must be the most recent one
|
||||||
|
known to manila. This API is available in versions later than or equal to 2.27.
|
||||||
|
|
||||||
|
Normal response codes: 202
|
||||||
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403),
|
||||||
|
itemNotFound(404), conflict(409)
|
||||||
|
|
||||||
|
Request
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- snapshot_id: snapshot_id
|
||||||
|
- share_id: share_id
|
||||||
|
- tenant_id: tenant_id_path
|
||||||
|
|
||||||
|
Request example
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. literalinclude:: samples/share-actions-revert-to-snapshot-request.json
|
||||||
|
:language: javascript
|
||||||
|
@ -64,6 +64,10 @@ A share has one of these status values:
|
|||||||
+----------------------------------------+--------------------------------------------------------+
|
+----------------------------------------+--------------------------------------------------------+
|
||||||
| ``shrinking_possible_data_loss_error`` | Shrink share failed due to possible data loss. |
|
| ``shrinking_possible_data_loss_error`` | Shrink share failed due to possible data loss. |
|
||||||
+----------------------------------------+--------------------------------------------------------+
|
+----------------------------------------+--------------------------------------------------------+
|
||||||
|
| ``reverting`` | Share is being reverted to a snapshot. |
|
||||||
|
+----------------------------------------+--------------------------------------------------------+
|
||||||
|
| ``reverting_error`` | Share revert to snapshot failed. |
|
||||||
|
+----------------------------------------+--------------------------------------------------------+
|
||||||
|
|
||||||
|
|
||||||
List shares
|
List shares
|
||||||
|
@ -8,7 +8,8 @@ Use the shared file service to make snapshots of shares. A share
|
|||||||
snapshot is a point-in-time, read-only copy of the data that is
|
snapshot is a point-in-time, read-only copy of the data that is
|
||||||
contained in a share. You can create, manage, update, and delete
|
contained in a share. You can create, manage, update, and delete
|
||||||
share snapshots. After you create or manage a share snapshot, you
|
share snapshots. After you create or manage a share snapshot, you
|
||||||
can create a share from it.
|
can create a share from it. You can also revert a share to its most
|
||||||
|
recent snapshot.
|
||||||
|
|
||||||
You can update a share snapshot to rename it, change its
|
You can update a share snapshot to rename it, change its
|
||||||
description, or update its state to one of these supported states:
|
description, or update its state to one of these supported states:
|
||||||
@ -31,6 +32,8 @@ description, or update its state to one of these supported states:
|
|||||||
|
|
||||||
- ``unmanage_error``
|
- ``unmanage_error``
|
||||||
|
|
||||||
|
- ``restoring``
|
||||||
|
|
||||||
As administrator, you can also reset the state of a snapshot and
|
As administrator, you can also reset the state of a snapshot and
|
||||||
force-delete a share snapshot in any state. Use the ``policy.json``
|
force-delete a share snapshot in any state. Use the ``policy.json``
|
||||||
file to grant permissions for these actions to other roles.
|
file to grant permissions for these actions to other roles.
|
||||||
|
@ -69,6 +69,7 @@ PASSWORD_FOR_SAMBA_USER=${PASSWORD_FOR_SAMBA_USER:-$USERNAME_FOR_USER_RULES}
|
|||||||
RUN_MANILA_QUOTA_TESTS=${RUN_MANILA_QUOTA_TESTS:-True}
|
RUN_MANILA_QUOTA_TESTS=${RUN_MANILA_QUOTA_TESTS:-True}
|
||||||
RUN_MANILA_SHRINK_TESTS=${RUN_MANILA_SHRINK_TESTS:-True}
|
RUN_MANILA_SHRINK_TESTS=${RUN_MANILA_SHRINK_TESTS:-True}
|
||||||
RUN_MANILA_SNAPSHOT_TESTS=${RUN_MANILA_SNAPSHOT_TESTS:-True}
|
RUN_MANILA_SNAPSHOT_TESTS=${RUN_MANILA_SNAPSHOT_TESTS:-True}
|
||||||
|
RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS=${RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS:-False}
|
||||||
RUN_MANILA_CG_TESTS=${RUN_MANILA_CG_TESTS:-True}
|
RUN_MANILA_CG_TESTS=${RUN_MANILA_CG_TESTS:-True}
|
||||||
RUN_MANILA_MANAGE_TESTS=${RUN_MANILA_MANAGE_TESTS:-True}
|
RUN_MANILA_MANAGE_TESTS=${RUN_MANILA_MANAGE_TESTS:-True}
|
||||||
RUN_MANILA_MANAGE_SNAPSHOT_TESTS=${RUN_MANILA_MANAGE_SNAPSHOT_TESTS:-False}
|
RUN_MANILA_MANAGE_SNAPSHOT_TESTS=${RUN_MANILA_MANAGE_SNAPSHOT_TESTS:-False}
|
||||||
@ -164,6 +165,7 @@ if [[ "$DRIVER" == "lvm" ]]; then
|
|||||||
RUN_MANILA_MANAGE_TESTS=False
|
RUN_MANILA_MANAGE_TESTS=False
|
||||||
RUN_MANILA_HOST_ASSISTED_MIGRATION_TESTS=True
|
RUN_MANILA_HOST_ASSISTED_MIGRATION_TESTS=True
|
||||||
RUN_MANILA_SHRINK_TESTS=False
|
RUN_MANILA_SHRINK_TESTS=False
|
||||||
|
RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS=True
|
||||||
iniset $TEMPEST_CONFIG share enable_ip_rules_for_protocols 'nfs'
|
iniset $TEMPEST_CONFIG share enable_ip_rules_for_protocols 'nfs'
|
||||||
iniset $TEMPEST_CONFIG share enable_user_rules_for_protocols 'cifs'
|
iniset $TEMPEST_CONFIG share enable_user_rules_for_protocols 'cifs'
|
||||||
iniset $TEMPEST_CONFIG share image_with_share_tools 'manila-service-image-master'
|
iniset $TEMPEST_CONFIG share image_with_share_tools 'manila-service-image-master'
|
||||||
@ -207,6 +209,7 @@ elif [[ "$DRIVER" == "dummy" ]]; then
|
|||||||
RUN_MANILA_CG_TESTS=True
|
RUN_MANILA_CG_TESTS=True
|
||||||
RUN_MANILA_MANAGE_TESTS=False
|
RUN_MANILA_MANAGE_TESTS=False
|
||||||
RUN_MANILA_DRIVER_ASSISTED_MIGRATION_TESTS=True
|
RUN_MANILA_DRIVER_ASSISTED_MIGRATION_TESTS=True
|
||||||
|
RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS=True
|
||||||
iniset $TEMPEST_CONFIG share enable_ip_rules_for_protocols 'nfs'
|
iniset $TEMPEST_CONFIG share enable_ip_rules_for_protocols 'nfs'
|
||||||
iniset $TEMPEST_CONFIG share enable_user_rules_for_protocols 'cifs'
|
iniset $TEMPEST_CONFIG share enable_user_rules_for_protocols 'cifs'
|
||||||
iniset $TEMPEST_CONFIG share enable_cert_rules_for_protocols ''
|
iniset $TEMPEST_CONFIG share enable_cert_rules_for_protocols ''
|
||||||
@ -243,6 +246,9 @@ iniset $TEMPEST_CONFIG share run_shrink_tests $RUN_MANILA_SHRINK_TESTS
|
|||||||
# Enable snapshot tests
|
# Enable snapshot tests
|
||||||
iniset $TEMPEST_CONFIG share run_snapshot_tests $RUN_MANILA_SNAPSHOT_TESTS
|
iniset $TEMPEST_CONFIG share run_snapshot_tests $RUN_MANILA_SNAPSHOT_TESTS
|
||||||
|
|
||||||
|
# Enable revert to snapshot tests
|
||||||
|
iniset $TEMPEST_CONFIG share run_revert_to_snapshot_tests $RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS
|
||||||
|
|
||||||
# Enable consistency group tests
|
# Enable consistency group tests
|
||||||
iniset $TEMPEST_CONFIG share run_consistency_group_tests $RUN_MANILA_CG_TESTS
|
iniset $TEMPEST_CONFIG share run_consistency_group_tests $RUN_MANILA_CG_TESTS
|
||||||
|
|
||||||
@ -262,6 +268,10 @@ iniset $TEMPEST_CONFIG share run_driver_assisted_migration_tests $RUN_MANILA_DRI
|
|||||||
# Create share from snapshot support
|
# Create share from snapshot support
|
||||||
iniset $TEMPEST_CONFIG share capability_create_share_from_snapshot_support $CAPABILITY_CREATE_SHARE_FROM_SNAPSHOT_SUPPORT
|
iniset $TEMPEST_CONFIG share capability_create_share_from_snapshot_support $CAPABILITY_CREATE_SHARE_FROM_SNAPSHOT_SUPPORT
|
||||||
|
|
||||||
|
# Revert share to snapshot support
|
||||||
|
CAPABILITY_REVERT_TO_SNAPSHOT_SUPPORT=${CAPABILITY_REVERT_TO_SNAPSHOT_SUPPORT:-$RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS}
|
||||||
|
iniset $TEMPEST_CONFIG share capability_revert_to_snapshot_support $CAPABILITY_REVERT_TO_SNAPSHOT_SUPPORT
|
||||||
|
|
||||||
iniset $TEMPEST_CONFIG validation ip_version_for_ssh 4
|
iniset $TEMPEST_CONFIG validation ip_version_for_ssh 4
|
||||||
iniset $TEMPEST_CONFIG validation network_for_ssh ${PRIVATE_NETWORK_NAME:-"private"}
|
iniset $TEMPEST_CONFIG validation network_for_ssh ${PRIVATE_NETWORK_NAME:-"private"}
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@ elif [[ "$DRIVER" == "windows" ]]; then
|
|||||||
save_configuration "SHARE_DRIVER" "manila.share.drivers.windows.windows_smb_driver.WindowsSMBDriver"
|
save_configuration "SHARE_DRIVER" "manila.share.drivers.windows.windows_smb_driver.WindowsSMBDriver"
|
||||||
elif [[ "$DRIVER" == "dummy" ]]; then
|
elif [[ "$DRIVER" == "dummy" ]]; then
|
||||||
driver_path="manila.tests.share.drivers.dummy.DummyDriver"
|
driver_path="manila.tests.share.drivers.dummy.DummyDriver"
|
||||||
|
DEFAULT_EXTRA_SPECS="'snapshot_support=True create_share_from_snapshot_support=True revert_to_snapshot_support=True'"
|
||||||
save_configuration "MANILA_SERVICE_IMAGE_ENABLED" "False"
|
save_configuration "MANILA_SERVICE_IMAGE_ENABLED" "False"
|
||||||
save_configuration "SHARE_DRIVER" "$driver_path"
|
save_configuration "SHARE_DRIVER" "$driver_path"
|
||||||
save_configuration "SUPPRESS_ERRORS_IN_CLEANUP" "False"
|
save_configuration "SUPPRESS_ERRORS_IN_CLEANUP" "False"
|
||||||
@ -148,6 +149,7 @@ elif [[ "$DRIVER" == "dummy" ]]; then
|
|||||||
|
|
||||||
elif [[ "$DRIVER" == "lvm" ]]; then
|
elif [[ "$DRIVER" == "lvm" ]]; then
|
||||||
MANILA_SERVICE_IMAGE_ENABLED=True
|
MANILA_SERVICE_IMAGE_ENABLED=True
|
||||||
|
DEFAULT_EXTRA_SPECS="'snapshot_support=True create_share_from_snapshot_support=True revert_to_snapshot_support=True'"
|
||||||
save_configuration "SHARE_DRIVER" "manila.share.drivers.lvm.LVMShareDriver"
|
save_configuration "SHARE_DRIVER" "manila.share.drivers.lvm.LVMShareDriver"
|
||||||
save_configuration "SHARE_BACKING_FILE_SIZE" "32000M"
|
save_configuration "SHARE_BACKING_FILE_SIZE" "32000M"
|
||||||
elif [[ "$DRIVER" == "zfsonlinux" ]]; then
|
elif [[ "$DRIVER" == "zfsonlinux" ]]; then
|
||||||
|
@ -171,6 +171,12 @@ can be used verbatim as extra_specs in share types used to create shares.
|
|||||||
type in pools without regard for whether creating shares from snapshots is
|
type in pools without regard for whether creating shares from snapshots is
|
||||||
supported, and those shares will not support creating shares from snapshots.
|
supported, and those shares will not support creating shares from snapshots.
|
||||||
|
|
||||||
|
* `revert_to_snapshot_support` - indicates that a driver is capable of
|
||||||
|
reverting a share in place to its most recent snapshot. When administrators
|
||||||
|
do not set this capability as an extra-spec in a share type, the scheduler
|
||||||
|
can place new shares of that type in pools without regard for whether
|
||||||
|
reverting shares to snapshots is supported, and those shares will not support
|
||||||
|
reverting shares to snapshots.
|
||||||
|
|
||||||
Reporting Capabilities
|
Reporting Capabilities
|
||||||
----------------------
|
----------------------
|
||||||
@ -210,6 +216,7 @@ example vendor prefix:
|
|||||||
'compression': True, #
|
'compression': True, #
|
||||||
'snapshot_support': True, #
|
'snapshot_support': True, #
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
'qos': True, # this backend supports QoS
|
'qos': True, # this backend supports QoS
|
||||||
'thin_provisioning': True, #
|
'thin_provisioning': True, #
|
||||||
'max_over_subscription_ratio': 10, # (mandatory for thin)
|
'max_over_subscription_ratio': 10, # (mandatory for thin)
|
||||||
@ -238,6 +245,7 @@ example vendor prefix:
|
|||||||
# allow creating
|
# allow creating
|
||||||
# shares from
|
# shares from
|
||||||
# snapshots
|
# snapshots
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
'reserved_percentage': 0,
|
'reserved_percentage': 0,
|
||||||
'dedupe': False,
|
'dedupe': False,
|
||||||
'compression': False,
|
'compression': False,
|
||||||
|
@ -30,57 +30,58 @@ Column value "-" means that this feature is not currently supported.
|
|||||||
Mapping of share drivers and share features support
|
Mapping of share drivers and share features support
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Driver name | create delete share | manage unmanage share | extend share | shrink share | create delete snapshot | create share from snapshot | manage unmanage snapshot |
|
| Driver name | create delete share | manage unmanage share | extend share | shrink share | create delete snapshot | create share from snapshot | manage unmanage snapshot | revert to snapshot |
|
||||||
+========================================+=======================+=======================+==============+==============+========================+============================+==========================+
|
+========================================+=======================+=======================+==============+==============+========================+============================+==========================+====================+
|
||||||
| ZFSonLinux | M | N | M | M | M | M | N |
|
| ZFSonLinux | M | N | M | M | M | M | N | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Container | N | \- | N | \- | \- | \- | \- |
|
| Container | N | \- | N | \- | \- | \- | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Generic (Cinder as back-end) | J | K | L | L | J | J | M |
|
| Generic (Cinder as back-end) | J | K | L | L | J | J | M | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| NetApp Clustered Data ONTAP | J | L | L | L | J | J | N |
|
| NetApp Clustered Data ONTAP | J | L | L | L | J | J | N | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| EMC VNX | J | \- | \- | \- | J | J | \- |
|
| EMC VNX | J | \- | \- | \- | J | J | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| EMC Unity | N | \- | N | \- | N | N | \- |
|
| EMC Unity | N | \- | N | \- | N | N | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| EMC Isilon | K | \- | M | \- | K | K | \- |
|
| EMC Isilon | K | \- | M | \- | K | K | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Red Hat GlusterFS | J | \- | \- | \- | volume layout (L) | volume layout (L) | \- |
|
| Red Hat GlusterFS | J | \- | \- | \- | volume layout (L) | volume layout (L) | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Red Hat GlusterFS-Native | J | \- | \- | \- | K | L | \- |
|
| Red Hat GlusterFS-Native | J | \- | \- | \- | K | L | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| HDFS | K | \- | M | \- | K | K | \- |
|
| HDFS | K | \- | M | \- | K | K | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Hitachi HNAS | L | L | L | M | L | L | O |
|
| Hitachi HNAS | L | L | L | M | L | L | O | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Hitachi HSP | N | N | N | N | \- | \- | \- |
|
| Hitachi HSP | N | N | N | N | \- | \- | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| HPE 3PAR | K | \- | \- | \- | K | K | \- |
|
| HPE 3PAR | K | \- | \- | \- | K | K | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Huawei | K | L | L | L | K | M | \- |
|
| Huawei | K | L | L | L | K | M | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| IBM GPFS | K | O | L | \- | K | K | \- |
|
| IBM GPFS | K | O | L | \- | K | K | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| LVM | M | \- | M | \- | M | M | \- |
|
| LVM | M | \- | M | \- | M | M | \- | O |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Quobyte | K | \- | M | M | \- | \- | \- |
|
| Quobyte | K | \- | M | M | \- | \- | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Windows SMB | L | L | L | L | L | L | \- |
|
| Windows SMB | L | L | L | L | L | L | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Oracle ZFSSA | K | N | M | M | K | K | \- |
|
| Oracle ZFSSA | K | N | M | M | K | K | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| CephFS Native | M | \- | M | M | M | \- | \- |
|
| CephFS Native | M | \- | M | M | M | \- | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| Tegile | M | \- | M | M | M | M | \- |
|
| Tegile | M | \- | M | M | M | M | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| NexentaStor4 | N | \- | N | \- | N | N | \- |
|
| NexentaStor4 | N | \- | N | \- | N | N | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| NexentaStor5 | N | \- | N | N | N | N | \- |
|
| NexentaStor5 | N | \- | N | N | N | N | \- | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
| MapRFS | O | O | O | O | O | O | O |
|
| MapRFS | O | O | O | O | O | O | O | \- |
|
||||||
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
|
+----------------------------------------+-----------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+--------------------+
|
||||||
|
|
||||||
|
|
||||||
Mapping of share drivers and share access rules support
|
Mapping of share drivers and share access rules support
|
||||||
-------------------------------------------------------
|
-------------------------------------------------------
|
||||||
@ -200,57 +201,57 @@ Mapping of share drivers and common capabilities
|
|||||||
|
|
||||||
More information: :ref:`capabilities_and_extra_specs`
|
More information: :ref:`capabilities_and_extra_specs`
|
||||||
|
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Driver name | DHSS=True | DHSS=False | dedupe | compression | thin_provisioning | thick_provisioning | qos | create share from snapshot |
|
| Driver name | DHSS=True | DHSS=False | dedupe | compression | thin_provisioning | thick_provisioning | qos | create share from snapshot | revert to snapshot |
|
||||||
+========================================+===========+============+========+=============+===================+====================+=====+============================+
|
+========================================+===========+============+========+=============+===================+====================+=====+============================+====================+
|
||||||
| ZFSonLinux | \- | M | M | M | M | \- | \- | M |
|
| ZFSonLinux | \- | M | M | M | M | \- | \- | M | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Container | N | \- | \- | \- | \- | N | \- | \- |
|
| Container | N | \- | \- | \- | \- | N | \- | \- | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J |
|
| Generic (Cinder as back-end) | J | K | \- | \- | \- | L | \- | J | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| NetApp Clustered Data ONTAP | J | K | M | M | M | L | \- | J |
|
| NetApp Clustered Data ONTAP | J | K | M | M | M | L | \- | J | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| EMC VNX | J | \- | \- | \- | \- | L | \- | J |
|
| EMC VNX | J | \- | \- | \- | \- | L | \- | J | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| EMC Unity | N | \- | \- | \- | N | \- | \- | N |
|
| EMC Unity | N | \- | \- | \- | N | \- | \- | N | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| EMC Isilon | \- | K | \- | \- | \- | L | \- | K |
|
| EMC Isilon | \- | K | \- | \- | \- | L | \- | K | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Red Hat GlusterFS | \- | J | \- | \- | \- | L | \- | volume layout (L) |
|
| Red Hat GlusterFS | \- | J | \- | \- | \- | L | \- | volume layout (L) | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Red Hat GlusterFS-Native | \- | J | \- | \- | \- | L | \- | L |
|
| Red Hat GlusterFS-Native | \- | J | \- | \- | \- | L | \- | L | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| HDFS | \- | K | \- | \- | \- | L | \- | K |
|
| HDFS | \- | K | \- | \- | \- | L | \- | K | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Hitachi HNAS | \- | L | N | \- | L | \- | \- | L |
|
| Hitachi HNAS | \- | L | N | \- | L | \- | \- | L | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Hitachi HSP | \- | N | \- | \- | N | \- | \- | \- |
|
| Hitachi HSP | \- | N | \- | \- | N | \- | \- | \- | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| HPE 3PAR | L | K | L | \- | L | L | \- | K |
|
| HPE 3PAR | L | K | L | \- | L | L | \- | K | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Huawei | M | K | L | L | L | L | M | M |
|
| Huawei | M | K | L | L | L | L | M | M | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| LVM | \- | M | \- | \- | \- | M | \- | K |
|
| LVM | \- | M | \- | \- | \- | M | \- | K | O |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Quobyte | \- | K | \- | \- | \- | L | \- | M |
|
| Quobyte | \- | K | \- | \- | \- | L | \- | M | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Windows SMB | L | L | \- | \- | \- | L | \- | \- |
|
| Windows SMB | L | L | \- | \- | \- | L | \- | \- | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| IBM GPFS | \- | K | \- | \- | \- | L | \- | L |
|
| IBM GPFS | \- | K | \- | \- | \- | L | \- | L | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Oracle ZFSSA | \- | K | \- | \- | \- | L | \- | K |
|
| Oracle ZFSSA | \- | K | \- | \- | \- | L | \- | K | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| CephFS Native | \- | M | \- | \- | \- | M | \- | \- |
|
| CephFS Native | \- | M | \- | \- | \- | M | \- | \- | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| Tegile | \- | M | M | M | M | \- | \- | M |
|
| Tegile | \- | M | M | M | M | \- | \- | M | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| NexentaStor4 | \- | N | N | N | N | N | \- | N |
|
| NexentaStor4 | \- | N | N | N | N | N | \- | N | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| NexentaStor5 | \- | N | N | N | N | N | \- | N |
|
| NexentaStor5 | \- | N | N | N | N | N | \- | N | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
| MapRFS | \- | N | \- | \- | \- | N | \- | O |
|
| MapRFS | \- | N | \- | \- | \- | N | \- | O | \- |
|
||||||
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+
|
+----------------------------------------+-----------+------------+--------+-------------+-------------------+--------------------+-----+----------------------------+--------------------+
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
"share:unmanage": "rule:admin_api",
|
"share:unmanage": "rule:admin_api",
|
||||||
"share:force_delete": "rule:admin_api",
|
"share:force_delete": "rule:admin_api",
|
||||||
"share:reset_status": "rule:admin_api",
|
"share:reset_status": "rule:admin_api",
|
||||||
|
"share:revert_to_snapshot": "rule:default",
|
||||||
"share_export_location:index": "rule:default",
|
"share_export_location:index": "rule:default",
|
||||||
"share_export_location:show": "rule:default",
|
"share_export_location:show": "rule:default",
|
||||||
|
|
||||||
|
@ -171,5 +171,12 @@ brctl: CommandFilter, brctl, root
|
|||||||
# manila/share/drivers/container/container.py: e2fsck <whatever>
|
# manila/share/drivers/container/container.py: e2fsck <whatever>
|
||||||
e2fsck: CommandFilter, e2fsck, root
|
e2fsck: CommandFilter, e2fsck, root
|
||||||
|
|
||||||
|
# manila/share/drivers/lvm.py: lvconvert --merge %s
|
||||||
|
lvconvert: CommandFilter, lvconvert, root
|
||||||
|
|
||||||
|
# manila/share/drivers/lvm.py: lvchange -an %s
|
||||||
|
# manila/share/drivers/lvm.py: lvchange -ay %s
|
||||||
|
lvchange: CommandFilter, lvchange, root
|
||||||
|
|
||||||
# manila/data/utils.py: 'sha256sum', '%s'
|
# manila/data/utils.py: 'sha256sum', '%s'
|
||||||
sha256sum: CommandFilter, sha256sum, root
|
sha256sum: CommandFilter, sha256sum, root
|
||||||
|
@ -84,13 +84,15 @@ REST_API_VERSION_HISTORY = """
|
|||||||
spec. Also made the 'snapshot_support' extra spec optional.
|
spec. Also made the 'snapshot_support' extra spec optional.
|
||||||
* 2.25 - Added quota-show detail API.
|
* 2.25 - Added quota-show detail API.
|
||||||
* 2.26 - Removed 'nova_net_id' parameter from share_network API.
|
* 2.26 - Removed 'nova_net_id' parameter from share_network API.
|
||||||
|
|
||||||
|
* 2.27 - Added share revert to snapshot API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The minimum and maximum versions of the API supported
|
# The minimum and maximum versions of the API supported
|
||||||
# The default api version request is defined to be the
|
# The default api version request is defined to be the
|
||||||
# minimum version of the API supported.
|
# minimum version of the API supported.
|
||||||
_MIN_API_VERSION = "2.0"
|
_MIN_API_VERSION = "2.0"
|
||||||
_MAX_API_VERSION = "2.26"
|
_MAX_API_VERSION = "2.27"
|
||||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,3 +156,10 @@ user documentation.
|
|||||||
----
|
----
|
||||||
Removed nova-net plugin support and removed 'nova_net_id' parameter from
|
Removed nova-net plugin support and removed 'nova_net_id' parameter from
|
||||||
share_network API.
|
share_network API.
|
||||||
|
|
||||||
|
2.27
|
||||||
|
----
|
||||||
|
Added share revert to snapshot. This API reverts a share to the specified
|
||||||
|
snapshot. The share is reverted in place, and the snapshot must be the most
|
||||||
|
recent one known to manila. The feature is controlled by a new standard
|
||||||
|
optional extra spec, revert_to_snapshot_support.
|
||||||
|
@ -267,8 +267,10 @@ class ShareMixin(object):
|
|||||||
# Verify that share can be created from a snapshot
|
# Verify that share can be created from a snapshot
|
||||||
if (check_create_share_from_snapshot_support and
|
if (check_create_share_from_snapshot_support and
|
||||||
not parent_share['create_share_from_snapshot_support']):
|
not parent_share['create_share_from_snapshot_support']):
|
||||||
msg = _("Share cannot be created from snapshot '%s', because "
|
msg = (_("A new share may not be created from snapshot '%s', "
|
||||||
"share back end does not support it.") % snapshot_id
|
"because the snapshot's parent share does not have "
|
||||||
|
"that capability.")
|
||||||
|
% snapshot_id)
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exc.HTTPBadRequest(explanation=msg)
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
import six
|
import six
|
||||||
import webob
|
import webob
|
||||||
from webob import exc
|
from webob import exc
|
||||||
@ -25,12 +26,15 @@ from manila.api.v1 import shares
|
|||||||
from manila.api.views import share_accesses as share_access_views
|
from manila.api.views import share_accesses as share_access_views
|
||||||
from manila.api.views import share_migration as share_migration_views
|
from manila.api.views import share_migration as share_migration_views
|
||||||
from manila.api.views import shares as share_views
|
from manila.api.views import shares as share_views
|
||||||
|
from manila.common import constants
|
||||||
from manila import db
|
from manila import db
|
||||||
from manila import exception
|
from manila import exception
|
||||||
from manila.i18n import _
|
from manila.i18n import _, _LI
|
||||||
from manila import share
|
from manila import share
|
||||||
from manila import utils
|
from manila import utils
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ShareController(shares.ShareMixin,
|
class ShareController(shares.ShareMixin,
|
||||||
share_manage.ShareManageMixin,
|
share_manage.ShareManageMixin,
|
||||||
@ -47,6 +51,118 @@ class ShareController(shares.ShareMixin,
|
|||||||
self._access_view_builder = share_access_views.ViewBuilder()
|
self._access_view_builder = share_access_views.ViewBuilder()
|
||||||
self._migration_view_builder = share_migration_views.ViewBuilder()
|
self._migration_view_builder = share_migration_views.ViewBuilder()
|
||||||
|
|
||||||
|
@wsgi.Controller.authorize('revert_to_snapshot')
|
||||||
|
def _revert(self, req, id, body=None):
|
||||||
|
"""Revert a share to a snapshot."""
|
||||||
|
context = req.environ['manila.context']
|
||||||
|
revert_data = self._validate_revert_parameters(context, body)
|
||||||
|
|
||||||
|
try:
|
||||||
|
share_id = id
|
||||||
|
snapshot_id = revert_data['snapshot_id']
|
||||||
|
|
||||||
|
share = self.share_api.get(context, share_id)
|
||||||
|
snapshot = self.share_api.get_snapshot(context, snapshot_id)
|
||||||
|
|
||||||
|
# Ensure share supports reverting to a snapshot
|
||||||
|
if not share['revert_to_snapshot_support']:
|
||||||
|
msg_args = {'share_id': share_id, 'snap_id': snapshot_id}
|
||||||
|
msg = _('Share %(share_id)s may not be reverted to snapshot '
|
||||||
|
'%(snap_id)s, because the share does not have that '
|
||||||
|
'capability.')
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg % msg_args)
|
||||||
|
|
||||||
|
# Ensure requested share & snapshot match.
|
||||||
|
if share['id'] != snapshot['share_id']:
|
||||||
|
msg_args = {'share_id': share_id, 'snap_id': snapshot_id}
|
||||||
|
msg = _('Snapshot %(snap_id)s is not associated with share '
|
||||||
|
'%(share_id)s.')
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg % msg_args)
|
||||||
|
|
||||||
|
# Ensure share status is 'available'.
|
||||||
|
if share['status'] != constants.STATUS_AVAILABLE:
|
||||||
|
msg_args = {
|
||||||
|
'share_id': share_id,
|
||||||
|
'state': share['status'],
|
||||||
|
'available': constants.STATUS_AVAILABLE,
|
||||||
|
}
|
||||||
|
msg = _("Share %(share_id)s is in '%(state)s' state, but it "
|
||||||
|
"must be in '%(available)s' state to be reverted to a "
|
||||||
|
"snapshot.")
|
||||||
|
raise exc.HTTPConflict(explanation=msg % msg_args)
|
||||||
|
|
||||||
|
# Ensure snapshot status is 'available'.
|
||||||
|
if snapshot['status'] != constants.STATUS_AVAILABLE:
|
||||||
|
msg_args = {
|
||||||
|
'snap_id': snapshot_id,
|
||||||
|
'state': snapshot['status'],
|
||||||
|
'available': constants.STATUS_AVAILABLE,
|
||||||
|
}
|
||||||
|
msg = _("Snapshot %(snap_id)s is in '%(state)s' state, but it "
|
||||||
|
"must be in '%(available)s' state to be restored.")
|
||||||
|
raise exc.HTTPConflict(explanation=msg % msg_args)
|
||||||
|
|
||||||
|
# Ensure a long-running task isn't active on the share
|
||||||
|
if share.is_busy:
|
||||||
|
msg_args = {'share_id': share_id}
|
||||||
|
msg = _("Share %(share_id)s may not be reverted while it has "
|
||||||
|
"an active task.")
|
||||||
|
raise exc.HTTPConflict(explanation=msg % msg_args)
|
||||||
|
|
||||||
|
# Ensure the snapshot is the most recent one.
|
||||||
|
latest_snapshot = self.share_api.get_latest_snapshot_for_share(
|
||||||
|
context, share_id)
|
||||||
|
if not latest_snapshot:
|
||||||
|
msg_args = {'share_id': share_id}
|
||||||
|
msg = _("Could not determine the latest snapshot for share "
|
||||||
|
"%(share_id)s.")
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg % msg_args)
|
||||||
|
if latest_snapshot['id'] != snapshot_id:
|
||||||
|
msg_args = {
|
||||||
|
'share_id': share_id,
|
||||||
|
'snap_id': snapshot_id,
|
||||||
|
'latest_snap_id': latest_snapshot['id'],
|
||||||
|
}
|
||||||
|
msg = _("Snapshot %(snap_id)s may not be restored because "
|
||||||
|
"it is not the most recent snapshot of share "
|
||||||
|
"%(share_id)s. Currently the latest snapshot is "
|
||||||
|
"%(latest_snap_id)s.")
|
||||||
|
raise exc.HTTPConflict(explanation=msg % msg_args)
|
||||||
|
|
||||||
|
msg_args = {'share_id': share_id, 'snap_id': snapshot_id}
|
||||||
|
msg = _LI('Reverting share %(share_id)s to snapshot %(snap_id)s.')
|
||||||
|
LOG.info(msg, msg_args)
|
||||||
|
|
||||||
|
self.share_api.revert_to_snapshot(context, snapshot)
|
||||||
|
except exception.ShareNotFound as e:
|
||||||
|
raise exc.HTTPNotFound(explanation=six.text_type(e))
|
||||||
|
except exception.ShareSnapshotNotFound as e:
|
||||||
|
raise exc.HTTPBadRequest(explanation=six.text_type(e))
|
||||||
|
except exception.ShareSizeExceedsAvailableQuota as e:
|
||||||
|
raise exc.HTTPForbidden(explanation=six.text_type(e))
|
||||||
|
except exception.ReplicationException as e:
|
||||||
|
raise exc.HTTPBadRequest(explanation=six.text_type(e))
|
||||||
|
|
||||||
|
return webob.Response(status_int=202)
|
||||||
|
|
||||||
|
def _validate_revert_parameters(self, context, body):
|
||||||
|
if not (body and self.is_valid_body(body, 'revert')):
|
||||||
|
msg = _("Revert entity not found in request body.")
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
|
required_parameters = ('snapshot_id',)
|
||||||
|
data = body['revert']
|
||||||
|
|
||||||
|
for parameter in required_parameters:
|
||||||
|
if parameter not in data:
|
||||||
|
msg = _("Required parameter %s not found.") % parameter
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
if not data.get(parameter):
|
||||||
|
msg = _("Required parameter %s is empty.") % parameter
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
@wsgi.Controller.api_version("2.0", "2.3")
|
@wsgi.Controller.api_version("2.0", "2.3")
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
# Remove consistency group attributes
|
# Remove consistency group attributes
|
||||||
@ -276,6 +392,11 @@ class ShareController(shares.ShareMixin,
|
|||||||
def unmanage(self, req, id, body=None):
|
def unmanage(self, req, id, body=None):
|
||||||
return self._unmanage(req, id, body)
|
return self._unmanage(req, id, body)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version('2.27')
|
||||||
|
@wsgi.action('revert')
|
||||||
|
def revert(self, req, id, body=None):
|
||||||
|
return self._revert(req, id, body)
|
||||||
|
|
||||||
|
|
||||||
def create_resource():
|
def create_resource():
|
||||||
return wsgi.Resource(ShareController())
|
return wsgi.Resource(ShareController())
|
||||||
|
@ -30,6 +30,7 @@ class ViewBuilder(common.ViewBuilder):
|
|||||||
"add_replication_fields",
|
"add_replication_fields",
|
||||||
"add_user_id",
|
"add_user_id",
|
||||||
"add_create_share_from_snapshot_support_field",
|
"add_create_share_from_snapshot_support_field",
|
||||||
|
"add_revert_to_snapshot_support_field",
|
||||||
]
|
]
|
||||||
|
|
||||||
def summary_list(self, request, shares):
|
def summary_list(self, request, shares):
|
||||||
@ -148,6 +149,11 @@ class ViewBuilder(common.ViewBuilder):
|
|||||||
share_dict['create_share_from_snapshot_support'] = share.get(
|
share_dict['create_share_from_snapshot_support'] = share.get(
|
||||||
'create_share_from_snapshot_support')
|
'create_share_from_snapshot_support')
|
||||||
|
|
||||||
|
@common.ViewBuilder.versioned_method("2.27")
|
||||||
|
def add_revert_to_snapshot_support_field(self, context, share_dict, share):
|
||||||
|
share_dict['revert_to_snapshot_support'] = share.get(
|
||||||
|
'revert_to_snapshot_support')
|
||||||
|
|
||||||
def _list_view(self, func, request, shares):
|
def _list_view(self, func, request, shares):
|
||||||
"""Provide a view for a list of shares."""
|
"""Provide a view for a list of shares."""
|
||||||
shares_list = [func(request, share)['share'] for share in shares]
|
shares_list = [func(request, share)['share'] for share in shares]
|
||||||
|
@ -40,6 +40,9 @@ STATUS_SHRINKING_POSSIBLE_DATA_LOSS_ERROR = (
|
|||||||
'shrinking_possible_data_loss_error'
|
'shrinking_possible_data_loss_error'
|
||||||
)
|
)
|
||||||
STATUS_REPLICATION_CHANGE = 'replication_change'
|
STATUS_REPLICATION_CHANGE = 'replication_change'
|
||||||
|
STATUS_RESTORING = 'restoring'
|
||||||
|
STATUS_REVERTING = 'reverting'
|
||||||
|
STATUS_REVERTING_ERROR = 'reverting_error'
|
||||||
|
|
||||||
TASK_STATE_MIGRATION_STARTING = 'migration_starting'
|
TASK_STATE_MIGRATION_STARTING = 'migration_starting'
|
||||||
TASK_STATE_MIGRATION_IN_PROGRESS = 'migration_in_progress'
|
TASK_STATE_MIGRATION_IN_PROGRESS = 'migration_in_progress'
|
||||||
@ -81,6 +84,7 @@ TRANSITIONAL_STATUSES = (
|
|||||||
STATUS_MANAGING, STATUS_UNMANAGING,
|
STATUS_MANAGING, STATUS_UNMANAGING,
|
||||||
STATUS_EXTENDING, STATUS_SHRINKING,
|
STATUS_EXTENDING, STATUS_SHRINKING,
|
||||||
STATUS_MIGRATING, STATUS_MIGRATING_TO,
|
STATUS_MIGRATING, STATUS_MIGRATING_TO,
|
||||||
|
STATUS_RESTORING, STATUS_REVERTING,
|
||||||
)
|
)
|
||||||
|
|
||||||
UPDATING_RULES_STATUSES = (
|
UPDATING_RULES_STATUSES = (
|
||||||
@ -161,6 +165,7 @@ class ExtraSpecs(object):
|
|||||||
SNAPSHOT_SUPPORT = "snapshot_support"
|
SNAPSHOT_SUPPORT = "snapshot_support"
|
||||||
REPLICATION_TYPE_SPEC = "replication_type"
|
REPLICATION_TYPE_SPEC = "replication_type"
|
||||||
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT = "create_share_from_snapshot_support"
|
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT = "create_share_from_snapshot_support"
|
||||||
|
REVERT_TO_SNAPSHOT_SUPPORT = "revert_to_snapshot_support"
|
||||||
|
|
||||||
# Extra specs containers
|
# Extra specs containers
|
||||||
REQUIRED = (
|
REQUIRED = (
|
||||||
@ -170,6 +175,7 @@ class ExtraSpecs(object):
|
|||||||
OPTIONAL = (
|
OPTIONAL = (
|
||||||
SNAPSHOT_SUPPORT,
|
SNAPSHOT_SUPPORT,
|
||||||
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT,
|
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT,
|
||||||
|
REVERT_TO_SNAPSHOT_SUPPORT,
|
||||||
REPLICATION_TYPE_SPEC,
|
REPLICATION_TYPE_SPEC,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -182,6 +188,7 @@ class ExtraSpecs(object):
|
|||||||
DRIVER_HANDLES_SHARE_SERVERS,
|
DRIVER_HANDLES_SHARE_SERVERS,
|
||||||
SNAPSHOT_SUPPORT,
|
SNAPSHOT_SUPPORT,
|
||||||
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT,
|
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT,
|
||||||
|
REVERT_TO_SNAPSHOT_SUPPORT,
|
||||||
)
|
)
|
||||||
|
|
||||||
# NOTE(cknight): Some extra specs are optional, but a nominal (typically
|
# NOTE(cknight): Some extra specs are optional, but a nominal (typically
|
||||||
@ -190,6 +197,7 @@ class ExtraSpecs(object):
|
|||||||
INFERRED_OPTIONAL_MAP = {
|
INFERRED_OPTIONAL_MAP = {
|
||||||
SNAPSHOT_SUPPORT: False,
|
SNAPSHOT_SUPPORT: False,
|
||||||
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT: False,
|
CREATE_SHARE_FROM_SNAPSHOT_SUPPORT: False,
|
||||||
|
REVERT_TO_SNAPSHOT_SUPPORT: False,
|
||||||
}
|
}
|
||||||
|
|
||||||
REPLICATION_TYPES = ('writable', 'readable', 'dr')
|
REPLICATION_TYPES = ('writable', 'readable', 'dr')
|
||||||
|
@ -530,6 +530,11 @@ def share_snapshot_get_all_for_share(context, share_id, filters=None,
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def share_snapshot_get_latest_for_share(context, share_id):
|
||||||
|
"""Get the most recent snapshot for a share."""
|
||||||
|
return IMPL.share_snapshot_get_latest_for_share(context, share_id)
|
||||||
|
|
||||||
|
|
||||||
def share_snapshot_update(context, snapshot_id, values):
|
def share_snapshot_update(context, snapshot_id, values):
|
||||||
"""Set the given properties on an snapshot and update it.
|
"""Set the given properties on an snapshot and update it.
|
||||||
|
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
# Copyright (c) 2016 Clinton Knight. 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.
|
||||||
|
|
||||||
|
"""add_revert_to_snapshot_support
|
||||||
|
|
||||||
|
Revision ID: 87ce15c59bbe
|
||||||
|
Revises: 3e7d62517afa
|
||||||
|
Create Date: 2016-08-18 00:12:34.587018
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '87ce15c59bbe'
|
||||||
|
down_revision = '95e3cf760840'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
"""Performs DB upgrade to add revert_to_snapshot_support.
|
||||||
|
|
||||||
|
Add attribute 'revert_to_snapshot_support' to Share model.
|
||||||
|
"""
|
||||||
|
session = sa.orm.Session(bind=op.get_bind().connect())
|
||||||
|
|
||||||
|
# Add create_share_from_snapshot_support attribute to shares table
|
||||||
|
op.add_column(
|
||||||
|
'shares',
|
||||||
|
sa.Column('revert_to_snapshot_support', sa.Boolean, default=False))
|
||||||
|
|
||||||
|
# Set revert_to_snapshot_support on each share
|
||||||
|
shares_table = sa.Table(
|
||||||
|
'shares',
|
||||||
|
sa.MetaData(),
|
||||||
|
sa.Column('id', sa.String(length=36)),
|
||||||
|
sa.Column('deleted', sa.String(length=36)),
|
||||||
|
sa.Column('revert_to_snapshot_support', sa.Boolean),
|
||||||
|
)
|
||||||
|
update = shares_table.update().where(
|
||||||
|
shares_table.c.deleted == 'False').values(
|
||||||
|
revert_to_snapshot_support=False)
|
||||||
|
session.execute(update)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
session.close_all()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
"""Performs DB downgrade removing revert_to_snapshot_support.
|
||||||
|
|
||||||
|
Remove attribute 'revert_to_snapshot_support' from Share model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
op.drop_column('shares', 'revert_to_snapshot_support')
|
@ -2192,6 +2192,14 @@ def share_snapshot_get_all_for_share(context, share_id, filters=None,
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@require_context
|
||||||
|
def share_snapshot_get_latest_for_share(context, share_id):
|
||||||
|
|
||||||
|
snapshots = _share_snapshot_get_all_with_filters(
|
||||||
|
context, share_id=share_id, sort_key='created_at', sort_dir='desc')
|
||||||
|
return snapshots[0] if snapshots else None
|
||||||
|
|
||||||
|
|
||||||
@require_context
|
@require_context
|
||||||
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
|
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
|
||||||
def share_snapshot_update(context, snapshot_id, values):
|
def share_snapshot_update(context, snapshot_id, values):
|
||||||
|
@ -264,7 +264,8 @@ class Share(BASE, ManilaBase):
|
|||||||
# preferred.
|
# preferred.
|
||||||
result = None
|
result = None
|
||||||
if len(self.instances) > 0:
|
if len(self.instances) > 0:
|
||||||
order = (constants.STATUS_REPLICATION_CHANGE,
|
order = (constants.STATUS_REVERTING,
|
||||||
|
constants.STATUS_REPLICATION_CHANGE,
|
||||||
constants.STATUS_MIGRATING, constants.STATUS_AVAILABLE,
|
constants.STATUS_MIGRATING, constants.STATUS_AVAILABLE,
|
||||||
constants.STATUS_ERROR)
|
constants.STATUS_ERROR)
|
||||||
other_statuses = (
|
other_statuses = (
|
||||||
@ -303,6 +304,7 @@ class Share(BASE, ManilaBase):
|
|||||||
snapshot_id = Column(String(36))
|
snapshot_id = Column(String(36))
|
||||||
snapshot_support = Column(Boolean, default=True)
|
snapshot_support = Column(Boolean, default=True)
|
||||||
create_share_from_snapshot_support = Column(Boolean, default=True)
|
create_share_from_snapshot_support = Column(Boolean, default=True)
|
||||||
|
revert_to_snapshot_support = Column(Boolean, default=False)
|
||||||
replication_type = Column(String(255), nullable=True)
|
replication_type = Column(String(255), nullable=True)
|
||||||
share_proto = Column(String(255))
|
share_proto = Column(String(255))
|
||||||
is_public = Column(Boolean, default=False)
|
is_public = Column(Boolean, default=False)
|
||||||
|
@ -131,6 +131,7 @@ class HostState(object):
|
|||||||
self.driver_handles_share_servers = False
|
self.driver_handles_share_servers = False
|
||||||
self.snapshot_support = True
|
self.snapshot_support = True
|
||||||
self.create_share_from_snapshot_support = True
|
self.create_share_from_snapshot_support = True
|
||||||
|
self.revert_to_snapshot_support = False
|
||||||
self.consistency_group_support = False
|
self.consistency_group_support = False
|
||||||
self.dedupe = False
|
self.dedupe = False
|
||||||
self.compression = False
|
self.compression = False
|
||||||
@ -299,6 +300,10 @@ class HostState(object):
|
|||||||
pool_cap['create_share_from_snapshot_support'] = (
|
pool_cap['create_share_from_snapshot_support'] = (
|
||||||
self.create_share_from_snapshot_support)
|
self.create_share_from_snapshot_support)
|
||||||
|
|
||||||
|
if 'revert_to_snapshot_support' not in pool_cap:
|
||||||
|
pool_cap['revert_to_snapshot_support'] = (
|
||||||
|
self.revert_to_snapshot_support)
|
||||||
|
|
||||||
if not pool_cap.get('consistency_group_support'):
|
if not pool_cap.get('consistency_group_support'):
|
||||||
pool_cap['consistency_group_support'] = \
|
pool_cap['consistency_group_support'] = \
|
||||||
self.consistency_group_support
|
self.consistency_group_support
|
||||||
@ -325,6 +330,8 @@ class HostState(object):
|
|||||||
self.snapshot_support = capability.get('snapshot_support')
|
self.snapshot_support = capability.get('snapshot_support')
|
||||||
self.create_share_from_snapshot_support = capability.get(
|
self.create_share_from_snapshot_support = capability.get(
|
||||||
'create_share_from_snapshot_support')
|
'create_share_from_snapshot_support')
|
||||||
|
self.revert_to_snapshot_support = capability.get(
|
||||||
|
'revert_to_snapshot_support', False)
|
||||||
self.consistency_group_support = capability.get(
|
self.consistency_group_support = capability.get(
|
||||||
'consistency_group_support', False)
|
'consistency_group_support', False)
|
||||||
self.updated = capability['timestamp']
|
self.updated = capability['timestamp']
|
||||||
|
@ -46,6 +46,7 @@ def generate_stats(host_state, properties):
|
|||||||
'snapshot_support': host_state.snapshot_support,
|
'snapshot_support': host_state.snapshot_support,
|
||||||
'create_share_from_snapshot_support':
|
'create_share_from_snapshot_support':
|
||||||
host_state.create_share_from_snapshot_support,
|
host_state.create_share_from_snapshot_support,
|
||||||
|
'revert_to_snapshot_support': host_state.revert_to_snapshot_support,
|
||||||
'replication_domain': host_state.replication_domain,
|
'replication_domain': host_state.replication_domain,
|
||||||
'replication_type': host_state.replication_type,
|
'replication_type': host_state.replication_type,
|
||||||
'provisioned_capacity_gb': host_state.provisioned_capacity_gb,
|
'provisioned_capacity_gb': host_state.provisioned_capacity_gb,
|
||||||
|
@ -266,40 +266,49 @@ class API(base.Base):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
inferred_map = constants.ExtraSpecs.INFERRED_OPTIONAL_MAP
|
inferred_map = constants.ExtraSpecs.INFERRED_OPTIONAL_MAP
|
||||||
snapshot_support_default = inferred_map.get(
|
snapshot_support_key = constants.ExtraSpecs.SNAPSHOT_SUPPORT
|
||||||
constants.ExtraSpecs.SNAPSHOT_SUPPORT)
|
|
||||||
create_share_from_snapshot_support_default = inferred_map.get(
|
|
||||||
constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT)
|
|
||||||
create_share_from_snapshot_key = (
|
create_share_from_snapshot_key = (
|
||||||
constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT)
|
constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT)
|
||||||
|
revert_to_snapshot_key = (
|
||||||
|
constants.ExtraSpecs.REVERT_TO_SNAPSHOT_SUPPORT)
|
||||||
|
|
||||||
try:
|
snapshot_support_default = inferred_map.get(snapshot_support_key)
|
||||||
if share_type:
|
create_share_from_snapshot_support_default = inferred_map.get(
|
||||||
snapshot_support = share_types.parse_boolean_extra_spec(
|
create_share_from_snapshot_key)
|
||||||
constants.ExtraSpecs.SNAPSHOT_SUPPORT,
|
revert_to_snapshot_support_default = inferred_map.get(
|
||||||
|
revert_to_snapshot_key)
|
||||||
|
|
||||||
|
if share_type:
|
||||||
|
snapshot_support = share_types.parse_boolean_extra_spec(
|
||||||
|
snapshot_support_key,
|
||||||
|
share_type.get('extra_specs', {}).get(
|
||||||
|
snapshot_support_key, snapshot_support_default))
|
||||||
|
create_share_from_snapshot_support = (
|
||||||
|
share_types.parse_boolean_extra_spec(
|
||||||
|
create_share_from_snapshot_key,
|
||||||
share_type.get('extra_specs', {}).get(
|
share_type.get('extra_specs', {}).get(
|
||||||
constants.ExtraSpecs.SNAPSHOT_SUPPORT,
|
create_share_from_snapshot_key,
|
||||||
snapshot_support_default))
|
create_share_from_snapshot_support_default)))
|
||||||
create_share_from_snapshot_support = (
|
revert_to_snapshot_support = (
|
||||||
share_types.parse_boolean_extra_spec(
|
share_types.parse_boolean_extra_spec(
|
||||||
create_share_from_snapshot_key, share_type.get(
|
revert_to_snapshot_key,
|
||||||
'extra_specs', {}).get(
|
share_type.get('extra_specs', {}).get(
|
||||||
create_share_from_snapshot_key,
|
revert_to_snapshot_key,
|
||||||
create_share_from_snapshot_support_default)))
|
revert_to_snapshot_support_default)))
|
||||||
replication_type = share_type.get('extra_specs', {}).get(
|
replication_type = share_type.get('extra_specs', {}).get(
|
||||||
'replication_type')
|
'replication_type')
|
||||||
else:
|
else:
|
||||||
snapshot_support = snapshot_support_default
|
snapshot_support = snapshot_support_default
|
||||||
create_share_from_snapshot_support = (
|
create_share_from_snapshot_support = (
|
||||||
create_share_from_snapshot_support_default)
|
create_share_from_snapshot_support_default)
|
||||||
replication_type = None
|
revert_to_snapshot_support = revert_to_snapshot_support_default
|
||||||
except Exception as e:
|
replication_type = None
|
||||||
raise exception.InvalidParameterValue(six.text_type(e))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'snapshot_support': snapshot_support,
|
'snapshot_support': snapshot_support,
|
||||||
'create_share_from_snapshot_support':
|
'create_share_from_snapshot_support':
|
||||||
create_share_from_snapshot_support,
|
create_share_from_snapshot_support,
|
||||||
|
'revert_to_snapshot_support': revert_to_snapshot_support,
|
||||||
'replication_type': replication_type,
|
'replication_type': replication_type,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,6 +391,7 @@ class API(base.Base):
|
|||||||
'snapshot_support': share['snapshot_support'],
|
'snapshot_support': share['snapshot_support'],
|
||||||
'create_share_from_snapshot_support':
|
'create_share_from_snapshot_support':
|
||||||
share['create_share_from_snapshot_support'],
|
share['create_share_from_snapshot_support'],
|
||||||
|
'revert_to_snapshot_support': share['revert_to_snapshot_support'],
|
||||||
'share_proto': share['share_proto'],
|
'share_proto': share['share_proto'],
|
||||||
'share_type_id': share_type_id,
|
'share_type_id': share_type_id,
|
||||||
'is_public': share['is_public'],
|
'is_public': share['is_public'],
|
||||||
@ -614,6 +624,12 @@ class API(base.Base):
|
|||||||
share_type.get('extra_specs', {}).get(
|
share_type.get('extra_specs', {}).get(
|
||||||
'create_share_from_snapshot_support')
|
'create_share_from_snapshot_support')
|
||||||
),
|
),
|
||||||
|
'revert_to_snapshot_support': kwargs.get(
|
||||||
|
'revert_to_snapshot_support',
|
||||||
|
share_type.get('extra_specs', {}).get(
|
||||||
|
'revert_to_snapshot_support')
|
||||||
|
),
|
||||||
|
|
||||||
'share_proto': kwargs.get('share_proto', share.get('share_proto')),
|
'share_proto': kwargs.get('share_proto', share.get('share_proto')),
|
||||||
'share_type_id': share_type['id'],
|
'share_type_id': share_type['id'],
|
||||||
'is_public': kwargs.get('is_public', share.get('is_public')),
|
'is_public': kwargs.get('is_public', share.get('is_public')),
|
||||||
@ -713,6 +729,120 @@ class API(base.Base):
|
|||||||
|
|
||||||
self.share_rpcapi.unmanage_snapshot(context, snapshot_ref, host)
|
self.share_rpcapi.unmanage_snapshot(context, snapshot_ref, host)
|
||||||
|
|
||||||
|
def revert_to_snapshot(self, context, snapshot):
|
||||||
|
"""Revert a share to a snapshot."""
|
||||||
|
|
||||||
|
share = self.db.share_get(context, snapshot['share_id'])
|
||||||
|
reservations = self._handle_revert_to_snapshot_quotas(
|
||||||
|
context, share, snapshot)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if share.get('has_replicas'):
|
||||||
|
self._revert_to_replicated_snapshot(
|
||||||
|
context, share, snapshot, reservations)
|
||||||
|
else:
|
||||||
|
self._revert_to_snapshot(
|
||||||
|
context, share, snapshot, reservations)
|
||||||
|
except Exception:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
if reservations:
|
||||||
|
QUOTAS.rollback(context, reservations)
|
||||||
|
|
||||||
|
def _handle_revert_to_snapshot_quotas(self, context, share, snapshot):
|
||||||
|
"""Reserve extra quota if a revert will result in a larger share."""
|
||||||
|
|
||||||
|
# Note(cknight): This value may be positive or negative.
|
||||||
|
size_increase = snapshot['size'] - share['size']
|
||||||
|
if not size_increase:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return QUOTAS.reserve(context,
|
||||||
|
project_id=share['project_id'],
|
||||||
|
gigabytes=size_increase,
|
||||||
|
user_id=share['user_id'])
|
||||||
|
except exception.OverQuota as exc:
|
||||||
|
usages = exc.kwargs['usages']
|
||||||
|
quotas = exc.kwargs['quotas']
|
||||||
|
consumed_gb = (usages['gigabytes']['reserved'] +
|
||||||
|
usages['gigabytes']['in_use'])
|
||||||
|
|
||||||
|
msg = _("Quota exceeded for %(s_pid)s. Reverting share "
|
||||||
|
"%(s_sid)s to snapshot %(s_ssid)s will increase the "
|
||||||
|
"share's size by %(s_size)sG, "
|
||||||
|
"(%(d_consumed)dG of %(d_quota)dG already consumed).")
|
||||||
|
msg_args = {
|
||||||
|
's_pid': context.project_id,
|
||||||
|
's_sid': share['id'],
|
||||||
|
's_ssid': snapshot['id'],
|
||||||
|
's_size': size_increase,
|
||||||
|
'd_consumed': consumed_gb,
|
||||||
|
'd_quota': quotas['gigabytes'],
|
||||||
|
}
|
||||||
|
message = msg % msg_args
|
||||||
|
LOG.error(message)
|
||||||
|
raise exception.ShareSizeExceedsAvailableQuota(message=message)
|
||||||
|
|
||||||
|
def _revert_to_snapshot(self, context, share, snapshot, reservations):
|
||||||
|
"""Revert a non-replicated share to a snapshot."""
|
||||||
|
|
||||||
|
# Set status of share to 'reverting'
|
||||||
|
self.db.share_update(
|
||||||
|
context, snapshot['share_id'],
|
||||||
|
{'status': constants.STATUS_REVERTING})
|
||||||
|
|
||||||
|
# Set status of snapshot to 'restoring'
|
||||||
|
self.db.share_snapshot_update(
|
||||||
|
context, snapshot['id'],
|
||||||
|
{'status': constants.STATUS_RESTORING})
|
||||||
|
|
||||||
|
# Send revert API to share host
|
||||||
|
self.share_rpcapi.revert_to_snapshot(
|
||||||
|
context, share, snapshot, share['instance']['host'], reservations)
|
||||||
|
|
||||||
|
def _revert_to_replicated_snapshot(self, context, share, snapshot,
|
||||||
|
reservations):
|
||||||
|
"""Revert a replicated share to a snapshot."""
|
||||||
|
|
||||||
|
# Get active replica
|
||||||
|
active_replica = self.db.share_replicas_get_available_active_replica(
|
||||||
|
context, share['id'])
|
||||||
|
|
||||||
|
if not active_replica:
|
||||||
|
msg = _('Share %s has no active replica in available state.')
|
||||||
|
raise exception.ReplicationException(reason=msg % share['id'])
|
||||||
|
|
||||||
|
# Get snapshot instance on active replica
|
||||||
|
snapshot_instance_filters = {
|
||||||
|
'share_instance_ids': active_replica['id'],
|
||||||
|
'snapshot_ids': snapshot['id'],
|
||||||
|
}
|
||||||
|
snapshot_instances = (
|
||||||
|
self.db.share_snapshot_instance_get_all_with_filters(
|
||||||
|
context, snapshot_instance_filters))
|
||||||
|
active_snapshot_instance = (
|
||||||
|
snapshot_instances[0] if snapshot_instances else None)
|
||||||
|
|
||||||
|
if not active_snapshot_instance:
|
||||||
|
msg = _('Share %(share)s has no snapshot %(snap)s associated with '
|
||||||
|
'its active replica.')
|
||||||
|
msg_args = {'share': share['id'], 'snap': snapshot['id']}
|
||||||
|
raise exception.ReplicationException(reason=msg % msg_args)
|
||||||
|
|
||||||
|
# Set active replica to 'reverting'
|
||||||
|
self.db.share_replica_update(
|
||||||
|
context, active_replica['id'],
|
||||||
|
{'status': constants.STATUS_REVERTING})
|
||||||
|
|
||||||
|
# Set snapshot instance on active replica to 'restoring'
|
||||||
|
self.db.share_snapshot_instance_update(
|
||||||
|
context, active_snapshot_instance['id'],
|
||||||
|
{'status': constants.STATUS_RESTORING})
|
||||||
|
|
||||||
|
# Send revert API to active replica host
|
||||||
|
self.share_rpcapi.revert_to_snapshot(
|
||||||
|
context, share, snapshot, active_replica['host'], reservations)
|
||||||
|
|
||||||
@policy.wrap_check_policy('share')
|
@policy.wrap_check_policy('share')
|
||||||
def delete(self, context, share, force=False):
|
def delete(self, context, share, force=False):
|
||||||
"""Delete share."""
|
"""Delete share."""
|
||||||
@ -1379,6 +1509,10 @@ class API(base.Base):
|
|||||||
snapshots = results
|
snapshots = results
|
||||||
return snapshots
|
return snapshots
|
||||||
|
|
||||||
|
def get_latest_snapshot_for_share(self, context, share_id):
|
||||||
|
"""Get the newest snapshot of a share."""
|
||||||
|
return self.db.share_snapshot_get_latest_for_share(context, share_id)
|
||||||
|
|
||||||
def allow_access(self, ctx, share, access_type, access_to,
|
def allow_access(self, ctx, share, access_type, access_to,
|
||||||
access_level=None):
|
access_level=None):
|
||||||
"""Allow access to share."""
|
"""Allow access to share."""
|
||||||
|
@ -855,6 +855,24 @@ class ShareDriver(object):
|
|||||||
the failure.
|
the failure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def revert_to_snapshot(self, context, snapshot, share_server=None):
|
||||||
|
"""Reverts a share (in place) to the specified snapshot.
|
||||||
|
|
||||||
|
Does not delete the share snapshot. The share and snapshot must both
|
||||||
|
be 'available' for the restore to be attempted. The snapshot must be
|
||||||
|
the most recent one taken by Manila; the API layer performs this check
|
||||||
|
so the driver doesn't have to.
|
||||||
|
|
||||||
|
The share must be reverted in place to the contents of the snapshot.
|
||||||
|
Application admins should quiesce or otherwise prepare the application
|
||||||
|
for the shared file system contents to change suddenly.
|
||||||
|
|
||||||
|
:param context: Current context
|
||||||
|
:param snapshot: The snapshot to be restored
|
||||||
|
:param share_server: Optional -- Share server model or None
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def extend_share(self, share, new_size, share_server=None):
|
def extend_share(self, share, new_size, share_server=None):
|
||||||
"""Extends size of existing share.
|
"""Extends size of existing share.
|
||||||
|
|
||||||
@ -957,6 +975,7 @@ class ShareDriver(object):
|
|||||||
snapshot_support=self.snapshots_are_supported,
|
snapshot_support=self.snapshots_are_supported,
|
||||||
create_share_from_snapshot_support=(
|
create_share_from_snapshot_support=(
|
||||||
self.creating_shares_from_snapshots_is_supported),
|
self.creating_shares_from_snapshots_is_supported),
|
||||||
|
revert_to_snapshot_support=False,
|
||||||
replication_domain=self.replication_domain,
|
replication_domain=self.replication_domain,
|
||||||
filter_function=self.get_filter_function(),
|
filter_function=self.get_filter_function(),
|
||||||
goodness_function=self.get_goodness_function(),
|
goodness_function=self.get_goodness_function(),
|
||||||
@ -1788,6 +1807,38 @@ class ShareDriver(object):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def revert_to_replicated_snapshot(self, context, active_replica,
|
||||||
|
replica_list, active_replica_snapshot,
|
||||||
|
replica_snapshots, share_server=None):
|
||||||
|
"""Reverts a replicated share (in place) to the specified snapshot.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This call is made on the 'active' replica's host, since drivers may
|
||||||
|
not be able to revert snapshots on individual replicas.
|
||||||
|
|
||||||
|
Does not delete the share snapshot. The share and snapshot must both
|
||||||
|
be 'available' for the restore to be attempted. The snapshot must be
|
||||||
|
the most recent one taken by Manila; the API layer performs this check
|
||||||
|
so the driver doesn't have to.
|
||||||
|
|
||||||
|
The share must be reverted in place to the contents of the snapshot.
|
||||||
|
Application admins should quiesce or otherwise prepare the application
|
||||||
|
for the shared file system contents to change suddenly.
|
||||||
|
|
||||||
|
:param context: Current context
|
||||||
|
:param active_replica: The current active replica
|
||||||
|
:param replica_list: List of all replicas for a particular share
|
||||||
|
The 'active' replica will have its 'replica_state' attr set to
|
||||||
|
'active' and its 'status' set to 'reverting'.
|
||||||
|
:param active_replica_snapshot: snapshot to be restored
|
||||||
|
:param replica_snapshots: List of dictionaries of snapshot instances.
|
||||||
|
These snapshot instances track the snapshot across the replicas.
|
||||||
|
The snapshot of the active replica to be restored with have its
|
||||||
|
status attribute set to 'restoring'.
|
||||||
|
:param share_server: Optional -- Share server model
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
def delete_replicated_snapshot(self, context, replica_list,
|
def delete_replicated_snapshot(self, context, replica_list,
|
||||||
replica_snapshots, share_server=None):
|
replica_snapshots, share_server=None):
|
||||||
"""Delete a snapshot by deleting its instances across the replicas.
|
"""Delete a snapshot by deleting its instances across the replicas.
|
||||||
|
@ -108,7 +108,7 @@ class LVMMixin(driver.ExecuteMixin):
|
|||||||
raise
|
raise
|
||||||
LOG.warning(_LW("Volume not found: %s") % exc.stderr)
|
LOG.warning(_LW("Volume not found: %s") % exc.stderr)
|
||||||
|
|
||||||
def create_snapshot(self, context, snapshot, share_server=None):
|
def _create_snapshot(self, context, snapshot):
|
||||||
"""Creates a snapshot."""
|
"""Creates a snapshot."""
|
||||||
orig_lv_name = "%s/%s" % (self.configuration.lvm_share_volume_group,
|
orig_lv_name = "%s/%s" % (self.configuration.lvm_share_volume_group,
|
||||||
snapshot['share_name'])
|
snapshot['share_name'])
|
||||||
@ -121,6 +121,9 @@ class LVMMixin(driver.ExecuteMixin):
|
|||||||
'tune2fs', '-U', 'random', snapshot_device_name, run_as_root=True,
|
'tune2fs', '-U', 'random', snapshot_device_name, run_as_root=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def create_snapshot(self, context, snapshot, share_server=None):
|
||||||
|
self._create_snapshot(context, snapshot)
|
||||||
|
|
||||||
def delete_snapshot(self, context, snapshot, share_server=None):
|
def delete_snapshot(self, context, snapshot, share_server=None):
|
||||||
"""Deletes a snapshot."""
|
"""Deletes a snapshot."""
|
||||||
self._deallocate_container(snapshot['name'])
|
self._deallocate_container(snapshot['name'])
|
||||||
@ -188,6 +191,7 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver):
|
|||||||
'consistency_group_support': None,
|
'consistency_group_support': None,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
'driver_name': 'LVMShareDriver',
|
'driver_name': 'LVMShareDriver',
|
||||||
'pools': self.get_share_server_pools()
|
'pools': self.get_share_server_pools()
|
||||||
}
|
}
|
||||||
@ -356,3 +360,25 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver):
|
|||||||
device_name = self._get_local_path(share)
|
device_name = self._get_local_path(share)
|
||||||
self._extend_container(share, device_name, new_size)
|
self._extend_container(share, device_name, new_size)
|
||||||
self._execute('resize2fs', device_name, run_as_root=True)
|
self._execute('resize2fs', device_name, run_as_root=True)
|
||||||
|
|
||||||
|
def revert_to_snapshot(self, context, snapshot, share_server=None):
|
||||||
|
# First we merge the snapshot LV and the share LV
|
||||||
|
# This won't actually do anything until the LV is reactivated
|
||||||
|
snap_lv_name = "%s/%s" % (self.configuration.lvm_share_volume_group,
|
||||||
|
snapshot['name'])
|
||||||
|
self._execute('lvconvert', '--merge', snap_lv_name, run_as_root=True)
|
||||||
|
# Unmount the share so we can deactivate it
|
||||||
|
share = snapshot['share']
|
||||||
|
self._unmount_device(share)
|
||||||
|
# Deactivate the share LV
|
||||||
|
share_lv_name = "%s/%s" % (self.configuration.lvm_share_volume_group,
|
||||||
|
share['name'])
|
||||||
|
self._execute('lvchange', '-an', share_lv_name, run_as_root=True)
|
||||||
|
# Reactivate the share LV. This will trigger the merge and delete the
|
||||||
|
# snapshot.
|
||||||
|
self._execute('lvchange', '-ay', share_lv_name, run_as_root=True)
|
||||||
|
# Now recreate the snapshot that was destroyed by the merge
|
||||||
|
self._create_snapshot(context, snapshot)
|
||||||
|
# Finally we can mount the share again
|
||||||
|
device_name = self._get_local_path(share)
|
||||||
|
self._mount_device(share, device_name)
|
||||||
|
@ -188,7 +188,7 @@ def add_hooks(f):
|
|||||||
class ShareManager(manager.SchedulerDependentManager):
|
class ShareManager(manager.SchedulerDependentManager):
|
||||||
"""Manages NAS storages."""
|
"""Manages NAS storages."""
|
||||||
|
|
||||||
RPC_API_VERSION = '1.12'
|
RPC_API_VERSION = '1.13'
|
||||||
|
|
||||||
def __init__(self, share_driver=None, service_name=None, *args, **kwargs):
|
def __init__(self, share_driver=None, service_name=None, *args, **kwargs):
|
||||||
"""Load the driver from args, or from flags."""
|
"""Load the driver from args, or from flags."""
|
||||||
@ -2140,6 +2140,76 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
self.db.share_snapshot_instance_delete(
|
self.db.share_snapshot_instance_delete(
|
||||||
context, snapshot_instance['id'])
|
context, snapshot_instance['id'])
|
||||||
|
|
||||||
|
@add_hooks
|
||||||
|
@utils.require_driver_initialized
|
||||||
|
def revert_to_snapshot(self, context, snapshot_id, reservations,
|
||||||
|
share_id=None):
|
||||||
|
|
||||||
|
context = context.elevated()
|
||||||
|
snapshot = self.db.share_snapshot_get(context, snapshot_id)
|
||||||
|
share = snapshot['share']
|
||||||
|
share_id = share['id']
|
||||||
|
|
||||||
|
if share.get('has_replicas'):
|
||||||
|
self._revert_to_replicated_snapshot(
|
||||||
|
context, share, snapshot, reservations, share_id=share_id)
|
||||||
|
else:
|
||||||
|
self._revert_to_snapshot(context, share, snapshot, reservations)
|
||||||
|
|
||||||
|
def _revert_to_snapshot(self, context, share, snapshot, reservations):
|
||||||
|
|
||||||
|
share_server = self._get_share_server(context, share)
|
||||||
|
share_id = share['id']
|
||||||
|
snapshot_id = snapshot['id']
|
||||||
|
project_id = share['project_id']
|
||||||
|
user_id = share['user_id']
|
||||||
|
|
||||||
|
snapshot_instance = self.db.share_snapshot_instance_get(
|
||||||
|
context, snapshot.instance['id'], with_share_data=True)
|
||||||
|
|
||||||
|
# Make primitive to pass the information to the driver
|
||||||
|
snapshot_instance_dict = self._get_snapshot_instance_dict(
|
||||||
|
context, snapshot_instance, snapshot=snapshot)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.driver.revert_to_snapshot(context,
|
||||||
|
snapshot_instance_dict,
|
||||||
|
share_server=share_server)
|
||||||
|
except Exception:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
|
||||||
|
msg = _LE('Share %(share)s could not be reverted '
|
||||||
|
'to snapshot %(snap)s.')
|
||||||
|
msg_args = {'share': share_id, 'snap': snapshot_id}
|
||||||
|
LOG.exception(msg, msg_args)
|
||||||
|
|
||||||
|
if reservations:
|
||||||
|
QUOTAS.rollback(
|
||||||
|
context, reservations, project_id=project_id,
|
||||||
|
user_id=user_id)
|
||||||
|
|
||||||
|
self.db.share_update(
|
||||||
|
context, share_id,
|
||||||
|
{'status': constants.STATUS_REVERTING_ERROR})
|
||||||
|
self.db.share_snapshot_update(
|
||||||
|
context, snapshot_id,
|
||||||
|
{'status': constants.STATUS_AVAILABLE})
|
||||||
|
|
||||||
|
if reservations:
|
||||||
|
QUOTAS.commit(
|
||||||
|
context, reservations, project_id=project_id, user_id=user_id)
|
||||||
|
|
||||||
|
self.db.share_update(
|
||||||
|
context, share_id,
|
||||||
|
{'status': constants.STATUS_AVAILABLE, 'size': snapshot['size']})
|
||||||
|
self.db.share_snapshot_update(
|
||||||
|
context, snapshot_id, {'status': constants.STATUS_AVAILABLE})
|
||||||
|
|
||||||
|
msg = _LI('Share %(share)s reverted to snapshot %(snap)s '
|
||||||
|
'successfully.')
|
||||||
|
msg_args = {'share': share_id, 'snap': snapshot_id}
|
||||||
|
LOG.info(msg, msg_args)
|
||||||
|
|
||||||
@add_hooks
|
@add_hooks
|
||||||
@utils.require_driver_initialized
|
@utils.require_driver_initialized
|
||||||
def delete_share_instance(self, context, share_instance_id, force=False):
|
def delete_share_instance(self, context, share_instance_id, force=False):
|
||||||
@ -2359,6 +2429,90 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
self.db.share_snapshot_instance_update(
|
self.db.share_snapshot_instance_update(
|
||||||
context, instance['id'], instance)
|
context, instance['id'], instance)
|
||||||
|
|
||||||
|
def _find_active_replica_on_host(self, replica_list):
|
||||||
|
"""Find the active replica matching this manager's host."""
|
||||||
|
for replica in replica_list:
|
||||||
|
if (replica['replica_state'] == constants.REPLICA_STATE_ACTIVE and
|
||||||
|
share_utils.extract_host(replica['host']) == self.host):
|
||||||
|
return replica
|
||||||
|
|
||||||
|
@locked_share_replica_operation
|
||||||
|
def _revert_to_replicated_snapshot(self, context, share, snapshot,
|
||||||
|
reservations, share_id=None):
|
||||||
|
|
||||||
|
share_server = self._get_share_server(context, share)
|
||||||
|
snapshot_id = snapshot['id']
|
||||||
|
project_id = share['project_id']
|
||||||
|
user_id = share['user_id']
|
||||||
|
|
||||||
|
# Get replicas, including an active replica
|
||||||
|
replica_list = self.db.share_replicas_get_all_by_share(
|
||||||
|
context, share_id, with_share_data=True, with_share_server=True)
|
||||||
|
active_replica = self._find_active_replica_on_host(replica_list)
|
||||||
|
|
||||||
|
# Get snapshot instances, including one on an active replica
|
||||||
|
replica_snapshots = (
|
||||||
|
self.db.share_snapshot_instance_get_all_with_filters(
|
||||||
|
context, {'snapshot_ids': snapshot_id},
|
||||||
|
with_share_data=True))
|
||||||
|
snapshot_instance_filters = {
|
||||||
|
'share_instance_ids': active_replica['id'],
|
||||||
|
'snapshot_ids': snapshot_id,
|
||||||
|
}
|
||||||
|
active_replica_snapshot = (
|
||||||
|
self.db.share_snapshot_instance_get_all_with_filters(
|
||||||
|
context, snapshot_instance_filters))[0]
|
||||||
|
|
||||||
|
# Make primitives to pass the information to the driver
|
||||||
|
replica_list = [self._get_share_replica_dict(context, replica)
|
||||||
|
for replica in replica_list]
|
||||||
|
active_replica = self._get_share_replica_dict(context, active_replica)
|
||||||
|
replica_snapshots = [self._get_snapshot_instance_dict(context, s)
|
||||||
|
for s in replica_snapshots]
|
||||||
|
active_replica_snapshot = self._get_snapshot_instance_dict(
|
||||||
|
context, active_replica_snapshot, snapshot=snapshot)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.driver.revert_to_replicated_snapshot(
|
||||||
|
context, active_replica, replica_list, active_replica_snapshot,
|
||||||
|
replica_snapshots, share_server=share_server)
|
||||||
|
except Exception:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
|
||||||
|
msg = _LE('Share %(share)s could not be reverted '
|
||||||
|
'to snapshot %(snap)s.')
|
||||||
|
msg_args = {'share': share_id, 'snap': snapshot_id}
|
||||||
|
LOG.exception(msg, msg_args)
|
||||||
|
|
||||||
|
if reservations:
|
||||||
|
QUOTAS.rollback(
|
||||||
|
context, reservations, project_id=project_id,
|
||||||
|
user_id=user_id)
|
||||||
|
|
||||||
|
self.db.share_replica_update(
|
||||||
|
context, active_replica['id'],
|
||||||
|
{'status': constants.STATUS_REVERTING_ERROR})
|
||||||
|
self.db.share_snapshot_instance_update(
|
||||||
|
context, active_replica_snapshot['id'],
|
||||||
|
{'status': constants.STATUS_AVAILABLE})
|
||||||
|
|
||||||
|
if reservations:
|
||||||
|
QUOTAS.commit(
|
||||||
|
context, reservations, project_id=project_id, user_id=user_id)
|
||||||
|
|
||||||
|
self.db.share_update(context, share_id, {'size': snapshot['size']})
|
||||||
|
self.db.share_replica_update(
|
||||||
|
context, active_replica['id'],
|
||||||
|
{'status': constants.STATUS_AVAILABLE})
|
||||||
|
self.db.share_snapshot_instance_update(
|
||||||
|
context, active_replica_snapshot['id'],
|
||||||
|
{'status': constants.STATUS_AVAILABLE})
|
||||||
|
|
||||||
|
msg = _LI('Share %(share)s reverted to snapshot %(snap)s '
|
||||||
|
'successfully.')
|
||||||
|
msg_args = {'share': share_id, 'snap': snapshot_id}
|
||||||
|
LOG.info(msg, msg_args)
|
||||||
|
|
||||||
@add_hooks
|
@add_hooks
|
||||||
@utils.require_driver_initialized
|
@utils.require_driver_initialized
|
||||||
@locked_share_replica_operation
|
@locked_share_replica_operation
|
||||||
@ -3236,7 +3390,8 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
|
|
||||||
return share_replica_ref
|
return share_replica_ref
|
||||||
|
|
||||||
def _get_snapshot_instance_dict(self, context, snapshot_instance):
|
def _get_snapshot_instance_dict(self, context, snapshot_instance,
|
||||||
|
snapshot=None):
|
||||||
# TODO(gouthamr): remove method when the db layer returns primitives
|
# TODO(gouthamr): remove method when the db layer returns primitives
|
||||||
snapshot_instance_ref = {
|
snapshot_instance_ref = {
|
||||||
'name': snapshot_instance.get('name'),
|
'name': snapshot_instance.get('name'),
|
||||||
@ -3255,4 +3410,9 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
'provider_location': snapshot_instance.get('provider_location'),
|
'provider_location': snapshot_instance.get('provider_location'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if snapshot:
|
||||||
|
snapshot_instance_ref.update({
|
||||||
|
'size': snapshot.get('size'),
|
||||||
|
})
|
||||||
|
|
||||||
return snapshot_instance_ref
|
return snapshot_instance_ref
|
||||||
|
@ -64,6 +64,7 @@ class ShareAPI(object):
|
|||||||
update migration_cancel(), migration_complete() and
|
update migration_cancel(), migration_complete() and
|
||||||
migration_get_progress method signature, rename
|
migration_get_progress method signature, rename
|
||||||
migration_get_info() to connection_get_info()
|
migration_get_info() to connection_get_info()
|
||||||
|
1.13 - Introduce share revert to snapshot: revert_to_snapshot()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BASE_RPC_API_VERSION = '1.0'
|
BASE_RPC_API_VERSION = '1.0'
|
||||||
@ -72,7 +73,7 @@ class ShareAPI(object):
|
|||||||
super(ShareAPI, self).__init__()
|
super(ShareAPI, self).__init__()
|
||||||
target = messaging.Target(topic=CONF.share_topic,
|
target = messaging.Target(topic=CONF.share_topic,
|
||||||
version=self.BASE_RPC_API_VERSION)
|
version=self.BASE_RPC_API_VERSION)
|
||||||
self.client = rpc.get_client(target, version_cap='1.12')
|
self.client = rpc.get_client(target, version_cap='1.13')
|
||||||
|
|
||||||
def create_share_instance(self, context, share_instance, host,
|
def create_share_instance(self, context, share_instance, host,
|
||||||
request_spec, filter_properties,
|
request_spec, filter_properties,
|
||||||
@ -116,6 +117,15 @@ class ShareAPI(object):
|
|||||||
'unmanage_snapshot',
|
'unmanage_snapshot',
|
||||||
snapshot_id=snapshot['id'])
|
snapshot_id=snapshot['id'])
|
||||||
|
|
||||||
|
def revert_to_snapshot(self, context, share, snapshot, host, reservations):
|
||||||
|
host = utils.extract_host(host)
|
||||||
|
call_context = self.client.prepare(server=host, version='1.13')
|
||||||
|
call_context.cast(context,
|
||||||
|
'revert_to_snapshot',
|
||||||
|
share_id=share['id'],
|
||||||
|
snapshot_id=snapshot['id'],
|
||||||
|
reservations=reservations)
|
||||||
|
|
||||||
def delete_share_instance(self, context, share_instance, force=False):
|
def delete_share_instance(self, context, share_instance, force=False):
|
||||||
host = utils.extract_host(share_instance['host'])
|
host = utils.extract_host(share_instance['host'])
|
||||||
call_context = self.client.prepare(server=host, version='1.4')
|
call_context = self.client.prepare(server=host, version='1.4')
|
||||||
|
@ -266,6 +266,8 @@ def is_valid_optional_extra_spec(key, value):
|
|||||||
return parse_boolean_extra_spec(key, value) is not None
|
return parse_boolean_extra_spec(key, value) is not None
|
||||||
elif key == constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT:
|
elif key == constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT:
|
||||||
return parse_boolean_extra_spec(key, value) is not None
|
return parse_boolean_extra_spec(key, value) is not None
|
||||||
|
elif key == constants.ExtraSpecs.REVERT_TO_SNAPSHOT_SUPPORT:
|
||||||
|
return parse_boolean_extra_spec(key, value) is not None
|
||||||
elif key == constants.ExtraSpecs.REPLICATION_TYPE_SPEC:
|
elif key == constants.ExtraSpecs.REPLICATION_TYPE_SPEC:
|
||||||
return value in constants.ExtraSpecs.REPLICATION_TYPES
|
return value in constants.ExtraSpecs.REPLICATION_TYPES
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ def stub_share(id, **kwargs):
|
|||||||
'is_public': False,
|
'is_public': False,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'replication_type': None,
|
'replication_type': None,
|
||||||
'has_replicas': False,
|
'has_replicas': False,
|
||||||
}
|
}
|
||||||
|
@ -233,6 +233,8 @@ class ShareTypesAPITest(test.TestCase):
|
|||||||
('2.23', 'share_type_access', False),
|
('2.23', 'share_type_access', False),
|
||||||
('2.24', 'share_type_access', True),
|
('2.24', 'share_type_access', True),
|
||||||
('2.24', 'share_type_access', False),
|
('2.24', 'share_type_access', False),
|
||||||
|
('2.27', 'share_type_access', True),
|
||||||
|
('2.27', 'share_type_access', False),
|
||||||
)
|
)
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
def test_view_builder_show(self, version, prefix, admin):
|
def test_view_builder_show(self, version, prefix, admin):
|
||||||
@ -284,6 +286,8 @@ class ShareTypesAPITest(test.TestCase):
|
|||||||
('2.23', 'share_type_access', False),
|
('2.23', 'share_type_access', False),
|
||||||
('2.24', 'share_type_access', True),
|
('2.24', 'share_type_access', True),
|
||||||
('2.24', 'share_type_access', False),
|
('2.24', 'share_type_access', False),
|
||||||
|
('2.27', 'share_type_access', True),
|
||||||
|
('2.27', 'share_type_access', False),
|
||||||
)
|
)
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
def test_view_builder_list(self, version, prefix, admin):
|
def test_view_builder_list(self, version, prefix, admin):
|
||||||
@ -292,6 +296,7 @@ class ShareTypesAPITest(test.TestCase):
|
|||||||
extra_specs = {
|
extra_specs = {
|
||||||
constants.ExtraSpecs.SNAPSHOT_SUPPORT: True,
|
constants.ExtraSpecs.SNAPSHOT_SUPPORT: True,
|
||||||
constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT: False,
|
constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT: False,
|
||||||
|
constants.ExtraSpecs.REVERT_TO_SNAPSHOT_SUPPORT: True,
|
||||||
}
|
}
|
||||||
|
|
||||||
now = timeutils.utcnow().isoformat()
|
now = timeutils.utcnow().isoformat()
|
||||||
|
@ -22,6 +22,7 @@ from oslo_config import cfg
|
|||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import six
|
import six
|
||||||
import webob
|
import webob
|
||||||
|
import webob.exc
|
||||||
|
|
||||||
from manila.api import common
|
from manila.api import common
|
||||||
from manila.api.openstack import api_version_request as api_version
|
from manila.api.openstack import api_version_request as api_version
|
||||||
@ -38,6 +39,7 @@ from manila import test
|
|||||||
from manila.tests.api.contrib import stubs
|
from manila.tests.api.contrib import stubs
|
||||||
from manila.tests.api import fakes
|
from manila.tests.api import fakes
|
||||||
from manila.tests import db_utils
|
from manila.tests import db_utils
|
||||||
|
from manila.tests import fake_share
|
||||||
from manila import utils
|
from manila import utils
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -61,12 +63,14 @@ class ShareAPITest(test.TestCase):
|
|||||||
stubs.stub_snapshot_get)
|
stubs.stub_snapshot_get)
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
self.share = {
|
self.share = {
|
||||||
|
"id": "1",
|
||||||
"size": 100,
|
"size": 100,
|
||||||
"display_name": "Share Test Name",
|
"display_name": "Share Test Name",
|
||||||
"display_description": "Share Test Desc",
|
"display_description": "Share Test Desc",
|
||||||
"share_proto": "fakeproto",
|
"share_proto": "fakeproto",
|
||||||
"availability_zone": "zone1:host1",
|
"availability_zone": "zone1:host1",
|
||||||
"is_public": False,
|
"is_public": False,
|
||||||
|
"task_state": None,
|
||||||
}
|
}
|
||||||
self.create_mock = mock.Mock(
|
self.create_mock = mock.Mock(
|
||||||
return_value=stubs.stub_share(
|
return_value=stubs.stub_share(
|
||||||
@ -83,6 +87,12 @@ class ShareAPITest(test.TestCase):
|
|||||||
'id': 'fake_volume_type_id',
|
'id': 'fake_volume_type_id',
|
||||||
'name': 'fake_volume_type_name',
|
'name': 'fake_volume_type_name',
|
||||||
}
|
}
|
||||||
|
self.snapshot = {
|
||||||
|
'id': '2',
|
||||||
|
'share_id': '1',
|
||||||
|
'status': constants.STATUS_AVAILABLE,
|
||||||
|
}
|
||||||
|
|
||||||
CONF.set_default("default_share_type", None)
|
CONF.set_default("default_share_type", None)
|
||||||
|
|
||||||
def _get_expected_share_detailed_response(self, values=None, admin=False):
|
def _get_expected_share_detailed_response(self, values=None, admin=False):
|
||||||
@ -133,6 +143,256 @@ class ShareAPITest(test.TestCase):
|
|||||||
share['share_server_id'] = 'fake_share_server_id'
|
share['share_server_id'] = 'fake_share_server_id'
|
||||||
return {'share': share}
|
return {'share': share}
|
||||||
|
|
||||||
|
def test__revert(self):
|
||||||
|
|
||||||
|
share = copy.deepcopy(self.share)
|
||||||
|
share['status'] = constants.STATUS_AVAILABLE
|
||||||
|
share['revert_to_snapshot_support'] = True
|
||||||
|
share = fake_share.fake_share(**share)
|
||||||
|
snapshot = copy.deepcopy(self.snapshot)
|
||||||
|
snapshot['status'] = constants.STATUS_AVAILABLE
|
||||||
|
body = {'revert': {'snapshot_id': '2'}}
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/shares/1/action', use_admin_context=False, version='2.27')
|
||||||
|
mock_validate_revert_parameters = self.mock_object(
|
||||||
|
self.controller, '_validate_revert_parameters',
|
||||||
|
mock.Mock(return_value=body['revert']))
|
||||||
|
mock_get = self.mock_object(
|
||||||
|
share_api.API, 'get', mock.Mock(return_value=share))
|
||||||
|
mock_get_snapshot = self.mock_object(
|
||||||
|
share_api.API, 'get_snapshot', mock.Mock(return_value=snapshot))
|
||||||
|
mock_get_latest_snapshot_for_share = self.mock_object(
|
||||||
|
share_api.API, 'get_latest_snapshot_for_share',
|
||||||
|
mock.Mock(return_value=snapshot))
|
||||||
|
mock_revert_to_snapshot = self.mock_object(
|
||||||
|
share_api.API, 'revert_to_snapshot')
|
||||||
|
|
||||||
|
response = self.controller._revert(req, '1', body=body)
|
||||||
|
|
||||||
|
self.assertEqual(202, response.status_int)
|
||||||
|
mock_validate_revert_parameters.assert_called_once_with(
|
||||||
|
utils.IsAMatcher(context.RequestContext), body)
|
||||||
|
mock_get.assert_called_once_with(
|
||||||
|
utils.IsAMatcher(context.RequestContext), '1')
|
||||||
|
mock_get_snapshot.assert_called_once_with(
|
||||||
|
utils.IsAMatcher(context.RequestContext), '2')
|
||||||
|
mock_get_latest_snapshot_for_share.assert_called_once_with(
|
||||||
|
utils.IsAMatcher(context.RequestContext), '1')
|
||||||
|
mock_revert_to_snapshot.assert_called_once_with(
|
||||||
|
utils.IsAMatcher(context.RequestContext), snapshot)
|
||||||
|
|
||||||
|
def test__revert_not_supported(self):
|
||||||
|
|
||||||
|
share = copy.deepcopy(self.share)
|
||||||
|
share['revert_to_snapshot_support'] = False
|
||||||
|
share = fake_share.fake_share(**share)
|
||||||
|
snapshot = copy.deepcopy(self.snapshot)
|
||||||
|
snapshot['status'] = constants.STATUS_AVAILABLE
|
||||||
|
snapshot['share_id'] = 'wrong_id'
|
||||||
|
body = {'revert': {'snapshot_id': '2'}}
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/shares/1/action', use_admin_context=False, version='2.27')
|
||||||
|
self.mock_object(
|
||||||
|
self.controller, '_validate_revert_parameters',
|
||||||
|
mock.Mock(return_value=body['revert']))
|
||||||
|
self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
|
||||||
|
self.mock_object(
|
||||||
|
share_api.API, 'get_snapshot', mock.Mock(return_value=snapshot))
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller._revert,
|
||||||
|
req,
|
||||||
|
'1',
|
||||||
|
body=body)
|
||||||
|
|
||||||
|
def test__revert_id_mismatch(self):
|
||||||
|
|
||||||
|
share = copy.deepcopy(self.share)
|
||||||
|
share['status'] = constants.STATUS_AVAILABLE
|
||||||
|
share['revert_to_snapshot_support'] = True
|
||||||
|
share = fake_share.fake_share(**share)
|
||||||
|
snapshot = copy.deepcopy(self.snapshot)
|
||||||
|
snapshot['status'] = constants.STATUS_AVAILABLE
|
||||||
|
snapshot['share_id'] = 'wrong_id'
|
||||||
|
body = {'revert': {'snapshot_id': '2'}}
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/shares/1/action', use_admin_context=False, version='2.27')
|
||||||
|
self.mock_object(
|
||||||
|
self.controller, '_validate_revert_parameters',
|
||||||
|
mock.Mock(return_value=body['revert']))
|
||||||
|
self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
|
||||||
|
self.mock_object(
|
||||||
|
share_api.API, 'get_snapshot', mock.Mock(return_value=snapshot))
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller._revert,
|
||||||
|
req,
|
||||||
|
'1',
|
||||||
|
body=body)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
{
|
||||||
|
'share_status': constants.STATUS_ERROR,
|
||||||
|
'share_is_busy': False,
|
||||||
|
'snapshot_status': constants.STATUS_AVAILABLE,
|
||||||
|
}, {
|
||||||
|
'share_status': constants.STATUS_AVAILABLE,
|
||||||
|
'share_is_busy': True,
|
||||||
|
'snapshot_status': constants.STATUS_AVAILABLE,
|
||||||
|
}, {
|
||||||
|
'share_status': constants.STATUS_AVAILABLE,
|
||||||
|
'share_is_busy': False,
|
||||||
|
'snapshot_status': constants.STATUS_ERROR,
|
||||||
|
})
|
||||||
|
@ddt.unpack
|
||||||
|
def test__revert_invalid_status(self, share_status, share_is_busy,
|
||||||
|
snapshot_status):
|
||||||
|
|
||||||
|
share = copy.deepcopy(self.share)
|
||||||
|
share['status'] = share_status
|
||||||
|
share['is_busy'] = share_is_busy
|
||||||
|
share['revert_to_snapshot_support'] = True
|
||||||
|
share = fake_share.fake_share(**share)
|
||||||
|
snapshot = copy.deepcopy(self.snapshot)
|
||||||
|
snapshot['status'] = snapshot_status
|
||||||
|
body = {'revert': {'snapshot_id': '2'}}
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/shares/1/action', use_admin_context=False, version='2.27')
|
||||||
|
self.mock_object(
|
||||||
|
self.controller, '_validate_revert_parameters',
|
||||||
|
mock.Mock(return_value=body['revert']))
|
||||||
|
self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
|
||||||
|
self.mock_object(
|
||||||
|
share_api.API, 'get_snapshot', mock.Mock(return_value=snapshot))
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPConflict,
|
||||||
|
self.controller._revert,
|
||||||
|
req,
|
||||||
|
'1',
|
||||||
|
body=body)
|
||||||
|
|
||||||
|
def test__revert_snapshot_latest_not_found(self):
|
||||||
|
|
||||||
|
share = copy.deepcopy(self.share)
|
||||||
|
share['status'] = constants.STATUS_AVAILABLE
|
||||||
|
share['revert_to_snapshot_support'] = True
|
||||||
|
share = fake_share.fake_share(**share)
|
||||||
|
snapshot = copy.deepcopy(self.snapshot)
|
||||||
|
snapshot['status'] = constants.STATUS_AVAILABLE
|
||||||
|
body = {'revert': {'snapshot_id': '2'}}
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/shares/1/action', use_admin_context=False, version='2.27')
|
||||||
|
self.mock_object(
|
||||||
|
self.controller, '_validate_revert_parameters',
|
||||||
|
mock.Mock(return_value=body['revert']))
|
||||||
|
self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
|
||||||
|
self.mock_object(
|
||||||
|
share_api.API, 'get_snapshot', mock.Mock(return_value=snapshot))
|
||||||
|
self.mock_object(
|
||||||
|
share_api.API, 'get_latest_snapshot_for_share',
|
||||||
|
mock.Mock(return_value=None))
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller._revert,
|
||||||
|
req,
|
||||||
|
'1',
|
||||||
|
body=body)
|
||||||
|
|
||||||
|
def test__revert_snapshot_not_latest(self):
|
||||||
|
|
||||||
|
share = copy.deepcopy(self.share)
|
||||||
|
share['status'] = constants.STATUS_AVAILABLE
|
||||||
|
share['revert_to_snapshot_support'] = True
|
||||||
|
share = fake_share.fake_share(**share)
|
||||||
|
snapshot = copy.deepcopy(self.snapshot)
|
||||||
|
snapshot['status'] = constants.STATUS_AVAILABLE
|
||||||
|
latest_snapshot = copy.deepcopy(self.snapshot)
|
||||||
|
latest_snapshot['status'] = constants.STATUS_AVAILABLE
|
||||||
|
latest_snapshot['id'] = '3'
|
||||||
|
body = {'revert': {'snapshot_id': '2'}}
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/shares/1/action', use_admin_context=False, version='2.27')
|
||||||
|
self.mock_object(
|
||||||
|
self.controller, '_validate_revert_parameters',
|
||||||
|
mock.Mock(return_value=body['revert']))
|
||||||
|
self.mock_object(share_api.API, 'get', mock.Mock(return_value=share))
|
||||||
|
self.mock_object(
|
||||||
|
share_api.API, 'get_snapshot', mock.Mock(return_value=snapshot))
|
||||||
|
self.mock_object(
|
||||||
|
share_api.API, 'get_latest_snapshot_for_share',
|
||||||
|
mock.Mock(return_value=latest_snapshot))
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPConflict,
|
||||||
|
self.controller._revert,
|
||||||
|
req,
|
||||||
|
'1',
|
||||||
|
body=body)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
{
|
||||||
|
'caught': exception.ShareNotFound,
|
||||||
|
'exc_args': {
|
||||||
|
'share_id': '1',
|
||||||
|
},
|
||||||
|
'thrown': webob.exc.HTTPNotFound,
|
||||||
|
}, {
|
||||||
|
'caught': exception.ShareSnapshotNotFound,
|
||||||
|
'exc_args': {
|
||||||
|
'snapshot_id': '2',
|
||||||
|
},
|
||||||
|
'thrown': webob.exc.HTTPBadRequest,
|
||||||
|
}, {
|
||||||
|
'caught': exception.ShareSizeExceedsAvailableQuota,
|
||||||
|
'exc_args': {},
|
||||||
|
'thrown': webob.exc.HTTPForbidden,
|
||||||
|
}, {
|
||||||
|
'caught': exception.ReplicationException,
|
||||||
|
'exc_args': {
|
||||||
|
'reason': 'catastrophic failure',
|
||||||
|
},
|
||||||
|
'thrown': webob.exc.HTTPBadRequest,
|
||||||
|
})
|
||||||
|
@ddt.unpack
|
||||||
|
def test__revert_exception(self, caught, exc_args, thrown):
|
||||||
|
|
||||||
|
body = {'revert': {'snapshot_id': '2'}}
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/shares/1/action', use_admin_context=False, version='2.27')
|
||||||
|
self.mock_object(
|
||||||
|
self.controller, '_validate_revert_parameters',
|
||||||
|
mock.Mock(return_value=body['revert']))
|
||||||
|
self.mock_object(
|
||||||
|
share_api.API, 'get', mock.Mock(side_effect=caught(**exc_args)))
|
||||||
|
|
||||||
|
self.assertRaises(thrown,
|
||||||
|
self.controller._revert,
|
||||||
|
req,
|
||||||
|
'1',
|
||||||
|
body=body)
|
||||||
|
|
||||||
|
def test_validate_revert_parameters(self):
|
||||||
|
|
||||||
|
body = {'revert': {'snapshot_id': 'fake_snapshot_id'}}
|
||||||
|
|
||||||
|
result = self.controller._validate_revert_parameters(
|
||||||
|
'fake_context', body)
|
||||||
|
|
||||||
|
self.assertEqual(body['revert'], result)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
None,
|
||||||
|
{},
|
||||||
|
{'manage': {'snapshot_id': 'fake_snapshot_id'}},
|
||||||
|
{'revert': {'share_id': 'fake_snapshot_id'}},
|
||||||
|
{'revert': {'snapshot_id': ''}},
|
||||||
|
)
|
||||||
|
def test_validate_revert_parameters_invalid(self, body):
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller._validate_revert_parameters,
|
||||||
|
'fake_context',
|
||||||
|
body)
|
||||||
|
|
||||||
@ddt.data("2.0", "2.1")
|
@ddt.data("2.0", "2.1")
|
||||||
def test_share_create_original(self, microversion):
|
def test_share_create_original(self, microversion):
|
||||||
self.mock_object(share_api.API, 'create', self.create_mock)
|
self.mock_object(share_api.API, 'create', self.create_mock)
|
||||||
@ -2071,3 +2331,28 @@ class ShareManageTest(test.TestCase):
|
|||||||
self.controller.manage,
|
self.controller.manage,
|
||||||
req,
|
req,
|
||||||
share_id)
|
share_id)
|
||||||
|
|
||||||
|
def test_revert(self):
|
||||||
|
|
||||||
|
mock_revert = self.mock_object(
|
||||||
|
self.controller, '_revert',
|
||||||
|
mock.Mock(return_value='fake_response'))
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/shares/fake_id/action', use_admin_context=False, version='2.27')
|
||||||
|
|
||||||
|
result = self.controller.revert(req, 'fake_id', 'fake_body')
|
||||||
|
|
||||||
|
self.assertEqual('fake_response', result)
|
||||||
|
mock_revert.assert_called_once_with(
|
||||||
|
req, 'fake_id', 'fake_body')
|
||||||
|
|
||||||
|
def test_revert_unsupported(self):
|
||||||
|
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
'/shares/fake_id/action', use_admin_context=False, version='2.24')
|
||||||
|
|
||||||
|
self.assertRaises(exception.VersionNotFoundForAPIMethod,
|
||||||
|
self.controller.revert,
|
||||||
|
req,
|
||||||
|
'fake_id',
|
||||||
|
'fake_body')
|
||||||
|
@ -45,13 +45,14 @@ class ViewBuilderTestCase(test.TestCase):
|
|||||||
'user_id': 'fake_userid',
|
'user_id': 'fake_userid',
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
}
|
}
|
||||||
return stubs.stub_share('fake_id', **fake_share)
|
return stubs.stub_share('fake_id', **fake_share)
|
||||||
|
|
||||||
def test__collection_name(self):
|
def test__collection_name(self):
|
||||||
self.assertEqual('shares', self.builder._collection_name)
|
self.assertEqual('shares', self.builder._collection_name)
|
||||||
|
|
||||||
@ddt.data('2.6', '2.9', '2.10', '2.11', '2.16', '2.24')
|
@ddt.data('2.6', '2.9', '2.10', '2.11', '2.16', '2.24', '2.27')
|
||||||
def test_detail(self, microversion):
|
def test_detail(self, microversion):
|
||||||
req = fakes.HTTPRequest.blank('/shares', version=microversion)
|
req = fakes.HTTPRequest.blank('/shares', version=microversion)
|
||||||
|
|
||||||
@ -77,5 +78,7 @@ class ViewBuilderTestCase(test.TestCase):
|
|||||||
expected['user_id'] = 'fake_userid'
|
expected['user_id'] = 'fake_userid'
|
||||||
if self.is_microversion_ge(microversion, '2.24'):
|
if self.is_microversion_ge(microversion, '2.24'):
|
||||||
expected['create_share_from_snapshot_support'] = True
|
expected['create_share_from_snapshot_support'] = True
|
||||||
|
if self.is_microversion_ge(microversion, '2.27'):
|
||||||
|
expected['revert_to_snapshot_support'] = True
|
||||||
|
|
||||||
self.assertSubDictMatch(expected, result['share'])
|
self.assertSubDictMatch(expected, result['share'])
|
||||||
|
@ -1371,6 +1371,7 @@ class CreateFromSnapshotExtraSpecAndShareColumn(BaseMigrationChecks):
|
|||||||
# Pre-existing Shares must be present
|
# Pre-existing Shares must be present
|
||||||
shares_in_db = engine.execute(shares_table.select()).fetchall()
|
shares_in_db = engine.execute(shares_table.select()).fetchall()
|
||||||
share_ids_in_db = [s['id'] for s in shares_in_db]
|
share_ids_in_db = [s['id'] for s in shares_in_db]
|
||||||
|
self.test_case.assertTrue(len(share_ids_in_db) > 1)
|
||||||
for share_id in share_ids:
|
for share_id in share_ids:
|
||||||
self.test_case.assertIn(share_id, share_ids_in_db)
|
self.test_case.assertIn(share_id, share_ids_in_db)
|
||||||
|
|
||||||
@ -1420,6 +1421,7 @@ class CreateFromSnapshotExtraSpecAndShareColumn(BaseMigrationChecks):
|
|||||||
# Pre-existing Shares must be present
|
# Pre-existing Shares must be present
|
||||||
shares_in_db = engine.execute(shares_table.select()).fetchall()
|
shares_in_db = engine.execute(shares_table.select()).fetchall()
|
||||||
share_ids_in_db = [s['id'] for s in shares_in_db]
|
share_ids_in_db = [s['id'] for s in shares_in_db]
|
||||||
|
self.test_case.assertTrue(len(share_ids_in_db) > 1)
|
||||||
for share_id in share_ids:
|
for share_id in share_ids:
|
||||||
self.test_case.assertIn(share_id, share_ids_in_db)
|
self.test_case.assertIn(share_id, share_ids_in_db)
|
||||||
|
|
||||||
@ -1449,6 +1451,102 @@ class CreateFromSnapshotExtraSpecAndShareColumn(BaseMigrationChecks):
|
|||||||
self.test_case.assertEqual(0, len(new_extra_spec))
|
self.test_case.assertEqual(0, len(new_extra_spec))
|
||||||
|
|
||||||
|
|
||||||
|
@map_to_migration('87ce15c59bbe')
|
||||||
|
class RevertToSnapshotShareColumn(BaseMigrationChecks):
|
||||||
|
|
||||||
|
expected_attr = constants.ExtraSpecs.REVERT_TO_SNAPSHOT_SUPPORT
|
||||||
|
|
||||||
|
def _get_fake_data(self):
|
||||||
|
extra_specs = []
|
||||||
|
shares = []
|
||||||
|
share_instances = []
|
||||||
|
share_types = [
|
||||||
|
{
|
||||||
|
'id': uuidutils.generate_uuid(),
|
||||||
|
'deleted': 'False',
|
||||||
|
'name': 'revert-1',
|
||||||
|
'is_public': False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': uuidutils.generate_uuid(),
|
||||||
|
'deleted': 'False',
|
||||||
|
'name': 'revert-2',
|
||||||
|
'is_public': True,
|
||||||
|
|
||||||
|
},
|
||||||
|
]
|
||||||
|
snapshot_support = ('0', '1')
|
||||||
|
dhss = ('True', 'False')
|
||||||
|
for idx, share_type in enumerate(share_types):
|
||||||
|
extra_specs.append({
|
||||||
|
'share_type_id': share_type['id'],
|
||||||
|
'spec_key': 'snapshot_support',
|
||||||
|
'spec_value': snapshot_support[idx],
|
||||||
|
'deleted': 0,
|
||||||
|
})
|
||||||
|
extra_specs.append({
|
||||||
|
'share_type_id': share_type['id'],
|
||||||
|
'spec_key': 'driver_handles_share_servers',
|
||||||
|
'spec_value': dhss[idx],
|
||||||
|
'deleted': 0,
|
||||||
|
})
|
||||||
|
share = fake_share(snapshot_support=snapshot_support[idx])
|
||||||
|
shares.append(share)
|
||||||
|
share_instances.append(
|
||||||
|
fake_instance(share_id=share['id'],
|
||||||
|
share_type_id=share_type['id'])
|
||||||
|
)
|
||||||
|
|
||||||
|
return share_types, extra_specs, shares, share_instances
|
||||||
|
|
||||||
|
def setup_upgrade_data(self, engine):
|
||||||
|
|
||||||
|
(self.share_types, self.extra_specs, self.shares,
|
||||||
|
self.share_instances) = self._get_fake_data()
|
||||||
|
|
||||||
|
share_types_table = utils.load_table('share_types', engine)
|
||||||
|
engine.execute(share_types_table.insert(self.share_types))
|
||||||
|
extra_specs_table = utils.load_table('share_type_extra_specs',
|
||||||
|
engine)
|
||||||
|
engine.execute(extra_specs_table.insert(self.extra_specs))
|
||||||
|
shares_table = utils.load_table('shares', engine)
|
||||||
|
engine.execute(shares_table.insert(self.shares))
|
||||||
|
share_instances_table = utils.load_table('share_instances', engine)
|
||||||
|
engine.execute(share_instances_table.insert(self.share_instances))
|
||||||
|
|
||||||
|
def check_upgrade(self, engine, data):
|
||||||
|
share_ids = [s['id'] for s in self.shares]
|
||||||
|
shares_table = utils.load_table('shares', engine)
|
||||||
|
|
||||||
|
# Pre-existing Shares must be present
|
||||||
|
shares_in_db = engine.execute(shares_table.select().where(
|
||||||
|
shares_table.c.deleted == 'False')).fetchall()
|
||||||
|
share_ids_in_db = [s['id'] for s in shares_in_db]
|
||||||
|
self.test_case.assertTrue(len(share_ids_in_db) > 1)
|
||||||
|
for share_id in share_ids:
|
||||||
|
self.test_case.assertIn(share_id, share_ids_in_db)
|
||||||
|
|
||||||
|
# New shares attr must be present and set to False
|
||||||
|
for share in shares_in_db:
|
||||||
|
self.test_case.assertTrue(hasattr(share, self.expected_attr))
|
||||||
|
self.test_case.assertEqual(False, share[self.expected_attr])
|
||||||
|
|
||||||
|
def check_downgrade(self, engine):
|
||||||
|
share_ids = [s['id'] for s in self.shares]
|
||||||
|
shares_table = utils.load_table('shares', engine)
|
||||||
|
|
||||||
|
# Pre-existing Shares must be present
|
||||||
|
shares_in_db = engine.execute(shares_table.select()).fetchall()
|
||||||
|
share_ids_in_db = [s['id'] for s in shares_in_db]
|
||||||
|
self.test_case.assertTrue(len(share_ids_in_db) > 1)
|
||||||
|
for share_id in share_ids:
|
||||||
|
self.test_case.assertIn(share_id, share_ids_in_db)
|
||||||
|
|
||||||
|
# Shares should have no attr to revert share to snapshot
|
||||||
|
for share in shares_in_db:
|
||||||
|
self.test_case.assertFalse(hasattr(share, self.expected_attr))
|
||||||
|
|
||||||
|
|
||||||
@map_to_migration('95e3cf760840')
|
@map_to_migration('95e3cf760840')
|
||||||
class RemoveNovaNetIdColumnFromShareNetworks(BaseMigrationChecks):
|
class RemoveNovaNetIdColumnFromShareNetworks(BaseMigrationChecks):
|
||||||
table_name = 'share_networks'
|
table_name = 'share_networks'
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
"""Testing of SQLAlchemy backend."""
|
"""Testing of SQLAlchemy backend."""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
@ -894,6 +896,35 @@ class ShareSnapshotDatabaseAPITestCase(test.TestCase):
|
|||||||
self.assertEqual(1, len(actual_result.instances))
|
self.assertEqual(1, len(actual_result.instances))
|
||||||
self.assertSubDictMatch(values, actual_result.to_dict())
|
self.assertSubDictMatch(values, actual_result.to_dict())
|
||||||
|
|
||||||
|
def test_share_snapshot_get_latest_for_share(self):
|
||||||
|
|
||||||
|
share = db_utils.create_share(size=1)
|
||||||
|
values = {
|
||||||
|
'share_id': share['id'],
|
||||||
|
'size': share['size'],
|
||||||
|
'user_id': share['user_id'],
|
||||||
|
'project_id': share['project_id'],
|
||||||
|
'status': constants.STATUS_CREATING,
|
||||||
|
'progress': '0%',
|
||||||
|
'share_size': share['size'],
|
||||||
|
'display_description': 'fake',
|
||||||
|
'share_proto': share['share_proto'],
|
||||||
|
}
|
||||||
|
values1 = copy.deepcopy(values)
|
||||||
|
values1['display_name'] = 'snap1'
|
||||||
|
db_api.share_snapshot_create(self.ctxt, values1)
|
||||||
|
values2 = copy.deepcopy(values)
|
||||||
|
values2['display_name'] = 'snap2'
|
||||||
|
db_api.share_snapshot_create(self.ctxt, values2)
|
||||||
|
values3 = copy.deepcopy(values)
|
||||||
|
values3['display_name'] = 'snap3'
|
||||||
|
db_api.share_snapshot_create(self.ctxt, values3)
|
||||||
|
|
||||||
|
result = db_api.share_snapshot_get_latest_for_share(self.ctxt,
|
||||||
|
share['id'])
|
||||||
|
|
||||||
|
self.assertSubDictMatch(values3, result.to_dict())
|
||||||
|
|
||||||
def test_get_instance(self):
|
def test_get_instance(self):
|
||||||
snapshot = db_utils.create_snapshot(with_share=True)
|
snapshot = db_utils.create_snapshot(with_share=True)
|
||||||
|
|
||||||
|
@ -76,6 +76,28 @@ class ShareTestCase(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(constants.STATUS_CREATING, share.instance['status'])
|
self.assertEqual(constants.STATUS_CREATING, share.instance['status'])
|
||||||
|
|
||||||
|
@ddt.data(constants.STATUS_REPLICATION_CHANGE, constants.STATUS_AVAILABLE,
|
||||||
|
constants.STATUS_ERROR, constants.STATUS_CREATING)
|
||||||
|
def test_share_instance_reverting(self, status):
|
||||||
|
|
||||||
|
instance_list = [
|
||||||
|
db_utils.create_share_instance(
|
||||||
|
status=constants.STATUS_REVERTING,
|
||||||
|
share_id='fake_id'),
|
||||||
|
db_utils.create_share_instance(
|
||||||
|
status=status, share_id='fake_id'),
|
||||||
|
db_utils.create_share_instance(
|
||||||
|
status=constants.STATUS_ERROR_DELETING, share_id='fake_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
share1 = db_utils.create_share(instances=instance_list)
|
||||||
|
share2 = db_utils.create_share(instances=list(reversed(instance_list)))
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
constants.STATUS_REVERTING, share1.instance['status'])
|
||||||
|
self.assertEqual(
|
||||||
|
constants.STATUS_REVERTING, share2.instance['status'])
|
||||||
|
|
||||||
@ddt.data(constants.STATUS_AVAILABLE, constants.STATUS_ERROR,
|
@ddt.data(constants.STATUS_AVAILABLE, constants.STATUS_ERROR,
|
||||||
constants.STATUS_CREATING)
|
constants.STATUS_CREATING)
|
||||||
def test_share_instance_replication_change(self, status):
|
def test_share_instance_replication_change(self, status):
|
||||||
|
@ -40,6 +40,7 @@ SERVICE_STATES_NO_POOLS = {
|
|||||||
thin_provisioning=False,
|
thin_provisioning=False,
|
||||||
snapshot_support=False,
|
snapshot_support=False,
|
||||||
create_share_from_snapshot_support=False,
|
create_share_from_snapshot_support=False,
|
||||||
|
revert_to_snapshot_support=True,
|
||||||
driver_handles_share_servers=False),
|
driver_handles_share_servers=False),
|
||||||
'host2@back1': dict(share_backend_name='BBB',
|
'host2@back1': dict(share_backend_name='BBB',
|
||||||
total_capacity_gb=256, free_capacity_gb=100,
|
total_capacity_gb=256, free_capacity_gb=100,
|
||||||
@ -49,6 +50,7 @@ SERVICE_STATES_NO_POOLS = {
|
|||||||
thin_provisioning=True,
|
thin_provisioning=True,
|
||||||
snapshot_support=True,
|
snapshot_support=True,
|
||||||
create_share_from_snapshot_support=True,
|
create_share_from_snapshot_support=True,
|
||||||
|
revert_to_snapshot_support=False,
|
||||||
driver_handles_share_servers=False),
|
driver_handles_share_servers=False),
|
||||||
'host2@back2': dict(share_backend_name='CCC',
|
'host2@back2': dict(share_backend_name='CCC',
|
||||||
total_capacity_gb=10000, free_capacity_gb=700,
|
total_capacity_gb=10000, free_capacity_gb=700,
|
||||||
@ -58,6 +60,7 @@ SERVICE_STATES_NO_POOLS = {
|
|||||||
thin_provisioning=True,
|
thin_provisioning=True,
|
||||||
snapshot_support=True,
|
snapshot_support=True,
|
||||||
create_share_from_snapshot_support=True,
|
create_share_from_snapshot_support=True,
|
||||||
|
revert_to_snapshot_support=False,
|
||||||
driver_handles_share_servers=False),
|
driver_handles_share_servers=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,6 +86,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
snapshot_support=True,
|
snapshot_support=True,
|
||||||
create_share_from_snapshot_support=True,
|
create_share_from_snapshot_support=True,
|
||||||
|
revert_to_snapshot_support=True,
|
||||||
replication_type=None,
|
replication_type=None,
|
||||||
pools=[dict(pool_name='pool1',
|
pools=[dict(pool_name='pool1',
|
||||||
total_capacity_gb=51,
|
total_capacity_gb=51,
|
||||||
@ -96,6 +100,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
snapshot_support=True,
|
snapshot_support=True,
|
||||||
create_share_from_snapshot_support=True,
|
create_share_from_snapshot_support=True,
|
||||||
|
revert_to_snapshot_support=False,
|
||||||
replication_type=None,
|
replication_type=None,
|
||||||
pools=[dict(pool_name='pool2',
|
pools=[dict(pool_name='pool2',
|
||||||
total_capacity_gb=52,
|
total_capacity_gb=52,
|
||||||
@ -109,6 +114,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
snapshot_support=True,
|
snapshot_support=True,
|
||||||
create_share_from_snapshot_support=True,
|
create_share_from_snapshot_support=True,
|
||||||
|
revert_to_snapshot_support=False,
|
||||||
replication_type=None,
|
replication_type=None,
|
||||||
pools=[dict(pool_name='pool3',
|
pools=[dict(pool_name='pool3',
|
||||||
total_capacity_gb=53,
|
total_capacity_gb=53,
|
||||||
@ -123,6 +129,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
snapshot_support=True,
|
snapshot_support=True,
|
||||||
create_share_from_snapshot_support=True,
|
create_share_from_snapshot_support=True,
|
||||||
|
revert_to_snapshot_support=False,
|
||||||
replication_type=None,
|
replication_type=None,
|
||||||
pools=[dict(pool_name='pool4a',
|
pools=[dict(pool_name='pool4a',
|
||||||
total_capacity_gb=541,
|
total_capacity_gb=541,
|
||||||
@ -145,6 +152,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
snapshot_support=True,
|
snapshot_support=True,
|
||||||
create_share_from_snapshot_support=True,
|
create_share_from_snapshot_support=True,
|
||||||
|
revert_to_snapshot_support=False,
|
||||||
replication_type=None,
|
replication_type=None,
|
||||||
pools=[dict(pool_name='pool5a',
|
pools=[dict(pool_name='pool5a',
|
||||||
total_capacity_gb=551,
|
total_capacity_gb=551,
|
||||||
@ -165,6 +173,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
snapshot_support=True,
|
snapshot_support=True,
|
||||||
create_share_from_snapshot_support=True,
|
create_share_from_snapshot_support=True,
|
||||||
|
revert_to_snapshot_support=False,
|
||||||
replication_type=None,
|
replication_type=None,
|
||||||
pools=[dict(pool_name='pool6a',
|
pools=[dict(pool_name='pool6a',
|
||||||
total_capacity_gb='unknown',
|
total_capacity_gb='unknown',
|
||||||
|
@ -211,6 +211,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': False,
|
'snapshot_support': False,
|
||||||
'create_share_from_snapshot_support': False,
|
'create_share_from_snapshot_support': False,
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
'consistency_group_support': False,
|
'consistency_group_support': False,
|
||||||
'dedupe': False,
|
'dedupe': False,
|
||||||
'compression': False,
|
'compression': False,
|
||||||
@ -237,6 +238,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'consistency_group_support': False,
|
'consistency_group_support': False,
|
||||||
'dedupe': False,
|
'dedupe': False,
|
||||||
'compression': False,
|
'compression': False,
|
||||||
@ -263,6 +265,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'consistency_group_support': False,
|
'consistency_group_support': False,
|
||||||
'dedupe': False,
|
'dedupe': False,
|
||||||
'compression': False,
|
'compression': False,
|
||||||
@ -311,6 +314,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
'consistency_group_support': False,
|
'consistency_group_support': False,
|
||||||
'dedupe': False,
|
'dedupe': False,
|
||||||
'compression': False,
|
'compression': False,
|
||||||
@ -338,6 +342,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'consistency_group_support': False,
|
'consistency_group_support': False,
|
||||||
'dedupe': False,
|
'dedupe': False,
|
||||||
'compression': False,
|
'compression': False,
|
||||||
@ -365,6 +370,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'consistency_group_support': 'pool',
|
'consistency_group_support': 'pool',
|
||||||
'dedupe': False,
|
'dedupe': False,
|
||||||
'compression': False,
|
'compression': False,
|
||||||
@ -392,6 +398,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'consistency_group_support': 'host',
|
'consistency_group_support': 'host',
|
||||||
'dedupe': False,
|
'dedupe': False,
|
||||||
'compression': False,
|
'compression': False,
|
||||||
@ -419,6 +426,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'consistency_group_support': 'host',
|
'consistency_group_support': 'host',
|
||||||
'dedupe': False,
|
'dedupe': False,
|
||||||
'compression': False,
|
'compression': False,
|
||||||
@ -469,6 +477,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': False,
|
'snapshot_support': False,
|
||||||
'create_share_from_snapshot_support': False,
|
'create_share_from_snapshot_support': False,
|
||||||
|
'revert_to_snapshot_support': True,
|
||||||
'share_backend_name': 'AAA',
|
'share_backend_name': 'AAA',
|
||||||
'free_capacity_gb': 200,
|
'free_capacity_gb': 200,
|
||||||
'driver_version': None,
|
'driver_version': None,
|
||||||
@ -495,6 +504,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'share_backend_name': 'BBB',
|
'share_backend_name': 'BBB',
|
||||||
'free_capacity_gb': 100,
|
'free_capacity_gb': 100,
|
||||||
'driver_version': None,
|
'driver_version': None,
|
||||||
@ -549,6 +559,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'share_backend_name': 'BBB',
|
'share_backend_name': 'BBB',
|
||||||
'free_capacity_gb': 42,
|
'free_capacity_gb': 42,
|
||||||
'driver_version': None,
|
'driver_version': None,
|
||||||
|
@ -125,6 +125,7 @@ class EMCShareFrameworkTestCase(test.TestCase):
|
|||||||
data['pools'] = None
|
data['pools'] = None
|
||||||
data['snapshot_support'] = True
|
data['snapshot_support'] = True
|
||||||
data['create_share_from_snapshot_support'] = True
|
data['create_share_from_snapshot_support'] = True
|
||||||
|
data['revert_to_snapshot_support'] = False
|
||||||
data['replication_domain'] = None
|
data['replication_domain'] = None
|
||||||
data['filter_function'] = None
|
data['filter_function'] = None
|
||||||
data['goodness_function'] = None
|
data['goodness_function'] = None
|
||||||
|
@ -278,6 +278,10 @@ class DummyDriver(driver.ShareDriver):
|
|||||||
def unmanage_snapshot(self, snapshot):
|
def unmanage_snapshot(self, snapshot):
|
||||||
"""Removes the specified snapshot from Manila management."""
|
"""Removes the specified snapshot from Manila management."""
|
||||||
|
|
||||||
|
@slow_me_down
|
||||||
|
def revert_to_snapshot(self, context, snapshot, share_server=None):
|
||||||
|
"""Reverts a share (in place) to the specified snapshot."""
|
||||||
|
|
||||||
@slow_me_down
|
@slow_me_down
|
||||||
def extend_share(self, share, new_size, share_server=None):
|
def extend_share(self, share, new_size, share_server=None):
|
||||||
"""Extends size of existing share."""
|
"""Extends size of existing share."""
|
||||||
@ -338,6 +342,7 @@ class DummyDriver(driver.ShareDriver):
|
|||||||
"consistency_group_support": "pool",
|
"consistency_group_support": "pool",
|
||||||
"snapshot_support": True,
|
"snapshot_support": True,
|
||||||
"create_share_from_snapshot_support": True,
|
"create_share_from_snapshot_support": True,
|
||||||
|
"revert_to_snapshot_support": True,
|
||||||
"driver_name": "Dummy",
|
"driver_name": "Dummy",
|
||||||
"pools": self._get_pools_info(),
|
"pools": self._get_pools_info(),
|
||||||
}
|
}
|
||||||
@ -443,6 +448,12 @@ class DummyDriver(driver.ShareDriver):
|
|||||||
{"id": r["id"], "status": constants.STATUS_AVAILABLE})
|
{"id": r["id"], "status": constants.STATUS_AVAILABLE})
|
||||||
return return_replica_snapshots
|
return return_replica_snapshots
|
||||||
|
|
||||||
|
@slow_me_down
|
||||||
|
def revert_to_replicated_snapshot(self, context, active_replica,
|
||||||
|
replica_list, active_replica_snapshot,
|
||||||
|
replica_snapshots, share_server=None):
|
||||||
|
"""Reverts a replicated share (in place) to the specified snapshot."""
|
||||||
|
|
||||||
@slow_me_down
|
@slow_me_down
|
||||||
def delete_replicated_snapshot(self, context, replica_list,
|
def delete_replicated_snapshot(self, context, replica_list,
|
||||||
replica_snapshots, share_server=None):
|
replica_snapshots, share_server=None):
|
||||||
|
@ -257,6 +257,7 @@ class GlusterfsNativeShareDriverTestCase(test.TestCase):
|
|||||||
'pools': None,
|
'pools': None,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'replication_domain': None,
|
'replication_domain': None,
|
||||||
'filter_function': None,
|
'filter_function': None,
|
||||||
'goodness_function': None,
|
'goodness_function': None,
|
||||||
|
@ -734,6 +734,7 @@ class HPE3ParDriverTestCase(test.TestCase):
|
|||||||
'share_backend_name': 'HPE_3PAR',
|
'share_backend_name': 'HPE_3PAR',
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'storage_protocol': 'NFS_CIFS',
|
'storage_protocol': 'NFS_CIFS',
|
||||||
'thin_provisioning': True,
|
'thin_provisioning': True,
|
||||||
'total_capacity_gb': 0,
|
'total_capacity_gb': 0,
|
||||||
@ -809,6 +810,7 @@ class HPE3ParDriverTestCase(test.TestCase):
|
|||||||
'provisioned_capacity_gb': expected_capacity}],
|
'provisioned_capacity_gb': expected_capacity}],
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'replication_domain': None,
|
'replication_domain': None,
|
||||||
'filter_function': None,
|
'filter_function': None,
|
||||||
'goodness_function': None,
|
'goodness_function': None,
|
||||||
@ -846,6 +848,7 @@ class HPE3ParDriverTestCase(test.TestCase):
|
|||||||
'vendor_name': 'HPE',
|
'vendor_name': 'HPE',
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'replication_domain': None,
|
'replication_domain': None,
|
||||||
'filter_function': None,
|
'filter_function': None,
|
||||||
'goodness_function': None,
|
'goodness_function': None,
|
||||||
|
@ -2424,6 +2424,7 @@ class HuaweiShareDriverTestCase(test.TestCase):
|
|||||||
"qos": True,
|
"qos": True,
|
||||||
"snapshot_support": snapshot_support,
|
"snapshot_support": snapshot_support,
|
||||||
"create_share_from_snapshot_support": snapshot_support,
|
"create_share_from_snapshot_support": snapshot_support,
|
||||||
|
"revert_to_snapshot_support": False,
|
||||||
"replication_domain": None,
|
"replication_domain": None,
|
||||||
"filter_function": None,
|
"filter_function": None,
|
||||||
"goodness_function": None,
|
"goodness_function": None,
|
||||||
|
@ -54,7 +54,11 @@ def fake_snapshot(**kwargs):
|
|||||||
'name': 'fakesnapshotname',
|
'name': 'fakesnapshotname',
|
||||||
'share_proto': 'NFS',
|
'share_proto': 'NFS',
|
||||||
'export_location': '127.0.0.1:/mnt/nfs/volume-00002',
|
'export_location': '127.0.0.1:/mnt/nfs/volume-00002',
|
||||||
'share': {'size': 1},
|
'share': {
|
||||||
|
'id': 'fakeid',
|
||||||
|
'name': 'fakename',
|
||||||
|
'size': 1
|
||||||
|
},
|
||||||
}
|
}
|
||||||
snapshot.update(kwargs)
|
snapshot.update(kwargs)
|
||||||
return db_fakes.FakeModel(snapshot)
|
return db_fakes.FakeModel(snapshot)
|
||||||
@ -520,3 +524,26 @@ class LVMShareDriverTestCase(test.TestCase):
|
|||||||
self.assertTrue(self._driver._stats['snapshot_support'])
|
self.assertTrue(self._driver._stats['snapshot_support'])
|
||||||
self.assertEqual('LVMShareDriver', self._driver._stats['driver_name'])
|
self.assertEqual('LVMShareDriver', self._driver._stats['driver_name'])
|
||||||
self.assertEqual('test-pool', self._driver._stats['pools'])
|
self.assertEqual('test-pool', self._driver._stats['pools'])
|
||||||
|
|
||||||
|
def test_revert_to_snapshot(self):
|
||||||
|
self._driver.revert_to_snapshot(self._context, self.snapshot,
|
||||||
|
self.share_server)
|
||||||
|
snap_lv = "%s/fakesnapshotname" % (CONF.lvm_share_volume_group)
|
||||||
|
share_lv = "%s/fakename" % (CONF.lvm_share_volume_group)
|
||||||
|
mount_path = self._get_mount_path(self.snapshot['share'])
|
||||||
|
expected_exec = [
|
||||||
|
("lvconvert --merge %s" % snap_lv),
|
||||||
|
("umount %s" % mount_path),
|
||||||
|
("rmdir %s" % mount_path),
|
||||||
|
("lvchange -an %s" % share_lv),
|
||||||
|
("lvchange -ay %s" % share_lv),
|
||||||
|
("lvcreate -L 1G --name fakesnapshotname --snapshot %s" %
|
||||||
|
share_lv),
|
||||||
|
('tune2fs -U random /dev/mapper/%s-fakesnapshotname' %
|
||||||
|
CONF.lvm_share_volume_group),
|
||||||
|
("mkdir -p %s" % mount_path),
|
||||||
|
("mount /dev/mapper/%s-fakename %s" %
|
||||||
|
(CONF.lvm_share_volume_group, mount_path)),
|
||||||
|
("chmod 777 %s" % mount_path),
|
||||||
|
]
|
||||||
|
self.assertEqual(expected_exec, fake_utils.fake_execute_get_log())
|
||||||
|
@ -355,6 +355,7 @@ class ZFSonLinuxShareDriverTestCase(test.TestCase):
|
|||||||
'share_backend_name': self.driver.backend_name,
|
'share_backend_name': self.driver.backend_name,
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': True,
|
'create_share_from_snapshot_support': True,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'storage_protocol': 'NFS',
|
'storage_protocol': 'NFS',
|
||||||
'total_capacity_gb': 'unknown',
|
'total_capacity_gb': 'unknown',
|
||||||
'vendor_name': 'Open Source',
|
'vendor_name': 'Open Source',
|
||||||
|
@ -749,6 +749,7 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
'extra_specs': {
|
'extra_specs': {
|
||||||
'snapshot_support': True,
|
'snapshot_support': True,
|
||||||
'create_share_from_snapshot_support': False,
|
'create_share_from_snapshot_support': False,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'replication_type': 'dr',
|
'replication_type': 'dr',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -765,6 +766,7 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
expected = {
|
expected = {
|
||||||
'snapshot_support': False,
|
'snapshot_support': False,
|
||||||
'create_share_from_snapshot_support': False,
|
'create_share_from_snapshot_support': False,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'replication_type': None,
|
'replication_type': None,
|
||||||
}
|
}
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
@ -773,7 +775,7 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
{'extra_specs': {'create_share_from_snapshot_support': 'fake'}})
|
{'extra_specs': {'create_share_from_snapshot_support': 'fake'}})
|
||||||
def test_get_share_attributes_from_share_type_invalid(self, share_type):
|
def test_get_share_attributes_from_share_type_invalid(self, share_type):
|
||||||
|
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidExtraSpec,
|
||||||
self.api._get_share_attributes_from_share_type,
|
self.api._get_share_attributes_from_share_type,
|
||||||
share_type)
|
share_type)
|
||||||
|
|
||||||
@ -798,6 +800,7 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
'snapshot_support': False,
|
'snapshot_support': False,
|
||||||
'replication_type': replication_type,
|
'replication_type': replication_type,
|
||||||
'create_share_from_snapshot_support': False,
|
'create_share_from_snapshot_support': False,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -824,6 +827,8 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
'snapshot_support': fake_type['extra_specs']['snapshot_support'],
|
'snapshot_support': fake_type['extra_specs']['snapshot_support'],
|
||||||
'create_share_from_snapshot_support':
|
'create_share_from_snapshot_support':
|
||||||
fake_type['extra_specs']['create_share_from_snapshot_support'],
|
fake_type['extra_specs']['create_share_from_snapshot_support'],
|
||||||
|
'revert_to_snapshot_support':
|
||||||
|
fake_type['extra_specs']['revert_to_snapshot_support'],
|
||||||
'replication_type': replication_type,
|
'replication_type': replication_type,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -883,6 +888,9 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
'create_share_from_snapshot_support',
|
'create_share_from_snapshot_support',
|
||||||
share_type['extra_specs']
|
share_type['extra_specs']
|
||||||
['create_share_from_snapshot_support']),
|
['create_share_from_snapshot_support']),
|
||||||
|
'revert_to_snapshot_support': kwargs.get(
|
||||||
|
'revert_to_snapshot_support',
|
||||||
|
share_type['extra_specs']['revert_to_snapshot_support']),
|
||||||
'share_proto': kwargs.get('share_proto', share.get('share_proto')),
|
'share_proto': kwargs.get('share_proto', share.get('share_proto')),
|
||||||
'share_type_id': share_type['id'],
|
'share_type_id': share_type['id'],
|
||||||
'is_public': kwargs.get('is_public', share.get('is_public')),
|
'is_public': kwargs.get('is_public', share.get('is_public')),
|
||||||
@ -983,6 +991,48 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
db_api.share_snapshot_create.assert_called_once_with(
|
db_api.share_snapshot_create.assert_called_once_with(
|
||||||
self.context, options)
|
self.context, options)
|
||||||
|
|
||||||
|
def test_create_snapshot_space_quota_exceeded(self):
|
||||||
|
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=uuidutils.generate_uuid(), size=1, project_id='fake_project',
|
||||||
|
user_id='fake_user', has_replicas=False, status='available')
|
||||||
|
usages = {'gigabytes': {'reserved': 10, 'in_use': 0}}
|
||||||
|
quotas = {'snapshot_gigabytes': 10}
|
||||||
|
side_effect = exception.OverQuota(
|
||||||
|
overs='snapshot_gigabytes', usages=usages, quotas=quotas)
|
||||||
|
self.mock_object(
|
||||||
|
quota.QUOTAS, 'reserve', mock.Mock(side_effect=side_effect))
|
||||||
|
mock_snap_create = self.mock_object(db_api, 'share_snapshot_create')
|
||||||
|
|
||||||
|
self.assertRaises(exception.SnapshotSizeExceedsAvailableQuota,
|
||||||
|
self.api.create_snapshot,
|
||||||
|
self.context,
|
||||||
|
share,
|
||||||
|
'fake_name',
|
||||||
|
'fake_description')
|
||||||
|
mock_snap_create.assert_not_called()
|
||||||
|
|
||||||
|
def test_create_snapshot_count_quota_exceeded(self):
|
||||||
|
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=uuidutils.generate_uuid(), size=1, project_id='fake_project',
|
||||||
|
user_id='fake_user', has_replicas=False, status='available')
|
||||||
|
usages = {'snapshots': {'reserved': 10, 'in_use': 0}}
|
||||||
|
quotas = {'snapshots': 10}
|
||||||
|
side_effect = exception.OverQuota(
|
||||||
|
overs='snapshots', usages=usages, quotas=quotas)
|
||||||
|
self.mock_object(
|
||||||
|
quota.QUOTAS, 'reserve', mock.Mock(side_effect=side_effect))
|
||||||
|
mock_snap_create = self.mock_object(db_api, 'share_snapshot_create')
|
||||||
|
|
||||||
|
self.assertRaises(exception.SnapshotLimitExceeded,
|
||||||
|
self.api.create_snapshot,
|
||||||
|
self.context,
|
||||||
|
share,
|
||||||
|
'fake_name',
|
||||||
|
'fake_description')
|
||||||
|
mock_snap_create.assert_not_called()
|
||||||
|
|
||||||
def test_manage_snapshot_share_not_found(self):
|
def test_manage_snapshot_share_not_found(self):
|
||||||
snapshot = fakes.fake_snapshot(share_id='fake_share',
|
snapshot = fakes.fake_snapshot(share_id='fake_share',
|
||||||
as_primitive=True)
|
as_primitive=True)
|
||||||
@ -1098,6 +1148,218 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
mock_rpc_call.assert_called_once_with(
|
mock_rpc_call.assert_called_once_with(
|
||||||
self.context, snapshot, fake_host)
|
self.context, snapshot, fake_host)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_revert_to_snapshot(self, has_replicas):
|
||||||
|
|
||||||
|
share = fakes.fake_share(id=uuidutils.generate_uuid(),
|
||||||
|
has_replicas=has_replicas)
|
||||||
|
self.mock_object(db_api, 'share_get', mock.Mock(return_value=share))
|
||||||
|
mock_handle_revert_to_snapshot_quotas = self.mock_object(
|
||||||
|
self.api, '_handle_revert_to_snapshot_quotas',
|
||||||
|
mock.Mock(return_value='fake_reservations'))
|
||||||
|
mock_revert_to_replicated_snapshot = self.mock_object(
|
||||||
|
self.api, '_revert_to_replicated_snapshot')
|
||||||
|
mock_revert_to_snapshot = self.mock_object(
|
||||||
|
self.api, '_revert_to_snapshot')
|
||||||
|
snapshot = fakes.fake_snapshot(share_id=share['id'])
|
||||||
|
|
||||||
|
self.api.revert_to_snapshot(self.context, snapshot)
|
||||||
|
|
||||||
|
mock_handle_revert_to_snapshot_quotas.assert_called_once_with(
|
||||||
|
self.context, share, snapshot)
|
||||||
|
if not has_replicas:
|
||||||
|
self.assertFalse(mock_revert_to_replicated_snapshot.called)
|
||||||
|
mock_revert_to_snapshot.assert_called_once_with(
|
||||||
|
self.context, share, snapshot, 'fake_reservations')
|
||||||
|
else:
|
||||||
|
mock_revert_to_replicated_snapshot.assert_called_once_with(
|
||||||
|
self.context, share, snapshot, 'fake_reservations')
|
||||||
|
self.assertFalse(mock_revert_to_snapshot.called)
|
||||||
|
|
||||||
|
@ddt.data(None, 'fake_reservations')
|
||||||
|
def test_revert_to_snapshot_exception(self, reservations):
|
||||||
|
|
||||||
|
share = fakes.fake_share(id=uuidutils.generate_uuid(),
|
||||||
|
has_replicas=False)
|
||||||
|
self.mock_object(db_api, 'share_get', mock.Mock(return_value=share))
|
||||||
|
self.mock_object(
|
||||||
|
self.api, '_handle_revert_to_snapshot_quotas',
|
||||||
|
mock.Mock(return_value=reservations))
|
||||||
|
side_effect = exception.ReplicationException(reason='error')
|
||||||
|
self.mock_object(
|
||||||
|
self.api, '_revert_to_snapshot',
|
||||||
|
mock.Mock(side_effect=side_effect))
|
||||||
|
mock_quotas_rollback = self.mock_object(quota.QUOTAS, 'rollback')
|
||||||
|
snapshot = fakes.fake_snapshot(share_id=share['id'])
|
||||||
|
|
||||||
|
self.assertRaises(exception.ReplicationException,
|
||||||
|
self.api.revert_to_snapshot,
|
||||||
|
self.context,
|
||||||
|
snapshot)
|
||||||
|
|
||||||
|
if reservations is not None:
|
||||||
|
mock_quotas_rollback.assert_called_once_with(
|
||||||
|
self.context, reservations)
|
||||||
|
else:
|
||||||
|
self.assertFalse(mock_quotas_rollback.called)
|
||||||
|
|
||||||
|
def test_handle_revert_to_snapshot_quotas(self):
|
||||||
|
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=uuidutils.generate_uuid(), size=1, project_id='fake_project',
|
||||||
|
user_id='fake_user', has_replicas=False)
|
||||||
|
snapshot = fakes.fake_snapshot(
|
||||||
|
id=uuidutils.generate_uuid(), share_id=share['id'], size=1)
|
||||||
|
mock_quotas_reserve = self.mock_object(quota.QUOTAS, 'reserve')
|
||||||
|
|
||||||
|
result = self.api._handle_revert_to_snapshot_quotas(
|
||||||
|
self.context, share, snapshot)
|
||||||
|
|
||||||
|
self.assertIsNone(result)
|
||||||
|
self.assertFalse(mock_quotas_reserve.called)
|
||||||
|
|
||||||
|
def test_handle_revert_to_snapshot_quotas_different_size(self):
|
||||||
|
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=uuidutils.generate_uuid(), size=1, project_id='fake_project',
|
||||||
|
user_id='fake_user', has_replicas=False)
|
||||||
|
snapshot = fakes.fake_snapshot(
|
||||||
|
id=uuidutils.generate_uuid(), share_id=share['id'], size=2)
|
||||||
|
mock_quotas_reserve = self.mock_object(
|
||||||
|
quota.QUOTAS, 'reserve',
|
||||||
|
mock.Mock(return_value='fake_reservations'))
|
||||||
|
|
||||||
|
result = self.api._handle_revert_to_snapshot_quotas(
|
||||||
|
self.context, share, snapshot)
|
||||||
|
|
||||||
|
self.assertEqual('fake_reservations', result)
|
||||||
|
mock_quotas_reserve.assert_called_once_with(
|
||||||
|
self.context, project_id='fake_project', gigabytes=1,
|
||||||
|
user_id='fake_user')
|
||||||
|
|
||||||
|
def test_handle_revert_to_snapshot_quotas_quota_exceeded(self):
|
||||||
|
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=uuidutils.generate_uuid(), size=1, project_id='fake_project',
|
||||||
|
user_id='fake_user', has_replicas=False)
|
||||||
|
snapshot = fakes.fake_snapshot(
|
||||||
|
id=uuidutils.generate_uuid(), share_id=share['id'], size=2)
|
||||||
|
usages = {'gigabytes': {'reserved': 10, 'in_use': 0}}
|
||||||
|
quotas = {'gigabytes': 10}
|
||||||
|
side_effect = exception.OverQuota(
|
||||||
|
overs='fake', usages=usages, quotas=quotas)
|
||||||
|
self.mock_object(
|
||||||
|
quota.QUOTAS, 'reserve', mock.Mock(side_effect=side_effect))
|
||||||
|
|
||||||
|
self.assertRaises(exception.ShareSizeExceedsAvailableQuota,
|
||||||
|
self.api._handle_revert_to_snapshot_quotas,
|
||||||
|
self.context,
|
||||||
|
share,
|
||||||
|
snapshot)
|
||||||
|
|
||||||
|
def test__revert_to_snapshot(self):
|
||||||
|
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=uuidutils.generate_uuid(), size=1, project_id='fake_project',
|
||||||
|
user_id='fake_user', has_replicas=False)
|
||||||
|
snapshot = fakes.fake_snapshot(
|
||||||
|
id=uuidutils.generate_uuid(), share_id=share['id'], size=2)
|
||||||
|
mock_share_update = self.mock_object(db_api, 'share_update')
|
||||||
|
mock_share_snapshot_update = self.mock_object(
|
||||||
|
db_api, 'share_snapshot_update')
|
||||||
|
mock_revert_rpc_call = self.mock_object(
|
||||||
|
self.share_rpcapi, 'revert_to_snapshot')
|
||||||
|
|
||||||
|
self.api._revert_to_snapshot(
|
||||||
|
self.context, share, snapshot, 'fake_reservations')
|
||||||
|
|
||||||
|
mock_share_update.assert_called_once_with(
|
||||||
|
self.context, share['id'], {'status': constants.STATUS_REVERTING})
|
||||||
|
mock_share_snapshot_update.assert_called_once_with(
|
||||||
|
self.context, snapshot['id'],
|
||||||
|
{'status': constants.STATUS_RESTORING})
|
||||||
|
mock_revert_rpc_call.assert_called_once_with(
|
||||||
|
self.context, share, snapshot, share['instance']['host'],
|
||||||
|
'fake_reservations')
|
||||||
|
|
||||||
|
def test_revert_to_replicated_snapshot(self):
|
||||||
|
|
||||||
|
share = fakes.fake_share(
|
||||||
|
has_replicas=True, status=constants.STATUS_AVAILABLE)
|
||||||
|
snapshot = fakes.fake_snapshot(share_instance_id='id1')
|
||||||
|
snapshot_instance = fakes.fake_snapshot_instance(
|
||||||
|
base_snapshot=snapshot, id='sid1')
|
||||||
|
replicas = [
|
||||||
|
fakes.fake_replica(
|
||||||
|
id='rid1', replica_state=constants.REPLICA_STATE_ACTIVE),
|
||||||
|
fakes.fake_replica(
|
||||||
|
id='rid2', replica_state=constants.REPLICA_STATE_IN_SYNC),
|
||||||
|
]
|
||||||
|
self.mock_object(
|
||||||
|
db_api, 'share_replicas_get_available_active_replica',
|
||||||
|
mock.Mock(return_value=replicas[0]))
|
||||||
|
self.mock_object(
|
||||||
|
db_api, 'share_snapshot_instance_get_all_with_filters',
|
||||||
|
mock.Mock(return_value=[snapshot_instance]))
|
||||||
|
mock_share_replica_update = self.mock_object(
|
||||||
|
db_api, 'share_replica_update')
|
||||||
|
mock_share_snapshot_instance_update = self.mock_object(
|
||||||
|
db_api, 'share_snapshot_instance_update')
|
||||||
|
mock_revert_rpc_call = self.mock_object(
|
||||||
|
self.share_rpcapi, 'revert_to_snapshot')
|
||||||
|
|
||||||
|
self.api._revert_to_replicated_snapshot(
|
||||||
|
self.context, share, snapshot, 'fake_reservations')
|
||||||
|
|
||||||
|
mock_share_replica_update.assert_called_once_with(
|
||||||
|
self.context, 'rid1', {'status': constants.STATUS_REVERTING})
|
||||||
|
mock_share_snapshot_instance_update.assert_called_once_with(
|
||||||
|
self.context, 'sid1', {'status': constants.STATUS_RESTORING})
|
||||||
|
mock_revert_rpc_call.assert_called_once_with(
|
||||||
|
self.context, share, snapshot, replicas[0]['host'],
|
||||||
|
'fake_reservations')
|
||||||
|
|
||||||
|
def test_revert_to_replicated_snapshot_no_active_replica(self):
|
||||||
|
|
||||||
|
share = fakes.fake_share(
|
||||||
|
has_replicas=True, status=constants.STATUS_AVAILABLE)
|
||||||
|
snapshot = fakes.fake_snapshot(share_instance_id='id1')
|
||||||
|
self.mock_object(
|
||||||
|
db_api, 'share_replicas_get_available_active_replica',
|
||||||
|
mock.Mock(return_value=None))
|
||||||
|
|
||||||
|
self.assertRaises(exception.ReplicationException,
|
||||||
|
self.api._revert_to_replicated_snapshot,
|
||||||
|
self.context,
|
||||||
|
share,
|
||||||
|
snapshot,
|
||||||
|
'fake_reservations')
|
||||||
|
|
||||||
|
def test_revert_to_replicated_snapshot_no_snapshot_instance(self):
|
||||||
|
|
||||||
|
share = fakes.fake_share(
|
||||||
|
has_replicas=True, status=constants.STATUS_AVAILABLE)
|
||||||
|
snapshot = fakes.fake_snapshot(share_instance_id='id1')
|
||||||
|
replicas = [
|
||||||
|
fakes.fake_replica(
|
||||||
|
id='rid1', replica_state=constants.REPLICA_STATE_ACTIVE),
|
||||||
|
fakes.fake_replica(
|
||||||
|
id='rid2', replica_state=constants.REPLICA_STATE_IN_SYNC),
|
||||||
|
]
|
||||||
|
self.mock_object(
|
||||||
|
db_api, 'share_replicas_get_available_active_replica',
|
||||||
|
mock.Mock(return_value=replicas[0]))
|
||||||
|
self.mock_object(
|
||||||
|
db_api, 'share_snapshot_instance_get_all_with_filters',
|
||||||
|
mock.Mock(return_value=[None]))
|
||||||
|
|
||||||
|
self.assertRaises(exception.ReplicationException,
|
||||||
|
self.api._revert_to_replicated_snapshot,
|
||||||
|
self.context,
|
||||||
|
share,
|
||||||
|
snapshot,
|
||||||
|
'fake_reservations')
|
||||||
|
|
||||||
def test_create_snapshot_for_replicated_share(self):
|
def test_create_snapshot_for_replicated_share(self):
|
||||||
share = fakes.fake_share(
|
share = fakes.fake_share(
|
||||||
has_replicas=True, status=constants.STATUS_AVAILABLE)
|
has_replicas=True, status=constants.STATUS_AVAILABLE)
|
||||||
@ -2051,6 +2313,7 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
'extra_specs': {
|
'extra_specs': {
|
||||||
'snapshot_support': False,
|
'snapshot_support': False,
|
||||||
'create_share_from_snapshot_support': False,
|
'create_share_from_snapshot_support': False,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'driver_handles_share_servers': dhss,
|
'driver_handles_share_servers': dhss,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -2061,6 +2324,7 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
'extra_specs': {
|
'extra_specs': {
|
||||||
'snapshot_support': False,
|
'snapshot_support': False,
|
||||||
'create_share_from_snapshot_support': False,
|
'create_share_from_snapshot_support': False,
|
||||||
|
'revert_to_snapshot_support': False,
|
||||||
'driver_handles_share_servers': dhss,
|
'driver_handles_share_servers': dhss,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -230,6 +230,14 @@ class ShareDriverTestCase(test.TestCase):
|
|||||||
|
|
||||||
self._assert_is_callable(share_driver, method)
|
self._assert_is_callable(share_driver, method)
|
||||||
|
|
||||||
|
@ddt.data('revert_to_snapshot',
|
||||||
|
'revert_to_replicated_snapshot')
|
||||||
|
def test_drivers_methods_needed_by_share_revert_to_snapshot_functionality(
|
||||||
|
self, method):
|
||||||
|
share_driver = self._instantiate_share_driver(None, False)
|
||||||
|
|
||||||
|
self._assert_is_callable(share_driver, method)
|
||||||
|
|
||||||
@ddt.data(True, False)
|
@ddt.data(True, False)
|
||||||
def test_get_share_server_pools(self, value):
|
def test_get_share_server_pools(self, value):
|
||||||
driver.CONF.set_default('driver_handles_share_servers', value)
|
driver.CONF.set_default('driver_handles_share_servers', value)
|
||||||
|
@ -1367,7 +1367,8 @@ class ShareManagerTestCase(test.TestCase):
|
|||||||
self.assertIsNone(retval)
|
self.assertIsNone(retval)
|
||||||
self.assertTrue(replica_update_call.called)
|
self.assertTrue(replica_update_call.called)
|
||||||
|
|
||||||
def _get_snapshot_instance_dict(self, snapshot_instance, share):
|
def _get_snapshot_instance_dict(self, snapshot_instance, share,
|
||||||
|
snapshot=None):
|
||||||
expected_snapshot_instance_dict = {
|
expected_snapshot_instance_dict = {
|
||||||
'status': constants.STATUS_CREATING,
|
'status': constants.STATUS_CREATING,
|
||||||
'share_id': share['id'],
|
'share_id': share['id'],
|
||||||
@ -1384,6 +1385,10 @@ class ShareManagerTestCase(test.TestCase):
|
|||||||
'deleted_at': snapshot_instance['deleted_at'],
|
'deleted_at': snapshot_instance['deleted_at'],
|
||||||
'provider_location': snapshot_instance['provider_location'],
|
'provider_location': snapshot_instance['provider_location'],
|
||||||
}
|
}
|
||||||
|
if snapshot:
|
||||||
|
expected_snapshot_instance_dict.update({
|
||||||
|
'size': snapshot['size'],
|
||||||
|
})
|
||||||
return expected_snapshot_instance_dict
|
return expected_snapshot_instance_dict
|
||||||
|
|
||||||
def test_create_snapshot_driver_exception(self):
|
def test_create_snapshot_driver_exception(self):
|
||||||
@ -4837,6 +4842,162 @@ class ShareManagerTestCase(test.TestCase):
|
|||||||
if quota_error:
|
if quota_error:
|
||||||
self.assertTrue(mock_log_warning.called)
|
self.assertTrue(mock_log_warning.called)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_revert_to_snapshot(self, has_replicas):
|
||||||
|
|
||||||
|
reservations = 'fake_reservations'
|
||||||
|
share_id = 'fake_share_id'
|
||||||
|
snapshot_id = 'fake_snapshot_id'
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=share_id, instance={'id': 'fake_instance_id'},
|
||||||
|
project_id='fake_project', user_id='fake_user', size=2,
|
||||||
|
has_replicas=has_replicas)
|
||||||
|
snapshot_instance = fakes.fake_snapshot_instance(
|
||||||
|
share_id=share_id, share=share, name='fake_snapshot')
|
||||||
|
snapshot = fakes.fake_snapshot(
|
||||||
|
id=snapshot_id, share_id=share_id, share=share,
|
||||||
|
instance=snapshot_instance, project_id='fake_project',
|
||||||
|
user_id='fake_user', size=1)
|
||||||
|
|
||||||
|
mock_share_snapshot_get = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_snapshot_get',
|
||||||
|
mock.Mock(return_value=snapshot))
|
||||||
|
mock_revert_to_snapshot = self.mock_object(
|
||||||
|
self.share_manager, '_revert_to_snapshot')
|
||||||
|
mock_revert_to_replicated_snapshot = self.mock_object(
|
||||||
|
self.share_manager, '_revert_to_replicated_snapshot')
|
||||||
|
|
||||||
|
self.share_manager.revert_to_snapshot(
|
||||||
|
self.context, snapshot_id, reservations, share_id=share_id)
|
||||||
|
|
||||||
|
mock_share_snapshot_get.assert_called_once_with(mock.ANY, snapshot_id)
|
||||||
|
|
||||||
|
if not has_replicas:
|
||||||
|
mock_revert_to_snapshot.assert_called_once_with(
|
||||||
|
mock.ANY, share, snapshot, reservations)
|
||||||
|
self.assertFalse(mock_revert_to_replicated_snapshot.called)
|
||||||
|
else:
|
||||||
|
self.assertFalse(mock_revert_to_snapshot.called)
|
||||||
|
mock_revert_to_replicated_snapshot.assert_called_once_with(
|
||||||
|
mock.ANY, share, snapshot, reservations, share_id=share_id)
|
||||||
|
|
||||||
|
@ddt.data(None, 'fake_reservations')
|
||||||
|
def test__revert_to_snapshot(self, reservations):
|
||||||
|
|
||||||
|
mock_quotas_rollback = self.mock_object(quota.QUOTAS, 'rollback')
|
||||||
|
mock_quotas_commit = self.mock_object(quota.QUOTAS, 'commit')
|
||||||
|
self.mock_object(
|
||||||
|
self.share_manager, '_get_share_server',
|
||||||
|
mock.Mock(return_value=None))
|
||||||
|
mock_driver = self.mock_object(self.share_manager, 'driver')
|
||||||
|
|
||||||
|
share_id = 'fake_share_id'
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=share_id, instance={'id': 'fake_instance_id'},
|
||||||
|
project_id='fake_project', user_id='fake_user', size=2)
|
||||||
|
snapshot_instance = fakes.fake_snapshot_instance(
|
||||||
|
share_id=share_id, share=share, name='fake_snapshot')
|
||||||
|
snapshot = fakes.fake_snapshot(
|
||||||
|
id='fake_snapshot_id', share_id=share_id, share=share,
|
||||||
|
instance=snapshot_instance, project_id='fake_project',
|
||||||
|
user_id='fake_user', size=1)
|
||||||
|
|
||||||
|
self.mock_object(
|
||||||
|
self.share_manager.db, 'share_snapshot_get',
|
||||||
|
mock.Mock(return_value=snapshot))
|
||||||
|
self.mock_object(
|
||||||
|
self.share_manager.db, 'share_snapshot_instance_get',
|
||||||
|
mock.Mock(return_value=snapshot_instance))
|
||||||
|
mock_share_update = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_update')
|
||||||
|
mock_share_snapshot_update = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_snapshot_update')
|
||||||
|
|
||||||
|
self.share_manager._revert_to_snapshot(
|
||||||
|
self.context, share, snapshot, reservations)
|
||||||
|
|
||||||
|
mock_driver.revert_to_snapshot.assert_called_once_with(
|
||||||
|
mock.ANY,
|
||||||
|
self._get_snapshot_instance_dict(
|
||||||
|
snapshot_instance, share, snapshot=snapshot),
|
||||||
|
share_server=None)
|
||||||
|
|
||||||
|
self.assertFalse(mock_quotas_rollback.called)
|
||||||
|
if reservations:
|
||||||
|
mock_quotas_commit.assert_called_once_with(
|
||||||
|
mock.ANY, reservations, project_id='fake_project',
|
||||||
|
user_id='fake_user')
|
||||||
|
else:
|
||||||
|
self.assertFalse(mock_quotas_commit.called)
|
||||||
|
|
||||||
|
mock_share_update.assert_called_once_with(
|
||||||
|
mock.ANY, share_id,
|
||||||
|
{'status': constants.STATUS_AVAILABLE, 'size': snapshot['size']})
|
||||||
|
mock_share_snapshot_update.assert_called_once_with(
|
||||||
|
mock.ANY, 'fake_snapshot_id',
|
||||||
|
{'status': constants.STATUS_AVAILABLE})
|
||||||
|
|
||||||
|
@ddt.data(None, 'fake_reservations')
|
||||||
|
def test__revert_to_snapshot_driver_exception(self, reservations):
|
||||||
|
|
||||||
|
mock_quotas_rollback = self.mock_object(quota.QUOTAS, 'rollback')
|
||||||
|
mock_quotas_commit = self.mock_object(quota.QUOTAS, 'commit')
|
||||||
|
self.mock_object(
|
||||||
|
self.share_manager, '_get_share_server',
|
||||||
|
mock.Mock(return_value=None))
|
||||||
|
mock_driver = self.mock_object(self.share_manager, 'driver')
|
||||||
|
mock_driver.revert_to_snapshot.side_effect = exception.ManilaException
|
||||||
|
|
||||||
|
share_id = 'fake_share_id'
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=share_id, instance={'id': 'fake_instance_id'},
|
||||||
|
project_id='fake_project', user_id='fake_user', size=2)
|
||||||
|
snapshot_instance = fakes.fake_snapshot_instance(
|
||||||
|
share_id=share_id, share=share, name='fake_snapshot')
|
||||||
|
snapshot = fakes.fake_snapshot(
|
||||||
|
id='fake_snapshot_id', share_id=share_id, share=share,
|
||||||
|
instance=snapshot_instance, project_id='fake_project',
|
||||||
|
user_id='fake_user', size=1)
|
||||||
|
|
||||||
|
self.mock_object(
|
||||||
|
self.share_manager.db, 'share_snapshot_get',
|
||||||
|
mock.Mock(return_value=snapshot))
|
||||||
|
self.mock_object(
|
||||||
|
self.share_manager.db, 'share_snapshot_instance_get',
|
||||||
|
mock.Mock(return_value=snapshot_instance))
|
||||||
|
mock_share_update = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_update')
|
||||||
|
mock_share_snapshot_update = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_snapshot_update')
|
||||||
|
|
||||||
|
self.assertRaises(exception.ManilaException,
|
||||||
|
self.share_manager._revert_to_snapshot,
|
||||||
|
self.context,
|
||||||
|
share,
|
||||||
|
snapshot,
|
||||||
|
reservations)
|
||||||
|
|
||||||
|
mock_driver.revert_to_snapshot.assert_called_once_with(
|
||||||
|
mock.ANY,
|
||||||
|
self._get_snapshot_instance_dict(
|
||||||
|
snapshot_instance, share, snapshot=snapshot),
|
||||||
|
share_server=None)
|
||||||
|
|
||||||
|
self.assertFalse(mock_quotas_commit.called)
|
||||||
|
if reservations:
|
||||||
|
mock_quotas_rollback.assert_called_once_with(
|
||||||
|
mock.ANY, reservations, project_id='fake_project',
|
||||||
|
user_id='fake_user')
|
||||||
|
else:
|
||||||
|
self.assertFalse(mock_quotas_rollback.called)
|
||||||
|
|
||||||
|
mock_share_update.assert_called_once_with(
|
||||||
|
mock.ANY, share_id,
|
||||||
|
{'status': constants.STATUS_REVERTING_ERROR})
|
||||||
|
mock_share_snapshot_update.assert_called_once_with(
|
||||||
|
mock.ANY, 'fake_snapshot_id',
|
||||||
|
{'status': constants.STATUS_AVAILABLE})
|
||||||
|
|
||||||
def _setup_crud_replicated_snapshot_data(self):
|
def _setup_crud_replicated_snapshot_data(self):
|
||||||
snapshot = fakes.fake_snapshot(create_instance=True)
|
snapshot = fakes.fake_snapshot(create_instance=True)
|
||||||
snapshot_instance = fakes.fake_snapshot_instance(
|
snapshot_instance = fakes.fake_snapshot_instance(
|
||||||
@ -4928,6 +5089,135 @@ class ShareManagerTestCase(test.TestCase):
|
|||||||
mock_db_update_call.assert_called_once_with(
|
mock_db_update_call.assert_called_once_with(
|
||||||
self.context, snapshot['instance']['id'], snapshot_dict)
|
self.context, snapshot['instance']['id'], snapshot_dict)
|
||||||
|
|
||||||
|
@ddt.data(None, 'fake_reservations')
|
||||||
|
def test_revert_to_replicated_snapshot(self, reservations):
|
||||||
|
|
||||||
|
share_id = 'id1'
|
||||||
|
mock_quotas_rollback = self.mock_object(quota.QUOTAS, 'rollback')
|
||||||
|
mock_quotas_commit = self.mock_object(quota.QUOTAS, 'commit')
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=share_id, project_id='fake_project', user_id='fake_user')
|
||||||
|
snapshot = fakes.fake_snapshot(
|
||||||
|
create_instance=True, share=share, size=1)
|
||||||
|
snapshot_instance = fakes.fake_snapshot_instance(
|
||||||
|
base_snapshot=snapshot)
|
||||||
|
snapshot_instances = [snapshot['instance'], snapshot_instance]
|
||||||
|
active_replica = fake_replica(
|
||||||
|
id='rid1', share_id=share_id, host=self.share_manager.host,
|
||||||
|
replica_state=constants.REPLICA_STATE_ACTIVE, as_primitive=False)
|
||||||
|
replica = fake_replica(
|
||||||
|
id='rid2', share_id=share_id, host='secondary',
|
||||||
|
replica_state=constants.REPLICA_STATE_IN_SYNC, as_primitive=False)
|
||||||
|
replicas = [active_replica, replica]
|
||||||
|
self.mock_object(
|
||||||
|
db, 'share_snapshot_get', mock.Mock(return_value=snapshot))
|
||||||
|
self.mock_object(
|
||||||
|
self.share_manager, '_get_share_server',
|
||||||
|
mock.Mock(return_value=None))
|
||||||
|
self.mock_object(
|
||||||
|
db, 'share_replicas_get_all_by_share',
|
||||||
|
mock.Mock(return_value=replicas))
|
||||||
|
self.mock_object(
|
||||||
|
db, 'share_snapshot_instance_get_all_with_filters',
|
||||||
|
mock.Mock(side_effect=[snapshot_instances,
|
||||||
|
[snapshot_instances[0]]]))
|
||||||
|
mock_driver = self.mock_object(self.share_manager, 'driver')
|
||||||
|
mock_share_update = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_update')
|
||||||
|
mock_share_replica_update = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_replica_update')
|
||||||
|
mock_share_snapshot_instance_update = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_snapshot_instance_update')
|
||||||
|
|
||||||
|
self.share_manager._revert_to_replicated_snapshot(
|
||||||
|
self.context, share, snapshot, reservations, share_id=share_id)
|
||||||
|
|
||||||
|
self.assertTrue(mock_driver.revert_to_replicated_snapshot.called)
|
||||||
|
self.assertFalse(mock_quotas_rollback.called)
|
||||||
|
if reservations:
|
||||||
|
mock_quotas_commit.assert_called_once_with(
|
||||||
|
mock.ANY, reservations, project_id='fake_project',
|
||||||
|
user_id='fake_user')
|
||||||
|
else:
|
||||||
|
self.assertFalse(mock_quotas_commit.called)
|
||||||
|
|
||||||
|
mock_share_update.assert_called_once_with(
|
||||||
|
mock.ANY, share_id, {'size': snapshot['size']})
|
||||||
|
mock_share_replica_update.assert_called_once_with(
|
||||||
|
mock.ANY, active_replica['id'],
|
||||||
|
{'status': constants.STATUS_AVAILABLE})
|
||||||
|
mock_share_snapshot_instance_update.assert_called_once_with(
|
||||||
|
mock.ANY, snapshot['instance']['id'],
|
||||||
|
{'status': constants.STATUS_AVAILABLE})
|
||||||
|
|
||||||
|
@ddt.data(None, 'fake_reservations')
|
||||||
|
def test_revert_to_replicated_snapshot_driver_exception(
|
||||||
|
self, reservations):
|
||||||
|
|
||||||
|
mock_quotas_rollback = self.mock_object(quota.QUOTAS, 'rollback')
|
||||||
|
mock_quotas_commit = self.mock_object(quota.QUOTAS, 'commit')
|
||||||
|
share_id = 'id1'
|
||||||
|
share = fakes.fake_share(
|
||||||
|
id=share_id, project_id='fake_project', user_id='fake_user')
|
||||||
|
snapshot = fakes.fake_snapshot(
|
||||||
|
create_instance=True, share=share, size=1)
|
||||||
|
snapshot_instance = fakes.fake_snapshot_instance(
|
||||||
|
base_snapshot=snapshot)
|
||||||
|
snapshot_instances = [snapshot['instance'], snapshot_instance]
|
||||||
|
active_replica = fake_replica(
|
||||||
|
id='rid1', share_id=share_id, host=self.share_manager.host,
|
||||||
|
replica_state=constants.REPLICA_STATE_ACTIVE, as_primitive=False)
|
||||||
|
replica = fake_replica(
|
||||||
|
id='rid2', share_id=share_id, host='secondary',
|
||||||
|
replica_state=constants.REPLICA_STATE_IN_SYNC, as_primitive=False)
|
||||||
|
replicas = [active_replica, replica]
|
||||||
|
self.mock_object(
|
||||||
|
db, 'share_snapshot_get', mock.Mock(return_value=snapshot))
|
||||||
|
self.mock_object(
|
||||||
|
self.share_manager, '_get_share_server',
|
||||||
|
mock.Mock(return_value=None))
|
||||||
|
self.mock_object(
|
||||||
|
db, 'share_replicas_get_all_by_share',
|
||||||
|
mock.Mock(return_value=replicas))
|
||||||
|
self.mock_object(
|
||||||
|
db, 'share_snapshot_instance_get_all_with_filters',
|
||||||
|
mock.Mock(side_effect=[snapshot_instances,
|
||||||
|
[snapshot_instances[0]]]))
|
||||||
|
mock_driver = self.mock_object(self.share_manager, 'driver')
|
||||||
|
mock_driver.revert_to_replicated_snapshot.side_effect = (
|
||||||
|
exception.ManilaException)
|
||||||
|
mock_share_update = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_update')
|
||||||
|
mock_share_replica_update = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_replica_update')
|
||||||
|
mock_share_snapshot_instance_update = self.mock_object(
|
||||||
|
self.share_manager.db, 'share_snapshot_instance_update')
|
||||||
|
|
||||||
|
self.assertRaises(exception.ManilaException,
|
||||||
|
self.share_manager._revert_to_replicated_snapshot,
|
||||||
|
self.context,
|
||||||
|
share,
|
||||||
|
snapshot,
|
||||||
|
reservations,
|
||||||
|
share_id=share_id)
|
||||||
|
|
||||||
|
self.assertTrue(mock_driver.revert_to_replicated_snapshot.called)
|
||||||
|
self.assertFalse(mock_quotas_commit.called)
|
||||||
|
if reservations:
|
||||||
|
mock_quotas_rollback.assert_called_once_with(
|
||||||
|
mock.ANY, reservations, project_id='fake_project',
|
||||||
|
user_id='fake_user')
|
||||||
|
else:
|
||||||
|
self.assertFalse(mock_quotas_rollback.called)
|
||||||
|
|
||||||
|
self.assertFalse(mock_share_update.called)
|
||||||
|
mock_share_replica_update.assert_called_once_with(
|
||||||
|
mock.ANY, active_replica['id'],
|
||||||
|
{'status': constants.STATUS_REVERTING_ERROR})
|
||||||
|
mock_share_snapshot_instance_update.assert_called_once_with(
|
||||||
|
mock.ANY, snapshot['instance']['id'],
|
||||||
|
{'status': constants.STATUS_AVAILABLE})
|
||||||
|
|
||||||
def delete_replicated_snapshot_driver_exception(self):
|
def delete_replicated_snapshot_driver_exception(self):
|
||||||
snapshot, snapshot_instances, replicas = (
|
snapshot, snapshot_instances, replicas = (
|
||||||
self._setup_crud_replicated_snapshot_data()
|
self._setup_crud_replicated_snapshot_data()
|
||||||
|
@ -325,6 +325,15 @@ class ShareRpcAPITestCase(test.TestCase):
|
|||||||
snapshot=self.fake_snapshot,
|
snapshot=self.fake_snapshot,
|
||||||
host='fake_host')
|
host='fake_host')
|
||||||
|
|
||||||
|
def test_revert_to_snapshot(self):
|
||||||
|
self._test_share_api('revert_to_snapshot',
|
||||||
|
rpc_method='cast',
|
||||||
|
version='1.13',
|
||||||
|
share=self.fake_share,
|
||||||
|
snapshot=self.fake_snapshot,
|
||||||
|
host='fake_host',
|
||||||
|
reservations={'fake': 'fake'})
|
||||||
|
|
||||||
def test_create_replicated_snapshot(self):
|
def test_create_replicated_snapshot(self):
|
||||||
self._test_share_api('create_replicated_snapshot',
|
self._test_share_api('create_replicated_snapshot',
|
||||||
rpc_method='cast',
|
rpc_method='cast',
|
||||||
|
@ -78,6 +78,7 @@ class ShareTypesTestCase(test.TestCase):
|
|||||||
fake_optional_extra_specs = {
|
fake_optional_extra_specs = {
|
||||||
constants.ExtraSpecs.SNAPSHOT_SUPPORT: 'true',
|
constants.ExtraSpecs.SNAPSHOT_SUPPORT: 'true',
|
||||||
constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT: 'false',
|
constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT: 'false',
|
||||||
|
constants.ExtraSpecs.REVERT_TO_SNAPSHOT_SUPPORT: 'false',
|
||||||
}
|
}
|
||||||
|
|
||||||
fake_type_w_valid_extra = {
|
fake_type_w_valid_extra = {
|
||||||
@ -237,7 +238,8 @@ class ShareTypesTestCase(test.TestCase):
|
|||||||
@ddt.data(*(
|
@ddt.data(*(
|
||||||
list(itertools.product(
|
list(itertools.product(
|
||||||
(constants.ExtraSpecs.SNAPSHOT_SUPPORT,
|
(constants.ExtraSpecs.SNAPSHOT_SUPPORT,
|
||||||
constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT),
|
constants.ExtraSpecs.CREATE_SHARE_FROM_SNAPSHOT_SUPPORT,
|
||||||
|
constants.ExtraSpecs.REVERT_TO_SNAPSHOT_SUPPORT),
|
||||||
strutils.TRUE_STRINGS + strutils.FALSE_STRINGS))) +
|
strutils.TRUE_STRINGS + strutils.FALSE_STRINGS))) +
|
||||||
list(itertools.product(
|
list(itertools.product(
|
||||||
(constants.ExtraSpecs.REPLICATION_TYPE_SPEC,),
|
(constants.ExtraSpecs.REPLICATION_TYPE_SPEC,),
|
||||||
|
@ -10,11 +10,13 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
# Shares
|
||||||
STATUS_ERROR = 'error'
|
STATUS_ERROR = 'error'
|
||||||
STATUS_AVAILABLE = 'available'
|
STATUS_AVAILABLE = 'available'
|
||||||
STATUS_ERROR_DELETING = 'error_deleting'
|
STATUS_ERROR_DELETING = 'error_deleting'
|
||||||
|
|
||||||
TEMPEST_MANILA_PREFIX = 'tempest-manila'
|
TEMPEST_MANILA_PREFIX = 'tempest-manila'
|
||||||
|
|
||||||
|
# Replication
|
||||||
REPLICATION_STYLE_READABLE = 'readable'
|
REPLICATION_STYLE_READABLE = 'readable'
|
||||||
REPLICATION_STYLE_WRITABLE = 'writable'
|
REPLICATION_STYLE_WRITABLE = 'writable'
|
||||||
REPLICATION_STYLE_DR = 'dr'
|
REPLICATION_STYLE_DR = 'dr'
|
||||||
@ -31,6 +33,7 @@ REPLICATION_STATE_ACTIVE = 'active'
|
|||||||
REPLICATION_STATE_IN_SYNC = 'in_sync'
|
REPLICATION_STATE_IN_SYNC = 'in_sync'
|
||||||
REPLICATION_STATE_OUT_OF_SYNC = 'out_of_sync'
|
REPLICATION_STATE_OUT_OF_SYNC = 'out_of_sync'
|
||||||
|
|
||||||
|
# Access Rules
|
||||||
RULE_STATE_ACTIVE = 'active'
|
RULE_STATE_ACTIVE = 'active'
|
||||||
RULE_STATE_OUT_OF_SYNC = 'out_of_sync'
|
RULE_STATE_OUT_OF_SYNC = 'out_of_sync'
|
||||||
RULE_STATE_ERROR = 'error'
|
RULE_STATE_ERROR = 'error'
|
||||||
@ -50,3 +53,10 @@ TASK_STATE_DATA_COPYING_COMPLETING = 'data_copying_completing'
|
|||||||
TASK_STATE_DATA_COPYING_COMPLETED = 'data_copying_completed'
|
TASK_STATE_DATA_COPYING_COMPLETED = 'data_copying_completed'
|
||||||
TASK_STATE_DATA_COPYING_CANCELLED = 'data_copying_cancelled'
|
TASK_STATE_DATA_COPYING_CANCELLED = 'data_copying_cancelled'
|
||||||
TASK_STATE_DATA_COPYING_ERROR = 'data_copying_error'
|
TASK_STATE_DATA_COPYING_ERROR = 'data_copying_error'
|
||||||
|
|
||||||
|
# Revert to snapshot
|
||||||
|
REVERT_TO_SNAPSHOT_MICROVERSION = '2.27'
|
||||||
|
REVERT_TO_SNAPSHOT_SUPPORT = 'revert_to_snapshot_support'
|
||||||
|
STATUS_RESTORING = 'restoring'
|
||||||
|
STATUS_REVERTING = 'reverting'
|
||||||
|
STATUS_REVERTING_ERROR = 'reverting_error'
|
||||||
|
@ -30,7 +30,7 @@ ShareGroup = [
|
|||||||
help="The minimum api microversion is configured to be the "
|
help="The minimum api microversion is configured to be the "
|
||||||
"value of the minimum microversion supported by Manila."),
|
"value of the minimum microversion supported by Manila."),
|
||||||
cfg.StrOpt("max_api_microversion",
|
cfg.StrOpt("max_api_microversion",
|
||||||
default="2.26",
|
default="2.27",
|
||||||
help="The maximum api microversion is configured to be the "
|
help="The maximum api microversion is configured to be the "
|
||||||
"value of the latest microversion supported by Manila."),
|
"value of the latest microversion supported by Manila."),
|
||||||
cfg.StrOpt("region",
|
cfg.StrOpt("region",
|
||||||
@ -103,7 +103,11 @@ ShareGroup = [
|
|||||||
"Defaults to the value of run_snapshot_tests. Set it to "
|
"Defaults to the value of run_snapshot_tests. Set it to "
|
||||||
"False if the driver being tested does not support "
|
"False if the driver being tested does not support "
|
||||||
"creating shares from snapshots."),
|
"creating shares from snapshots."),
|
||||||
|
cfg.BoolOpt("capability_revert_to_snapshot_support",
|
||||||
|
help="Defines extra spec that satisfies specific back end "
|
||||||
|
"capability called 'revert_to_snapshot_support' "
|
||||||
|
"and will be used for setting up custom share type. "
|
||||||
|
"Defaults to the value of run_revert_to_snapshot_tests."),
|
||||||
cfg.StrOpt("share_network_id",
|
cfg.StrOpt("share_network_id",
|
||||||
default="",
|
default="",
|
||||||
help="Some backend drivers requires share network "
|
help="Some backend drivers requires share network "
|
||||||
@ -161,6 +165,11 @@ ShareGroup = [
|
|||||||
help="Defines whether to run tests that use share snapshots "
|
help="Defines whether to run tests that use share snapshots "
|
||||||
"or not. Disable this feature if used driver doesn't "
|
"or not. Disable this feature if used driver doesn't "
|
||||||
"support it."),
|
"support it."),
|
||||||
|
cfg.BoolOpt("run_revert_to_snapshot_tests",
|
||||||
|
default=False,
|
||||||
|
help="Defines whether to run tests that revert shares "
|
||||||
|
"to snapshots or not. Enable this feature if used "
|
||||||
|
"driver supports it."),
|
||||||
cfg.BoolOpt("run_consistency_group_tests",
|
cfg.BoolOpt("run_consistency_group_tests",
|
||||||
default=True,
|
default=True,
|
||||||
help="Defines whether to run consistency group tests or not. "
|
help="Defines whether to run consistency group tests or not. "
|
||||||
|
@ -50,6 +50,12 @@ class ManilaTempestPlugin(plugins.TempestPlugin):
|
|||||||
conf.share.run_snapshot_tests,
|
conf.share.run_snapshot_tests,
|
||||||
group="share",
|
group="share",
|
||||||
)
|
)
|
||||||
|
if conf.share.capability_revert_to_snapshot_support is None:
|
||||||
|
conf.set_default(
|
||||||
|
"capability_revert_to_snapshot_support",
|
||||||
|
conf.share.run_revert_to_snapshot_tests,
|
||||||
|
group="share",
|
||||||
|
)
|
||||||
|
|
||||||
def get_opt_lists(self):
|
def get_opt_lists(self):
|
||||||
return [(config_share.share_group.name, config_share.ShareGroup),
|
return [(config_share.share_group.name, config_share.ShareGroup),
|
||||||
|
@ -551,6 +551,67 @@ class SharesV2Client(shares_client.SharesClient):
|
|||||||
self.expected_success(202, resp.status)
|
self.expected_success(202, resp.status)
|
||||||
return body
|
return body
|
||||||
|
|
||||||
|
###############
|
||||||
|
|
||||||
|
def revert_to_snapshot(self, share_id, snapshot_id,
|
||||||
|
version=LATEST_MICROVERSION):
|
||||||
|
url = 'shares/%s/action' % share_id
|
||||||
|
body = json.dumps({'revert': {'snapshot_id': snapshot_id}})
|
||||||
|
resp, body = self.post(url, body, version=version)
|
||||||
|
self.expected_success(202, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
###############
|
||||||
|
|
||||||
|
def create_share_type_extra_specs(self, share_type_id, extra_specs,
|
||||||
|
version=LATEST_MICROVERSION):
|
||||||
|
url = "types/%s/extra_specs" % share_type_id
|
||||||
|
post_body = json.dumps({'extra_specs': extra_specs})
|
||||||
|
resp, body = self.post(url, post_body, version=version)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def get_share_type_extra_spec(self, share_type_id, extra_spec_name,
|
||||||
|
version=LATEST_MICROVERSION):
|
||||||
|
uri = "types/%s/extra_specs/%s" % (share_type_id, extra_spec_name)
|
||||||
|
resp, body = self.get(uri, version=version)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def get_share_type_extra_specs(self, share_type_id, params=None,
|
||||||
|
version=LATEST_MICROVERSION):
|
||||||
|
uri = "types/%s/extra_specs" % share_type_id
|
||||||
|
if params is not None:
|
||||||
|
uri += '?%s' % urlparse.urlencode(params)
|
||||||
|
resp, body = self.get(uri, version=version)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def update_share_type_extra_spec(self, share_type_id, spec_name,
|
||||||
|
spec_value, version=LATEST_MICROVERSION):
|
||||||
|
uri = "types/%s/extra_specs/%s" % (share_type_id, spec_name)
|
||||||
|
extra_spec = {spec_name: spec_value}
|
||||||
|
post_body = json.dumps(extra_spec)
|
||||||
|
resp, body = self.put(uri, post_body, version=version)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def update_share_type_extra_specs(self, share_type_id, extra_specs,
|
||||||
|
version=LATEST_MICROVERSION):
|
||||||
|
uri = "types/%s/extra_specs" % share_type_id
|
||||||
|
extra_specs = {"extra_specs": extra_specs}
|
||||||
|
post_body = json.dumps(extra_specs)
|
||||||
|
resp, body = self.post(uri, post_body, version=version)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
return self._parse_resp(body)
|
||||||
|
|
||||||
|
def delete_share_type_extra_spec(self, share_type_id, extra_spec_name,
|
||||||
|
version=LATEST_MICROVERSION):
|
||||||
|
uri = "types/%s/extra_specs/%s" % (share_type_id, extra_spec_name)
|
||||||
|
resp, body = self.delete(uri, version=version)
|
||||||
|
self.expected_success(202, resp.status)
|
||||||
|
return body
|
||||||
|
|
||||||
###############
|
###############
|
||||||
|
|
||||||
def get_snapshot_instance(self, instance_id, version=LATEST_MICROVERSION):
|
def get_snapshot_instance(self, instance_id, version=LATEST_MICROVERSION):
|
||||||
@ -726,13 +787,6 @@ class SharesV2Client(shares_client.SharesClient):
|
|||||||
self.expected_success(200, resp.status)
|
self.expected_success(200, resp.status)
|
||||||
return self._parse_resp(body)
|
return self._parse_resp(body)
|
||||||
|
|
||||||
def delete_share_type_extra_spec(self, share_type_id, extra_spec_name,
|
|
||||||
version=LATEST_MICROVERSION):
|
|
||||||
uri = "types/%s/extra_specs/%s" % (share_type_id, extra_spec_name)
|
|
||||||
resp, body = self.delete(uri, version=version)
|
|
||||||
self.expected_success(202, resp.status)
|
|
||||||
return body
|
|
||||||
|
|
||||||
def list_access_to_share_type(self, share_type_id,
|
def list_access_to_share_type(self, share_type_id,
|
||||||
version=LATEST_MICROVERSION,
|
version=LATEST_MICROVERSION,
|
||||||
action_name=None):
|
action_name=None):
|
||||||
|
@ -14,11 +14,16 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
|
from tempest import config
|
||||||
from tempest.lib.common.utils import data_utils
|
from tempest.lib.common.utils import data_utils
|
||||||
from tempest.lib import exceptions as lib_exc
|
from tempest.lib import exceptions as lib_exc
|
||||||
from testtools import testcase as tc
|
from testtools import testcase as tc
|
||||||
|
|
||||||
|
from manila_tempest_tests.common import constants
|
||||||
from manila_tempest_tests.tests.api import base
|
from manila_tempest_tests.tests.api import base
|
||||||
|
from manila_tempest_tests import utils
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
@ -69,8 +74,12 @@ class ExtraSpecsAdminNegativeTest(base.BaseSharesMixedTest):
|
|||||||
share_type = self.shares_v2_client.get_share_type(
|
share_type = self.shares_v2_client.get_share_type(
|
||||||
st['share_type']['id'])
|
st['share_type']['id'])
|
||||||
# Verify a non-admin can only read the required extra-specs
|
# Verify a non-admin can only read the required extra-specs
|
||||||
expected_keys = ['driver_handles_share_servers', 'snapshot_support',
|
expected_keys = ['driver_handles_share_servers', 'snapshot_support']
|
||||||
'create_share_from_snapshot_support']
|
if utils.is_microversion_ge(CONF.share.max_api_microversion, '2.24'):
|
||||||
|
expected_keys.append('create_share_from_snapshot_support')
|
||||||
|
if utils.is_microversion_ge(CONF.share.max_api_microversion,
|
||||||
|
constants.REVERT_TO_SNAPSHOT_MICROVERSION):
|
||||||
|
expected_keys.append('revert_to_snapshot_support')
|
||||||
actual_keys = share_type['share_type']['extra_specs'].keys()
|
actual_keys = share_type['share_type']['extra_specs'].keys()
|
||||||
self.assertEqual(sorted(expected_keys), sorted(actual_keys),
|
self.assertEqual(sorted(expected_keys), sorted(actual_keys),
|
||||||
'Incorrect extra specs visible to non-admin user; '
|
'Incorrect extra specs visible to non-admin user; '
|
||||||
|
@ -740,6 +740,8 @@ class BaseSharesTest(test.BaseTestCase):
|
|||||||
CONF.share.capability_snapshot_support)
|
CONF.share.capability_snapshot_support)
|
||||||
create_from_snapshot_support = six.text_type(
|
create_from_snapshot_support = six.text_type(
|
||||||
CONF.share.capability_create_share_from_snapshot_support)
|
CONF.share.capability_create_share_from_snapshot_support)
|
||||||
|
revert_to_snapshot_support = six.text_type(
|
||||||
|
CONF.share.capability_revert_to_snapshot_support)
|
||||||
|
|
||||||
extra_specs_dict = {
|
extra_specs_dict = {
|
||||||
"driver_handles_share_servers": dhss,
|
"driver_handles_share_servers": dhss,
|
||||||
@ -748,6 +750,7 @@ class BaseSharesTest(test.BaseTestCase):
|
|||||||
optional = {
|
optional = {
|
||||||
"snapshot_support": snapshot_support,
|
"snapshot_support": snapshot_support,
|
||||||
"create_share_from_snapshot_support": create_from_snapshot_support,
|
"create_share_from_snapshot_support": create_from_snapshot_support,
|
||||||
|
"revert_to_snapshot_support": revert_to_snapshot_support,
|
||||||
}
|
}
|
||||||
# NOTE(gouthamr): In micro-versions < 2.24, snapshot_support is a
|
# NOTE(gouthamr): In micro-versions < 2.24, snapshot_support is a
|
||||||
# required extra-spec
|
# required extra-spec
|
||||||
|
109
manila_tempest_tests/tests/api/test_revert_to_snapshot.py
Normal file
109
manila_tempest_tests/tests/api/test_revert_to_snapshot.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# Copyright 2016 Andrew Kerr
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import ddt
|
||||||
|
|
||||||
|
from tempest import config
|
||||||
|
from tempest.lib.common.utils import data_utils
|
||||||
|
from testtools import testcase as tc
|
||||||
|
|
||||||
|
from manila_tempest_tests.common import constants
|
||||||
|
from manila_tempest_tests.tests.api import base
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@base.skip_if_microversion_not_supported(
|
||||||
|
constants.REVERT_TO_SNAPSHOT_MICROVERSION)
|
||||||
|
@ddt.ddt
|
||||||
|
class RevertToSnapshotTest(base.BaseSharesMixedTest):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def skip_checks(cls):
|
||||||
|
super(RevertToSnapshotTest, cls).skip_checks()
|
||||||
|
if not CONF.share.run_revert_to_snapshot_tests:
|
||||||
|
msg = "Revert to snapshot tests are disabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
if not CONF.share.capability_revert_to_snapshot_support:
|
||||||
|
msg = "Revert to snapshot support is disabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
if not CONF.share.capability_snapshot_support:
|
||||||
|
msg = "Snapshot support is disabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
if not CONF.share.run_snapshot_tests:
|
||||||
|
msg = "Snapshot tests are disabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(RevertToSnapshotTest, cls).resource_setup()
|
||||||
|
cls.admin_client = cls.admin_shares_v2_client
|
||||||
|
pools = cls.admin_client.list_pools(detail=True)['pools']
|
||||||
|
revert_support = [
|
||||||
|
pool['capabilities'][constants.REVERT_TO_SNAPSHOT_SUPPORT]
|
||||||
|
for pool in pools]
|
||||||
|
if not any(revert_support):
|
||||||
|
msg = "Revert to snapshot not supported."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
|
||||||
|
cls.share_type_name = data_utils.rand_name("share-type")
|
||||||
|
extra_specs = {constants.REVERT_TO_SNAPSHOT_SUPPORT: True}
|
||||||
|
cls.revert_enabled_extra_specs = cls.add_extra_specs_to_dict(
|
||||||
|
extra_specs=extra_specs)
|
||||||
|
|
||||||
|
cls.share_type = cls.create_share_type(
|
||||||
|
cls.share_type_name,
|
||||||
|
extra_specs=cls.revert_enabled_extra_specs,
|
||||||
|
client=cls.admin_client)
|
||||||
|
|
||||||
|
cls.st_id = cls.share_type['share_type']['id']
|
||||||
|
|
||||||
|
cls.share = cls.create_share(share_type_id=cls.st_id)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
|
||||||
|
@ddt.data(
|
||||||
|
*{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
|
||||||
|
CONF.share.max_api_microversion}
|
||||||
|
)
|
||||||
|
def test_revert_to_latest_snapshot(self, version):
|
||||||
|
snapshot = self.create_snapshot_wait_for_active(self.share['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
self.shares_v2_client.revert_to_snapshot(
|
||||||
|
self.share['id'],
|
||||||
|
snapshot['id'],
|
||||||
|
version=version)
|
||||||
|
self.shares_v2_client.wait_for_share_status(self.share['id'],
|
||||||
|
constants.STATUS_AVAILABLE)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
|
||||||
|
@ddt.data(
|
||||||
|
*{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
|
||||||
|
CONF.share.max_api_microversion}
|
||||||
|
)
|
||||||
|
def test_revert_to_previous_snapshot(self, version):
|
||||||
|
snapshot1 = self.create_snapshot_wait_for_active(
|
||||||
|
self.share['id'], cleanup_in_class=False)
|
||||||
|
snapshot2 = self.create_snapshot_wait_for_active(
|
||||||
|
self.share['id'], cleanup_in_class=False)
|
||||||
|
|
||||||
|
self.shares_v2_client.delete_snapshot(snapshot2['id'])
|
||||||
|
self.shares_v2_client.wait_for_resource_deletion(
|
||||||
|
snapshot_id=snapshot2['id'])
|
||||||
|
|
||||||
|
self.shares_v2_client.revert_to_snapshot(self.share['id'],
|
||||||
|
snapshot1['id'],
|
||||||
|
version=version)
|
||||||
|
self.shares_v2_client.wait_for_share_status(self.share['id'],
|
||||||
|
constants.STATUS_AVAILABLE)
|
@ -0,0 +1,162 @@
|
|||||||
|
# Copyright 2016 Andrew Kerr
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import ddt
|
||||||
|
|
||||||
|
from tempest import config
|
||||||
|
from tempest.lib.common.utils import data_utils
|
||||||
|
from tempest.lib import exceptions
|
||||||
|
from testtools import testcase as tc
|
||||||
|
|
||||||
|
from manila_tempest_tests.common import constants
|
||||||
|
from manila_tempest_tests.tests.api import base
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@base.skip_if_microversion_not_supported(
|
||||||
|
constants.REVERT_TO_SNAPSHOT_MICROVERSION)
|
||||||
|
@ddt.ddt
|
||||||
|
class RevertToSnapshotNegativeTest(base.BaseSharesMixedTest):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def skip_checks(cls):
|
||||||
|
super(RevertToSnapshotNegativeTest, cls).skip_checks()
|
||||||
|
if not CONF.share.run_revert_to_snapshot_tests:
|
||||||
|
msg = "Revert to snapshot tests are disabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
if not CONF.share.capability_revert_to_snapshot_support:
|
||||||
|
msg = "Revert to snapshot support is disabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
if not CONF.share.capability_snapshot_support:
|
||||||
|
msg = "Snapshot support is disabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
if not CONF.share.run_snapshot_tests:
|
||||||
|
msg = "Snapshot tests are disabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(RevertToSnapshotNegativeTest, cls).resource_setup()
|
||||||
|
cls.admin_client = cls.admin_shares_v2_client
|
||||||
|
pools = cls.admin_client.list_pools(detail=True)['pools']
|
||||||
|
revert_support = [
|
||||||
|
pool['capabilities'][constants.REVERT_TO_SNAPSHOT_SUPPORT]
|
||||||
|
for pool in pools]
|
||||||
|
if not any(revert_support):
|
||||||
|
msg = "Revert to snapshot not supported."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
|
||||||
|
cls.share_type_name = data_utils.rand_name("share-type")
|
||||||
|
extra_specs = {constants.REVERT_TO_SNAPSHOT_SUPPORT: True}
|
||||||
|
cls.revert_enabled_extra_specs = cls.add_extra_specs_to_dict(
|
||||||
|
extra_specs=extra_specs)
|
||||||
|
|
||||||
|
cls.share_type = cls.create_share_type(
|
||||||
|
cls.share_type_name,
|
||||||
|
extra_specs=cls.revert_enabled_extra_specs,
|
||||||
|
client=cls.admin_client)
|
||||||
|
|
||||||
|
cls.st_id = cls.share_type['share_type']['id']
|
||||||
|
|
||||||
|
cls.share = cls.create_share(share_type_id=cls.st_id)
|
||||||
|
cls.share2 = cls.create_share(share_type_id=cls.st_id)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
|
||||||
|
@ddt.data(
|
||||||
|
*{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
|
||||||
|
CONF.share.max_api_microversion}
|
||||||
|
)
|
||||||
|
def test_revert_to_second_latest_snapshot(self, version):
|
||||||
|
snapshot1 = self.create_snapshot_wait_for_active(
|
||||||
|
self.share['id'], cleanup_in_class=False)
|
||||||
|
self.create_snapshot_wait_for_active(self.share['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.shares_v2_client.revert_to_snapshot,
|
||||||
|
self.share['id'],
|
||||||
|
snapshot1['id'],
|
||||||
|
version=version)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
|
||||||
|
@ddt.data(
|
||||||
|
*{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
|
||||||
|
CONF.share.max_api_microversion}
|
||||||
|
)
|
||||||
|
def test_revert_to_error_snapshot(self, version):
|
||||||
|
snapshot = self.create_snapshot_wait_for_active(self.share['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
self.admin_client.reset_state(snapshot['id'],
|
||||||
|
status=constants.STATUS_ERROR,
|
||||||
|
s_type='snapshots')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.shares_v2_client.revert_to_snapshot,
|
||||||
|
self.share['id'],
|
||||||
|
snapshot['id'],
|
||||||
|
version=version)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
|
||||||
|
@ddt.data(
|
||||||
|
*{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
|
||||||
|
CONF.share.max_api_microversion}
|
||||||
|
)
|
||||||
|
def test_revert_error_share_to_snapshot(self, version):
|
||||||
|
snapshot = self.create_snapshot_wait_for_active(self.share['id'],
|
||||||
|
cleanup_in_class=False)
|
||||||
|
|
||||||
|
self.admin_client.reset_state(self.share['id'],
|
||||||
|
status=constants.STATUS_ERROR,
|
||||||
|
s_type='shares')
|
||||||
|
|
||||||
|
self.addCleanup(self.admin_client.reset_state,
|
||||||
|
self.share['id'],
|
||||||
|
status=constants.STATUS_AVAILABLE,
|
||||||
|
s_type='shares')
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.Conflict,
|
||||||
|
self.shares_v2_client.revert_to_snapshot,
|
||||||
|
self.share['id'],
|
||||||
|
snapshot['id'],
|
||||||
|
version=version)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
|
||||||
|
@ddt.data(
|
||||||
|
*{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
|
||||||
|
CONF.share.max_api_microversion}
|
||||||
|
)
|
||||||
|
def test_revert_to_missing_snapshot(self, version):
|
||||||
|
self.assertRaises(exceptions.BadRequest,
|
||||||
|
self.shares_v2_client.revert_to_snapshot,
|
||||||
|
self.share['id'],
|
||||||
|
self.share['id'],
|
||||||
|
version=version)
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
|
||||||
|
@ddt.data(
|
||||||
|
*{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
|
||||||
|
CONF.share.max_api_microversion}
|
||||||
|
)
|
||||||
|
def test_revert_to_invalid_snapshot(self, version):
|
||||||
|
snapshot = self.create_snapshot_wait_for_active(
|
||||||
|
self.share['id'], cleanup_in_class=False)
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.BadRequest,
|
||||||
|
self.shares_v2_client.revert_to_snapshot,
|
||||||
|
self.share2['id'],
|
||||||
|
snapshot['id'],
|
||||||
|
version=version)
|
@ -20,6 +20,7 @@ from tempest.lib.common.utils import data_utils
|
|||||||
import testtools
|
import testtools
|
||||||
from testtools import testcase as tc
|
from testtools import testcase as tc
|
||||||
|
|
||||||
|
from manila_tempest_tests.common import constants
|
||||||
from manila_tempest_tests.tests.api import base
|
from manila_tempest_tests.tests.api import base
|
||||||
from manila_tempest_tests import utils
|
from manila_tempest_tests import utils
|
||||||
|
|
||||||
@ -106,6 +107,9 @@ class SharesActionsTest(base.BaseSharesTest):
|
|||||||
expected_keys.append("user_id")
|
expected_keys.append("user_id")
|
||||||
if utils.is_microversion_ge(version, '2.24'):
|
if utils.is_microversion_ge(version, '2.24'):
|
||||||
expected_keys.append("create_share_from_snapshot_support")
|
expected_keys.append("create_share_from_snapshot_support")
|
||||||
|
if utils.is_microversion_ge(version,
|
||||||
|
constants.REVERT_TO_SNAPSHOT_MICROVERSION):
|
||||||
|
expected_keys.append("revert_to_snapshot_support")
|
||||||
actual_keys = list(share.keys())
|
actual_keys = list(share.keys())
|
||||||
[self.assertIn(key, actual_keys) for key in expected_keys]
|
[self.assertIn(key, actual_keys) for key in expected_keys]
|
||||||
|
|
||||||
@ -167,6 +171,12 @@ class SharesActionsTest(base.BaseSharesTest):
|
|||||||
def test_get_share_with_create_share_from_snapshot_support(self):
|
def test_get_share_with_create_share_from_snapshot_support(self):
|
||||||
self._get_share('2.24')
|
self._get_share('2.24')
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
|
||||||
|
@utils.skip_if_microversion_not_supported(
|
||||||
|
constants.REVERT_TO_SNAPSHOT_MICROVERSION)
|
||||||
|
def test_get_share_with_revert_to_snapshot_support(self):
|
||||||
|
self._get_share(constants.REVERT_TO_SNAPSHOT_MICROVERSION)
|
||||||
|
|
||||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
|
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
|
||||||
def test_list_shares(self):
|
def test_list_shares(self):
|
||||||
|
|
||||||
@ -213,6 +223,9 @@ class SharesActionsTest(base.BaseSharesTest):
|
|||||||
keys.append("user_id")
|
keys.append("user_id")
|
||||||
if utils.is_microversion_ge(version, '2.24'):
|
if utils.is_microversion_ge(version, '2.24'):
|
||||||
keys.append("create_share_from_snapshot_support")
|
keys.append("create_share_from_snapshot_support")
|
||||||
|
if utils.is_microversion_ge(version,
|
||||||
|
constants.REVERT_TO_SNAPSHOT_MICROVERSION):
|
||||||
|
keys.append("revert_to_snapshot_support")
|
||||||
[self.assertIn(key, sh.keys()) for sh in shares for key in keys]
|
[self.assertIn(key, sh.keys()) for sh in shares for key in keys]
|
||||||
|
|
||||||
# our shares in list and have no duplicates
|
# our shares in list and have no duplicates
|
||||||
@ -264,6 +277,13 @@ class SharesActionsTest(base.BaseSharesTest):
|
|||||||
self):
|
self):
|
||||||
self._list_shares_with_detail('2.24')
|
self._list_shares_with_detail('2.24')
|
||||||
|
|
||||||
|
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
|
||||||
|
@utils.skip_if_microversion_not_supported(
|
||||||
|
constants.REVERT_TO_SNAPSHOT_MICROVERSION)
|
||||||
|
def test_list_shares_with_detail_with_revert_to_snapshot_support(self):
|
||||||
|
self._list_shares_with_detail(
|
||||||
|
constants.REVERT_TO_SNAPSHOT_MICROVERSION)
|
||||||
|
|
||||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
|
@tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
|
||||||
def test_list_shares_with_detail_filter_by_metadata(self):
|
def test_list_shares_with_detail_filter_by_metadata(self):
|
||||||
filters = {'metadata': self.metadata}
|
filters = {'metadata': self.metadata}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added revert-to-snapshot feature for regular and replicated shares.
|
||||||
|
- Added revert-to-snapshot support to the LVM driver.
|
Loading…
x
Reference in New Issue
Block a user