Add tests for encryption_key_ref
Positive test - 1. create barbican secret and create share using secret UUID Negative test - 1. invalid encryption key ref 2. invalid share type extra-spec 3. absent encryption key ref partially-implements: blueprint share-encryption Depends-On: https://review.opendev.org/c/openstack/requirements/+/963685 Change-Id: I3145f9cd6847464b2920f1b0a35e6c211e45b26e Signed-off-by: Kiran Pawar <kinpaa@gmail.com>
This commit is contained in:
73
manila_tempest_tests/common/barbican_client_mgr.py
Normal file
73
manila_tempest_tests/common/barbican_client_mgr.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Copyright 2025 Cloudifcation 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 base64
|
||||
import secrets
|
||||
|
||||
from oslo_log import log as logging
|
||||
from tempest import config
|
||||
from tempest.lib.services import clients
|
||||
from tempest import test
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BarbicanClientManager(test.BaseTestCase):
|
||||
"""Class for interacting with the barbican service.
|
||||
|
||||
This class is an abstraction for interacting with the barbican service.
|
||||
"""
|
||||
|
||||
credentials = ['primary',]
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls, tempest_client_mgr):
|
||||
super(BarbicanClientManager, cls).setup_clients()
|
||||
if CONF.identity.auth_version == 'v3':
|
||||
auth_uri = CONF.identity.uri_v3
|
||||
else:
|
||||
auth_uri = CONF.identity.uri
|
||||
service_clients = clients.ServiceClients(
|
||||
tempest_client_mgr.credentials,
|
||||
auth_uri)
|
||||
cls.secret_client = service_clients.secret_v1.SecretClient(
|
||||
service='key-manager')
|
||||
|
||||
@classmethod
|
||||
def ref_to_uuid(cls, href):
|
||||
return href.split('/')[-1]
|
||||
|
||||
def store_secret(self):
|
||||
"""Store a secret in barbican.
|
||||
|
||||
:returns: The barbican secret_ref.
|
||||
"""
|
||||
|
||||
key = secrets.token_bytes(32)
|
||||
|
||||
manila_secret = self.secret_client.create_secret(
|
||||
algorithm='AES',
|
||||
bit_length=256,
|
||||
secret_type='symmetric',
|
||||
payload=base64.b64encode(key).decode(),
|
||||
payload_content_type='application/octet-stream',
|
||||
payload_content_encoding='base64',
|
||||
mode='CBC'
|
||||
)
|
||||
LOG.debug('Manila Secret has ref %s', manila_secret.get('secret_ref'))
|
||||
return manila_secret.get('secret_ref')
|
||||
|
||||
def delete_secret(self, secret_ref):
|
||||
self.secret_client.delete_secret(secret_ref)
|
||||
@@ -40,7 +40,7 @@ ShareGroup = [
|
||||
"This value is only used to validate the versions "
|
||||
"response from Manila."),
|
||||
cfg.StrOpt("max_api_microversion",
|
||||
default="2.88",
|
||||
default="2.90",
|
||||
help="The maximum api microversion is configured to be the "
|
||||
"value of the latest microversion supported by Manila."),
|
||||
cfg.StrOpt("region",
|
||||
@@ -220,6 +220,11 @@ ShareGroup = [
|
||||
"ss_type:<ldap, kerberos or active_directory>, "
|
||||
"ss_dns_ip:value, ss_user:value, ss_password=value, "
|
||||
"ss_domain:value, ss_server:value"),
|
||||
cfg.ListOpt("capability_encryption_support",
|
||||
default=[],
|
||||
help="Encryption support capability. Possible values are "
|
||||
"share_server, share etc. "),
|
||||
|
||||
|
||||
# Switching ON/OFF test suites filtered by features
|
||||
cfg.BoolOpt("run_quota_tests",
|
||||
@@ -386,4 +391,7 @@ ShareGroup = [
|
||||
cfg.DictOpt("driver_assisted_backup_test_driver_options",
|
||||
default={'dummy': True},
|
||||
help="Share backup driver options specified as dict."),
|
||||
cfg.BoolOpt("run_encryption_tests",
|
||||
default=False,
|
||||
help="Enable or disable share encryption tests."),
|
||||
]
|
||||
|
||||
@@ -248,7 +248,7 @@ class SharesV2Client(shares_client.SharesClient):
|
||||
share_type_id=None, is_public=False,
|
||||
share_group_id=None, availability_zone=None,
|
||||
version=LATEST_MICROVERSION, experimental=False,
|
||||
scheduler_hints=None):
|
||||
scheduler_hints=None, encryption_key_ref=None):
|
||||
headers = EXPERIMENTAL if experimental else None
|
||||
metadata = metadata or {}
|
||||
scheduler_hints = scheduler_hints or {}
|
||||
@@ -283,6 +283,8 @@ class SharesV2Client(shares_client.SharesClient):
|
||||
post_body["share"]["share_group_id"] = share_group_id
|
||||
if scheduler_hints:
|
||||
post_body["share"]["scheduler_hints"] = scheduler_hints
|
||||
if encryption_key_ref:
|
||||
post_body["share"]["encryption_key_ref"] = encryption_key_ref
|
||||
|
||||
body = json.dumps(post_body)
|
||||
resp, body = self.post("shares", body, headers=headers,
|
||||
@@ -1102,7 +1104,7 @@ class SharesV2Client(shares_client.SharesClient):
|
||||
share_networks=None,
|
||||
share_groups=None, share_group_snapshots=None,
|
||||
force=True, share_type=None, share_replicas=None,
|
||||
replica_gigabytes=None, url=None,
|
||||
replica_gigabytes=None, encryption_keys=None, url=None,
|
||||
version=LATEST_MICROVERSION):
|
||||
if url is None:
|
||||
url = self._get_quotas_url(version)
|
||||
@@ -1130,6 +1132,8 @@ class SharesV2Client(shares_client.SharesClient):
|
||||
put_body["share_replicas"] = share_replicas
|
||||
if replica_gigabytes is not None:
|
||||
put_body["replica_gigabytes"] = replica_gigabytes
|
||||
if encryption_keys is not None:
|
||||
put_body["encryption_keys"] = encryption_keys
|
||||
put_body = json.dumps({"quota_set": put_body})
|
||||
|
||||
resp, body = self.put(url, put_body, version=version)
|
||||
|
||||
@@ -66,6 +66,8 @@ class SharesAdminQuotasTest(base.BaseSharesAdminTest):
|
||||
if utils.share_replica_quotas_are_supported():
|
||||
self.assertGreater(int(quotas["share_replicas"]), -2)
|
||||
self.assertGreater(int(quotas["replica_gigabytes"]), -2)
|
||||
if utils.encryption_keys_quota_supported():
|
||||
self.assertGreater(int(quotas["encryption_keys"]), -2)
|
||||
|
||||
@decorators.idempotent_id('1ff57cfa-cd8d-495f-86eb-9fead307428e')
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||
@@ -82,6 +84,8 @@ class SharesAdminQuotasTest(base.BaseSharesAdminTest):
|
||||
if utils.share_replica_quotas_are_supported():
|
||||
self.assertGreater(int(quotas["share_replicas"]), -2)
|
||||
self.assertGreater(int(quotas["replica_gigabytes"]), -2)
|
||||
if utils.encryption_keys_quota_supported():
|
||||
self.assertGreater(int(quotas["encryption_keys"]), -2)
|
||||
|
||||
@decorators.idempotent_id('9b96dd45-7c0d-41ee-88e4-600185f61358')
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||
@@ -471,6 +475,19 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
|
||||
|
||||
self.assertEqual(new_quota, int(updated["share_networks"]))
|
||||
|
||||
@decorators.idempotent_id('78957d97-afad-4371-a21e-79641fff83f7')
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||
@utils.skip_if_microversion_not_supported("2.90")
|
||||
def test_update_tenant_quota_encryption_keys(self):
|
||||
# get current quotas
|
||||
quotas = self.client.show_quotas(self.tenant_id)['quota_set']
|
||||
new_quota = int(quotas["encryption_keys"]) + 2
|
||||
|
||||
# set new quota for encryption keys
|
||||
updated = self.update_quotas(self.tenant_id, encryption_keys=new_quota)
|
||||
|
||||
self.assertEqual(new_quota, int(updated["encryption_keys"]))
|
||||
|
||||
@decorators.idempotent_id('84e24c32-ee78-461e-ac1f-f9e4d99f88e2')
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_API)
|
||||
def test_reset_tenant_quotas(self):
|
||||
@@ -496,6 +513,8 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
|
||||
if utils.share_replica_quotas_are_supported():
|
||||
data["share_replicas"] = int(custom["share_replicas"]) + 2
|
||||
data["replica_gigabytes"] = int(custom["replica_gigabytes"]) + 2
|
||||
if utils.encryption_keys_quota_supported():
|
||||
data["encryption_keys"] = int(custom["encryption_keys"]) + 2
|
||||
|
||||
# set new quota, turn off cleanup - we'll do it right below
|
||||
updated = self.update_quotas(self.tenant_id, cleanup=False, **data)
|
||||
@@ -518,6 +537,9 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
|
||||
data["share_replicas"], int(updated["share_replicas"]))
|
||||
self.assertEqual(
|
||||
data["replica_gigabytes"], int(updated["replica_gigabytes"]))
|
||||
if utils.encryption_keys_quota_supported():
|
||||
self.assertEqual(
|
||||
data["encryption_keys"], int(updated["encryption_keys"]))
|
||||
|
||||
# Reset customized quotas
|
||||
self.client.reset_quotas(self.tenant_id)
|
||||
@@ -545,6 +567,10 @@ class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest):
|
||||
self.assertEqual(
|
||||
int(default["replica_gigabytes"]),
|
||||
int(reseted["replica_gigabytes"]))
|
||||
if utils.encryption_keys_quota_supported():
|
||||
self.assertEqual(
|
||||
int(default["encryption_keys"]),
|
||||
int(reseted["encryption_keys"]))
|
||||
|
||||
def _get_new_replica_quota_values(self, default_quotas, value_to_set):
|
||||
new_values = {
|
||||
|
||||
@@ -80,6 +80,7 @@ class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest):
|
||||
{"gigabytes": -2},
|
||||
{"snapshot_gigabytes": -2},
|
||||
{"share_networks": -2},
|
||||
{"encryption_keys": -2},
|
||||
)
|
||||
@decorators.idempotent_id('07d3e69a-7cda-4ca7-9fea-c32f6830fdd3')
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
|
||||
94
manila_tempest_tests/tests/api/test_share_encryption.py
Normal file
94
manila_tempest_tests/tests/api/test_share_encryption.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# Copyright 2025 Cloudifcation 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 ddt
|
||||
from oslo_log import log
|
||||
from tempest import config
|
||||
from tempest.lib import decorators
|
||||
from testtools import testcase as tc
|
||||
|
||||
from manila_tempest_tests.common import barbican_client_mgr
|
||||
from manila_tempest_tests.tests.api import base
|
||||
from manila_tempest_tests import utils
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ShareEncryptionNFSTest(base.BaseSharesMixedTest):
|
||||
"""Covers share functionality, that is related to NFS share type."""
|
||||
protocol = "nfs"
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(ShareEncryptionNFSTest, cls).skip_checks()
|
||||
if not CONF.share.run_encryption_tests:
|
||||
raise cls.skipException('Encryption tests are disabled.')
|
||||
utils.check_skip_if_microversion_not_supported("2.90")
|
||||
|
||||
if cls.protocol not in CONF.share.enable_protocols:
|
||||
message = "%s tests are disabled" % cls.protocol
|
||||
raise cls.skipException(message)
|
||||
|
||||
if ('share_server' not in CONF.share.capability_encryption_support and
|
||||
'share' not in CONF.share.capability_encryption_support):
|
||||
message = "Unsupported value of encryption support capability"
|
||||
raise cls.skipException(message)
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(ShareEncryptionNFSTest, cls).resource_setup()
|
||||
|
||||
extra_specs = {
|
||||
'driver_handles_share_servers': CONF.share.multitenancy_enabled,
|
||||
}
|
||||
if 'share_server' in CONF.share.capability_encryption_support:
|
||||
extra_specs.update({'encryption_support': 'share_server'})
|
||||
elif 'share' in CONF.share.capability_encryption_support:
|
||||
extra_specs.update({'encryption_support': 'share'})
|
||||
|
||||
# create share_type
|
||||
cls.share_type_enc = cls.create_share_type(extra_specs=extra_specs)
|
||||
cls.share_type_enc_id = cls.share_type_enc['id']
|
||||
|
||||
# setup barbican client
|
||||
cls.barbican_mgr = barbican_client_mgr.BarbicanClientManager()
|
||||
cls.barbican_mgr.setup_clients(cls.os_primary)
|
||||
|
||||
@decorators.idempotent_id('21ad41fb-04cf-493c-bc2f-66c80220898c')
|
||||
@tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
|
||||
def test_create_share_with_share_server_encryption_key_ref(self):
|
||||
|
||||
secret_href = self.barbican_mgr.store_secret()
|
||||
secret_href_uuid = self.barbican_mgr.ref_to_uuid(secret_href)
|
||||
|
||||
share = self.create_share(
|
||||
share_protocol=self.protocol,
|
||||
share_type_id=self.share_type_enc_id,
|
||||
share_network_id=self.shares_v2_client.share_network_id,
|
||||
size=1,
|
||||
name="encrypted_share",
|
||||
encryption_key_ref=secret_href_uuid,
|
||||
cleanup_in_class=False)
|
||||
|
||||
self.assertEqual(share['encryption_key_ref'], secret_href_uuid)
|
||||
|
||||
# Delete Barbican secret
|
||||
self.barbican_mgr.delete_secret(secret_href_uuid)
|
||||
|
||||
|
||||
class ShareEncryptionCIFSTest(ShareEncryptionNFSTest):
|
||||
"""Covers share functionality, that is related to CIFS share type."""
|
||||
protocol = "cifs"
|
||||
@@ -0,0 +1,74 @@
|
||||
# Copyright 2025 Cloudifcation 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 tempest import config
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
from testtools import testcase as tc
|
||||
|
||||
from manila_tempest_tests.tests.api import base
|
||||
from manila_tempest_tests import utils
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class SharesEncryptionNegativeTest(base.BaseSharesMixedTest):
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(SharesEncryptionNegativeTest, cls).skip_checks()
|
||||
if not CONF.share.run_encryption_tests:
|
||||
raise cls.skipException('Encryption tests are disabled.')
|
||||
utils.check_skip_if_microversion_not_supported("2.90")
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(SharesEncryptionNegativeTest, cls).resource_setup()
|
||||
# create share_type
|
||||
cls.no_encryption_type = cls.create_share_type()
|
||||
cls.no_encryption_type_id = cls.no_encryption_type['id']
|
||||
cls.encryption_type = cls.create_share_type(
|
||||
extra_specs={
|
||||
'encryption_support': 'share_server',
|
||||
})
|
||||
cls.encryption_type_id = cls.encryption_type['id']
|
||||
|
||||
@decorators.idempotent_id('b8097d56-067e-4d7c-8401-31bc7021fe81')
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
def test_create_share_with_invalid_share_type(self):
|
||||
# should not create share when encryption isn't supported by
|
||||
# share type
|
||||
self.assertRaises(lib_exc.BadRequest,
|
||||
self.shares_v2_client.create_share,
|
||||
share_type_id=self.no_encryption_type_id,
|
||||
encryption_key_ref='fake_ref')
|
||||
|
||||
@decorators.idempotent_id('b8097d56-067e-4d7c-8401-31bc7021fe88')
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
def test_create_share_with_invalid_encryption_key_ref(self):
|
||||
# should not create share when key ref is invalid UUID
|
||||
self.assertRaises(lib_exc.BadRequest,
|
||||
self.shares_v2_client.create_share,
|
||||
share_type_id=self.encryption_type_id,
|
||||
encryption_key_ref='fake_ref')
|
||||
|
||||
@decorators.idempotent_id('b8097d56-067e-4d7c-8401-31bc7021fe82')
|
||||
@tc.attr(base.TAG_NEGATIVE, base.TAG_API)
|
||||
def test_create_share_with_encryption_key_ref_absent_in_barbican(self):
|
||||
# should not create share when key ref is not present in barbican
|
||||
self.assertRaises(
|
||||
lib_exc.BadRequest,
|
||||
self.shares_v2_client.create_share,
|
||||
share_type_id=self.encryption_type_id,
|
||||
encryption_key_ref='cfbe8ae1-7932-43f2-bf82-3fd3ddba30c3')
|
||||
@@ -27,6 +27,7 @@ from manila_tempest_tests import utils
|
||||
CONF = config.CONF
|
||||
SHARE_NETWORK_SUBNETS_MICROVERSION = '2.51'
|
||||
SHARE_REPLICA_QUOTAS_MICROVERSION = "2.53"
|
||||
ENCRYPTION_KEYS_QUOTA_MICROVERSION = "2.90"
|
||||
EXPERIMENTAL = {'X-OpenStack-Manila-API-Experimental': 'True'}
|
||||
|
||||
|
||||
@@ -276,6 +277,10 @@ def share_replica_quotas_are_supported():
|
||||
return is_microversion_supported(SHARE_REPLICA_QUOTAS_MICROVERSION)
|
||||
|
||||
|
||||
def encryption_keys_quota_supported():
|
||||
return is_microversion_supported(ENCRYPTION_KEYS_QUOTA_MICROVERSION)
|
||||
|
||||
|
||||
def share_network_get_default_subnet(share_network):
|
||||
return next((
|
||||
subnet for subnet in share_network.get('share_network_subnets', [])
|
||||
|
||||
@@ -3,3 +3,6 @@ pbr>=3.0.0 # Apache-2.0
|
||||
ddt>=1.6.0 # MIT
|
||||
oslo.log>=3.36.0 # Apache-2.0
|
||||
tempest>=31.1.0 # Apache-2.0
|
||||
|
||||
# the encryption tests require it
|
||||
barbican-tempest-plugin>=1.6.0 # Apache-2.0
|
||||
|
||||
@@ -691,6 +691,7 @@
|
||||
parent: manila-tempest-plugin-standalone-base
|
||||
required-projects:
|
||||
- openstack/barbican
|
||||
- openstack/barbican-tempest-plugin
|
||||
vars:
|
||||
devstack_plugins:
|
||||
barbican: https://opendev.org/openstack/barbican
|
||||
@@ -761,6 +762,8 @@
|
||||
run_share_server_migration_tests: true
|
||||
run_share_server_multiple_subnet_tests: true
|
||||
run_network_allocation_update_tests: true
|
||||
run_encryption_tests: true
|
||||
capability_encryption_support: share_server
|
||||
|
||||
- job:
|
||||
name: manila-tempest-plugin-glusterfs-native
|
||||
|
||||
Reference in New Issue
Block a user