Use encryption key ref during share create
- Share type extra-spec "encryption_support" should be set to "share_server". - Use encryption_key_ref from user provided share create API option - Create ACL for barbican user from current user context, so barbican user can fetch the secret from secret_ref in backend driver. - Create server_encryption_keys project level quota which helps to control the encrypted share servers. Such share servers do not support migration/unmanage etc. - Create application credentials for barbican user during share server creation and delete during share server deletion. partially-implements: blueprint share-encryption Change-Id: I9e3ff98e12027eaf75cdc5f6089a016c102315d3 Signed-off-by: Kiran Pawar <kinpaa@gmail.com>
This commit is contained in:
1 parent
d49fbdc9b7
commit
82a94bbcec
59 files changed
+1470
-58
No files matched your search
@@ -344,6 +344,14 @@ detail_id:
|
||||
type: string
|
||||
description: >
|
||||
The ID of the message detail.
|
||||
encryption_key_ref_query:
|
||||
description: |
|
||||
The encryption key ref that can be used to filter shares or
|
||||
share instances.
|
||||
in: query
|
||||
required: false
|
||||
type: string
|
||||
min_version: 2.90
|
||||
export_location_id_query:
|
||||
description: |
|
||||
The export location UUID that can be used to filter shares or
|
||||
@@ -1369,6 +1377,21 @@ driver_options:
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
encryption_key_ref_request:
|
||||
description: |
|
||||
The encryption key ref is valid barbican secret UUID that will be used to
|
||||
get encryption key by the storage drivers.
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
min_version: 2.90
|
||||
encryption_key_ref_response:
|
||||
description: |
|
||||
The encryption key ref is valid barbican secret UUID that will be used to
|
||||
get encryption key by the storage drivers.
|
||||
in: body
|
||||
required: true
|
||||
type: object
|
||||
export_location:
|
||||
description: |
|
||||
The export location. For newer API versions it is available in
|
||||
@@ -1805,6 +1828,20 @@ maxTotalBackupGigabytesOptional:
|
||||
required: false
|
||||
type: integer
|
||||
min_version: 2.80
|
||||
maxTotalEncryptionKeys:
|
||||
description: |
|
||||
The number of encryption keys allowed in a project.
|
||||
in: body
|
||||
required: true
|
||||
type: integer
|
||||
min_version: 2.90
|
||||
maxTotalEncryptionKeysOptional:
|
||||
description: |
|
||||
The number of encryption keys allowed in a project.
|
||||
in: body
|
||||
required: false
|
||||
type: integer
|
||||
min_version: 2.90
|
||||
maxTotalReplicaGigabytes:
|
||||
description: |
|
||||
The maximum number of replica gigabytes that are allowed in a project.
|
||||
@@ -2384,6 +2421,28 @@ quota_class_set:
|
||||
in: body
|
||||
required: true
|
||||
type: object
|
||||
quota_encryption_keys:
|
||||
description: |
|
||||
The number of encryption keys allowed for each project.
|
||||
in: body
|
||||
required: true
|
||||
min_version: 2.90
|
||||
type: integer
|
||||
quota_encryption_keys_detail:
|
||||
description: |
|
||||
The limit, in_use, reserved number of encryption keys
|
||||
for each project.
|
||||
in: body
|
||||
min_version: 2.90
|
||||
required: true
|
||||
type: object
|
||||
quota_encryption_keys_request:
|
||||
description: |
|
||||
The number of encryption keys allowed for each project.
|
||||
in: body
|
||||
min_version: 2.90
|
||||
required: false
|
||||
type: integer
|
||||
quota_gigabytes:
|
||||
description: |
|
||||
The number of gigabytes allowed for each project.
|
||||
|
||||
@@ -12,6 +12,7 @@ Quota classes can be shown and updated for a project.
|
||||
version 2.62.
|
||||
Share backups and backup gigabytes were added to quota management
|
||||
APIs in API version 2.80.
|
||||
Encryption keys was added to quota management APIs in API version 2.90.
|
||||
|
||||
|
||||
Show quota classes for a project
|
||||
@@ -62,6 +63,7 @@ Response Parameters
|
||||
- per_share_gigabytes: perShareGigabytes
|
||||
- backups: maxTotalShareBackups
|
||||
- backup_gigabytes: maxTotalBackupGigabytes
|
||||
- encryption_keys: maxTotalEncryptionKeys
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
@@ -108,6 +110,7 @@ Request
|
||||
- per-share-gigabytes: perShareGigabytesOptional
|
||||
- backups: maxTotalShareBackupsOptional
|
||||
- backup-gigabytes: maxTotalBackupGigabytesOptional
|
||||
- encryption_keys: maxTotalEncryptionKeysOptional
|
||||
|
||||
Request Example
|
||||
---------------
|
||||
@@ -134,6 +137,7 @@ Response Parameters
|
||||
- per_share_gigabytes: perShareGigabytes
|
||||
- backups: maxTotalShareBackups
|
||||
- backup_gigabytes: maxTotalBackupGigabytes
|
||||
- encryption_keys: maxTotalEncryptionKeys
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
@@ -24,6 +24,7 @@ Provides quotas management support.
|
||||
- ``per_share_gigabytes`` (since API version 2.62)
|
||||
- ``backups`` (since API version 2.80)
|
||||
- ``backup_gigabytes`` (since API version 2.80)
|
||||
- ``encryption_keys`` (since API version 2.90)
|
||||
|
||||
In order to manipulate share type quotas, the requests will be similar
|
||||
to the examples below, except that the ``user_id={user_id}`` must be
|
||||
@@ -41,6 +42,8 @@ Provides quotas management support.
|
||||
Share backups and backup gigabytes were added to quota management
|
||||
APIs in API version 2.80.
|
||||
|
||||
Encryption keys was added to quota management APIs in API version 2.90.
|
||||
|
||||
|
||||
Show default quota set
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -90,6 +93,7 @@ Response parameters
|
||||
- per_share_gigabytes: quota_per_share_gigabytes
|
||||
- backups: quota_backups
|
||||
- backup_gigabytes: quota_backup_gigabytes
|
||||
- encryption_keys: quota_encryption_keys
|
||||
|
||||
Response example
|
||||
----------------
|
||||
@@ -151,6 +155,7 @@ Response parameters
|
||||
- per_share_gigabytes: quota_per_share_gigabytes
|
||||
- backups: quota_backups
|
||||
- backup_gigabytes: quota_backup_gigabytes
|
||||
- encryption_keys: quota_encryption_keys
|
||||
|
||||
Response example
|
||||
----------------
|
||||
@@ -214,6 +219,7 @@ Response parameters
|
||||
- per_share_gigabytes: quota_per_share_gigabytes_detail
|
||||
- backups: quota_backups_detail
|
||||
- backup_gigabytes: quota_backup_gigabytes_detail
|
||||
- encryption_keys: quota_encryption_keys_detail
|
||||
|
||||
Response example
|
||||
----------------
|
||||
@@ -269,6 +275,7 @@ Request
|
||||
- per_share_gigabytes: quota_per_share_gigabytes_request
|
||||
- backups: quota_backups_request
|
||||
- backup_gigabytes: quota_backup_gigabytes_request
|
||||
- encryption_keys: quota_encryption_keys_request
|
||||
|
||||
Request example
|
||||
---------------
|
||||
@@ -295,6 +302,7 @@ Response parameters
|
||||
- per_share_gigabytes: quota_per_share_gigabytes
|
||||
- backups: quota_backups
|
||||
- backup_gigabytes: quota_backup_gigabytes
|
||||
- encryption_keys: quota_encryption_keys
|
||||
|
||||
Response example
|
||||
----------------
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"scheduler_hints": {
|
||||
"same_host": "d9c66489-cf02-4156-b0f2-527f3211b243,4ffee55f-ba98-42d2-a8ce-e7cecb169182",
|
||||
"different_host": "903685eb-f242-4105-903d-4bef2db94be4"
|
||||
}
|
||||
},
|
||||
"encryption_key_ref": "86babe9b-7277-4c3a-a081-6eb3eac9231d"
|
||||
}
|
||||
}
|
||||
@@ -135,6 +135,7 @@ Response parameters
|
||||
- is_auto_deletable: share_server_show_is_auto_deletable
|
||||
- share_network_subnet_id: share_network_subnet_id_share_server_body
|
||||
- security_service_update_support: security_service_update_support
|
||||
- encryption_key_ref: encryption_key_ref_response
|
||||
|
||||
Response example
|
||||
----------------
|
||||
@@ -281,6 +282,7 @@ Response parameters
|
||||
- identifier: identifier
|
||||
- share_network_subnet_id: share_network_subnet_id_manage_server_body
|
||||
- security_service_update_support: security_service_update_support
|
||||
- encryption_key_ref: encryption_key_ref_response
|
||||
|
||||
|
||||
Response examples
|
||||
|
||||
@@ -144,6 +144,7 @@ Request
|
||||
- offset: offset
|
||||
- sort_key: sort_key
|
||||
- sort_dir: sort_dir
|
||||
- encryption_key_ref: encryption_key_ref_query
|
||||
|
||||
Response parameters
|
||||
-------------------
|
||||
@@ -214,6 +215,7 @@ Request
|
||||
- offset: offset
|
||||
- sort_key: sort_key
|
||||
- sort_dir: sort_dir
|
||||
- encryption_key_ref: encryption_key_ref_query
|
||||
|
||||
Response parameters
|
||||
-------------------
|
||||
@@ -257,6 +259,7 @@ Response parameters
|
||||
- export_locations: export_locations
|
||||
- is_soft_deleted: is_soft_deleted_response
|
||||
- scheduled_to_be_deleted_at: scheduled_to_be_deleted_at_response
|
||||
- encryption_key_ref: encryption_key_ref_response
|
||||
|
||||
|
||||
Response example
|
||||
@@ -337,6 +340,7 @@ Response parameters
|
||||
- export_locations: export_locations
|
||||
- is_soft_deleted: is_soft_deleted_response
|
||||
- scheduled_to_be_deleted_at: scheduled_to_be_deleted_at_response
|
||||
- encryption_key_ref: encryption_key_ref_response
|
||||
|
||||
Response example
|
||||
----------------
|
||||
@@ -405,6 +409,7 @@ Response parameters
|
||||
- access_rules_status: access_rules_status
|
||||
- share_type_id: share_type_id
|
||||
- id: id_13
|
||||
- encryption_key_ref: encryption_key_ref_response
|
||||
|
||||
Response example
|
||||
----------------
|
||||
@@ -455,6 +460,7 @@ Request
|
||||
- share_network_id: share_network_id_request
|
||||
- availability_zone: availability_zone_request
|
||||
- scheduler_hints: scheduler_hints
|
||||
- encryption_key_ref: encryption_key_ref_request
|
||||
|
||||
Request example
|
||||
---------------
|
||||
@@ -503,6 +509,7 @@ Response parameters
|
||||
- export_locations: export_locations
|
||||
- is_soft_deleted: is_soft_deleted_response
|
||||
- scheduled_to_be_deleted_at: scheduled_to_be_deleted_at_response
|
||||
- encryption_key_ref: encryption_key_ref_response
|
||||
|
||||
Response example
|
||||
----------------
|
||||
@@ -610,6 +617,7 @@ Response parameters
|
||||
- export_locations: export_locations
|
||||
- is_soft_deleted: is_soft_deleted_response
|
||||
- scheduled_to_be_deleted_at: scheduled_to_be_deleted_at_response
|
||||
- encryption_key_ref: encryption_key_ref_response
|
||||
|
||||
Response example
|
||||
----------------
|
||||
@@ -710,6 +718,7 @@ Response parameters
|
||||
- export_locations: export_locations
|
||||
- is_soft_deleted: is_soft_deleted_response
|
||||
- scheduled_to_be_deleted_at: scheduled_to_be_deleted_at_response
|
||||
- encryption_key_ref: encryption_key_ref_response
|
||||
|
||||
Response example
|
||||
----------------
|
||||
|
||||
@@ -63,6 +63,10 @@ important terms:
|
||||
+------------------------------------+-------------------------+---------------------------------------------------------+
|
||||
| mount_point_name_support | true or false | share can or cannot have customized export location |
|
||||
+------------------------------------+-------------------------+---------------------------------------------------------+
|
||||
| encryption_support | share | share is encrypted with share encryption key |
|
||||
| +-------------------------+---------------------------------------------------------+
|
||||
| | share_server | share is encrypted with share server encryption key |
|
||||
+------------------------------------+-------------------------+---------------------------------------------------------+
|
||||
| provisioning:mount_point_prefix | string | prefix used for custom export location |
|
||||
+------------------------------------+-------------------------+---------------------------------------------------------+
|
||||
|
||||
@@ -505,6 +509,58 @@ Create a share
|
||||
+---------------------------------------+-------------------------------------------------------------------------+
|
||||
|
||||
|
||||
* Create a share using encryption key reference.
|
||||
|
||||
User can create share using their own encryption key. The key must be stored
|
||||
in key-manager (Openstack Barbican) service. First create share type and
|
||||
specify extra-spec ``encryption_support``. It can have value ``share`` or
|
||||
``share_server`` based on support by backend storage driver. Then use
|
||||
`--encryption-key-ref` option in share create command. Users can use
|
||||
encryption key reference or UUID of key reference here.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack share create NFS 1 \
|
||||
--name myshare3 \
|
||||
--description "My Manila share - Encrypted" \
|
||||
--share-network mysharenetwork \
|
||||
--share-type encrypted_share_type \
|
||||
--encryption-key-ref 86babe9b-7277-4c3a-a081-6eb3eac9231d
|
||||
|
||||
+---------------------------------------+-----------------------------------------------------------------------+
|
||||
| Property | Value |
|
||||
+---------------------------------------+-----------------------------------------------------------------------+
|
||||
| id | 40de4f4c-4588-4d9c-844b-f74d8951053a |
|
||||
| size | 1 |
|
||||
| availability_zone | None |
|
||||
| created_at | 2020-08-07T05:24:14.000000 |
|
||||
| status | creating |
|
||||
| name | myshare3 |
|
||||
| description | My Manila share - Encrypted |
|
||||
| project_id | d9932a60d9ee4087b6cff9ce6e9b4e3b |
|
||||
| snapshot_id | None |
|
||||
| share_network_id | c4bfdd5e-7502-4a65-8876-0ce8b9914a64 |
|
||||
| share_proto | NFS |
|
||||
| metadata | {} |
|
||||
| share_type | af7b64ec-cdb3-4a5f-93c9-51672d72e172 |
|
||||
| is_public | False |
|
||||
| snapshot_support | True |
|
||||
| task_state | None |
|
||||
| share_type_name | encrypted_share_type |
|
||||
| access_rules_status | active |
|
||||
| replication_type | None |
|
||||
| has_replicas | False |
|
||||
| user_id | 2cebd96a794f431caa06ce5215e0da21 |
|
||||
| create_share_from_snapshot_support | True |
|
||||
| revert_to_snapshot_support | True |
|
||||
| share_group_id | None |
|
||||
| encryption_key_ref | 86babe9b-7277-4c3a-a081-6eb3eac9231d |
|
||||
| source_share_group_snapshot_member_id | None |
|
||||
| mount_snapshot_support | True |
|
||||
| progress | None |
|
||||
+---------------------------------------+-----------------------------------------------------------------------+
|
||||
|
||||
|
||||
Grant and revoke share access
|
||||
-----------------------------
|
||||
|
||||
|
||||
@@ -208,13 +208,14 @@ REST_API_VERSION_HISTORY = """
|
||||
* 2.88 - Added support for update Share access rule.
|
||||
* 2.89 - Added support for passing Share network subnet metadata updates
|
||||
to driver.
|
||||
* 2.90 - Added encryption key reference option to share create API.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
# The default api version request is defined to be the
|
||||
# minimum version of the API supported.
|
||||
_MIN_API_VERSION = "2.0"
|
||||
_MAX_API_VERSION = "2.89"
|
||||
_MAX_API_VERSION = "2.90"
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
|
||||
|
||||
@@ -477,3 +477,8 @@ user documentation.
|
||||
------------------------------
|
||||
Added support for passing share network subnet metadata updates to share
|
||||
backend driver.
|
||||
|
||||
|
||||
2.90
|
||||
----
|
||||
Added ``encryption_key_ref`` option to share create API.
|
||||
+24
-4
@@ -257,7 +257,7 @@ class ShareMixin(object):
|
||||
def _create(self, req, body,
|
||||
check_create_share_from_snapshot_support=False,
|
||||
check_availability_zones_extra_spec=False,
|
||||
scheduler_hints=None):
|
||||
scheduler_hints=None, encryption_key_ref=None):
|
||||
"""Creates a new share."""
|
||||
context = req.environ['manila.context']
|
||||
|
||||
@@ -413,11 +413,16 @@ class ShareMixin(object):
|
||||
# Only use in create share feature. Create share from snapshot
|
||||
# and create share with share group features not
|
||||
# need this check.
|
||||
if share_type and share_type.get('extra_specs'):
|
||||
dhss = (strutils.bool_from_string(
|
||||
share_type.get('extra_specs').get(
|
||||
'driver_handles_share_servers')))
|
||||
else:
|
||||
dhss = False
|
||||
|
||||
if (not share_network_id and not snapshot
|
||||
and not share_group_id
|
||||
and share_type and share_type.get('extra_specs')
|
||||
and (strutils.bool_from_string(share_type.get('extra_specs').
|
||||
get('driver_handles_share_servers')))):
|
||||
and dhss):
|
||||
msg = _('Share network must be set when the '
|
||||
'driver_handles_share_servers is true.')
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
@@ -440,12 +445,27 @@ class ShareMixin(object):
|
||||
payload = {'type': type_chosen, 'az': availability_zone}
|
||||
raise exc.HTTPBadRequest(explanation=msg % payload)
|
||||
|
||||
if share_type and encryption_key_ref:
|
||||
type_enc = share_type.get(
|
||||
'extra_specs', {}).get('encryption_support')
|
||||
if type_enc not in constants.SUPPORTED_ENCRYPTION_TYPES:
|
||||
msg = _("Share type %(type)s extra-specs 'encryption_support' "
|
||||
"is missing valid value e.g. share, share_server.")
|
||||
payload = {'type': share_type}
|
||||
raise exc.HTTPBadRequest(explanation=msg % payload)
|
||||
if not dhss:
|
||||
msg = _("Share type %(type)s must set dhss=True for share "
|
||||
"encryption.")
|
||||
payload = {'type': share_type}
|
||||
raise exc.HTTPBadRequest(explanation=msg % payload)
|
||||
|
||||
if share_type:
|
||||
kwargs['share_type'] = share_type
|
||||
if share_network_id:
|
||||
kwargs['share_network_id'] = share_network_id
|
||||
|
||||
kwargs['scheduler_hints'] = scheduler_hints
|
||||
kwargs['encryption_key_ref'] = encryption_key_ref
|
||||
|
||||
if req.api_version_request >= api_version.APIVersionRequest("2.84"):
|
||||
kwargs['mount_point_name'] = share.pop('mount_point_name', None)
|
||||
|
||||
@@ -151,7 +151,8 @@ class QuotaSetsMixin(object):
|
||||
body.get('replica_gigabytes') is None and
|
||||
body.get('per_share_gigabytes') is None and
|
||||
body.get('backups') is None and
|
||||
body.get('backup_gigabytes') is None):
|
||||
body.get('backup_gigabytes') is None and
|
||||
body.get('encryption_keys') is None):
|
||||
msg = _("Must supply at least one quota field to update.")
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
@@ -351,6 +352,9 @@ class QuotaSetsController(QuotaSetsMixin, wsgi.Controller):
|
||||
elif req.api_version_request < api_version.APIVersionRequest("2.80"):
|
||||
self._ensure_specific_microversion_args_are_absent(
|
||||
body, ['backups', 'backup_gigabytes'], "2.80")
|
||||
elif req.api_version_request < api_version.APIVersionRequest("2.90"):
|
||||
self._ensure_specific_microversion_args_are_absent(
|
||||
body, ['encryption_keys'], "2.90")
|
||||
return self._update(req, id, body)
|
||||
|
||||
@wsgi.Controller.api_version('2.7')
|
||||
|
||||
@@ -176,6 +176,11 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin):
|
||||
"since it has been soft deleted.") % share_id
|
||||
raise exc.HTTPForbidden(explanation=msg)
|
||||
|
||||
if share_ref.get('encryption_key_ref'):
|
||||
msg = _("Replica cannot be created for share '%s' "
|
||||
"since it is encrypted.") % share_id
|
||||
raise exc.HTTPForbidden(explanation=msg)
|
||||
|
||||
share_network_id = body.get('share_replica').get('share_network_id')
|
||||
if share_network_id:
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.72"):
|
||||
|
||||
@@ -114,6 +114,11 @@ class ShareServerController(share_servers.ShareServerController,
|
||||
"subnets.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
if share_server.get('encryption_key_ref'):
|
||||
msg = _("Cannot unmanage the share server containing encryption "
|
||||
"key reference")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
share_network_id = share_server['share_network_id']
|
||||
share_network = db_api.share_network_get(context, share_network_id)
|
||||
common.check_share_network_is_active(share_network)
|
||||
@@ -242,6 +247,11 @@ class ShareServerController(share_servers.ShareServerController,
|
||||
if not params:
|
||||
raise exc.HTTPBadRequest(explanation=_("Request is missing body."))
|
||||
|
||||
if share_server['encryption_key_ref']:
|
||||
msg = _("Cannot migrate the share server containing encryption "
|
||||
"key reference")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
bool_params = ['writable', 'nondisruptive', 'preserve_snapshots']
|
||||
mandatory_params = bool_params + ['host']
|
||||
|
||||
@@ -370,6 +380,11 @@ class ShareServerController(share_servers.ShareServerController,
|
||||
if not params:
|
||||
raise exc.HTTPBadRequest(explanation=_("Request is missing body."))
|
||||
|
||||
if share_server.get('encryption_key_ref'):
|
||||
msg = _("Cannot migrate the share server containing encryption "
|
||||
"key reference")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
bool_params = ['writable', 'nondisruptive', 'preserve_snapshots']
|
||||
mandatory_params = bool_params + ['host']
|
||||
|
||||
|
||||
+29
-5
@@ -194,20 +194,38 @@ class ShareController(wsgi.Controller,
|
||||
|
||||
return data
|
||||
|
||||
@wsgi.Controller.api_version("2.65")
|
||||
@wsgi.Controller.api_version("2.90")
|
||||
def create(self, req, body):
|
||||
if not self.is_valid_body(body, 'share'):
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
share = body['share']
|
||||
scheduler_hints = share.pop('scheduler_hints', None)
|
||||
encryption_key_ref = share.pop('encryption_key_ref', None)
|
||||
|
||||
return self._create(
|
||||
req, body,
|
||||
check_create_share_from_snapshot_support=True,
|
||||
check_availability_zones_extra_spec=True,
|
||||
scheduler_hints=scheduler_hints,
|
||||
encryption_key_ref=encryption_key_ref)
|
||||
|
||||
@wsgi.Controller.api_version("2.65", "2.89")
|
||||
def create(self, req, body): # pylint: disable=function-redefined # noqa F811
|
||||
if not self.is_valid_body(body, 'share'):
|
||||
raise exc.HTTPUnprocessableEntity()
|
||||
|
||||
share = body['share']
|
||||
scheduler_hints = share.pop('scheduler_hints', None)
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.67"):
|
||||
if scheduler_hints:
|
||||
scheduler_hints.pop('only_host', None)
|
||||
return self._create(req, body,
|
||||
check_create_share_from_snapshot_support=True,
|
||||
check_availability_zones_extra_spec=True,
|
||||
scheduler_hints=scheduler_hints)
|
||||
|
||||
return self._create(
|
||||
req, body,
|
||||
check_create_share_from_snapshot_support=True,
|
||||
check_availability_zones_extra_spec=True,
|
||||
scheduler_hints=scheduler_hints)
|
||||
|
||||
@wsgi.Controller.api_version("2.48", "2.64") # noqa
|
||||
def create(self, req, body): # pylint: disable=function-redefined # noqa F811
|
||||
@@ -605,6 +623,9 @@ class ShareController(wsgi.Controller,
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.69"):
|
||||
req.GET.pop('is_soft_deleted', None)
|
||||
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.90"):
|
||||
req.GET.pop('encryption_key_ref', None)
|
||||
|
||||
return self._get_shares(req, is_detail=False)
|
||||
|
||||
@wsgi.Controller.api_version("2.0")
|
||||
@@ -623,6 +644,9 @@ class ShareController(wsgi.Controller,
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.69"):
|
||||
req.GET.pop('is_soft_deleted', None)
|
||||
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.90"):
|
||||
req.GET.pop('encryption_key_ref', None)
|
||||
|
||||
return self._get_shares(req, is_detail=True)
|
||||
|
||||
def _validate_metadata_for_update(self, req, share_id, metadata,
|
||||
|
||||
@@ -24,6 +24,7 @@ class ViewBuilder(common.ViewBuilder):
|
||||
"add_share_replica_quotas",
|
||||
"add_per_share_gigabytes_quotas",
|
||||
"add_share_backup_quotas",
|
||||
"add_encryption_keys_quotas",
|
||||
]
|
||||
|
||||
def detail_list(self, request, quota_class_set, quota_class=None):
|
||||
@@ -64,3 +65,7 @@ class ViewBuilder(common.ViewBuilder):
|
||||
def add_share_backup_quotas(self, context, view, quota_class_set):
|
||||
view['backups'] = quota_class_set.get('backups')
|
||||
view['backup_gigabytes'] = quota_class_set.get('backup_gigabytes')
|
||||
|
||||
@common.ViewBuilder.versioned_method("2.90")
|
||||
def add_encryption_keys_quotas(self, context, view, quota_class_set):
|
||||
view['encryption_keys'] = quota_class_set.get('encryption_keys')
|
||||
@@ -24,6 +24,7 @@ class ViewBuilder(common.ViewBuilder):
|
||||
"add_share_replica_quotas",
|
||||
"add_per_share_gigabytes_quotas",
|
||||
"add_share_backup_quotas",
|
||||
"add_encryption_keys_quotas",
|
||||
]
|
||||
|
||||
def detail_list(self, request, quota_set, project_id=None,
|
||||
@@ -70,3 +71,7 @@ class ViewBuilder(common.ViewBuilder):
|
||||
def add_share_backup_quotas(self, context, view, quota_set):
|
||||
view['backups'] = quota_set.get('backups')
|
||||
view['backup_gigabytes'] = quota_set.get('backup_gigabytes')
|
||||
|
||||
@common.ViewBuilder.versioned_method("2.90")
|
||||
def add_encryption_keys_quotas(self, context, view, quota_set):
|
||||
view['encryption_keys'] = quota_set.get('encryption_keys')
|
||||
@@ -25,7 +25,8 @@ class ViewBuilder(common.ViewBuilder):
|
||||
"add_share_network_subnet_id_field",
|
||||
"add_task_state_and_source_server_fields",
|
||||
"add_sec_service_update_fields",
|
||||
"add_share_network_subnet_ids_and_network_allocation_update_support"
|
||||
"add_share_network_subnet_ids_and_network_allocation_update_support",
|
||||
"add_encryption_key_ref_field",
|
||||
]
|
||||
|
||||
def build_share_server(self, request, share_server):
|
||||
@@ -100,3 +101,9 @@ class ViewBuilder(common.ViewBuilder):
|
||||
share_server['share_network_subnet_ids'])
|
||||
share_server_dict['network_allocation_update_support'] = (
|
||||
share_server['network_allocation_update_support'])
|
||||
|
||||
@common.ViewBuilder.versioned_method("2.90")
|
||||
def add_encryption_key_ref_field(
|
||||
self, context, share_server_dict, share_server):
|
||||
share_server_dict['encryption_key_ref'] = share_server[
|
||||
'encryption_key_ref']
|
||||
@@ -39,6 +39,7 @@ class ViewBuilder(common.ViewBuilder):
|
||||
"translate_creating_from_snapshot_status",
|
||||
"add_share_recycle_bin_field",
|
||||
"add_source_backup_id_field",
|
||||
"add_encryption_key_ref_field",
|
||||
]
|
||||
|
||||
def summary_list(self, request, shares, count=None):
|
||||
@@ -210,3 +211,8 @@ class ViewBuilder(common.ViewBuilder):
|
||||
@common.ViewBuilder.versioned_method("2.80")
|
||||
def add_source_backup_id_field(self, context, share_dict, share):
|
||||
share_dict['source_backup_id'] = share.get('source_backup_id')
|
||||
|
||||
@common.ViewBuilder.versioned_method("2.90")
|
||||
def add_encryption_key_ref_field(self, context, share_dict, share):
|
||||
share_dict['encryption_key_ref'] = share.get('instance', {}).get(
|
||||
'encryption_key_ref')
|
||||
@@ -156,6 +156,8 @@ INVALID_SHARE_INSTANCE_STATUSES_FOR_ACCESS_RULE_UPDATES = (
|
||||
TRANSITIONAL_STATUSES
|
||||
)
|
||||
|
||||
SUPPORTED_ENCRYPTION_TYPES = ['share', 'share_server']
|
||||
|
||||
SUPPORTED_SHARE_PROTOCOLS = (
|
||||
'NFS', 'CIFS', 'GLUSTERFS', 'HDFS', 'CEPHFS', 'MAPRFS')
|
||||
|
||||
@@ -313,6 +315,7 @@ class ExtraSpecs(object):
|
||||
MOUNT_SNAPSHOT_SUPPORT = "mount_snapshot_support"
|
||||
MOUNT_POINT_NAME_SUPPORT = "mount_point_name_support"
|
||||
AVAILABILITY_ZONES = "availability_zones"
|
||||
ENCRYPTION_SUPPORT = "encryption_support"
|
||||
PROVISIONING_MAX_SHARE_SIZE = "provisioning:max_share_size"
|
||||
PROVISIONING_MIN_SHARE_SIZE = "provisioning:min_share_size"
|
||||
PROVISIONING_MAX_SHARE_EXTEND_SIZE = "provisioning:max_share_extend_size"
|
||||
@@ -331,6 +334,7 @@ class ExtraSpecs(object):
|
||||
MOUNT_SNAPSHOT_SUPPORT,
|
||||
MOUNT_POINT_NAME_SUPPORT,
|
||||
AVAILABILITY_ZONES,
|
||||
ENCRYPTION_SUPPORT,
|
||||
PROVISIONING_MAX_SHARE_SIZE,
|
||||
PROVISIONING_MIN_SHARE_SIZE,
|
||||
PROVISIONING_MAX_SHARE_EXTEND_SIZE,
|
||||
@@ -362,6 +366,8 @@ class ExtraSpecs(object):
|
||||
|
||||
REPLICATION_TYPES = ('writable', 'readable', 'dr')
|
||||
|
||||
ENCRYPTION_TYPES = ('share', 'share_server')
|
||||
|
||||
|
||||
class AdminOnlyMetadata(object):
|
||||
AFFINITY_KEY = "__affinity_same_host"
|
||||
|
||||
@@ -1892,3 +1892,15 @@ def resource_lock_get(context, lock_id):
|
||||
def resource_lock_get_all(context, **kwargs):
|
||||
"""Retrieve all resource locks."""
|
||||
return IMPL.resource_lock_get_all(context, **kwargs)
|
||||
|
||||
##################
|
||||
|
||||
|
||||
def encryption_keys_get_count(context, filters=None):
|
||||
"""Get count of encryption keys."""
|
||||
return IMPL.encryption_keys_get_count(context, filters=filters)
|
||||
|
||||
|
||||
def encryption_keys_get_all(context, filters=None):
|
||||
"""Get all encryption keys."""
|
||||
return IMPL.encryption_keys_get_all(context, filters=filters)
|
||||
@@ -0,0 +1,111 @@
|
||||
# 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 share server encryption
|
||||
|
||||
Revision ID: e975ea83b712
|
||||
Revises: 0d8c8f6d54a4
|
||||
Create Date: 2025-01-20 14:25:29.141460
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e975ea83b712'
|
||||
down_revision = '0d8c8f6d54a4'
|
||||
|
||||
from alembic import op
|
||||
from oslo_log import log
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
encryption_refs_table = 'encryption_refs'
|
||||
share_servers_table = 'share_servers'
|
||||
share_instances_table = 'share_instances'
|
||||
|
||||
|
||||
def upgrade():
|
||||
try:
|
||||
op.add_column(
|
||||
share_servers_table,
|
||||
sa.Column('encryption_key_ref', sa.String(36), nullable=True))
|
||||
except Exception:
|
||||
LOG.error("Column 'encryption_key_ref' can not be added to the "
|
||||
"'share_servers' table!")
|
||||
raise
|
||||
|
||||
try:
|
||||
op.add_column(
|
||||
share_servers_table,
|
||||
sa.Column('application_credential_id',
|
||||
sa.String(36), nullable=True))
|
||||
except Exception:
|
||||
LOG.error("Column 'application_credential_id' can not be added to the "
|
||||
"'share_servers' table!")
|
||||
raise
|
||||
|
||||
try:
|
||||
op.add_column(
|
||||
share_instances_table,
|
||||
sa.Column('encryption_key_ref', sa.String(36), nullable=True))
|
||||
except Exception:
|
||||
LOG.error("Column can not be added to the 'share_instances' table!")
|
||||
raise
|
||||
|
||||
context = op.get_context()
|
||||
mysql_dl = context.bind.dialect.name == 'mysql'
|
||||
datetime_type = (sa.dialects.mysql.DATETIME(fsp=6)
|
||||
if mysql_dl else sa.DateTime)
|
||||
|
||||
try:
|
||||
op.create_table(
|
||||
encryption_refs_table,
|
||||
sa.Column('id', sa.String(36), primary_key=True, nullable=False),
|
||||
sa.Column('share_server_id', sa.String(length=36),
|
||||
sa.ForeignKey('share_servers.id'), unique=True),
|
||||
sa.Column('share_instance_id', sa.String(length=36),
|
||||
sa.ForeignKey('share_instances.id'), unique=True),
|
||||
sa.Column('encryption_key_ref', sa.String(36), nullable=True),
|
||||
sa.Column('project_id', sa.String(length=255), nullable=False),
|
||||
sa.Column('deleted', sa.String(36), default='False'),
|
||||
sa.Column('created_at', datetime_type),
|
||||
sa.Column('updated_at', datetime_type),
|
||||
sa.Column('deleted_at', datetime_type),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8'
|
||||
)
|
||||
except Exception:
|
||||
LOG.error("Table |%s| not created!", encryption_refs_table)
|
||||
raise
|
||||
|
||||
|
||||
def downgrade():
|
||||
try:
|
||||
op.drop_table(encryption_refs_table)
|
||||
except Exception:
|
||||
LOG.error("%s table not dropped", encryption_refs_table)
|
||||
raise
|
||||
|
||||
try:
|
||||
op.drop_column(share_servers_table, 'encryption_key_ref')
|
||||
op.drop_column(share_servers_table, 'application_credential_id')
|
||||
except Exception:
|
||||
LOG.error("Column can not be dropped for 'share_servers' table!")
|
||||
raise
|
||||
|
||||
try:
|
||||
op.drop_column(share_instances_table, 'encryption_key_ref')
|
||||
except Exception:
|
||||
LOG.error("Column can not be dropped for 'share_instances' table!")
|
||||
raise
|
||||
@@ -504,6 +504,13 @@ def _sync_replica_gigabytes(context, project_id, user_id, share_type_id=None):
|
||||
return {'replica_gigabytes': replica_gigs}
|
||||
|
||||
|
||||
def _sync_encryption_keys(context, project_id, user_id, share_type_id=None):
|
||||
encryption_keys = _count_encryption_keys_for_project(
|
||||
context, project_id, user_id
|
||||
)
|
||||
return {'encryption_keys': encryption_keys}
|
||||
|
||||
|
||||
QUOTA_SYNC_FUNCTIONS = {
|
||||
'_sync_shares': _sync_shares,
|
||||
'_sync_snapshots': _sync_snapshots,
|
||||
@@ -516,6 +523,7 @@ QUOTA_SYNC_FUNCTIONS = {
|
||||
'_sync_replica_gigabytes': _sync_replica_gigabytes,
|
||||
'_sync_backups': _sync_backups,
|
||||
'_sync_backup_gigabytes': _sync_backup_gigabytes,
|
||||
'_sync_encryption_keys': _sync_encryption_keys,
|
||||
}
|
||||
|
||||
|
||||
@@ -1619,7 +1627,7 @@ def _extract_share_instance_values(values):
|
||||
'status', 'host', 'scheduled_at', 'launched_at', 'terminated_at',
|
||||
'share_server_id', 'share_network_id', 'availability_zone_id',
|
||||
'replica_state', 'share_type_id', 'share_type', 'access_rules_status',
|
||||
'mount_point_name',
|
||||
'mount_point_name', 'encryption_key_ref',
|
||||
]
|
||||
share_instance_values, share_values = (
|
||||
_extract_subdict_by_fields(values, share_instance_model_fields)
|
||||
@@ -1851,6 +1859,11 @@ def _share_instance_get_all(context, filters=None):
|
||||
if status:
|
||||
query = query.filter(models.ShareInstance.status == status)
|
||||
|
||||
encryption_key_ref = filters.get('encryption_key_ref')
|
||||
if encryption_key_ref:
|
||||
query = query.filter(
|
||||
models.ShareInstance.encryption_key_ref == encryption_key_ref)
|
||||
|
||||
# Returns list of share instances that satisfy filters.
|
||||
query = query.all()
|
||||
return query
|
||||
@@ -2209,7 +2222,8 @@ def _process_share_filters(query, filters, project_id=None, is_public=False):
|
||||
share_filter_keys = ['share_group_id', 'snapshot_id',
|
||||
'is_soft_deleted', 'source_backup_id']
|
||||
instance_filter_keys = ['share_server_id', 'status', 'share_type_id',
|
||||
'host', 'share_network_id', 'mount_point_name']
|
||||
'host', 'share_network_id', 'mount_point_name',
|
||||
'encryption_key_ref']
|
||||
share_filters = {}
|
||||
instance_filters = {}
|
||||
|
||||
@@ -5411,7 +5425,20 @@ def share_server_create(context, values):
|
||||
# updated_at is needed for judgement of automatic cleanup
|
||||
server_ref.updated_at = timeutils.utcnow()
|
||||
server_ref.update(values)
|
||||
|
||||
# If encryption_key_ref is present, create associated record
|
||||
encryption_key_ref = values.get('encryption_key_ref')
|
||||
if encryption_key_ref:
|
||||
encryption_ref = models.EncryptionRef(
|
||||
id=uuidutils.generate_uuid(),
|
||||
share_server_id=server_ref['id'],
|
||||
encryption_key_ref=encryption_key_ref,
|
||||
project_id=context.project_id,
|
||||
)
|
||||
server_ref.server_encryption_ref_entry = encryption_ref
|
||||
|
||||
server_ref.save(session=context.session)
|
||||
|
||||
# NOTE(u_glide): Do so to prevent errors with relationships
|
||||
return _share_server_get(context, server_ref['id'])
|
||||
|
||||
@@ -5428,6 +5455,11 @@ def share_server_delete(context, id):
|
||||
_share_server_backend_details_delete(context, id)
|
||||
server_ref.soft_delete(session=context.session, update_status=True)
|
||||
|
||||
# If encryption_key_ref is present, delete associated encryption ref entry
|
||||
if server_ref.server_encryption_ref_entry:
|
||||
server_ref.server_encryption_ref_entry.soft_delete(
|
||||
session=context.session)
|
||||
|
||||
|
||||
@require_context
|
||||
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
|
||||
@@ -5580,6 +5612,9 @@ def _share_server_get_all_with_filters(context, filters):
|
||||
if filters.get('source_share_server_id'):
|
||||
query = query.filter_by(
|
||||
source_share_server_id=filters.get('source_share_server_id'))
|
||||
if filters.get('encryption_key_ref'):
|
||||
query = query.filter_by(
|
||||
encryption_key_ref=filters.get('encryption_key_ref'))
|
||||
if filters.get('share_network_id'):
|
||||
query = query.join(
|
||||
models.ShareServerShareNetworkSubnetMapping,
|
||||
@@ -6637,6 +6672,15 @@ def _share_replica_data_get_for_project(
|
||||
return result[0] or 0, result[1] or 0
|
||||
|
||||
|
||||
@require_context
|
||||
def _count_encryption_keys_for_project(
|
||||
context, project_id, user_id=None,
|
||||
):
|
||||
return encryption_keys_get_count(
|
||||
context, filters={'project_id': project_id}
|
||||
)
|
||||
|
||||
|
||||
@require_context
|
||||
@context_manager.reader
|
||||
def count_share_group_snapshots_in_share_group(context, share_group_id):
|
||||
@@ -7716,3 +7760,43 @@ def resource_lock_get_all(context, filters=None, limit=None, offset=None,
|
||||
offset=offset)
|
||||
|
||||
return query.all(), count
|
||||
|
||||
###############################
|
||||
|
||||
|
||||
@require_context
|
||||
@context_manager.reader
|
||||
def encryption_keys_get_count(context, filters=None):
|
||||
if filters:
|
||||
project_id = filters.get('project_id')
|
||||
else:
|
||||
project_id = context.project_id
|
||||
|
||||
query = model_query(
|
||||
context, models.EncryptionRef, read_deleted="no"
|
||||
).filter_by(
|
||||
project_id=project_id,
|
||||
)
|
||||
|
||||
return query.count()
|
||||
|
||||
|
||||
@require_context
|
||||
@context_manager.reader
|
||||
def encryption_keys_get_all(context, filters=None):
|
||||
if filters:
|
||||
project_id = filters.get('project_id')
|
||||
else:
|
||||
project_id = context.project_id
|
||||
|
||||
query = model_query(
|
||||
context, models.EncryptionRef, read_deleted="no"
|
||||
).filter_by(
|
||||
project_id=project_id,
|
||||
)
|
||||
|
||||
encryption_key_ref = filters.get('encryption_key_ref')
|
||||
if encryption_key_ref:
|
||||
query = query.filter_by(encryption_key_ref=encryption_key_ref)
|
||||
|
||||
return query.all()
|
||||
@@ -203,6 +203,11 @@ class Share(BASE, ManilaBase):
|
||||
if len(self.instances) > 0:
|
||||
return self.instance.export_location
|
||||
|
||||
@property
|
||||
def encryption_key_ref(self):
|
||||
if len(self.instances) > 0:
|
||||
return self.instance.encryption_key_ref
|
||||
|
||||
@property
|
||||
def is_busy(self):
|
||||
# Make sure share is not busy, i.e., not part of a migration
|
||||
@@ -386,6 +391,7 @@ class ShareInstance(BASE, ManilaBase):
|
||||
scheduled_at = Column(DateTime)
|
||||
launched_at = Column(DateTime)
|
||||
terminated_at = Column(DateTime)
|
||||
encryption_key_ref = Column(String(36), nullable=True)
|
||||
replica_state = Column(String(255), nullable=True)
|
||||
cast_rules_to_readonly = Column(Boolean, default=False, nullable=False)
|
||||
share_type_id = Column(String(36), ForeignKey('share_types.id'),
|
||||
@@ -1132,6 +1138,8 @@ class ShareServer(BASE, ManilaBase):
|
||||
Boolean, nullable=False, default=False)
|
||||
share_replicas_migration_support = Column(
|
||||
Boolean, nullable=False, default=False)
|
||||
encryption_key_ref = Column(String(36), nullable=True)
|
||||
application_credential_id = Column(String(36), nullable=True)
|
||||
status = Column(Enum(
|
||||
constants.STATUS_INACTIVE, constants.STATUS_ACTIVE,
|
||||
constants.STATUS_ERROR, constants.STATUS_DELETING,
|
||||
@@ -1197,6 +1205,39 @@ class ShareServer(BASE, ManilaBase):
|
||||
_extra_keys = ['backend_details', 'share_network_subnet_ids']
|
||||
|
||||
|
||||
class EncryptionRef(BASE, ManilaBase):
|
||||
"""Represents a share server with encryption keys."""
|
||||
__tablename__ = 'encryption_refs'
|
||||
id = Column(String(36), primary_key=True, nullable=False)
|
||||
share_server_id = Column(
|
||||
String(36), ForeignKey('share_servers.id'), unique=True)
|
||||
share_instance_id = Column(
|
||||
String(36), ForeignKey('share_instances.id'), unique=True)
|
||||
encryption_key_ref = Column(String(36), nullable=True)
|
||||
project_id = Column(String(255), nullable=True)
|
||||
deleted = Column(String(36), default='False')
|
||||
|
||||
share_server = orm.relationship(
|
||||
ShareServer,
|
||||
backref=orm.backref(
|
||||
'server_encryption_ref_entry', lazy='joined', uselist=False),
|
||||
foreign_keys=share_server_id,
|
||||
primaryjoin='and_('
|
||||
'EncryptionRef.share_server_id == ShareServer.id,'
|
||||
'EncryptionRef.deleted == "False")'
|
||||
)
|
||||
|
||||
share_instance = orm.relationship(
|
||||
ShareInstance,
|
||||
backref=orm.backref(
|
||||
'instance_encryption_ref_entry', lazy='joined', uselist=False),
|
||||
foreign_keys=share_instance_id,
|
||||
primaryjoin='and_('
|
||||
'EncryptionRef.share_instance_id == ShareInstance.id,'
|
||||
'EncryptionRef.deleted == "False")'
|
||||
)
|
||||
|
||||
|
||||
class ShareServerBackendDetails(BASE, ManilaBase):
|
||||
"""Represents a metadata key/value pair for a share server."""
|
||||
__tablename__ = 'share_server_backend_details'
|
||||
|
||||
@@ -500,6 +500,11 @@ class ShareReplicaSizeExceedsAvailableQuota(QuotaError):
|
||||
"gigabytes quota.")
|
||||
|
||||
|
||||
class EncryptionKeysLimitExceeded(QuotaError):
|
||||
message = _(
|
||||
"Maximum number of allowed encryption keys is exceeded.")
|
||||
|
||||
|
||||
class GlusterfsException(ManilaException):
|
||||
message = _("Unknown Gluster exception.")
|
||||
|
||||
@@ -783,6 +788,15 @@ class ShareTypeUpdateFailed(ManilaException):
|
||||
message = _("Cannot update share_type %(id)s.")
|
||||
|
||||
|
||||
class ManilaBarbicanACLError(ManilaException):
|
||||
message = _("Failed while communicating to Barbican. "
|
||||
"Please check the provided credentials .")
|
||||
|
||||
|
||||
class ManilaBarbicanAppCredsError(ManilaException):
|
||||
message = _("Error occured while dealing with barbican for App Creds.")
|
||||
|
||||
|
||||
class ShareGroupTypeCreateFailed(ManilaException):
|
||||
message = _("Cannot create share group type with "
|
||||
"name %(name)s and specs %(group_specs)s.")
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from castellan import options as castellan_opts
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
castellan_opts.set_defaults(CONF)
|
||||
@@ -0,0 +1,295 @@
|
||||
# Copyright 2025 Cloudification GmbH.
|
||||
# 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 urllib
|
||||
|
||||
from barbicanclient import client as barbican_client
|
||||
from castellan import options as castellan_options
|
||||
from keystoneauth1 import identity as ks_identity
|
||||
from keystoneauth1 import loading as ks_loading
|
||||
from keystoneauth1 import session as ks_session
|
||||
from keystoneclient.v3 import client as ks_client
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
|
||||
|
||||
BARBICAN_GROUP = 'barbican'
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BarbicanSecretACL(object):
|
||||
def __init__(self, conf):
|
||||
self.conf = conf
|
||||
|
||||
def get_client_and_href(self, context, secret_ref):
|
||||
"""Get user barbican client and a secret href"""
|
||||
|
||||
castellan_options.set_defaults(self.conf)
|
||||
backend = self.conf.key_manager.backend or ''
|
||||
|
||||
backend = backend.split('.')[-1]
|
||||
if backend not in ('barbican', 'BarbicanKeyManager'):
|
||||
LOG.warning("The '%s' key_manager backend is not supported. Please"
|
||||
" use barbican as key_manager.", backend)
|
||||
raise exception.ManilaBarbicanACLError()
|
||||
|
||||
if not getattr(self.conf, 'barbican', None) or \
|
||||
not getattr(self.conf.barbican, 'auth_endpoint', None):
|
||||
LOG.error("Missing auth_endpoint for barbican connection")
|
||||
raise exception.ManilaBarbicanACLError()
|
||||
|
||||
if not secret_ref:
|
||||
LOG.error("Missing secret_ref provided in current user context.")
|
||||
raise exception.ManilaBarbicanACLError()
|
||||
|
||||
# Establish a Barbican client session of current user and keystone
|
||||
# session of barbican user to get its user_id. Grant ACL to barbican
|
||||
# user that it will be used for the key_ref handover process.
|
||||
try:
|
||||
user_auth = ks_identity.V3Token(
|
||||
auth_url=self.conf.barbican.auth_endpoint,
|
||||
token=context.auth_token,
|
||||
project_id=context.project_id)
|
||||
user_sess = ks_session.Session(auth=user_auth)
|
||||
user_barbican_client = barbican_client.Client(session=user_sess)
|
||||
|
||||
barbican_endpoint = self._get_barbican_endpoint(user_auth,
|
||||
user_sess)
|
||||
base_url = self._create_base_url(user_auth, user_sess,
|
||||
barbican_endpoint)
|
||||
secret_ref = self._create_secret_ref(base_url, secret_ref)
|
||||
except Exception as e:
|
||||
LOG.error("Failed to create barbican client. Error: %s", e)
|
||||
raise exception.ManilaBarbicanACLError()
|
||||
|
||||
return user_barbican_client, secret_ref
|
||||
|
||||
def create_secret_access(self, context, secret_ref):
|
||||
try:
|
||||
user_barbican_client, secret_href = self.get_client_and_href(
|
||||
context, secret_ref)
|
||||
ks_loading.register_auth_conf_options(self.conf, BARBICAN_GROUP)
|
||||
barbican_auth = ks_loading.load_auth_from_conf_options(
|
||||
self.conf, BARBICAN_GROUP)
|
||||
barbican_sess = ks_session.Session(auth=barbican_auth)
|
||||
barbican_ks_client = ks_client.Client(session=barbican_sess)
|
||||
barbican_user_id = barbican_ks_client.session.get_user_id()
|
||||
|
||||
# Create a Barbican ACL so the barbican user can access it.
|
||||
acl = user_barbican_client.acls.create(entity_ref=secret_href,
|
||||
users=[barbican_user_id],
|
||||
project_access=False)
|
||||
acl.submit()
|
||||
except Exception as e:
|
||||
LOG.error("Failed to create secret ACL. Error: %s", e)
|
||||
raise exception.ManilaBarbicanACLError()
|
||||
|
||||
def delete_secret_access(self, context, secret_ref):
|
||||
try:
|
||||
user_barbican_client, secret_href = self.get_client_and_href(
|
||||
context, secret_ref)
|
||||
ks_loading.register_auth_conf_options(self.conf, BARBICAN_GROUP)
|
||||
barbican_auth = ks_loading.load_auth_from_conf_options(
|
||||
self.conf, BARBICAN_GROUP)
|
||||
barbican_sess = ks_session.Session(auth=barbican_auth)
|
||||
barbican_ks_client = ks_client.Client(session=barbican_sess)
|
||||
barbican_user_id = barbican_ks_client.session.get_user_id()
|
||||
|
||||
# Remove a Barbican ACL for the barbican user.
|
||||
acl_entity = user_barbican_client.acls.get(entity_ref=secret_href)
|
||||
existing_users = acl_entity.read.users
|
||||
remove_users = [barbican_user_id]
|
||||
updated_users = set(existing_users).difference(remove_users)
|
||||
acl_entity.read.users = list(updated_users)
|
||||
|
||||
acl_entity.submit()
|
||||
except Exception as e:
|
||||
LOG.error("Failed to delete secret ACL. Error: %s", e)
|
||||
|
||||
def get_secret_href(self, context, secret_ref):
|
||||
try:
|
||||
user_barbican_client, secret_href = self.get_client_and_href(
|
||||
context, secret_ref)
|
||||
return secret_href
|
||||
except Exception as e:
|
||||
LOG.error("Failed to get barbican secret href. Error: %s", e)
|
||||
raise exception.ManilaBarbicanACLError()
|
||||
|
||||
def _get_barbican_endpoint(self, auth, sess):
|
||||
if self.conf.barbican.barbican_endpoint:
|
||||
return self.conf.barbican.barbican_endpoint
|
||||
elif getattr(auth, 'service_catalog', None):
|
||||
endpoint_data = auth.service_catalog.endpoint_data_for(
|
||||
service_type='key-manager',
|
||||
interface=self.conf.barbican.barbican_endpoint_type,
|
||||
region_name=self.conf.barbican.barbican_region_name)
|
||||
return endpoint_data.url
|
||||
else:
|
||||
return auth.get_endpoint(
|
||||
sess,
|
||||
service_type='key-manager',
|
||||
interface=self.conf.barbican.barbican_endpoint_type,
|
||||
region_name=self.conf.barbican.barbican_region_name)
|
||||
|
||||
def _create_base_url(self, auth, sess, endpoint):
|
||||
api_version = None
|
||||
if self.conf.barbican.barbican_api_version:
|
||||
api_version = self.conf.barbican.barbican_api_version
|
||||
elif getattr(auth, 'service_catalog', None):
|
||||
endpoint_data = auth.service_catalog.endpoint_data_for(
|
||||
service_type='key-manager',
|
||||
interface=self.conf.barbican.barbican_endpoint_type,
|
||||
region_name=self.conf.barbican.barbican_region_name)
|
||||
api_version = endpoint_data.api_version
|
||||
elif getattr(auth, 'get_discovery', None):
|
||||
discovery = auth.get_discovery(sess, url=endpoint)
|
||||
raw_data = discovery.raw_version_data()
|
||||
if len(raw_data) == 0:
|
||||
msg = _(
|
||||
"Could not find discovery information for %s") % endpoint
|
||||
LOG.error(msg)
|
||||
raise exception.KeyManagerError(reason=msg)
|
||||
latest_version = raw_data[-1]
|
||||
api_version = latest_version.get('id')
|
||||
|
||||
if not endpoint.endswith('/'):
|
||||
endpoint += '/'
|
||||
|
||||
base_url = urllib.parse.urljoin(endpoint, api_version)
|
||||
return base_url
|
||||
|
||||
def _create_secret_ref(self, base_url, object_id):
|
||||
if not object_id:
|
||||
msg = _("Key ID is None")
|
||||
raise exception.KeyManagerError(reason=msg)
|
||||
if not base_url.endswith('/'):
|
||||
base_url += '/'
|
||||
return urllib.parse.urljoin(base_url, "secrets/" + object_id)
|
||||
|
||||
|
||||
class BarbicanUserAppCreds(object):
|
||||
def __init__(self, conf):
|
||||
self.conf = conf
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
return self.get_client()
|
||||
|
||||
def get_client(self):
|
||||
castellan_options.set_defaults(self.conf)
|
||||
backend = self.conf.key_manager.backend or ''
|
||||
|
||||
backend = backend.split('.')[-1]
|
||||
if backend not in ('barbican', 'BarbicanKeyManager'):
|
||||
LOG.warning("The '%s' key_manager backend is not supported. Please"
|
||||
" use barbican key_manager backend.", backend)
|
||||
|
||||
if not getattr(self.conf, 'barbican', None) or \
|
||||
not getattr(self.conf.barbican, 'auth_endpoint', None):
|
||||
LOG.error("Missing auth_endpoint for barbican connection")
|
||||
raise exception.ManilaBarbicanACLError()
|
||||
|
||||
ks_loading.register_auth_conf_options(self.conf, BARBICAN_GROUP)
|
||||
auth = ks_loading.load_auth_from_conf_options(self.conf,
|
||||
BARBICAN_GROUP)
|
||||
sess = ks_session.Session(auth=auth)
|
||||
return ks_client.Client(session=sess)
|
||||
|
||||
def get_application_credentials(self, context, application_credential_id):
|
||||
if not application_credential_id:
|
||||
LOG.warning("Missing application credentials ID")
|
||||
raise exception.ManilaBarbicanAppCredsError()
|
||||
|
||||
try:
|
||||
return self.client.application_credentials.get(
|
||||
application_credential=application_credential_id)
|
||||
except Exception as e:
|
||||
LOG.error("Aborting App Creds request due to error: %s", e)
|
||||
raise exception.ManilaBarbicanAppCredsError()
|
||||
|
||||
def create_application_credentials(self, context, secret):
|
||||
try:
|
||||
secrets_path = "/key-manager/v1/secrets"
|
||||
return self.client.application_credentials.create(
|
||||
name='manila_barbican_' + uuidutils.generate_uuid(),
|
||||
user=self.client.session.get_user_id(),
|
||||
roles=[{'name': 'service'}],
|
||||
secret=str(secret),
|
||||
access_rules=[
|
||||
{
|
||||
"path": secrets_path + "/%s" % secret,
|
||||
"method": "GET",
|
||||
"service": "key-manager",
|
||||
},
|
||||
{
|
||||
"path": secrets_path + "/%s/payload" % secret,
|
||||
"method": "GET",
|
||||
"service": "key-manager",
|
||||
}
|
||||
]
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error("Aborting App Creds create due to error: %s", e)
|
||||
raise exception.ManilaBarbicanAppCredsError()
|
||||
|
||||
def delete_application_credentials(self, context,
|
||||
application_credential_id):
|
||||
if not application_credential_id:
|
||||
LOG.warning("Missing application credentials ID")
|
||||
raise exception.ManilaBarbicanAppCredsError()
|
||||
|
||||
try:
|
||||
return self.client.application_credentials.delete(
|
||||
application_credential=application_credential_id)
|
||||
except Exception as e:
|
||||
LOG.error("Aborting App Creds request due to error: %s", e)
|
||||
raise exception.ManilaBarbicanAppCredsError()
|
||||
|
||||
|
||||
def create_secret_access(context, secret_ref, conf=CONF):
|
||||
BarbicanSecretACL(conf).create_secret_access(context, secret_ref)
|
||||
|
||||
|
||||
def delete_secret_access(context, secret_ref, conf=CONF):
|
||||
BarbicanSecretACL(conf).delete_secret_access(context, secret_ref)
|
||||
|
||||
|
||||
def get_secret_href(context, secret_ref, conf=CONF):
|
||||
return BarbicanSecretACL(conf).get_secret_href(context, secret_ref)
|
||||
|
||||
|
||||
def create_application_credentials(context, secret, conf=CONF):
|
||||
return BarbicanUserAppCreds(conf).create_application_credentials(
|
||||
context,
|
||||
secret)
|
||||
|
||||
|
||||
def get_application_credentials(context, application_credential_id, conf=CONF):
|
||||
return BarbicanUserAppCreds(conf).get_application_credentials(
|
||||
context,
|
||||
application_credential_id)
|
||||
|
||||
|
||||
def delete_application_credentials(context,
|
||||
application_credential_id, conf=CONF):
|
||||
BarbicanUserAppCreds(conf).delete_application_credentials(
|
||||
context,
|
||||
application_credential_id)
|
||||
+8
-2
@@ -108,7 +108,10 @@ quota_opts = [
|
||||
help='Total amount of storage, in gigabytes, allowed '
|
||||
'for backups per project.',
|
||||
deprecated_group='DEFAULT',
|
||||
deprecated_name='quota_backup_gigabytes'), ]
|
||||
deprecated_name='quota_backup_gigabytes'),
|
||||
cfg.IntOpt('encryption_keys',
|
||||
default=100,
|
||||
help='Number of encryption keys allowed per project.'), ]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(quota_opts, QUOTA_GROUP)
|
||||
@@ -1186,7 +1189,10 @@ resources = [
|
||||
'replica_gigabytes'),
|
||||
ReservableResource('backups', '_sync_backups', 'backups'),
|
||||
ReservableResource('backup_gigabytes', '_sync_backup_gigabytes',
|
||||
'backup_gigabytes')
|
||||
'backup_gigabytes'),
|
||||
ReservableResource('encryption_keys',
|
||||
'_sync_encryption_keys',
|
||||
'encryption_keys'),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -161,6 +161,7 @@ class HostState(object):
|
||||
self.share_server_multiple_subnet_support = False
|
||||
self.mount_point_name_support = False
|
||||
self.share_replicas_migration_support = False
|
||||
self.encryption_support = None
|
||||
|
||||
# PoolState for all pools
|
||||
self.pools = {}
|
||||
@@ -370,6 +371,10 @@ class HostState(object):
|
||||
pool_cap['share_replicas_migration_support'] = (
|
||||
self.share_replicas_migration_support)
|
||||
|
||||
if 'encryption_support' not in pool_cap:
|
||||
pool_cap['encryption_support'] = (
|
||||
self.encryption_support)
|
||||
|
||||
if self.ipv4_support is not None:
|
||||
pool_cap['ipv4_support'] = self.ipv4_support
|
||||
|
||||
@@ -407,6 +412,7 @@ class HostState(object):
|
||||
'share_server_multiple_subnet_support', False)
|
||||
self.share_replicas_migration_support = capability.get(
|
||||
'share_replicas_migration_support', False)
|
||||
self.encryption_support = capability.get('encryption_support', None)
|
||||
|
||||
def consume_from_share(self, share):
|
||||
"""Incrementally update host state from an share."""
|
||||
@@ -515,6 +521,7 @@ class PoolState(HostState):
|
||||
'share_server_multiple_subnet_support', False)
|
||||
self.share_replicas_migration_support = capability.get(
|
||||
'share_replicas_migration_support', False)
|
||||
self.encryption_support = capability.get('encryption_support')
|
||||
|
||||
def update_pools(self, capability):
|
||||
# Do nothing, since we don't have pools within pool, yet
|
||||
|
||||
@@ -71,6 +71,7 @@ def generate_stats(host_state, properties):
|
||||
host_state.mount_point_name_support),
|
||||
'share_replicas_migration_support': (
|
||||
host_state.share_replicas_migration_support),
|
||||
'encryption_support': host_state.encryption_support,
|
||||
}
|
||||
|
||||
host_caps = host_state.capabilities
|
||||
|
||||
+48
-7
@@ -206,6 +206,24 @@ class API(base.Base):
|
||||
compatible_azs_name.append(az['name'])
|
||||
return compatible_azs_name, compatible_azs_multiple
|
||||
|
||||
@staticmethod
|
||||
def check_if_encryption_keys_quotas_exceeded(context, quota_exception):
|
||||
overs = quota_exception.kwargs['overs']
|
||||
usages = quota_exception.kwargs['usages']
|
||||
quotas = quota_exception.kwargs['quotas']
|
||||
|
||||
def _consumed(name):
|
||||
return (usages[name]['reserved'] + usages[name]['in_use'])
|
||||
|
||||
if 'encryption_keys' in overs:
|
||||
LOG.warning("Encryption keys quota exceeded for %(s_pid)s "
|
||||
"(%(d_consumed)d of %(d_quota)d keys already "
|
||||
"consumed).", {
|
||||
's_pid': context.project_id,
|
||||
'd_consumed': _consumed('encryption_keys'),
|
||||
'd_quota': quotas['encryption_keys']})
|
||||
raise exception.EncryptionKeysLimitExceeded()
|
||||
|
||||
@staticmethod
|
||||
def check_if_share_quotas_exceeded(context, quota_exception,
|
||||
share_size, operation='create'):
|
||||
@@ -286,7 +304,7 @@ class API(base.Base):
|
||||
share_group_id=None, share_group_snapshot_member=None,
|
||||
availability_zones=None, scheduler_hints=None,
|
||||
az_request_multiple_subnet_support_map=None,
|
||||
mount_point_name=None):
|
||||
mount_point_name=None, encryption_key_ref=None):
|
||||
"""Create new share."""
|
||||
|
||||
api_common.check_metadata_properties(metadata)
|
||||
@@ -382,11 +400,28 @@ class API(base.Base):
|
||||
deltas.update(
|
||||
{'share_replicas': 1, 'replica_gigabytes': size})
|
||||
|
||||
if encryption_key_ref:
|
||||
# Make sure encryption_key_ref is valid UUID
|
||||
if not uuidutils.is_uuid_like(encryption_key_ref):
|
||||
msg = _('Encryption key ref is not valid UUID')
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
# Check if its new encryption_key_ref
|
||||
filters = {
|
||||
'encryption_key_ref': encryption_key_ref,
|
||||
'project_id': context.project_id,
|
||||
}
|
||||
is_existing_key = self.db.encryption_keys_get_all(
|
||||
context, filters=filters)
|
||||
if not is_existing_key:
|
||||
deltas.update({'encryption_keys': 1})
|
||||
|
||||
try:
|
||||
reservations = QUOTAS.reserve(
|
||||
context, share_type_id=share_type_id, **deltas)
|
||||
except exception.OverQuota as e:
|
||||
self.check_if_share_quotas_exceeded(context, e, size)
|
||||
self.check_if_encryption_keys_quotas_exceeded(context, e)
|
||||
if share_type_supports_replication:
|
||||
self.check_if_replica_quotas_exceeded(context, e, size,
|
||||
resource_type='share')
|
||||
@@ -517,7 +552,9 @@ class API(base.Base):
|
||||
snapshot_host=snapshot_host, scheduler_hints=scheduler_hints,
|
||||
az_request_multiple_subnet_support_map=(
|
||||
az_request_multiple_subnet_support_map),
|
||||
mount_point_name=mount_point_name)
|
||||
mount_point_name=mount_point_name,
|
||||
encryption_key_ref=encryption_key_ref,
|
||||
)
|
||||
|
||||
# Retrieve the share with instance details
|
||||
share = self.db.share_get(context, share['id'])
|
||||
@@ -677,7 +714,7 @@ class API(base.Base):
|
||||
share_type_id=None, availability_zones=None,
|
||||
snapshot_host=None, scheduler_hints=None,
|
||||
az_request_multiple_subnet_support_map=None,
|
||||
mount_point_name=None):
|
||||
mount_point_name=None, encryption_key_ref=None):
|
||||
request_spec, share_instance = (
|
||||
self.create_share_instance_and_get_request_spec(
|
||||
context, share, availability_zone=availability_zone,
|
||||
@@ -688,7 +725,8 @@ class API(base.Base):
|
||||
snapshot_host=snapshot_host,
|
||||
az_request_multiple_subnet_support_map=(
|
||||
az_request_multiple_subnet_support_map),
|
||||
mount_point_name=mount_point_name))
|
||||
mount_point_name=mount_point_name,
|
||||
encryption_key_ref=encryption_key_ref))
|
||||
|
||||
if share_group_snapshot_member:
|
||||
# Inherit properties from the share_group_snapshot_member
|
||||
@@ -732,7 +770,7 @@ class API(base.Base):
|
||||
share_type_id=None, cast_rules_to_readonly=False,
|
||||
availability_zones=None, snapshot_host=None,
|
||||
az_request_multiple_subnet_support_map=None,
|
||||
mount_point_name=None):
|
||||
mount_point_name=None, encryption_key_ref=None):
|
||||
|
||||
availability_zone_id = None
|
||||
if availability_zone:
|
||||
@@ -753,6 +791,7 @@ class API(base.Base):
|
||||
'share_type_id': share_type_id,
|
||||
'cast_rules_to_readonly': cast_rules_to_readonly,
|
||||
'mount_point_name': mount_point_name,
|
||||
'encryption_key_ref': encryption_key_ref,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -786,7 +825,8 @@ class API(base.Base):
|
||||
'host': share_instance['host'],
|
||||
'status': share_instance['status'],
|
||||
'replica_state': share_instance['replica_state'],
|
||||
'share_type_id': share_instance['share_type_id']
|
||||
'share_type_id': share_instance['share_type_id'],
|
||||
'encryption_key_ref': share_instance['encryption_key_ref'],
|
||||
}
|
||||
|
||||
share_type = None
|
||||
@@ -2339,7 +2379,8 @@ class API(base.Base):
|
||||
'display_description', 'display_description~', 'snapshot_id',
|
||||
'status', 'share_type_id', 'project_id', 'export_location_id',
|
||||
'export_location_path', 'limit', 'offset', 'host',
|
||||
'share_network_id', 'is_soft_deleted', 'mount_point_name']
|
||||
'share_network_id', 'is_soft_deleted', 'mount_point_name',
|
||||
'encryption_key_ref']
|
||||
|
||||
for key in filter_keys:
|
||||
if key in search_opts:
|
||||
|
||||
@@ -293,6 +293,7 @@ class ShareDriver(object):
|
||||
self.network_allocation_update_support = False
|
||||
self.dhss_mandatory_security_service_association = {}
|
||||
self.share_replicas_migration_support = False
|
||||
self.encryption_support = None
|
||||
|
||||
self.pools = []
|
||||
if self.configuration:
|
||||
@@ -970,7 +971,8 @@ class ShareDriver(object):
|
||||
|
||||
def choose_share_server_compatible_with_share(self, context, share_servers,
|
||||
share, snapshot=None,
|
||||
share_group=None):
|
||||
share_group=None,
|
||||
encryption_key_ref=None):
|
||||
"""Method that allows driver to choose share server for provided share.
|
||||
|
||||
If compatible share-server is not found, method should return None.
|
||||
@@ -980,6 +982,7 @@ class ShareDriver(object):
|
||||
:param share: share model
|
||||
:param snapshot: snapshot model
|
||||
:param share_group: ShareGroup model with shares
|
||||
:param encryption_key_ref: Encryption key reference
|
||||
:returns: share-server or None
|
||||
"""
|
||||
# If creating in a share group, use its share server
|
||||
@@ -1362,6 +1365,7 @@ class ShareDriver(object):
|
||||
mount_point_name_support=False,
|
||||
share_replicas_migration_support=(
|
||||
self.share_replicas_migration_support),
|
||||
encryption_support=self.encryption_support,
|
||||
)
|
||||
if isinstance(data, dict):
|
||||
common.update(data)
|
||||
|
||||
@@ -370,7 +370,8 @@ class HPE3ParShareDriver(driver.ShareDriver):
|
||||
|
||||
def choose_share_server_compatible_with_share(self, context, share_servers,
|
||||
share, snapshot=None,
|
||||
share_group=None):
|
||||
share_group=None,
|
||||
encryption_key_ref=None):
|
||||
"""Method that allows driver to choose share server for provided share.
|
||||
|
||||
If compatible share-server is not found, method should return None.
|
||||
|
||||
+129
-6
@@ -26,6 +26,7 @@ import hashlib
|
||||
import json
|
||||
from operator import xor
|
||||
|
||||
from keystoneauth1 import loading as ks_loading
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_serialization import jsonutils
|
||||
@@ -40,6 +41,7 @@ from manila import coordination
|
||||
from manila.data import rpcapi as data_rpcapi
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.keymgr import barbican as barbican_api
|
||||
from manila import manager
|
||||
from manila.message import api as message_api
|
||||
from manila.message import message_field
|
||||
@@ -167,11 +169,22 @@ share_manager_opts = [
|
||||
'snapshots in backend driver.'),
|
||||
]
|
||||
|
||||
|
||||
ks_opts = [
|
||||
cfg.StrOpt('auth_url',
|
||||
help='Keystone authentication URL.'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(share_manager_opts)
|
||||
CONF.import_opt('periodic_hooks_interval', 'manila.share.hook')
|
||||
CONF.import_opt('periodic_interval', 'manila.service')
|
||||
|
||||
KEYSTONE_AUTHTOKEN_GROUP = 'keystone_authtoken'
|
||||
CONF.register_opts(ks_opts, KEYSTONE_AUTHTOKEN_GROUP)
|
||||
ks_loading.register_auth_conf_options(CONF, KEYSTONE_AUTHTOKEN_GROUP)
|
||||
keystone_url = getattr(CONF.keystone_authtoken, 'auth_url')
|
||||
|
||||
# Drivers that need to change module paths or class names can add their
|
||||
# old/new path here to maintain backward compatibility.
|
||||
MAPPING = {
|
||||
@@ -785,6 +798,8 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
context, available_share_servers,
|
||||
share_instance=share_instance))
|
||||
|
||||
encryption_key_ref = share_instance.get('encryption_key_ref')
|
||||
|
||||
compatible_share_server = None
|
||||
if available_share_servers:
|
||||
try:
|
||||
@@ -792,7 +807,8 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
self.driver.choose_share_server_compatible_with_share(
|
||||
context, available_share_servers, share_instance,
|
||||
snapshot=snapshot.instance if snapshot else None,
|
||||
share_group=share_group
|
||||
share_group=share_group,
|
||||
encryption_key_ref=encryption_key_ref,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
@@ -800,7 +816,38 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
error("Cannot choose compatible share server: %s",
|
||||
e)
|
||||
|
||||
share_server_should_be_encrypted = (
|
||||
(encryption_key_ref and self.driver.encryption_support) and
|
||||
("share_server" in self.driver.encryption_support))
|
||||
|
||||
app_cred = None
|
||||
if not compatible_share_server:
|
||||
if share_server_should_be_encrypted:
|
||||
# Create secret_ref ACL for Barbican User
|
||||
try:
|
||||
barbican_api.create_secret_access(context,
|
||||
encryption_key_ref)
|
||||
LOG.debug('Created Barbican ACL for encryption key '
|
||||
'reference %s.', encryption_key_ref)
|
||||
except Exception as e:
|
||||
self._delete_encryption_keys_quota(context)
|
||||
with excutils.save_and_reraise_exception():
|
||||
error("Cannot create ACL for Barbican user %s", e)
|
||||
|
||||
# Create application credentials for barbican user
|
||||
try:
|
||||
app_cred = (
|
||||
barbican_api.create_application_credentials(
|
||||
context, encryption_key_ref).to_dict())
|
||||
LOG.debug("Created app cred id %s", app_cred['id'])
|
||||
except Exception as e:
|
||||
self._delete_encryption_keys_quota(context)
|
||||
barbican_api.delete_secret_access(context,
|
||||
encryption_key_ref)
|
||||
with excutils.save_and_reraise_exception():
|
||||
error("Cannot create application credential: "
|
||||
"%s", e)
|
||||
|
||||
compatible_share_server = self.db.share_server_create(
|
||||
context,
|
||||
{
|
||||
@@ -813,8 +860,25 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
self.driver.network_allocation_update_support),
|
||||
'share_replicas_migration_support': (
|
||||
self.driver.share_replicas_migration_support),
|
||||
'encryption_key_ref': (
|
||||
encryption_key_ref if
|
||||
share_server_should_be_encrypted else None),
|
||||
'application_credential_id': (
|
||||
app_cred['id'] if app_cred else None),
|
||||
}
|
||||
)
|
||||
else:
|
||||
if share_server_should_be_encrypted:
|
||||
# Get application credentials for barbican user
|
||||
try:
|
||||
app_cred = barbican_api.get_application_credentials(
|
||||
context,
|
||||
compatible_share_server.get(
|
||||
'application_credential_id')).to_dict()
|
||||
LOG.debug('Got app cred id %s', app_cred['id'])
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
error("Cannot get application credential: %s", e)
|
||||
|
||||
msg = ("Using share_server %(share_server)s for share instance"
|
||||
" %(share_instance_id)s")
|
||||
@@ -829,9 +893,15 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
{'share_server_id': compatible_share_server['id']},
|
||||
with_share_data=True
|
||||
)
|
||||
|
||||
if create_on_backend:
|
||||
metadata = self._build_server_metadata(
|
||||
share_instance['host'], share_instance['share_type_id'])
|
||||
context,
|
||||
share_instance['host'],
|
||||
share_instance['share_type_id'],
|
||||
app_cred=app_cred,
|
||||
encryption_key_ref=encryption_key_ref
|
||||
)
|
||||
compatible_share_server = (
|
||||
self._create_share_server_in_backend(
|
||||
context, compatible_share_server, metadata))
|
||||
@@ -840,11 +910,29 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
|
||||
return _wrapped_provide_share_server_for_share()
|
||||
|
||||
def _build_server_metadata(self, host, share_type_id):
|
||||
return {
|
||||
def _build_server_metadata(self, context, host, share_type_id,
|
||||
app_cred=None,
|
||||
encryption_key_ref=None):
|
||||
|
||||
encryption_key_href = None
|
||||
if encryption_key_ref:
|
||||
encryption_key_href = barbican_api.get_secret_href(
|
||||
context, encryption_key_ref)
|
||||
LOG.debug("Generated encryption_key_href %s for backend share "
|
||||
"server.", encryption_key_href)
|
||||
|
||||
metadata = {
|
||||
'request_host': host,
|
||||
'share_type_id': share_type_id,
|
||||
'encryption_key_ref': encryption_key_href,
|
||||
'keystone_url': keystone_url,
|
||||
}
|
||||
if app_cred:
|
||||
metadata.update({
|
||||
'application_credential_id': app_cred.get('id'),
|
||||
'application_credential_secret': encryption_key_ref,
|
||||
})
|
||||
return metadata
|
||||
|
||||
def _provide_share_server_for_migration(self, context,
|
||||
source_share_server,
|
||||
@@ -963,7 +1051,7 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
share_server = self.db.share_server_get(context, share_server_id)
|
||||
share = self.db.share_instance_get(
|
||||
context, share_instance_id, with_share_data=True)
|
||||
metadata = self._build_server_metadata(share['host'],
|
||||
metadata = self._build_server_metadata(context, share['host'],
|
||||
share['share_type_id'])
|
||||
|
||||
self._create_share_server_in_backend(context, share_server, metadata)
|
||||
@@ -1113,6 +1201,7 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
if compatible_share_server['status'] == constants.STATUS_CREATING:
|
||||
# Create share server on backend with data from db.
|
||||
metadata = self._build_server_metadata(
|
||||
context,
|
||||
share_group_ref['host'],
|
||||
share_group_ref['share_types'][0]['share_type_id'])
|
||||
compatible_share_server = self._setup_server(
|
||||
@@ -4708,7 +4797,7 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
# has been changed.
|
||||
server_id = share_server['id']
|
||||
try:
|
||||
self.db.share_server_get(
|
||||
server = self.db.share_server_get(
|
||||
context, server_id)
|
||||
except exception.ShareServerNotFound:
|
||||
raise
|
||||
@@ -4740,6 +4829,21 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
share_net,
|
||||
share_net_subnet)
|
||||
|
||||
application_credential_id = server.get(
|
||||
'application_credential_id')
|
||||
if application_credential_id:
|
||||
# Delete application credentials for barbican user
|
||||
try:
|
||||
barbican_api.delete_application_credentials(
|
||||
context, application_credential_id)
|
||||
except Exception:
|
||||
LOG.warning('Application credentials not found '
|
||||
'during deletion of share server.')
|
||||
|
||||
encryption_key_ref = server.get('encryption_key_ref')
|
||||
barbican_api.delete_secret_access(context,
|
||||
encryption_key_ref)
|
||||
|
||||
LOG.debug("Deleting share server '%s'", server_id)
|
||||
security_services = []
|
||||
for ss_name in constants.SECURITY_SERVICES_ALLOWED_TYPES:
|
||||
@@ -4758,6 +4862,9 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
self.db.share_server_update(
|
||||
context, server_id, {'status': constants.STATUS_ERROR})
|
||||
else:
|
||||
encryption_key_ref = server.get('encryption_key_ref')
|
||||
if encryption_key_ref:
|
||||
self._delete_encryption_keys_quota(context)
|
||||
self.db.share_server_delete(context, share_server['id'])
|
||||
|
||||
_wrapped_delete_share_server()
|
||||
@@ -4765,6 +4872,22 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||
"Share server '%s' has been deleted successfully.",
|
||||
share_server['id'])
|
||||
|
||||
def _delete_encryption_keys_quota(self, context):
|
||||
reservations = None
|
||||
try:
|
||||
reservations = QUOTAS.reserve(
|
||||
context, project_id=context.project_id,
|
||||
encryption_keys=-1,
|
||||
)
|
||||
except Exception:
|
||||
LOG.exception("Failed to update encryption_keys quota "
|
||||
"usages while deleting share server.")
|
||||
|
||||
if reservations:
|
||||
QUOTAS.commit(
|
||||
context, reservations, project_id=context.project_id,
|
||||
)
|
||||
|
||||
@add_hooks
|
||||
@utils.require_driver_initialized
|
||||
def extend_share(self, context, share_id, new_size, reservations):
|
||||
|
||||
@@ -366,6 +366,8 @@ def is_valid_optional_extra_spec(key, value):
|
||||
return is_valid_csv(value)
|
||||
elif key == constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX:
|
||||
return is_valid_string(value, min_length=0)
|
||||
elif key == constants.ExtraSpecs.ENCRYPTION_SUPPORT:
|
||||
return value in constants.ExtraSpecs.ENCRYPTION_TYPES
|
||||
elif key in [constants.ExtraSpecs.PROVISIONING_MAX_SHARE_SIZE,
|
||||
constants.ExtraSpecs.PROVISIONING_MIN_SHARE_SIZE,
|
||||
constants.ExtraSpecs.PROVISIONING_MAX_SHARE_EXTEND_SIZE]:
|
||||
|
||||
@@ -56,6 +56,7 @@ def stub_share(id, **kwargs):
|
||||
'share_server_id': 'fake_share_server_id',
|
||||
'access_rules_status': 'active',
|
||||
'share_type_id': '1',
|
||||
'encryption_key_ref': None,
|
||||
}
|
||||
if 'instance' in kwargs:
|
||||
share_instance.update(kwargs.pop('instance'))
|
||||
|
||||
@@ -45,7 +45,8 @@ fake_share_server_list = {
|
||||
'source_share_server_id': None,
|
||||
'identifier': 'fake_id',
|
||||
'security_service_update_support': False,
|
||||
'network_allocation_update_support': False
|
||||
'network_allocation_update_support': False,
|
||||
'encryption_key_ref': None
|
||||
},
|
||||
{
|
||||
'status': constants.STATUS_ERROR,
|
||||
@@ -61,7 +62,8 @@ fake_share_server_list = {
|
||||
'source_share_server_id': None,
|
||||
'identifier': 'fake_id_2',
|
||||
'security_service_update_support': False,
|
||||
'network_allocation_update_support': False
|
||||
'network_allocation_update_support': False,
|
||||
'encryption_key_ref': None
|
||||
|
||||
},
|
||||
]
|
||||
@@ -102,7 +104,8 @@ fake_share_server_get_result = {
|
||||
'source_share_server_id': None,
|
||||
'identifier': 'fake_id',
|
||||
'security_service_update_support': False,
|
||||
'network_allocation_update_support': False
|
||||
'network_allocation_update_support': False,
|
||||
'encryption_key_ref': None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +148,7 @@ class FakeShareServer(object):
|
||||
self.network_allocation_update_support = kwargs.get(
|
||||
'network_allocation_update_support', False)
|
||||
self.share_network_id = kwargs.get('share_network_id', 'fake_sn_id')
|
||||
self.encryption_key_ref = kwargs.get('encryption_key_ref', None)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
@@ -358,6 +362,7 @@ class ShareServerAPITest(test.TestCase):
|
||||
self.assertEqual(0, len(result['share_servers']))
|
||||
|
||||
@ddt.data({'version': '2.70', 'share_network_name': ''},
|
||||
{'version': '2.90', 'share_network_name': 'fake_sn_name'},
|
||||
{'version': '2.70', 'share_network_name': 'fake_sn_name'},
|
||||
{'version': '2.68', 'share_network_name': 'fake_sn_name'})
|
||||
@ddt.unpack
|
||||
@@ -384,6 +389,9 @@ class ShareServerAPITest(test.TestCase):
|
||||
else:
|
||||
share_server['share_network_name'] = share_network['id']
|
||||
|
||||
if version < '2.90':
|
||||
share_server.pop('encryption_key_ref')
|
||||
|
||||
self.mock_object(db_api, 'share_network_get', mock.Mock(
|
||||
return_value=share_network))
|
||||
result = self.controller.show(
|
||||
|
||||
@@ -63,6 +63,8 @@ class QuotaSetsControllerTest(test.TestCase):
|
||||
('', '2.7', quota_class_sets.QuotaClassSetsController),
|
||||
('', '2.53', quota_class_sets.QuotaClassSetsController),
|
||||
('', '2.62', quota_class_sets.QuotaClassSetsController),
|
||||
('', '2.80', quota_class_sets.QuotaClassSetsController),
|
||||
('', '2.90', quota_class_sets.QuotaClassSetsController),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_show_quota(self, url, version, controller):
|
||||
@@ -100,6 +102,8 @@ class QuotaSetsControllerTest(test.TestCase):
|
||||
if req.api_version_request >= api_version.APIVersionRequest("2.80"):
|
||||
expected['quota_class_set']['backups'] = 10
|
||||
expected['quota_class_set']['backup_gigabytes'] = 1000
|
||||
if req.api_version_request >= api_version.APIVersionRequest("2.90"):
|
||||
expected['quota_class_set']['encryption_keys'] = 100
|
||||
|
||||
result = controller().show(req, self.class_name)
|
||||
|
||||
|
||||
@@ -399,6 +399,27 @@ class ShareReplicasApiTest(test.TestCase):
|
||||
self.mock_policy_check.assert_called_once_with(
|
||||
self.member_context, self.resource_name, 'create')
|
||||
|
||||
def test_create_has_been_encrypted(self):
|
||||
share_ref = fake_share.fake_share(encryption_key_ref='fake')
|
||||
body = {
|
||||
'share_replica': {
|
||||
'share_id': 'FAKE_SHAREID',
|
||||
'availability_zone': 'FAKE_AZ'
|
||||
}
|
||||
}
|
||||
mock__view_builder_call = self.mock_object(
|
||||
share_replicas.replication_view.ReplicationViewBuilder,
|
||||
'detail_list')
|
||||
self.mock_object(share_replicas.db, 'share_get',
|
||||
mock.Mock(return_value=share_ref))
|
||||
|
||||
self.assertRaises(exc.HTTPForbidden,
|
||||
self.controller.create,
|
||||
self.replicas_req, body)
|
||||
self.assertFalse(mock__view_builder_call.called)
|
||||
self.mock_policy_check.assert_called_once_with(
|
||||
self.member_context, self.resource_name, 'create')
|
||||
|
||||
def test_create_invalid_network_id(self):
|
||||
fake_replica, _ = self._get_fake_replica(
|
||||
replication_type='writable')
|
||||
|
||||
@@ -1158,7 +1158,10 @@ class ShareServerControllerTest(test.TestCase):
|
||||
else:
|
||||
body = {}
|
||||
|
||||
server_get = mock.Mock()
|
||||
server = db_utils.create_share_server(
|
||||
id='fake_server_id', status=constants.STATUS_ACTIVE)
|
||||
|
||||
server_get = mock.Mock(return_value=server)
|
||||
network_get = mock.Mock()
|
||||
if raise_server_get_exception:
|
||||
server_get = mock.Mock(
|
||||
|
||||
@@ -151,6 +151,7 @@ class ShareAPITest(test.TestCase):
|
||||
'2.31': {'share_group_id': None,
|
||||
'source_share_group_snapshot_member_id': None},
|
||||
'2.32': {'mount_snapshot_support': False},
|
||||
'2.90': {'encryption_key_ref': None},
|
||||
}
|
||||
|
||||
# Apply all the share transformations
|
||||
@@ -606,7 +607,8 @@ class ShareAPITest(test.TestCase):
|
||||
metadata=None,
|
||||
snapshot_id=None,
|
||||
availability_zone=az_name,
|
||||
scheduler_hints=None)
|
||||
scheduler_hints=None,
|
||||
encryption_key_ref=None)
|
||||
|
||||
def test_share_create_with_sg_and_different_availability_zone(self):
|
||||
sg_id = 'fake_sg_id'
|
||||
@@ -663,6 +665,71 @@ class ShareAPITest(test.TestCase):
|
||||
db.share_group_get.assert_called_once_with(
|
||||
req.environ['manila.context'], sg_id)
|
||||
|
||||
@ddt.data({'encryption_support': True, 'dhss': True},
|
||||
{'encryption_support': True, 'dhss': False},
|
||||
{'encryption_support': False, 'dhss': True},
|
||||
{'encryption_support': False, 'dhss': False})
|
||||
@ddt.unpack
|
||||
def test_share_create_with_encryption(self, encryption_support, dhss):
|
||||
share = {
|
||||
"id": "1",
|
||||
"size": 100,
|
||||
"display_name": "Share Test Name",
|
||||
"display_description": "Share Test Desc",
|
||||
"share_proto": "fakeproto",
|
||||
"share_network_id": "fakenetid"
|
||||
}
|
||||
|
||||
fake_network = {'id': 'fakenetid'}
|
||||
create_mock = mock.Mock(
|
||||
return_value=stubs.stub_share(
|
||||
'1',
|
||||
size=100,
|
||||
display_name=share['display_name'],
|
||||
display_description=share['display_description'],
|
||||
share_proto=share['share_proto'].upper(),
|
||||
share_network_id=share['share_network_id'],
|
||||
instance={
|
||||
'encryption_key_ref': 'fake_key_uuid',
|
||||
})
|
||||
)
|
||||
self.mock_object(share_api.API, 'create', create_mock)
|
||||
self.mock_object(share_api.API, 'get_share_network', mock.Mock(
|
||||
return_value=fake_network))
|
||||
self.mock_object(common, 'check_share_network_is_active',
|
||||
mock.Mock(return_value=True))
|
||||
|
||||
stype = copy.deepcopy(self.vt)
|
||||
if dhss:
|
||||
stype['extra_specs']['driver_handles_share_servers'] = 'true'
|
||||
else:
|
||||
stype['extra_specs']['driver_handles_share_servers'] = 'false'
|
||||
if encryption_support:
|
||||
stype['extra_specs']['encryption_support'] = 'share'
|
||||
else:
|
||||
stype['extra_specs']['encryption_support'] = 'fake'
|
||||
|
||||
self.mock_object(share_types, 'get_share_type_by_name', mock.Mock(
|
||||
return_value=stype))
|
||||
|
||||
share['encryption_key_ref'] = 'fake_key_uuid'
|
||||
|
||||
request_args = copy.deepcopy(share)
|
||||
request_args['share_type'] = 'fake_volume_type_name'
|
||||
|
||||
body = {"share": request_args}
|
||||
req = fakes.HTTPRequest.blank('/v2/fake/shares', version='2.90')
|
||||
if dhss and encryption_support:
|
||||
result = self.controller.create(req, body)
|
||||
expected = self._get_expected_share_detailed_response(
|
||||
share, version='2.90')
|
||||
self.assertEqual(
|
||||
expected['share']['encryption_key_ref'],
|
||||
result['share']['encryption_key_ref'])
|
||||
else:
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create, req, body)
|
||||
|
||||
def test_share_create_with_valid_default_share_type(self):
|
||||
self.mock_object(share_types, 'get_share_type_by_name',
|
||||
mock.Mock(return_value=self.vt))
|
||||
|
||||
@@ -37,6 +37,7 @@ class ViewBuilderTestCase(test.TestCase):
|
||||
("fake_quota_class", "2.53"), (None, "2.53"),
|
||||
("fake_quota_class", "2.62"), (None, "2.62"),
|
||||
("fake_quota_class", "2.80"), (None, "2.80"),
|
||||
("fake_quota_class", "2.90"), (None, "2.90"),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_detail_list_with_share_type(self, quota_class, microversion):
|
||||
@@ -93,6 +94,13 @@ class ViewBuilderTestCase(test.TestCase):
|
||||
quota_class_set['backups'] = fake_share_backups_value
|
||||
quota_class_set['backup_gigabytes'] = fake_backup_gigabytes_value
|
||||
|
||||
if req.api_version_request >= api_version.APIVersionRequest("2.90"):
|
||||
fake_encryption_keys = 10
|
||||
expected[self.builder._collection_name][
|
||||
"encryption_keys"] = fake_encryption_keys
|
||||
quota_class_set['encryption_keys'] = (
|
||||
fake_encryption_keys)
|
||||
|
||||
result = self.builder.detail_list(
|
||||
req, quota_class_set, quota_class=quota_class)
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ class ViewBuilderTestCase(test.TestCase):
|
||||
(None, None, "2.62"),
|
||||
('fake_project_id', None, "2.80"),
|
||||
(None, None, "2.80"),
|
||||
('fake_project_id', None, "2.90"),
|
||||
(None, None, "2.90"),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_detail_list_with_share_type(self, project_id, share_type,
|
||||
@@ -107,6 +109,13 @@ class ViewBuilderTestCase(test.TestCase):
|
||||
quota_set['backups'] = fake_share_backups_value
|
||||
quota_set['backup_gigabytes'] = fake_backup_gigabytes_value
|
||||
|
||||
if req.api_version_request >= api_version.APIVersionRequest("2.90"):
|
||||
fake_encryption_keys = 10
|
||||
expected[self.builder._collection_name][
|
||||
"encryption_keys"] = (
|
||||
fake_per_share_gigabytes)
|
||||
quota_set['encryption_keys'] = fake_encryption_keys
|
||||
|
||||
result = self.builder.detail_list(
|
||||
req, quota_set, project_id=project_id, share_type=share_type)
|
||||
|
||||
|
||||
@@ -4048,6 +4048,13 @@ class ShareServerDatabaseAPITestCase(test.TestCase):
|
||||
for ss in share_servers:
|
||||
self.assertEqual(constants.STATUS_NETWORK_CHANGE, ss['status'])
|
||||
|
||||
def test_encryption_keys_get_count(self):
|
||||
servers = [db_utils.create_share_server(
|
||||
encryption_key_ref=uuidutils.generate_uuid())
|
||||
for __ in range(1, 4)]
|
||||
count = db_api.encryption_keys_get_count(context.get_admin_context())
|
||||
self.assertEqual(count, len(servers))
|
||||
|
||||
|
||||
class ServiceDatabaseAPITestCase(test.TestCase):
|
||||
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,113 @@
|
||||
# Copyright 2025 Cloudification GmbH.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from manila import exception
|
||||
from manila.keymgr import barbican as barbican_module
|
||||
from manila import test
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class BarbicanSecretACLTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BarbicanSecretACLTestCase, self).setUp()
|
||||
self.context = mock.Mock()
|
||||
self.secret_ref = 'mock-secret-id'
|
||||
self.barbican_acl = barbican_module.BarbicanSecretACL(CONF)
|
||||
|
||||
@mock.patch('barbicanclient.client.Client')
|
||||
@mock.patch('keystoneauth1.session.Session')
|
||||
@mock.patch('keystoneauth1.identity.v3.Token')
|
||||
def test_get_client_and_href_with_valid_secret(self, mock_token,
|
||||
mock_session, mock_client):
|
||||
mock_href = uuidutils.generate_uuid()
|
||||
mock_instance = mock.Mock()
|
||||
mock_client.return_value = mock_instance
|
||||
self.mock_object(self.barbican_acl, '_get_barbican_endpoint')
|
||||
self.mock_object(self.barbican_acl, '_create_base_url')
|
||||
self.mock_object(self.barbican_acl, '_create_secret_ref',
|
||||
mock.Mock(return_value=mock_href))
|
||||
|
||||
result_client, result_href = self.barbican_acl.get_client_and_href(
|
||||
self.context, mock_href)
|
||||
self.assertEqual(mock_instance, result_client)
|
||||
self.assertIn(mock_href, result_href)
|
||||
|
||||
def test_get_client_and_href_missing_backend(self):
|
||||
CONF.set_default('backend', 'wrong.backend', group='key_manager')
|
||||
self.assertRaises(
|
||||
exception.ManilaBarbicanACLError,
|
||||
self.barbican_acl.get_client_and_href,
|
||||
self.context,
|
||||
self.secret_ref)
|
||||
|
||||
def test_get_client_and_href_missing_secret_ref(self):
|
||||
self.assertRaises(
|
||||
exception.ManilaBarbicanACLError,
|
||||
self.barbican_acl.get_client_and_href,
|
||||
self.context,
|
||||
None)
|
||||
|
||||
|
||||
class BarbicanUserAppCredsTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BarbicanUserAppCredsTestCase, self).setUp()
|
||||
self.context = mock.Mock()
|
||||
self.app_creds = barbican_module.BarbicanUserAppCreds(CONF)
|
||||
|
||||
@mock.patch('keystoneclient.v3.client.Client')
|
||||
@mock.patch('keystoneauth1.session.Session')
|
||||
@mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
|
||||
def test_get_application_credentials_success(self, mock_auth,
|
||||
mock_session, mock_client):
|
||||
fake_cred = mock.Mock()
|
||||
mock_instance = mock.Mock()
|
||||
mock_instance.application_credentials.get.return_value = fake_cred
|
||||
mock_client.return_value = mock_instance
|
||||
|
||||
result = self.app_creds.get_application_credentials(
|
||||
self.context, 'fake_id')
|
||||
self.assertEqual(fake_cred, result)
|
||||
|
||||
def test_get_application_credentials_missing_id(self):
|
||||
self.assertRaises(
|
||||
exception.ManilaBarbicanAppCredsError,
|
||||
self.app_creds.get_application_credentials,
|
||||
self.context,
|
||||
None)
|
||||
|
||||
@mock.patch('keystoneclient.v3.client.Client')
|
||||
@mock.patch('keystoneauth1.session.Session')
|
||||
@mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
|
||||
def test_delete_application_credentials(self, mock_auth,
|
||||
mock_session, mock_client):
|
||||
mock_instance = mock.Mock()
|
||||
mock_client.return_value = mock_instance
|
||||
self.app_creds.delete_application_credentials(self.context, 'cred_id')
|
||||
mock_instance.application_credentials.delete.assert_called_once()
|
||||
|
||||
def test_delete_application_credentials_missing_id(self):
|
||||
self.assertRaises(
|
||||
exception.ManilaBarbicanAppCredsError,
|
||||
self.app_creds.delete_application_credentials,
|
||||
self.context,
|
||||
None)
|
||||
@@ -218,7 +218,8 @@ class HostManagerTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None
|
||||
},
|
||||
}, {
|
||||
'name': 'host2@back1#BBB',
|
||||
@@ -253,7 +254,8 @@ class HostManagerTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None
|
||||
},
|
||||
}, {
|
||||
'name': 'host2@back2#CCC',
|
||||
@@ -288,7 +290,8 @@ class HostManagerTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -345,7 +348,8 @@ class HostManagerTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None
|
||||
},
|
||||
}, {
|
||||
'name': 'host2@BBB#pool2',
|
||||
@@ -381,7 +385,8 @@ class HostManagerTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None
|
||||
},
|
||||
}, {
|
||||
'name': 'host3@CCC#pool3',
|
||||
@@ -417,7 +422,8 @@ class HostManagerTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None
|
||||
},
|
||||
}, {
|
||||
'name': 'host4@DDD#pool4a',
|
||||
@@ -453,7 +459,8 @@ class HostManagerTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None
|
||||
},
|
||||
}, {
|
||||
'name': 'host4@DDD#pool4b',
|
||||
@@ -489,7 +496,8 @@ class HostManagerTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -558,7 +566,8 @@ class HostManagerTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None
|
||||
},
|
||||
}, {
|
||||
'name': 'host2@back1#BBB',
|
||||
@@ -593,7 +602,8 @@ class HostManagerTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -657,6 +667,7 @@ class HostManagerTestCase(test.TestCase):
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'mount_point_name_support': False,
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -183,6 +183,7 @@ class EMCShareFrameworkTestCase(test.TestCase):
|
||||
data['share_server_multiple_subnet_support'] = False
|
||||
data['network_allocation_update_support'] = False
|
||||
data['share_replicas_migration_support'] = False
|
||||
data['encryption_support'] = None
|
||||
self.assertEqual(data, self.driver._stats)
|
||||
|
||||
def _fake_safe_get(self, value):
|
||||
|
||||
@@ -40,6 +40,7 @@ from oslo_utils import timeutils
|
||||
from manila.common import constants
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.keymgr import barbican as barbican_api
|
||||
from manila.share import configuration
|
||||
from manila.share import driver
|
||||
from manila.share.manager import share_manager_opts # noqa
|
||||
@@ -150,6 +151,7 @@ class DummyDriver(driver.ShareDriver):
|
||||
self.security_service_update_support = True
|
||||
self.network_allocation_update_support = True
|
||||
self.share_replicas_migration_support = True
|
||||
self.encryption_support = ['share_server']
|
||||
|
||||
def _verify_configuration(self):
|
||||
allowed_driver_methods = [m for m in dir(self) if m[0] != '_']
|
||||
@@ -251,12 +253,19 @@ class DummyDriver(driver.ShareDriver):
|
||||
|
||||
return export_locations
|
||||
|
||||
def _create_share(self, share, share_server=None):
|
||||
def _create_share(self, context, share, share_server=None):
|
||||
share_proto = share["share_proto"]
|
||||
if share_proto not in ("NFS", "CIFS"):
|
||||
msg = _("Unsupported share protocol provided - %s.") % share_proto
|
||||
raise exception.InvalidShareAccess(reason=msg)
|
||||
|
||||
encryption_key_ref = share.get('encryption_key_ref')
|
||||
if encryption_key_ref and context:
|
||||
encryption_key_href = barbican_api.get_secret_href(
|
||||
context, encryption_key_ref)
|
||||
LOG.debug("Generated encryption_key_href %s for share create "
|
||||
"request.", encryption_key_href)
|
||||
|
||||
share_name = self._get_share_name(share)
|
||||
mountpoint = "/path/to/fake/share/%s" % share_name
|
||||
self.private_storage.update(
|
||||
@@ -271,13 +280,14 @@ class DummyDriver(driver.ShareDriver):
|
||||
@slow_me_down
|
||||
def create_share(self, context, share, share_server=None):
|
||||
"""Is called to create share."""
|
||||
return self._create_share(share, share_server=share_server)
|
||||
return self._create_share(context, share, share_server=share_server)
|
||||
|
||||
@slow_me_down
|
||||
def create_share_from_snapshot(self, context, share, snapshot,
|
||||
share_server=None, parent_share=None):
|
||||
"""Is called to create share from snapshot."""
|
||||
export_locations = self._create_share(share, share_server=share_server)
|
||||
export_locations = self._create_share(
|
||||
context, share, share_server=share_server)
|
||||
return {
|
||||
'export_locations': export_locations,
|
||||
'status': constants.STATUS_AVAILABLE
|
||||
@@ -361,7 +371,10 @@ class DummyDriver(driver.ShareDriver):
|
||||
old_export = self.private_storage.get(
|
||||
old_share_id, key='export_location')
|
||||
if old_export.split(":/")[-1] == new_export.split(":/")[-1]:
|
||||
result = {"size": 1, "export_locations": self._create_share(share)}
|
||||
result = {
|
||||
"size": 1,
|
||||
"export_locations": self._create_share(None, share)
|
||||
}
|
||||
self.private_storage.delete(old_share_id)
|
||||
return result
|
||||
else:
|
||||
|
||||
@@ -275,6 +275,7 @@ class GlusterfsNativeShareDriverTestCase(test.TestCase):
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'network_allocation_update_support': False,
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None,
|
||||
}
|
||||
self.assertEqual(test_data, self._driver._stats)
|
||||
|
||||
|
||||
@@ -755,6 +755,7 @@ class HPE3ParDriverTestCase(test.TestCase):
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'network_allocation_update_support': False,
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None,
|
||||
}
|
||||
|
||||
result = self.driver.get_share_stats(refresh=True)
|
||||
@@ -836,6 +837,7 @@ class HPE3ParDriverTestCase(test.TestCase):
|
||||
'network_allocation_update_support': False,
|
||||
'mount_snapshot_support': False,
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None,
|
||||
'share_group_stats': {
|
||||
'consistent_snapshot_support': None,
|
||||
},
|
||||
@@ -888,6 +890,7 @@ class HPE3ParDriverTestCase(test.TestCase):
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'network_allocation_update_support': False,
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None,
|
||||
'mount_snapshot_support': False,
|
||||
'share_group_stats': {
|
||||
'consistent_snapshot_support': None,
|
||||
|
||||
@@ -2440,6 +2440,7 @@ class HuaweiShareDriverTestCase(test.TestCase):
|
||||
"share_server_multiple_subnet_support": False,
|
||||
"network_allocation_update_support": False,
|
||||
"share_replicas_migration_support": False,
|
||||
'encryption_support': None,
|
||||
}
|
||||
|
||||
if replication_support:
|
||||
|
||||
@@ -452,6 +452,7 @@ class ACCESSShareDriverTestCase(test.TestCase):
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'network_allocation_update_support': False,
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None,
|
||||
}
|
||||
|
||||
self.assertEqual(data, self._driver._stats)
|
||||
|
||||
@@ -383,6 +383,7 @@ class ZFSonLinuxShareDriverTestCase(test.TestCase):
|
||||
'share_server_multiple_subnet_support': False,
|
||||
'network_allocation_update_support': False,
|
||||
'share_replicas_migration_support': False,
|
||||
'encryption_support': None,
|
||||
}
|
||||
if replication_domain:
|
||||
expected['replication_type'] = 'readable'
|
||||
|
||||
@@ -32,6 +32,7 @@ from manila.data import rpcapi as data_rpc
|
||||
from manila import db as db_api
|
||||
from manila.db.sqlalchemy import models
|
||||
from manila import exception
|
||||
from manila.keymgr import barbican as barbican_api
|
||||
from manila import policy
|
||||
from manila import quota
|
||||
from manila import share
|
||||
@@ -947,6 +948,7 @@ class ShareAPITestCase(test.TestCase):
|
||||
az_request_multiple_subnet_support_map=compatible_azs_multiple,
|
||||
snapshot_host=None,
|
||||
scheduler_hints=None, mount_point_name=None,
|
||||
encryption_key_ref=None,
|
||||
)
|
||||
db_api.share_get.assert_called_once()
|
||||
|
||||
@@ -1202,6 +1204,38 @@ class ShareAPITestCase(test.TestCase):
|
||||
share_data['display_description']
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
{'overs': {'encryption_keys': 'fake'},
|
||||
'expected_exception': (
|
||||
exception.EncryptionKeysLimitExceeded)
|
||||
})
|
||||
@ddt.unpack
|
||||
def test_create_share_over_encryption_keys_quota(self, overs,
|
||||
expected_exception):
|
||||
share, share_data = self._setup_create_mocks()
|
||||
quota.CONF.set_default("encryption_keys", 2, 'quota')
|
||||
share_data['encryption_key_ref'] = uuidutils.generate_uuid()
|
||||
|
||||
usages = {'encryption_keys': {'reserved': 0, 'in_use': 2}}
|
||||
quotas = {'encryption_keys': 2}
|
||||
exc = exception.OverQuota(overs=overs, usages=usages, quotas=quotas)
|
||||
self.mock_object(quota.QUOTAS, 'reserve', mock.Mock(side_effect=exc))
|
||||
self.mock_object(db_api, 'share_server_get_all_with_filters',
|
||||
mock.Mock(return_value={}))
|
||||
self.mock_object(barbican_api, 'create_secret_access')
|
||||
self.mock_object(db_api, 'encryption_keys_get_count',
|
||||
mock.Mock(return_value=2))
|
||||
self.assertRaises(
|
||||
expected_exception,
|
||||
self.api.create,
|
||||
self.context,
|
||||
share_data['share_proto'],
|
||||
share_data['size'],
|
||||
share_data['display_name'],
|
||||
share_data['display_description'],
|
||||
encryption_key_ref=share_data['encryption_key_ref']
|
||||
)
|
||||
|
||||
@ddt.data(exception.QuotaError, exception.InvalidShare)
|
||||
def test_create_share_error_on_quota_commit(self, expected_exception):
|
||||
share, share_data = self._setup_create_mocks()
|
||||
@@ -1245,6 +1279,7 @@ class ShareAPITestCase(test.TestCase):
|
||||
'share_type_id': 'fake_share_type',
|
||||
'cast_rules_to_readonly': False,
|
||||
'mount_point_name': None,
|
||||
'encryption_key_ref': None
|
||||
}
|
||||
)
|
||||
db_api.share_type_get.assert_called_once_with(
|
||||
@@ -1279,6 +1314,7 @@ class ShareAPITestCase(test.TestCase):
|
||||
'share_type_id': 'fake_share_type',
|
||||
'cast_rules_to_readonly': False,
|
||||
'mount_point_name': 'fake_mp',
|
||||
'encryption_key_ref': None
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1749,7 +1785,6 @@ class ShareAPITestCase(test.TestCase):
|
||||
task_state=None)
|
||||
|
||||
self.mock_object(db_api, 'share_update', mock.Mock())
|
||||
|
||||
self.api.unmanage(self.context, share)
|
||||
|
||||
self.share_rpcapi.unmanage_share.assert_called_once_with(
|
||||
@@ -2689,7 +2724,8 @@ class ShareAPITestCase(test.TestCase):
|
||||
availability_zones=None,
|
||||
az_request_multiple_subnet_support_map=None,
|
||||
snapshot_host=snapshot['share']['instance']['host'],
|
||||
scheduler_hints=None, mount_point_name=None)
|
||||
scheduler_hints=None, mount_point_name=None,
|
||||
encryption_key_ref=None)
|
||||
share_api.policy.check_policy.assert_called_once_with(
|
||||
self.context, 'share_snapshot', 'get_snapshot',
|
||||
snapshot, do_raise=False)
|
||||
|
||||
@@ -33,6 +33,7 @@ from manila.data import rpcapi as data_rpc
|
||||
from manila import db
|
||||
from manila.db.sqlalchemy import models
|
||||
from manila import exception
|
||||
from manila.keymgr import barbican as barbican_api
|
||||
from manila.message import message_field
|
||||
from manila import quota
|
||||
from manila.share import api
|
||||
@@ -3296,7 +3297,80 @@ class ShareManagerTestCase(test.TestCase):
|
||||
)
|
||||
driver_method_mock.assert_called_once_with(
|
||||
self.context, [fake_share_server], share.instance,
|
||||
snapshot=None, share_group=None)
|
||||
snapshot=None, share_group=None, encryption_key_ref=None)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_provide_share_server_encryption_key_ref(self, acl_error):
|
||||
fake_share_network = db_utils.create_share_network(id='fake_sn_id')
|
||||
fake_share_net_subnets = [db_utils.create_share_network_subnet(
|
||||
id='fake_sns_id', share_network_id=fake_share_network['id']
|
||||
)]
|
||||
|
||||
fake_share_server = db_utils.create_share_server(
|
||||
id='fake',
|
||||
encryption_key_ref='fake_ref'
|
||||
)
|
||||
share = db_utils.create_share(encryption_key_ref='fake_ref')
|
||||
|
||||
self.share_manager.driver.encryption_support = 'share_server'
|
||||
|
||||
db_method_mock = self.mock_object(
|
||||
db, 'share_network_subnets_get_all_by_availability_zone_id',
|
||||
mock.Mock(return_value=fake_share_net_subnets))
|
||||
self.mock_object(
|
||||
db, 'share_server_get_all_by_host_and_share_subnet_valid',
|
||||
mock.Mock(return_value=[fake_share_server]))
|
||||
self.mock_object(
|
||||
self.share_manager, '_check_share_server_backend_limits',
|
||||
mock.Mock(return_value=[fake_share_server]))
|
||||
self.mock_object(
|
||||
self.share_manager.driver,
|
||||
"choose_share_server_compatible_with_share",
|
||||
mock.Mock(return_value=None)
|
||||
)
|
||||
mock_server_create = self.mock_object(
|
||||
db, 'share_server_create',
|
||||
mock.Mock(return_value=fake_share_server))
|
||||
fake_exception = exception.ManilaBarbicanACLError()
|
||||
self.mock_object(
|
||||
barbican_api, 'create_secret_access',
|
||||
mock.Mock(side_effect=fake_exception) if acl_error else
|
||||
mock.Mock())
|
||||
|
||||
mock_obj = mock.Mock()
|
||||
mock_obj.to_dict.return_value = {
|
||||
'id': 'fake_application_credential_id'
|
||||
}
|
||||
mock_create_app_cred = self.mock_object(
|
||||
barbican_api, 'create_application_credentials',
|
||||
mock.Mock(return_value=mock_obj))
|
||||
mock_get_secret_ref = self.mock_object(
|
||||
barbican_api, 'get_secret_href',
|
||||
mock.Mock(return_value='fake_secret_href'))
|
||||
|
||||
if acl_error:
|
||||
self.assertRaises(
|
||||
exception.ManilaBarbicanACLError,
|
||||
self.share_manager._provide_share_server_for_share,
|
||||
self.context, fake_share_network['id'], share.instance)
|
||||
else:
|
||||
self.share_manager._provide_share_server_for_share(
|
||||
self.context, fake_share_network['id'], share.instance)
|
||||
mock_server_create.assert_called()
|
||||
mock_create_app_cred.assert_called()
|
||||
mock_get_secret_ref.assert_called()
|
||||
|
||||
db_method_mock.assert_called_once_with(
|
||||
self.context, fake_share_network['id'],
|
||||
availability_zone_id=share.instance.get('availability_zone_id')
|
||||
)
|
||||
driver_mock = self.share_manager.driver
|
||||
driver_method_mock = (
|
||||
driver_mock.choose_share_server_compatible_with_share
|
||||
)
|
||||
driver_method_mock.assert_called_once_with(
|
||||
self.context, [fake_share_server], share.instance,
|
||||
snapshot=None, share_group=None, encryption_key_ref='fake_ref')
|
||||
|
||||
def test_provide_share_server_for_share_invalid_arguments(self):
|
||||
self.assertRaises(ValueError,
|
||||
@@ -9086,10 +9160,11 @@ class ShareManagerTestCase(test.TestCase):
|
||||
|
||||
def test__build_server_metadata(self):
|
||||
share = {'host': 'host', 'share_type_id': 'id'}
|
||||
expected_metadata = {'request_host': 'host', 'share_type_id': 'id'}
|
||||
expected_metadata = {'request_host': 'host', 'share_type_id': 'id',
|
||||
'encryption_key_ref': None, 'keystone_url': None}
|
||||
|
||||
metadata = self.share_manager._build_server_metadata(
|
||||
share['host'], share['share_type_id'])
|
||||
self.context, share['host'], share['share_type_id'])
|
||||
|
||||
self.assertDictEqual(expected_metadata, metadata)
|
||||
|
||||
|
||||
@@ -412,12 +412,17 @@ class ShareTypesTestCase(test.TestCase):
|
||||
list(itertools.product(
|
||||
(constants.ExtraSpecs.REPLICATION_TYPE_SPEC,),
|
||||
constants.ExtraSpecs.REPLICATION_TYPES)) +
|
||||
list(itertools.product(
|
||||
(constants.ExtraSpecs.ENCRYPTION_SUPPORT,),
|
||||
constants.ExtraSpecs.ENCRYPTION_TYPES)) +
|
||||
[(constants.ExtraSpecs.AVAILABILITY_ZONES, 'zone a, zoneb$c'),
|
||||
(constants.ExtraSpecs.AVAILABILITY_ZONES, ' zonea, zoneb'),
|
||||
(constants.ExtraSpecs.AVAILABILITY_ZONES, 'zone1')] +
|
||||
[(constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX, 'gold'),
|
||||
(constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX, 'silver'),
|
||||
(constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX, 'bronze')]
|
||||
(constants.ExtraSpecs.PROVISIONING_MOUNT_POINT_PREFIX, 'bronze')] +
|
||||
[(constants.ExtraSpecs.ENCRYPTION_SUPPORT, 'share'),
|
||||
(constants.ExtraSpecs.ENCRYPTION_SUPPORT, 'share_server')]
|
||||
))
|
||||
@ddt.unpack
|
||||
def test_is_valid_optional_extra_spec_valid(self, key, value):
|
||||
|
||||
@@ -717,5 +717,6 @@ class QuotaEngineTestCase(test.TestCase):
|
||||
sorted(['gigabytes', 'per_share_gigabytes', 'replica_gigabytes',
|
||||
'share_group_snapshots', 'share_groups', 'share_networks',
|
||||
'share_replicas', 'shares', 'snapshot_gigabytes',
|
||||
'snapshots', 'backups', 'backup_gigabytes']),
|
||||
'snapshots', 'backups', 'backup_gigabytes',
|
||||
'encryption_keys']),
|
||||
quota.QUOTAS.resources)
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Starting with microversion 2.90, Manila allows end users to provide
|
||||
barbican secret_ref as encryption key reference in share create API. This
|
||||
will be used as either share encryption key reference or share server
|
||||
encryption key reference, based on backend driver support for share
|
||||
encryption.
|
||||
issues:
|
||||
- |
|
||||
When using share types with ``encryption_support=share_server``, share
|
||||
servers created cannot currently be unmanaged from Manila. This limitation
|
||||
will be addressed in a future release.
|
||||
|
||||
Additionally, care must be taken when managing existing shares with
|
||||
``driver_handles_share_servers=True`` (DHSS=True). You must use a share type
|
||||
that supports encryption if the share must be encrypted or if its existing
|
||||
encryption status needs to be retained.
|
||||
@@ -6,6 +6,7 @@
|
||||
pbr>=5.5.0 # Apache-2.0
|
||||
|
||||
alembic>=1.4.2 # MIT
|
||||
castellan>=3.7.0 # Apache-2.0
|
||||
defusedxml>=0.7.1 # PSFL
|
||||
eventlet>=0.27.0 # MIT
|
||||
greenlet>=0.4.16 # MIT
|
||||
@@ -42,6 +43,7 @@ SQLAlchemy>=1.4.0 # MIT
|
||||
SQLAlchemy-Utils>=0.38.3 # BSD License
|
||||
stevedore>=3.2.2 # Apache-2.0
|
||||
tooz>=2.7.1 # Apache-2.0
|
||||
python-barbicanclient>=5.0.1 # Apache-2.0
|
||||
python-cinderclient>=4.0.1 # Apache-2.0
|
||||
python-novaclient>=17.2.1 # Apache-2.0
|
||||
python-glanceclient>=3.2.2 # Apache-2.0
|
||||
|
||||
Reference in new issue
Block a user