Merge "Consolidates rbac_base for v2 and v3 identity tests"
diff --git a/contrib/post_test_hook.sh b/contrib/post_test_hook.sh
index af7c856..e934cc4 100644
--- a/contrib/post_test_hook.sh
+++ b/contrib/post_test_hook.sh
@@ -28,7 +28,6 @@
TEMPEST_COMMAND="sudo -H -u tempest tox"
DEVSTACK_GATE_TEMPEST_REGEX="(?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)"
-DEVSTACK_GATE_TEMPEST_HEAT_REGEX="(?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api\.orchestration)"
DEVSTACK_MULTINODE_GATE_TEMPEST_REGEX="(?=.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)"
# Import devstack function 'iniset'.
@@ -76,8 +75,6 @@
if [[ "$TYPE" == "multinode" ]]; then
$TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_MULTINODE_GATE_TEMPEST_REGEX --concurrency=$TEMPEST_CONCURRENCY
- elif [[ "$TYPE" == "heat" ]]; then
- $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_GATE_TEMPEST_HEAT_REGEX --concurrency=$TEMPEST_CONCURRENCY
else
$TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_GATE_TEMPEST_REGEX --concurrency=$TEMPEST_CONCURRENCY
fi
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index e68921f..8256889 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -168,7 +168,7 @@
return self._allowed(
access=self._get_access_token(role),
apply_rule='context_is_admin')
- return role == 'admin'
+ return role == CONF.identity.admin_role
def _get_access_token(self, role):
access_token = {
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index 4382259..8de3d97 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -77,7 +77,7 @@
LOG.info("As admin_only is True, only admin role should be "
"allowed to perform the API. Skipping oslo.policy "
"check for policy action {0}.".format(rule))
- allowed = CONF.rbac.rbac_test_role == 'admin'
+ allowed = CONF.rbac.rbac_test_role == CONF.identity.admin_role
else:
allowed = _is_authorized(test_obj, service, rule,
extra_target_data)
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index 55a5599..4cddb8d 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -160,7 +160,7 @@
for role in available_roles['roles']:
if role['name'] == CONF.rbac.rbac_test_role:
rbac_role_id = role['id']
- if role['name'] == 'admin':
+ if role['name'] == CONF.identity.admin_role:
admin_role_id = role['id']
if not admin_role_id or not rbac_role_id:
diff --git a/patrole_tempest_plugin/tests/api/compute/test_floating_ip_pools_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_floating_ip_pools_rbac.py
index 755bacd..75cc8f4 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_floating_ip_pools_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_floating_ip_pools_rbac.py
@@ -25,9 +25,10 @@
class FloatingIpPoolsRbacTest(rbac_base.BaseV2ComputeRbacTest):
- # Tests will fail with a 404 starting from microversion 2.36, according to:
- # https://developer.openstack.org/api-ref/
- # compute/?expanded=list-floating-ip-pools-detail
+ # Tests will fail with a 404 starting from microversion 2.36:
+ # See the following link for details:
+ # https://developer.openstack.org/api-ref/compute/#floating-ip-pools-os-floating-ip-pools-deprecated
+ min_microversion = '2.10'
max_microversion = '2.35'
@classmethod
diff --git a/patrole_tempest_plugin/tests/api/compute/test_floating_ips_bulk_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_floating_ips_bulk_rbac.py
index 7fe4847..bff0612 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_floating_ips_bulk_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_floating_ips_bulk_rbac.py
@@ -25,9 +25,10 @@
class FloatingIpsBulkRbacTest(rbac_base.BaseV2ComputeRbacTest):
- # Tests will fail with a 404 starting from microversion 2.36, according to:
- # https://developer.openstack.org/api-ref/
- # compute/?expanded=list-floating-ips-detail
+ # Tests will fail with a 404 starting from microversion 2.36:
+ # See the following link for details:
+ # https://developer.openstack.org/api-ref/compute/#floating-ips-bulk-os-floating-ips-bulk-deprecated
+ min_microversion = '2.10'
max_microversion = '2.35'
@classmethod
diff --git a/patrole_tempest_plugin/tests/api/compute/test_floating_ips_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_floating_ips_rbac.py
index 784a82d..0bd00a4 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_floating_ips_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_floating_ips_rbac.py
@@ -25,9 +25,10 @@
class FloatingIpsRbacTest(rbac_base.BaseV2ComputeRbacTest):
- # Tests will fail with a 404 starting from microversion 2.36, according to:
- # https://developer.openstack.org/api-ref/
- # compute/?expanded=list-floating-ip-addresses-detail
+ # Tests will fail with a 404 starting from microversion 2.36:
+ # See the following link for details:
+ # https://developer.openstack.org/api-ref/compute/#floating-ips-os-floating-ips-deprecated
+ min_microversion = '2.10'
max_microversion = '2.35'
@classmethod
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
index 81266af..86b650e 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
@@ -301,12 +301,9 @@
class ServerActionsV216RbacTest(rbac_base.BaseV2ComputeRbacTest):
- # This class has test case(s) that requires at least version 2.16.
- #
+ # This class has test case(s) that requires at least microversion 2.16.
# See the following link for details:
- # http://developer.openstack.org/
- # api-ref-compute-v2.1.html#show-server-details
-
+ # http://developer.openstack.org/api-ref-compute-v2.1.html#show-server-details
min_microversion = '2.16'
max_microversion = 'latest'
diff --git a/patrole_tempest_plugin/tests/api/compute/test_tenant_networks_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_tenant_networks_rbac.py
index fb834bd..ee2b9a2 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_tenant_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_tenant_networks_rbac.py
@@ -26,9 +26,10 @@
class TenantNetworksRbacTest(rbac_base.BaseV2ComputeRbacTest):
- # Tests will fail with a 404 starting from microversion 2.36, according to:
- # https://developer.openstack.org/api-ref/
- # compute/?expanded=list-project-networks-detail
+ # Tests will fail with a 404 starting from microversion 2.36.
+ # See the following link for details:
+ # https://developer.openstack.org/api-ref/compute/#project-networks-os-tenant-networks-deprecated
+ min_microversion = '2.10'
max_microversion = '2.35'
@classmethod
diff --git a/patrole_tempest_plugin/tests/api/orchestration/__init__.py b/patrole_tempest_plugin/tests/api/orchestration/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/patrole_tempest_plugin/tests/api/orchestration/__init__.py
+++ /dev/null
diff --git a/patrole_tempest_plugin/tests/api/orchestration/rbac_base.py b/patrole_tempest_plugin/tests/api/orchestration/rbac_base.py
deleted file mode 100644
index 7be7d40..0000000
--- a/patrole_tempest_plugin/tests/api/orchestration/rbac_base.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 2017 AT&T Corporation.
-# 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.api.orchestration import base as heat_base
-from tempest import config
-
-from patrole_tempest_plugin.rbac_utils import rbac_utils
-
-CONF = config.CONF
-
-
-class BaseOrchestrationRbacTest(heat_base.BaseOrchestrationTest):
-
- credentials = ['admin', 'primary']
-
- @classmethod
- def skip_checks(cls):
- super(BaseOrchestrationRbacTest, cls).skip_checks()
- if not CONF.rbac.enable_rbac:
- raise cls.skipException(
- "%s skipped as RBAC Flag not enabled" % cls.__name__)
-
- @classmethod
- def setup_clients(cls):
- super(BaseOrchestrationRbacTest, cls).setup_clients()
- cls.auth_provider = cls.os_primary.auth_provider
- cls.rbac_utils = rbac_utils()
- cls.rbac_utils.switch_role(cls, toggle_rbac_role=False)
diff --git a/patrole_tempest_plugin/tests/api/orchestration/test_resource_types_rbac.py b/patrole_tempest_plugin/tests/api/orchestration/test_resource_types_rbac.py
deleted file mode 100644
index a348512..0000000
--- a/patrole_tempest_plugin/tests/api/orchestration/test_resource_types_rbac.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright 2017 AT&T Corporation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.lib import decorators
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.orchestration import rbac_base
-
-
-class ResourceTypesRbacTest(rbac_base.BaseOrchestrationRbacTest):
-
- @classmethod
- def setup_clients(cls):
- super(ResourceTypesRbacTest, cls).setup_clients()
- cls.client = cls.orchestration_client
-
- @classmethod
- def resource_setup(cls):
- super(ResourceTypesRbacTest, cls).resource_setup()
-
- cls.resource_types = cls.client.list_resource_types()['resource_types']
-
- # There should always be several resource types on a system. But just
- # in case there are none, skip these tests, as that implies the system
- # is misconfigured.
- if cls.resource_types:
- cls.resource_type_name = cls.resource_types[0]
- else:
- raise cls.skipException('No resource types found.')
-
- @decorators.idempotent_id('56c06e92-df96-47b5-bcf2-0104e74e2511')
- @rbac_rule_validation.action(service="heat",
- rule="stacks:list_resource_types")
- def test_list_resource_types(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.list_resource_types()['resource_types']
-
- @decorators.idempotent_id('8b0290f9-0d53-479e-8e4d-3d865b0107a4')
- @rbac_rule_validation.action(service="heat",
- rule="stacks:generate_template")
- def test_show_resource_type_template(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.show_resource_type_template(self.resource_type_name)
-
- @decorators.idempotent_id('2cdcd47f-6abe-43af-b736-c188df27dd38')
- @rbac_rule_validation.action(service="heat",
- rule="stacks:resource_schema")
- def test_show_resource_type_schema(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.show_resource_type(self.resource_type_name)[
- 'resource_type']
diff --git a/patrole_tempest_plugin/tests/api/orchestration/test_soft_config_rbac.py b/patrole_tempest_plugin/tests/api/orchestration/test_soft_config_rbac.py
deleted file mode 100644
index d219ace..0000000
--- a/patrole_tempest_plugin/tests/api/orchestration/test_soft_config_rbac.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# Copyright 2017 AT&T Corporation.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
-from tempest.lib import decorators
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.orchestration import rbac_base
-
-
-class TestRbacSoftwareConfig(rbac_base.BaseOrchestrationRbacTest):
-
- def setUp(self):
- super(TestRbacSoftwareConfig, self).setUp()
- self.config = self._config_create('a')
- self._deployment_create(self.config['id'])
-
- @rbac_rule_validation.action(service="heat",
- rule="software_configs:show")
- @decorators.idempotent_id('b2e7c98c-e17b-4f37-82f3-5d21eff86e79')
- def test_get_software_config(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.show_software_config(self.config['id'])
-
- @rbac_rule_validation.action(service="heat",
- rule="software_deployments:metadata")
- @decorators.idempotent_id('defa34ab-9d1f-4b14-8613-34e964c0c478')
- def test_get_deployment_metadata(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.show_software_deployment_metadata(self.server_id)
-
- @rbac_rule_validation.action(service="heat",
- rule="software_deployments:index")
- @decorators.idempotent_id('2a4dcb91-1803-4749-9cb7-5b69ba668b18')
- def test_get_deployment_list(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.list_software_deployments()
-
- @rbac_rule_validation.action(service="heat",
- rule="software_deployments:show")
- @decorators.idempotent_id('d4e627bc-88a3-4189-8092-151f22ed989d')
- def test_software_show_deployment(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.show_software_deployment(self.deployment_id)
-
- @rbac_rule_validation.action(service="heat",
- rule="software_deployments:update")
- @decorators.idempotent_id('90e8958c-6fa7-4515-b6d7-6d6952979f8c')
- def test_software_deployment_update(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- new_action = data_utils.rand_name('ACTION')
- new_status = data_utils.rand_name('STATUS')
- new_reason = data_utils.rand_name('REASON')
- self.client.update_software_deploy(self.deployment_id,
- self.server_id,
- self.config['id'],
- new_action, new_status,
- self.input_values,
- self.output_values,
- new_reason,
- self.signal_transport)
-
- @rbac_rule_validation.action(service="heat",
- rule="software_deployments:create")
- @decorators.idempotent_id('9175fe7b-4210-4c1d-acbb-954998a9fd77')
- def test_software_deployment_create(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self._deployment_create(self.config['id'])
-
- @rbac_rule_validation.action(service="heat",
- rule="software_deployments:delete")
- @decorators.idempotent_id('20f4683d-7316-4d88-a6ea-1ee6013da908')
- def test_software_deployment_delete(self):
- deploy_id = self._deployment_create(self.config['id'])
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.delete_software_deploy(deploy_id)
-
- @rbac_rule_validation.action(service="heat",
- rule="software_configs:create")
- @decorators.idempotent_id('c8fb1c73-fcb6-46c2-9510-8ef0083c9620')
- def test_config_create(self):
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self._config_create('e')
-
- @rbac_rule_validation.action(service="heat",
- rule="software_configs:delete")
- @decorators.idempotent_id('f4f784ea-9878-4306-bc5f-041ba5307ce5')
- def test_config_delete(self):
- configuration = self._config_create('d')
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.delete_software_config(configuration['id'])
-
- def _config_create(self, suffix):
- configuration = {'group': 'script',
- 'inputs': [],
- 'outputs': [],
- 'options': {}}
- configuration['name'] = 'heat_soft_config_%s' % suffix
- configuration['config'] = '#!/bin/bash echo init-%s' % suffix
- api_config = self.client.create_software_config(**configuration)
- self.addCleanup(
- test_utils.call_and_ignore_notfound_exc,
- self.client.delete_software_config,
- api_config['software_config']['id'])
- configuration['id'] = api_config['software_config']['id']
- return configuration
-
- def _deployment_create(self, config_id):
- self.server_id = data_utils.rand_name('dummy-server')
- self.action = 'ACTION_0'
- self.status = 'STATUS_0'
- self.input_values = {}
- self.output_values = []
- self.status_reason = 'REASON_0'
- self.signal_transport = 'NO_SIGNAL'
- self.deployment = self.client.create_software_deploy(
- self.server_id, config_id, self.action, self.status,
- self.input_values, self.output_values, self.status_reason,
- self.signal_transport)
- self.addCleanup(
- test_utils.call_and_ignore_notfound_exc,
- self.client.delete_software_deploy,
- self.deployment['software_deployment']['id'])
- self.deployment_id = self.deployment['software_deployment']['id']
- return self.deployment_id
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_services_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_services_rbac.py
new file mode 100644
index 0000000..c1d8a65
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_services_rbac.py
@@ -0,0 +1,51 @@
+# Copyright 2017 AT&T Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib import decorators
+from tempest import test
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.volume import rbac_base
+
+
+class VolumeServicesRbacTest(rbac_base.BaseVolumeRbacTest):
+
+ # TODO(felipemonteiro): Implement a test to cover the policy action,
+ # "volume_extension:services:update", once the Tempest client endpoint
+ # is implemented.
+
+ @classmethod
+ def skip_checks(cls):
+ super(VolumeServicesRbacTest, cls).skip_checks()
+ if not test.is_extension_enabled('os-services', 'volume'):
+ msg = "%s skipped as os-services not enabled." % cls.__name__
+ raise cls.skipException(msg)
+
+ @classmethod
+ def setup_clients(cls):
+ super(VolumeServicesRbacTest, cls).setup_clients()
+ cls.client = cls.os_primary.volume_services_v2_client
+
+ @decorators.idempotent_id('b9134f01-97c0-4abd-9455-fe2f03e3f966')
+ @rbac_rule_validation.action(
+ service="cinder",
+ rule="volume_extension:services:index")
+ def test_list_services(self):
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.client.list_services()['services']
+
+
+class VolumeServicesV3RbacTest(VolumeServicesRbacTest):
+ _api_version = 3
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py
index 50973b8..8c04a8d 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volumes_backup_rbac.py
@@ -116,18 +116,6 @@
self.backups_client.delete_backup(backup['id'])
self.backups_client.wait_for_resource_deletion(backup['id'])
- @decorators.idempotent_id('48325aaa-13f5-4ba3-96a3-24b6c9d77b6d')
- @test.attr(type=["slow"])
- @rbac_rule_validation.action(
- service="cinder",
- rule="volume_extension:backup_admin_actions:force_delete")
- def test_volume_backup_force_delete(self):
- backup = self._create_backup(volume_id=self.volume['id'])
-
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.backups_client.force_delete_backup(backup['id'])
- self.backups_client.wait_for_resource_deletion(backup['id'])
-
@test.attr(type=["slow"])
@rbac_rule_validation.action(service="cinder",
rule="backup:backup-export")
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
index 057ce20..a2917cf 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
@@ -51,12 +51,15 @@
self.mock_test_obj.os_admin = mock.Mock(
**{'roles_v3_client.list_roles.return_value': available_roles})
- CONF.set_override('rbac_test_role', 'Member', group='rbac',
+ CONF.set_override('admin_role', 'admin', group='identity',
enforce_type=True)
CONF.set_override('auth_version', 'v3', group='identity',
enforce_type=True)
+ CONF.set_override('rbac_test_role', 'Member', group='rbac',
+ enforce_type=True)
self.addCleanup(CONF.clear_override, 'rbac_test_role', group='rbac')
+ self.addCleanup(CONF.clear_override, 'admin_role', group='identity')
self.addCleanup(CONF.clear_override, 'auth_version', group='identity')
self.addCleanup(mock.patch.stopall)
diff --git a/releasenotes/notes/volume-services-rbac-test-57e69f9952c8746e.yaml b/releasenotes/notes/volume-services-rbac-test-57e69f9952c8746e.yaml
new file mode 100644
index 0000000..5b6f0cd
--- /dev/null
+++ b/releasenotes/notes/volume-services-rbac-test-57e69f9952c8746e.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Added RBAC test for the volume services API, which covers the following
+ policy action: "volume_extension:services:index".