summaryrefslogtreecommitdiff
path: root/openstack_dashboard
diff options
context:
space:
mode:
Diffstat (limited to 'openstack_dashboard')
-rw-r--r--openstack_dashboard/api/neutron.py132
-rw-r--r--openstack_dashboard/conf/cinder_policy.yaml176
-rw-r--r--openstack_dashboard/conf/default_policies/neutron.yaml204
-rw-r--r--openstack_dashboard/conf/default_policies/nova.yaml4
-rw-r--r--openstack_dashboard/conf/neutron_policy.yaml558
-rw-r--r--openstack_dashboard/conf/nova_policy.yaml8
-rw-r--r--openstack_dashboard/dashboards/project/floating_ip_portforwardings/__init__.py0
-rw-r--r--openstack_dashboard/dashboards/project/floating_ip_portforwardings/panel.py37
-rw-r--r--openstack_dashboard/dashboards/project/floating_ip_portforwardings/tables.py194
-rw-r--r--openstack_dashboard/dashboards/project/floating_ip_portforwardings/tests.py262
-rw-r--r--openstack_dashboard/dashboards/project/floating_ip_portforwardings/urls.py24
-rw-r--r--openstack_dashboard/dashboards/project/floating_ip_portforwardings/views.py110
-rw-r--r--openstack_dashboard/dashboards/project/floating_ip_portforwardings/workflows.py270
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/tables.py116
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/tests.py25
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/views.py15
-rw-r--r--openstack_dashboard/dashboards/project/floating_ips/workflows.py5
-rw-r--r--openstack_dashboard/enabled/_1520_project_floating_ip_portforwardings_panel.py7
-rw-r--r--openstack_dashboard/locale/as/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/bn_IN/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/brx/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/cs/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/de/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/en_AU/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/en_GB/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/es/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/fr/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/gu/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/hi/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/id/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/it/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/ja/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/kn/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/ko_KR/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/kok/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/ks/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/mai/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/mni/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/mr/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/ne/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/pa_IN/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/pl_PL/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/pt_BR/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/ru/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/ta/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/tr_TR/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/ur/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/zh_Hans/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/locale/zh_Hant/LC_MESSAGES/django.po5
-rw-r--r--openstack_dashboard/static/app/core/openstack-service-api/glance.service.js6
-rw-r--r--openstack_dashboard/test/integration_tests/helpers.py6
-rw-r--r--openstack_dashboard/test/integration_tests/horizon.conf2
-rw-r--r--openstack_dashboard/test/integration_tests/pages/basepage.py25
-rw-r--r--openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py2
-rw-r--r--openstack_dashboard/test/integration_tests/regions/messages.py32
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_defaults.py16
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_flavors.py12
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_floatingips.py49
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_groups.py18
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_grouptypes.py12
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_host_aggregates.py12
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_images.py77
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_instances.py62
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_keypairs.py8
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_metadata_definitions.py8
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_networks.py27
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_projects.py22
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_router.py47
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_router_gateway.py20
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_security_groups.py25
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_user_settings.py24
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_users.py8
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_volume_snapshots.py53
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_volumes.py104
-rw-r--r--openstack_dashboard/test/integration_tests/tests/test_volumetypes.py56
-rw-r--r--openstack_dashboard/test/test_data/neutron_data.py44
-rw-r--r--openstack_dashboard/test/unit/api/test_neutron.py75
77 files changed, 2276 insertions, 878 deletions
diff --git a/openstack_dashboard/api/neutron.py b/openstack_dashboard/api/neutron.py
index dcc9ac78b..3df0e8a8c 100644
--- a/openstack_dashboard/api/neutron.py
+++ b/openstack_dashboard/api/neutron.py
@@ -510,15 +510,38 @@ class SecurityGroupManager(object):
class FloatingIp(base.APIDictWrapper):
_attrs = ['id', 'ip', 'fixed_ip', 'port_id', 'instance_id',
- 'instance_type', 'pool', 'dns_domain', 'dns_name']
+ 'instance_type', 'pool', 'dns_domain', 'dns_name',
+ 'port_forwardings']
def __init__(self, fip):
fip['ip'] = fip['floating_ip_address']
fip['fixed_ip'] = fip['fixed_ip_address']
fip['pool'] = fip['floating_network_id']
+ fip['port_forwardings'] = fip.get('portforwardings', {})
super().__init__(fip)
+class PortForwarding(base.APIDictWrapper):
+ _attrs = ['id', 'floating_ip_id', 'protocol', 'internal_port_range',
+ 'external_port_range', 'internal_ip_address',
+ 'description', 'internal_port_id', 'external_ip_address']
+
+ def __init__(self, pfw, fip):
+ pfw['floating_ip_id'] = fip
+ port_forwarding = pfw
+ if 'port_forwarding' in pfw:
+ port_forwarding = pfw['port_forwarding']
+ port_forwarding['internal_port_range'] = ':'.join(
+ map(str, sorted(
+ map(int, set(port_forwarding.get(
+ 'internal_port_range', '').split(':'))))))
+ port_forwarding['external_port_range'] = ':'.join(
+ map(str, sorted(
+ map(int, set(port_forwarding.get(
+ 'external_port_range', '').split(':'))))))
+ super().__init__(pfw)
+
+
class FloatingIpPool(base.APIDictWrapper):
pass
@@ -544,6 +567,81 @@ class FloatingIpTarget(base.APIDictWrapper):
super().__init__(target)
+class PortForwardingManager(object):
+
+ def __init__(self, request):
+ self.request = request
+ self.client = neutronclient(request)
+
+ @profiler.trace
+ def list(self, floating_ip_id, **search_opts):
+ port_forwarding_rules = self.client.list_port_forwardings(
+ floating_ip_id, **search_opts)
+ port_forwarding_rules = port_forwarding_rules.get('port_forwardings')
+ LOG.debug("Portforwarding rules listed=%s", port_forwarding_rules)
+ return [PortForwarding(port_forwarding_rule, floating_ip_id)
+ for port_forwarding_rule in port_forwarding_rules]
+
+ @profiler.trace
+ def update(self, floating_ip_id, **params):
+ portforwarding_dict = self.create_port_forwarding_dict(**params)
+ portforwarding_id = params['portforwarding_id']
+ LOG.debug("Updating Portforwarding rule with id %s", portforwarding_id)
+ pfw = self.client.update_port_forwarding(
+ floating_ip_id,
+ portforwarding_id,
+ {'port_forwarding': portforwarding_dict}).get('port_forwarding')
+
+ return PortForwarding(pfw, floating_ip_id)
+
+ @profiler.trace
+ def create(self, floating_ip_id, **params):
+ portforwarding_dict = self.create_port_forwarding_dict(**params)
+ portforwarding_rule = self.client.create_port_forwarding(
+ floating_ip_id,
+ {'port_forwarding': portforwarding_dict}).get('port_forwarding')
+ LOG.debug("Created a Portforwarding rule to floating IP %s with id %s",
+ floating_ip_id,
+ portforwarding_rule['id'])
+ return PortForwarding(portforwarding_rule, floating_ip_id)
+
+ def create_port_forwarding_dict(self, **params):
+ portforwarding_dict = {}
+ if 'protocol' in params:
+ portforwarding_dict['protocol'] = str(params['protocol']).lower()
+ if 'internal_port' in params:
+ internal_port = str(params['internal_port'])
+ if ':' not in internal_port:
+ portforwarding_dict['internal_port'] = int(internal_port)
+ else:
+ portforwarding_dict['internal_port_range'] = internal_port
+ if 'external_port' in params:
+ external_port = str(params['external_port'])
+ if ':' not in external_port:
+ portforwarding_dict['external_port'] = int(external_port)
+ else:
+ portforwarding_dict['external_port_range'] = external_port
+ if 'internal_ip_address' in params:
+ portforwarding_dict['internal_ip_address'] = params[
+ 'internal_ip_address']
+ if 'description' in params:
+ portforwarding_dict['description'] = params['description']
+ if 'internal_port_id' in params:
+ portforwarding_dict['internal_port_id'] = params['internal_port_id']
+ return portforwarding_dict
+
+ def delete(self, floating_ip_id, portforwarding_id):
+ self.client.delete_port_forwarding(floating_ip_id, portforwarding_id)
+ LOG.debug(
+ "The Portforwarding rule of floating IP %s with id %s was deleted",
+ floating_ip_id, portforwarding_id)
+
+ def get(self, floating_ip_id, portforwarding_id):
+ pfw = self.client.show_port_forwarding(floating_ip_id,
+ portforwarding_id)
+ return PortForwarding(pfw, portforwarding_id)
+
+
class FloatingIpManager(object):
"""Manager class to implement Floating IP methods
@@ -1956,6 +2054,26 @@ def tenant_floating_ip_list(request, all_tenants=False, **search_opts):
**search_opts)
+def floating_ip_port_forwarding_list(request, fip):
+ return PortForwardingManager(request).list(fip)
+
+
+def floating_ip_port_forwarding_create(request, fip, **params):
+ return PortForwardingManager(request).create(fip, **params)
+
+
+def floating_ip_port_forwarding_update(request, fip, **params):
+ return PortForwardingManager(request).update(fip, **params)
+
+
+def floating_ip_port_forwarding_get(request, fip, pfw):
+ return PortForwardingManager(request).get(fip, pfw)
+
+
+def floating_ip_port_forwarding_delete(request, fip, pfw):
+ return PortForwardingManager(request).delete(fip, pfw)
+
+
def tenant_floating_ip_get(request, floating_ip_id):
return FloatingIpManager(request).get(floating_ip_id)
@@ -2179,6 +2297,18 @@ def is_extension_supported(request, extension_alias):
return False
+@profiler.trace
+def is_extension_floating_ip_port_forwarding_supported(request):
+ try:
+ return is_extension_supported(
+ request, extension_alias='floating-ip-port-forwarding')
+ except Exception as e:
+ LOG.error("It was not possible to check if the "
+ "floating-ip-port-forwarding extension is enabled in "
+ "neutron. Port forwardings will not be enabled.: %s", e)
+ return False
+
+
# TODO(amotoki): Clean up 'default' parameter because the default
# values are pre-defined now, so 'default' argument is meaningless
# in most cases.
diff --git a/openstack_dashboard/conf/cinder_policy.yaml b/openstack_dashboard/conf/cinder_policy.yaml
index ab30bae09..f4d54075d 100644
--- a/openstack_dashboard/conf/cinder_policy.yaml
+++ b/openstack_dashboard/conf/cinder_policy.yaml
@@ -454,7 +454,16 @@
# since X in favor of "group:group_types:create":"rule:admin_api".
# group:group_types_manage has been replaced by more granular policies
# that separately govern POST, PUT, and DELETE operations.
-#"group:group_types_manage": "rule:group:group_types:create"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "group:group_types_manage": "rule:group:group_types:create"
# Update a group type.
# PUT /group_types/{group_type_id}
@@ -465,7 +474,16 @@
# since X in favor of "group:group_types:update":"rule:admin_api".
# group:group_types_manage has been replaced by more granular policies
# that separately govern POST, PUT, and DELETE operations.
-#"group:group_types_manage": "rule:group:group_types:update"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "group:group_types_manage": "rule:group:group_types:update"
# Delete a group type.
# DELETE /group_types/{group_type_id}
@@ -476,7 +494,16 @@
# since X in favor of "group:group_types:delete":"rule:admin_api".
# group:group_types_manage has been replaced by more granular policies
# that separately govern POST, PUT, and DELETE operations.
-#"group:group_types_manage": "rule:group:group_types:delete"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "group:group_types_manage": "rule:group:group_types:delete"
# Show group type with type specs attributes.
# GET /group_types/{group_type_id}
@@ -491,7 +518,16 @@
# X in favor of "group:group_types_specs:get":"rule:admin_api".
# group:group_types_specs has been replaced by more granular policies
# that separately govern GET, POST, PUT, and DELETE operations.
-#"group:group_types_specs": "rule:group:group_types_specs:get"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "group:group_types_specs": "rule:group:group_types_specs:get"
# List group type specs.
# GET /group_types/{group_type_id}/group_specs
@@ -502,7 +538,16 @@
# X in favor of "group:group_types_specs:get_all":"rule:admin_api".
# group:group_types_specs has been replaced by more granular policies
# that separately govern GET, POST, PUT, and DELETE operations.
-#"group:group_types_specs": "rule:group:group_types_specs:get_all"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "group:group_types_specs": "rule:group:group_types_specs:get_all"
# Create a group type spec.
# POST /group_types/{group_type_id}/group_specs
@@ -513,7 +558,16 @@
# X in favor of "group:group_types_specs:create":"rule:admin_api".
# group:group_types_specs has been replaced by more granular policies
# that separately govern GET, POST, PUT, and DELETE operations.
-#"group:group_types_specs": "rule:group:group_types_specs:create"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "group:group_types_specs": "rule:group:group_types_specs:create"
# Update a group type spec.
# PUT /group_types/{group_type_id}/group_specs/{g_spec_id}
@@ -524,7 +578,16 @@
# X in favor of "group:group_types_specs:update":"rule:admin_api".
# group:group_types_specs has been replaced by more granular policies
# that separately govern GET, POST, PUT, and DELETE operations.
-#"group:group_types_specs": "rule:group:group_types_specs:update"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "group:group_types_specs": "rule:group:group_types_specs:update"
# Delete a group type spec.
# DELETE /group_types/{group_type_id}/group_specs/{g_spec_id}
@@ -535,7 +598,16 @@
# X in favor of "group:group_types_specs:delete":"rule:admin_api".
# group:group_types_specs has been replaced by more granular policies
# that separately govern GET, POST, PUT, and DELETE operations.
-#"group:group_types_specs": "rule:group:group_types_specs:delete"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "group:group_types_specs": "rule:group:group_types_specs:delete"
# List group snapshots.
# GET /group_snapshots
@@ -715,7 +787,16 @@
# "volume_extension:quota_classes:get":"rule:admin_api".
# volume_extension:quota_classes has been replaced by more granular
# policies that separately govern GET and PUT operations.
-#"volume_extension:quota_classes": "rule:volume_extension:quota_classes:get"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "volume_extension:quota_classes": "rule:volume_extension:quota_classes:get"
# Update project quota class.
# PUT /os-quota-class-sets/{project_id}
@@ -727,7 +808,16 @@
# "volume_extension:quota_classes:update":"rule:admin_api".
# volume_extension:quota_classes has been replaced by more granular
# policies that separately govern GET and PUT operations.
-#"volume_extension:quota_classes": "rule:volume_extension:quota_classes:update"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "volume_extension:quota_classes": "rule:volume_extension:quota_classes:update"
# Show project quota (including usage and default).
# GET /os-quota-sets/{project_id}
@@ -819,7 +909,16 @@
# since X in favor of "volume_extension:type_create":"rule:admin_api".
# volume_extension:types_manage has been replaced by more granular
# policies that separately govern POST, PUT, and DELETE operations.
-#"volume_extension:types_manage": "rule:volume_extension:type_create"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "volume_extension:types_manage": "rule:volume_extension:type_create"
# Update volume type.
# PUT /types
@@ -830,7 +929,16 @@
# since X in favor of "volume_extension:type_update":"rule:admin_api".
# volume_extension:types_manage has been replaced by more granular
# policies that separately govern POST, PUT, and DELETE operations.
-#"volume_extension:types_manage": "rule:volume_extension:type_update"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "volume_extension:types_manage": "rule:volume_extension:type_update"
# Delete volume type.
# DELETE /types
@@ -841,7 +949,16 @@
# since X in favor of "volume_extension:type_delete":"rule:admin_api".
# volume_extension:types_manage has been replaced by more granular
# policies that separately govern POST, PUT, and DELETE operations.
-#"volume_extension:types_manage": "rule:volume_extension:type_delete"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "volume_extension:types_manage": "rule:volume_extension:type_delete"
# Get one specific volume type.
# GET /types/{type_id}
@@ -1351,7 +1468,16 @@
# volume_extension:volume_image_metadata has been replaced by more
# granular policies that separately govern show, set, and remove
# operations.
-#"volume_extension:volume_image_metadata": "rule:volume_extension:volume_image_metadata:show"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "volume_extension:volume_image_metadata": "rule:volume_extension:volume_image_metadata:show"
# Set image metadata for a volume
# POST /volumes/{volume_id}/action (os-set_image_metadata)
@@ -1364,7 +1490,16 @@
# volume_extension:volume_image_metadata has been replaced by more
# granular policies that separately govern show, set, and remove
# operations.
-#"volume_extension:volume_image_metadata": "rule:volume_extension:volume_image_metadata:set"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "volume_extension:volume_image_metadata": "rule:volume_extension:volume_image_metadata:set"
# Remove specific image metadata from a volume
# POST /volumes/{volume_id}/action (os-unset_image_metadata)
@@ -1377,7 +1512,16 @@
# volume_extension:volume_image_metadata has been replaced by more
# granular policies that separately govern show, set, and remove
# operations.
-#"volume_extension:volume_image_metadata": "rule:volume_extension:volume_image_metadata:remove"
+# WARNING: A rule name change has been identified.
+# This may be an artifact of new rules being
+# included which require legacy fallback
+# rules to ensure proper policy behavior.
+# Alternatively, this may just be an alias.
+# Please evaluate on a case by case basis
+# keeping in mind the format for aliased
+# rules is:
+# "old_rule_name": "new_rule_name".
+# "volume_extension:volume_image_metadata": "rule:volume_extension:volume_image_metadata:remove"
# Update volume admin metadata. This permission is required to
# complete these API calls, though the ability to make these calls is
diff --git a/openstack_dashboard/conf/default_policies/neutron.yaml b/openstack_dashboard/conf/default_policies/neutron.yaml
index 403f35923..ca2d544b4 100644
--- a/openstack_dashboard/conf/default_policies/neutron.yaml
+++ b/openstack_dashboard/conf/default_policies/neutron.yaml
@@ -73,7 +73,7 @@
name: shared_address_groups
operations: []
scope_types: null
-- check_str: role:reader and project_id:%(project_id)s or rule:shared_address_groups
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared_address_groups
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner or rule:shared_address_groups
@@ -93,7 +93,7 @@
name: shared_address_scopes
operations: []
scope_types: null
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -134,7 +134,7 @@
path: /address-scopes/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -160,7 +160,7 @@
path: /address-scopes/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -318,7 +318,7 @@
path: /routers/{router_id}/l3-agents
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -331,7 +331,7 @@
path: /auto-allocated-topology/{project_id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -370,7 +370,7 @@
path: /flavors
scope_types:
- project
-- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
+- check_str: role:reader
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -504,7 +504,7 @@
path: /flavors/{flavor_id}/service_profiles/{profile_id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -530,7 +530,7 @@
path: /floatingips
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -545,7 +545,7 @@
path: /floatingips/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -558,7 +558,7 @@
path: /floatingips/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -571,7 +571,7 @@
path: /floatingips/{id}
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -584,7 +584,7 @@
path: /floatingip_pools
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -597,7 +597,7 @@
path: /floatingips/{floatingip_id}/port_forwardings
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -612,7 +612,7 @@
path: /floatingips/{floatingip_id}/port_forwardings/{port_forwarding_id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -625,7 +625,7 @@
path: /floatingips/{floatingip_id}/port_forwardings/{port_forwarding_id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -638,7 +638,7 @@
path: /floatingips/{floatingip_id}/port_forwardings/{port_forwarding_id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -651,7 +651,7 @@
path: /routers/{router_id}/conntrack_helpers
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -666,7 +666,7 @@
path: /routers/{router_id}/conntrack_helpers/{conntrack_helper_id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -679,7 +679,7 @@
path: /routers/{router_id}/conntrack_helpers/{conntrack_helper_id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -692,7 +692,7 @@
path: /routers/{router_id}/conntrack_helpers/{conntrack_helper_id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -705,7 +705,7 @@
path: /local-ips
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -720,7 +720,7 @@
path: /local-ips/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -733,7 +733,7 @@
path: /local-ips/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -746,7 +746,7 @@
path: /local-ips/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -759,7 +759,7 @@
path: /local_ips/{local_ip_id}/port_associations
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -774,7 +774,7 @@
path: /local_ips/{local_ip_id}/port_associations/{fixed_port_id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s or rule:ext_parent_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_ext_parent_owner
@@ -867,7 +867,7 @@
path: /metering/metering-labels
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_only
@@ -908,7 +908,7 @@
path: /metering/metering-label-rules
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_only
@@ -936,7 +936,7 @@
path: /metering/metering-label-rules/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -949,7 +949,7 @@
path: /ndp_proxies
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -964,7 +964,7 @@
path: /ndp_proxies/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -977,7 +977,7 @@
path: /ndp_proxies/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -995,7 +995,7 @@
name: external
operations: []
scope_types: null
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -1041,7 +1041,7 @@
operations: *id001
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -1096,7 +1096,7 @@
operations: *id001
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s or rule:shared
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared
or rule:external or rule:context_is_advsvc
deprecated_reason: null
deprecated_rule:
@@ -1112,17 +1112,6 @@
path: /networks/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
- deprecated_reason: null
- deprecated_rule:
- check_str: rule:regular_user
- name: get_network:router:external
- deprecated_since: null
- description: Get ``router:external`` attribute of a network
- name: get_network:router:external
- operations: *id002
- scope_types:
- - project
- check_str: rule:admin_only
deprecated_reason: null
deprecated_rule:
@@ -1167,7 +1156,7 @@
operations: *id002
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -1257,7 +1246,7 @@
operations: *id003
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -1268,7 +1257,7 @@
operations: *id003
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -1360,7 +1349,7 @@
name: admin_or_data_plane_int
operations: []
scope_types: null
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -1462,7 +1451,7 @@
operations: *id004
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -1737,6 +1726,7 @@
scope_types:
- project
- check_str: rule:admin_only or rule:context_is_advsvc or role:member and project_id:%(project_id)s
+ or rule:network_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:context_is_advsvc or rule:admin_owner_or_network_owner
@@ -1749,7 +1739,12 @@
path: /ports/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: field:policies:shared=True
+ description: Rule of shared qos policy
+ name: shared_qos_policy
+ operations: []
+ scope_types: null
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared_qos_policy
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -1818,7 +1813,7 @@
path: /qos/rule-types/{rule_type}
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -1872,7 +1867,7 @@
path: /qos/policies/{policy_id}/bandwidth_limit_rules/{rule_id}
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
description: Get a QoS packet rate limit rule
name: get_policy_packet_rate_limit_rule
operations:
@@ -1906,7 +1901,7 @@
path: /qos/policies/{policy_id}/packet_rate_limit_rules/{rule_id}
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -1960,7 +1955,7 @@
path: /qos/policies/{policy_id}/dscp_marking_rules/{rule_id}
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -2014,7 +2009,7 @@
path: /qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
description: Get a QoS minimum packet rate rule
name: get_policy_minimum_packet_rate_rule
operations:
@@ -2048,7 +2043,7 @@
path: /qos/policies/{policy_id}/minimum_packet_rate_rules/{rule_id}
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -2087,7 +2082,7 @@
path: /qos/alias_bandwidth_limit_rules/{rule_id}/
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -2126,7 +2121,7 @@
path: /qos/alias_dscp_marking_rules/{rule_id}/
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -2236,7 +2231,7 @@
name: restrict_wildcard
operations: []
scope_types: null
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -2262,7 +2257,7 @@
path: /rbac-policies
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2288,7 +2283,7 @@
path: /rbac-policies/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2303,7 +2298,7 @@
path: /rbac-policies/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2316,7 +2311,7 @@
path: /rbac-policies/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -2351,7 +2346,7 @@
operations: *id007
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2362,7 +2357,7 @@
operations: *id007
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2398,7 +2393,7 @@
operations: *id007
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2435,7 +2430,7 @@
operations: *id008
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2470,7 +2465,7 @@
operations: *id009
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2481,7 +2476,7 @@
operations: *id009
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2517,7 +2512,7 @@
operations: *id009
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2530,7 +2525,7 @@
path: /routers/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2543,7 +2538,7 @@
path: /routers/{id}/add_router_interface
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2556,7 +2551,7 @@
path: /routers/{id}/remove_router_interface
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2569,7 +2564,7 @@
path: /routers/{id}/add_extraroutes
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2592,7 +2587,12 @@
name: admin_owner_or_sg_owner
operations: []
scope_types: null
-- check_str: role:member and project_id:%(project_id)s
+- check_str: field:security_groups:shared=True
+ description: Definition of a shared security group
+ name: shared_security_group
+ operations: []
+ scope_types: null
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2605,7 +2605,7 @@
path: /security-groups
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared_security_group
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -2620,7 +2620,7 @@
path: /security-groups/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2633,7 +2633,7 @@
path: /security-groups/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2646,7 +2646,7 @@
path: /security-groups/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2659,7 +2659,7 @@
path: /security-group-rules
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s or rule:sg_owner
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:sg_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_owner_or_sg_owner
@@ -2674,7 +2674,7 @@
path: /security-group-rules/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2754,7 +2754,7 @@
path: /service-providers
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s or rule:network_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:network_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_network_owner
@@ -2789,7 +2789,7 @@
operations: *id010
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s or rule:shared
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner or rule:shared
@@ -2815,7 +2815,7 @@
operations: *id011
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s or rule:network_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:network_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_network_owner
@@ -2850,7 +2850,7 @@
operations: *id012
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s or rule:network_owner
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:network_owner
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_network_owner
@@ -2868,7 +2868,7 @@
name: shared_subnetpools
operations: []
scope_types: null
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -2907,7 +2907,7 @@
path: /subnetpools
scope_types:
- project
-- check_str: rule:admin_only or role:reader and project_id:%(project_id)s or rule:shared_subnetpools
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared_subnetpools
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner or rule:shared_subnetpools
@@ -2922,7 +2922,7 @@
path: /subnetpools/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2948,7 +2948,7 @@
path: /subnetpools/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2961,7 +2961,7 @@
path: /subnetpools/{id}
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2974,7 +2974,7 @@
path: /subnetpools/{id}/onboard_network_subnets
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2987,7 +2987,7 @@
path: /subnetpools/{id}/add_prefixes
scope_types:
- project
-- check_str: rule:admin_only or role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -3000,7 +3000,7 @@
path: /subnetpools/{id}/remove_prefixes
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -3013,7 +3013,7 @@
path: /trunks
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -3028,7 +3028,7 @@
path: /trunks/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -3041,7 +3041,7 @@
path: /trunks/{id}
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -3054,7 +3054,7 @@
path: /trunks/{id}
scope_types:
- project
-- check_str: role:reader and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:reader and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:regular_user
@@ -3067,7 +3067,7 @@
path: /trunks/{id}/get_subports
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -3080,7 +3080,7 @@
path: /trunks/{id}/add_subports
scope_types:
- project
-- check_str: role:member and project_id:%(project_id)s
+- check_str: (rule:admin_only) or (role:member and project_id:%(project_id)s)
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
diff --git a/openstack_dashboard/conf/default_policies/nova.yaml b/openstack_dashboard/conf/default_policies/nova.yaml
index 016f30a4c..5b0f4b21c 100644
--- a/openstack_dashboard/conf/default_policies/nova.yaml
+++ b/openstack_dashboard/conf/default_policies/nova.yaml
@@ -2055,7 +2055,7 @@
path: /servers/{server_id}/action (suspend)
scope_types:
- project
-- check_str: rule:project_reader_api
+- check_str: rule:project_reader_or_admin
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
@@ -2071,7 +2071,7 @@
path: /os-tenant-networks
scope_types:
- project
-- check_str: rule:project_reader_api
+- check_str: rule:project_reader_or_admin
deprecated_reason: null
deprecated_rule:
check_str: rule:admin_or_owner
diff --git a/openstack_dashboard/conf/neutron_policy.yaml b/openstack_dashboard/conf/neutron_policy.yaml
index 92f13daa8..00f28fe36 100644
--- a/openstack_dashboard/conf/neutron_policy.yaml
+++ b/openstack_dashboard/conf/neutron_policy.yaml
@@ -47,13 +47,13 @@
# GET /address-groups
# GET /address-groups/{id}
# Intended scope(s): project
-#"get_address_group": "role:reader and project_id:%(project_id)s or rule:shared_address_groups"
+#"get_address_group": "(rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared_address_groups"
# DEPRECATED
# "get_address_group":"rule:admin_or_owner or
# rule:shared_address_groups" has been deprecated since W in favor of
-# "get_address_group":"role:reader and project_id:%(project_id)s or
-# rule:shared_address_groups".
+# "get_address_group":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s) or rule:shared_address_groups".
# The Address scope API now supports system scope and default roles.
# Definition of a shared address scope
@@ -62,12 +62,12 @@
# Create an address scope
# POST /address-scopes
# Intended scope(s): project
-#"create_address_scope": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_address_scope": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_address_scope":"rule:regular_user" has been deprecated since
-# W in favor of "create_address_scope":"rule:admin_only or role:member
-# and project_id:%(project_id)s".
+# W in favor of "create_address_scope":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The Address scope API now supports system scope and default roles.
# Create a shared address scope
@@ -96,12 +96,12 @@
# Update an address scope
# PUT /address-scopes/{id}
# Intended scope(s): project
-#"update_address_scope": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"update_address_scope": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_address_scope":"rule:admin_or_owner" has been deprecated
-# since W in favor of "update_address_scope":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# since W in favor of "update_address_scope":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The Address scope API now supports system scope and default roles.
# Update ``shared`` attribute of an address scope
@@ -117,12 +117,12 @@
# Delete an address scope
# DELETE /address-scopes/{id}
# Intended scope(s): project
-#"delete_address_scope": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"delete_address_scope": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_address_scope":"rule:admin_or_owner" has been deprecated
-# since W in favor of "delete_address_scope":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# since W in favor of "delete_address_scope":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The Address scope API now supports system scope and default roles.
# Get an agent
@@ -239,26 +239,26 @@
# Get a project's auto-allocated topology
# GET /auto-allocated-topology/{project_id}
# Intended scope(s): project
-#"get_auto_allocated_topology": "role:reader and project_id:%(project_id)s"
+#"get_auto_allocated_topology": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_auto_allocated_topology":"rule:admin_or_owner" has been
# deprecated since W in favor of
-# "get_auto_allocated_topology":"role:reader and
-# project_id:%(project_id)s".
+# "get_auto_allocated_topology":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s)".
# The Auto allocated topology API now supports system scope and
# default roles.
# Delete a project's auto-allocated topology
# DELETE /auto-allocated-topology/{project_id}
# Intended scope(s): project
-#"delete_auto_allocated_topology": "role:member and project_id:%(project_id)s"
+#"delete_auto_allocated_topology": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_auto_allocated_topology":"rule:admin_or_owner" has been
# deprecated since W in favor of
-# "delete_auto_allocated_topology":"role:member and
-# project_id:%(project_id)s".
+# "delete_auto_allocated_topology":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The Auto allocated topology API now supports system scope and
# default roles.
@@ -287,12 +287,11 @@
# GET /flavors
# GET /flavors/{id}
# Intended scope(s): project
-#"get_flavor": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
+#"get_flavor": "role:reader"
# DEPRECATED
# "get_flavor":"rule:regular_user" has been deprecated since W in
-# favor of "get_flavor":"(rule:admin_only) or (role:reader and
-# project_id:%(project_id)s)".
+# favor of "get_flavor":"role:reader".
# The flavor API now supports project scope and default roles.
# Update a flavor
@@ -393,12 +392,12 @@
# Create a floating IP
# POST /floatingips
# Intended scope(s): project
-#"create_floatingip": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_floatingip": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_floatingip":"rule:regular_user" has been deprecated since W
-# in favor of "create_floatingip":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# in favor of "create_floatingip":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The Floating IP API now supports system scope and default roles.
# Create a floating IP with a specific IP address
@@ -416,58 +415,59 @@
# GET /floatingips
# GET /floatingips/{id}
# Intended scope(s): project
-#"get_floatingip": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_floatingip": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_floatingip":"rule:admin_or_owner" has been deprecated since W
-# in favor of "get_floatingip":"rule:admin_only or role:reader and
-# project_id:%(project_id)s".
+# in favor of "get_floatingip":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s)".
# The Floating IP API now supports system scope and default roles.
# Update a floating IP
# PUT /floatingips/{id}
# Intended scope(s): project
-#"update_floatingip": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"update_floatingip": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_floatingip":"rule:admin_or_owner" has been deprecated since
-# W in favor of "update_floatingip":"rule:admin_only or role:member
-# and project_id:%(project_id)s".
+# W in favor of "update_floatingip":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The Floating IP API now supports system scope and default roles.
# Delete a floating IP
# DELETE /floatingips/{id}
# Intended scope(s): project
-#"delete_floatingip": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"delete_floatingip": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_floatingip":"rule:admin_or_owner" has been deprecated since
-# W in favor of "delete_floatingip":"rule:admin_only or role:member
-# and project_id:%(project_id)s".
+# W in favor of "delete_floatingip":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The Floating IP API now supports system scope and default roles.
# Get floating IP pools
# GET /floatingip_pools
# Intended scope(s): project
-#"get_floatingip_pool": "role:reader and project_id:%(project_id)s"
+#"get_floatingip_pool": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_floatingip_pool":"rule:regular_user" has been deprecated since
-# W in favor of "get_floatingip_pool":"role:reader and
-# project_id:%(project_id)s".
+# W in favor of "get_floatingip_pool":"(rule:admin_only) or
+# (role:reader and project_id:%(project_id)s)".
# The Floating IP Pool API now supports system scope and default
# roles.
# Create a floating IP port forwarding
# POST /floatingips/{floatingip_id}/port_forwardings
# Intended scope(s): project
-#"create_floatingip_port_forwarding": "role:member and project_id:%(project_id)s or rule:ext_parent_owner"
+#"create_floatingip_port_forwarding": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "create_floatingip_port_forwarding":"rule:admin_or_ext_parent_owner"
# has been deprecated since W in favor of
-# "create_floatingip_port_forwarding":"role:member and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "create_floatingip_port_forwarding":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s) or
+# rule:ext_parent_owner".
# The floating IP port forwarding API now supports system scope and
# default roles.
@@ -475,52 +475,54 @@
# GET /floatingips/{floatingip_id}/port_forwardings
# GET /floatingips/{floatingip_id}/port_forwardings/{port_forwarding_id}
# Intended scope(s): project
-#"get_floatingip_port_forwarding": "role:reader and project_id:%(project_id)s or rule:ext_parent_owner"
+#"get_floatingip_port_forwarding": "(rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "get_floatingip_port_forwarding":"rule:admin_or_ext_parent_owner"
# has been deprecated since W in favor of
-# "get_floatingip_port_forwarding":"role:reader and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "get_floatingip_port_forwarding":"(rule:admin_only) or (role:reader
+# and project_id:%(project_id)s) or rule:ext_parent_owner".
# The floating IP port forwarding API now supports system scope and
# default roles.
# Update a floating IP port forwarding
# PUT /floatingips/{floatingip_id}/port_forwardings/{port_forwarding_id}
# Intended scope(s): project
-#"update_floatingip_port_forwarding": "role:member and project_id:%(project_id)s or rule:ext_parent_owner"
+#"update_floatingip_port_forwarding": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "update_floatingip_port_forwarding":"rule:admin_or_ext_parent_owner"
# has been deprecated since W in favor of
-# "update_floatingip_port_forwarding":"role:member and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "update_floatingip_port_forwarding":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s) or
+# rule:ext_parent_owner".
# The floating IP port forwarding API now supports system scope and
# default roles.
# Delete a floating IP port forwarding
# DELETE /floatingips/{floatingip_id}/port_forwardings/{port_forwarding_id}
# Intended scope(s): project
-#"delete_floatingip_port_forwarding": "role:member and project_id:%(project_id)s or rule:ext_parent_owner"
+#"delete_floatingip_port_forwarding": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "delete_floatingip_port_forwarding":"rule:admin_or_ext_parent_owner"
# has been deprecated since W in favor of
-# "delete_floatingip_port_forwarding":"role:member and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "delete_floatingip_port_forwarding":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s) or
+# rule:ext_parent_owner".
# The floating IP port forwarding API now supports system scope and
# default roles.
# Create a router conntrack helper
# POST /routers/{router_id}/conntrack_helpers
# Intended scope(s): project
-#"create_router_conntrack_helper": "role:member and project_id:%(project_id)s or rule:ext_parent_owner"
+#"create_router_conntrack_helper": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "create_router_conntrack_helper":"rule:admin_or_ext_parent_owner"
# has been deprecated since W in favor of
-# "create_router_conntrack_helper":"role:member and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "create_router_conntrack_helper":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s) or rule:ext_parent_owner".
# The router conntrack API now supports system scope and default
# roles.
@@ -528,121 +530,124 @@
# GET /routers/{router_id}/conntrack_helpers
# GET /routers/{router_id}/conntrack_helpers/{conntrack_helper_id}
# Intended scope(s): project
-#"get_router_conntrack_helper": "role:reader and project_id:%(project_id)s or rule:ext_parent_owner"
+#"get_router_conntrack_helper": "(rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "get_router_conntrack_helper":"rule:admin_or_ext_parent_owner" has
# been deprecated since W in favor of
-# "get_router_conntrack_helper":"role:reader and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "get_router_conntrack_helper":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s) or rule:ext_parent_owner".
# The router conntrack API now supports system scope and default
# roles.
# Update a router conntrack helper
# PUT /routers/{router_id}/conntrack_helpers/{conntrack_helper_id}
# Intended scope(s): project
-#"update_router_conntrack_helper": "role:member and project_id:%(project_id)s or rule:ext_parent_owner"
+#"update_router_conntrack_helper": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "update_router_conntrack_helper":"rule:admin_or_ext_parent_owner"
# has been deprecated since W in favor of
-# "update_router_conntrack_helper":"role:member and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "update_router_conntrack_helper":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s) or rule:ext_parent_owner".
# The router conntrack API now supports system scope and default
# roles.
# Delete a router conntrack helper
# DELETE /routers/{router_id}/conntrack_helpers/{conntrack_helper_id}
# Intended scope(s): project
-#"delete_router_conntrack_helper": "role:member and project_id:%(project_id)s or rule:ext_parent_owner"
+#"delete_router_conntrack_helper": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "delete_router_conntrack_helper":"rule:admin_or_ext_parent_owner"
# has been deprecated since W in favor of
-# "delete_router_conntrack_helper":"role:member and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "delete_router_conntrack_helper":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s) or rule:ext_parent_owner".
# The router conntrack API now supports system scope and default
# roles.
# Create a Local IP
# POST /local-ips
# Intended scope(s): project
-#"create_local_ip": "role:member and project_id:%(project_id)s"
+#"create_local_ip": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_local_ip":"rule:regular_user" has been deprecated since W in
-# favor of "create_local_ip":"role:member and
-# project_id:%(project_id)s".
+# favor of "create_local_ip":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The Local IP API now supports system scope and default roles.
# Get a Local IP
# GET /local-ips
# GET /local-ips/{id}
# Intended scope(s): project
-#"get_local_ip": "role:reader and project_id:%(project_id)s"
+#"get_local_ip": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_local_ip":"rule:admin_or_owner" has been deprecated since W in
-# favor of "get_local_ip":"role:reader and project_id:%(project_id)s".
+# favor of "get_local_ip":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s)".
# The Local IP API now supports system scope and default roles.
# Update a Local IP
# PUT /local-ips/{id}
# Intended scope(s): project
-#"update_local_ip": "role:member and project_id:%(project_id)s"
+#"update_local_ip": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_local_ip":"rule:admin_or_owner" has been deprecated since W
-# in favor of "update_local_ip":"role:member and
-# project_id:%(project_id)s".
+# in favor of "update_local_ip":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The Local IP API now supports system scope and default roles.
# Delete a Local IP
# DELETE /local-ips/{id}
# Intended scope(s): project
-#"delete_local_ip": "role:member and project_id:%(project_id)s"
+#"delete_local_ip": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_local_ip":"rule:admin_or_owner" has been deprecated since W
-# in favor of "delete_local_ip":"role:member and
-# project_id:%(project_id)s".
+# in favor of "delete_local_ip":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The Local IP API now supports system scope and default roles.
# Create a Local IP port association
# POST /local_ips/{local_ip_id}/port_associations
# Intended scope(s): project
-#"create_local_ip_port_association": "role:member and project_id:%(project_id)s or rule:ext_parent_owner"
+#"create_local_ip_port_association": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "create_local_ip_port_association":"rule:admin_or_ext_parent_owner"
# has been deprecated since W in favor of
-# "create_local_ip_port_association":"role:member and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "create_local_ip_port_association":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s) or
+# rule:ext_parent_owner".
# The Local IP API now supports system scope and default roles.
# Get a Local IP port association
# GET /local_ips/{local_ip_id}/port_associations
# GET /local_ips/{local_ip_id}/port_associations/{fixed_port_id}
# Intended scope(s): project
-#"get_local_ip_port_association": "role:reader and project_id:%(project_id)s or rule:ext_parent_owner"
+#"get_local_ip_port_association": "(rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "get_local_ip_port_association":"rule:admin_or_ext_parent_owner" has
# been deprecated since W in favor of
-# "get_local_ip_port_association":"role:reader and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "get_local_ip_port_association":"(rule:admin_only) or (role:reader
+# and project_id:%(project_id)s) or rule:ext_parent_owner".
# The Local IP API now supports system scope and default roles.
# Delete a Local IP port association
# DELETE /local_ips/{local_ip_id}/port_associations/{fixed_port_id}
# Intended scope(s): project
-#"delete_local_ip_port_association": "role:member and project_id:%(project_id)s or rule:ext_parent_owner"
+#"delete_local_ip_port_association": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:ext_parent_owner"
# DEPRECATED
# "delete_local_ip_port_association":"rule:admin_or_ext_parent_owner"
# has been deprecated since W in favor of
-# "delete_local_ip_port_association":"role:member and
-# project_id:%(project_id)s or rule:ext_parent_owner".
+# "delete_local_ip_port_association":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s) or
+# rule:ext_parent_owner".
# The Local IP API now supports system scope and default roles.
# Get loggable resources
@@ -710,12 +715,12 @@
# GET /metering/metering-labels
# GET /metering/metering-labels/{id}
# Intended scope(s): project
-#"get_metering_label": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_metering_label": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_metering_label":"rule:admin_only" has been deprecated since W
-# in favor of "get_metering_label":"rule:admin_only or role:reader and
-# project_id:%(project_id)s".
+# in favor of "get_metering_label":"(rule:admin_only) or (role:reader
+# and project_id:%(project_id)s)".
# The metering API now supports system scope and default roles.
# Delete a metering label
@@ -742,12 +747,12 @@
# GET /metering/metering-label-rules
# GET /metering/metering-label-rules/{id}
# Intended scope(s): project
-#"get_metering_label_rule": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_metering_label_rule": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_metering_label_rule":"rule:admin_only" has been deprecated
-# since W in favor of "get_metering_label_rule":"rule:admin_only or
-# role:reader and project_id:%(project_id)s".
+# since W in favor of "get_metering_label_rule":"(rule:admin_only) or
+# (role:reader and project_id:%(project_id)s)".
# The metering API now supports system scope and default roles.
# Delete a metering label rule
@@ -763,46 +768,46 @@
# Create a ndp proxy
# POST /ndp_proxies
# Intended scope(s): project
-#"create_ndp_proxy": "role:member and project_id:%(project_id)s"
+#"create_ndp_proxy": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_ndp_proxy":"rule:regular_user" has been deprecated since W
-# in favor of "create_ndp_proxy":"role:member and
-# project_id:%(project_id)s".
+# in favor of "create_ndp_proxy":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The ndp proxy API now supports system scope and default roles.
# Get a ndp proxy
# GET /ndp_proxies
# GET /ndp_proxies/{id}
# Intended scope(s): project
-#"get_ndp_proxy": "role:reader and project_id:%(project_id)s"
+#"get_ndp_proxy": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_ndp_proxy":"rule:admin_or_owner" has been deprecated since W in
-# favor of "get_ndp_proxy":"role:reader and
-# project_id:%(project_id)s".
+# favor of "get_ndp_proxy":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s)".
# The ndp proxy API now supports system scope and default roles.
# Update a ndp proxy
# PUT /ndp_proxies/{id}
# Intended scope(s): project
-#"update_ndp_proxy": "role:member and project_id:%(project_id)s"
+#"update_ndp_proxy": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_ndp_proxy":"rule:admin_or_owner" has been deprecated since W
-# in favor of "update_ndp_proxy":"role:member and
-# project_id:%(project_id)s".
+# in favor of "update_ndp_proxy":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The ndp proxy API now supports system scope and default roles.
# Delete a ndp proxy
# DELETE /ndp_proxies/{id}
# Intended scope(s): project
-#"delete_ndp_proxy": "role:member and project_id:%(project_id)s"
+#"delete_ndp_proxy": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_ndp_proxy":"rule:admin_or_owner" has been deprecated since W
-# in favor of "delete_ndp_proxy":"role:member and
-# project_id:%(project_id)s".
+# in favor of "delete_ndp_proxy":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The ndp proxy API now supports system scope and default roles.
# Definition of an external network
@@ -811,12 +816,12 @@
# Create a network
# POST /networks
# Intended scope(s): project
-#"create_network": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_network": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_network":"rule:regular_user" has been deprecated since W in
-# favor of "create_network":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# favor of "create_network":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The network API now supports system scope and default roles.
# Create a shared network
@@ -853,13 +858,13 @@
# Specify ``port_security_enabled`` attribute when creating a network
# POST /networks
# Intended scope(s): project
-#"create_network:port_security_enabled": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_network:port_security_enabled": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_network:port_security_enabled":"rule:regular_user" has been
# deprecated since W in favor of
-# "create_network:port_security_enabled":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# "create_network:port_security_enabled":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The network API now supports system scope and default roles.
# Specify ``segments`` attribute when creating a network
@@ -909,29 +914,16 @@
# GET /networks
# GET /networks/{id}
# Intended scope(s): project
-#"get_network": "rule:admin_only or role:reader and project_id:%(project_id)s or rule:shared or rule:external or rule:context_is_advsvc"
+#"get_network": "(rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared or rule:external or rule:context_is_advsvc"
# DEPRECATED
# "get_network":"rule:admin_or_owner or rule:shared or rule:external
# or rule:context_is_advsvc" has been deprecated since W in favor of
-# "get_network":"rule:admin_only or role:reader and
-# project_id:%(project_id)s or rule:shared or rule:external or
+# "get_network":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s) or rule:shared or rule:external or
# rule:context_is_advsvc".
# The network API now supports system scope and default roles.
-# Get ``router:external`` attribute of a network
-# GET /networks
-# GET /networks/{id}
-# Intended scope(s): project
-#"get_network:router:external": "rule:admin_only or role:reader and project_id:%(project_id)s"
-
-# DEPRECATED
-# "get_network:router:external":"rule:regular_user" has been
-# deprecated since W in favor of
-# "get_network:router:external":"rule:admin_only or role:reader and
-# project_id:%(project_id)s".
-# The network API now supports system scope and default roles.
-
# Get ``segments`` attribute of a network
# GET /networks
# GET /networks/{id}
@@ -982,12 +974,12 @@
# Update a network
# PUT /networks/{id}
# Intended scope(s): project
-#"update_network": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"update_network": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_network":"rule:admin_or_owner" has been deprecated since W
-# in favor of "update_network":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# in favor of "update_network":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The network API now supports system scope and default roles.
# Update ``segments`` attribute of a network
@@ -1067,24 +1059,24 @@
# Update ``port_security_enabled`` attribute of a network
# PUT /networks/{id}
# Intended scope(s): project
-#"update_network:port_security_enabled": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"update_network:port_security_enabled": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_network:port_security_enabled":"rule:admin_or_owner" has
# been deprecated since W in favor of
-# "update_network:port_security_enabled":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# "update_network:port_security_enabled":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The network API now supports system scope and default roles.
# Delete a network
# DELETE /networks/{id}
# Intended scope(s): project
-#"delete_network": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"delete_network": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_network":"rule:admin_or_owner" has been deprecated since W
-# in favor of "delete_network":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# in favor of "delete_network":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The network API now supports system scope and default roles.
# Get network IP availability
@@ -1156,12 +1148,12 @@
# Create a port
# POST /ports
# Intended scope(s): project
-#"create_port": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_port": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_port":"rule:regular_user" has been deprecated since W in
-# favor of "create_port":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# favor of "create_port":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The port API now supports project scope and default roles.
# Specify ``device_owner`` attribute when creating a port
@@ -1261,13 +1253,13 @@
# Specify ``binding:vnic_type`` attribute when creating a port
# POST /ports
# Intended scope(s): project
-#"create_port:binding:vnic_type": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_port:binding:vnic_type": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_port:binding:vnic_type":"rule:regular_user" has been
# deprecated since W in favor of
-# "create_port:binding:vnic_type":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# "create_port:binding:vnic_type":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The port API now supports project scope and default roles.
# Specify ``allowed_address_pairs`` attribute when creating a port
@@ -1548,25 +1540,28 @@
# Delete a port
# DELETE /ports/{id}
# Intended scope(s): project
-#"delete_port": "rule:admin_only or rule:context_is_advsvc or role:member and project_id:%(project_id)s"
+#"delete_port": "rule:admin_only or rule:context_is_advsvc or role:member and project_id:%(project_id)s or rule:network_owner"
# DEPRECATED
# "delete_port":"rule:context_is_advsvc or
# rule:admin_owner_or_network_owner" has been deprecated since W in
# favor of "delete_port":"rule:admin_only or rule:context_is_advsvc or
-# role:member and project_id:%(project_id)s".
+# role:member and project_id:%(project_id)s or rule:network_owner".
# The port API now supports project scope and default roles.
+# Rule of shared qos policy
+#"shared_qos_policy": "field:policies:shared=True"
+
# Get QoS policies
# GET /qos/policies
# GET /qos/policies/{id}
# Intended scope(s): project
-#"get_policy": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_policy": "(rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared_qos_policy"
# DEPRECATED
# "get_policy":"rule:regular_user" has been deprecated since W in
-# favor of "get_policy":"rule:admin_only or role:reader and
-# project_id:%(project_id)s".
+# favor of "get_policy":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s) or rule:shared_qos_policy".
# The QoS API now supports project scope and default roles.
# Create a QoS policy
@@ -1614,13 +1609,13 @@
# GET /qos/policies/{policy_id}/bandwidth_limit_rules
# GET /qos/policies/{policy_id}/bandwidth_limit_rules/{rule_id}
# Intended scope(s): project
-#"get_policy_bandwidth_limit_rule": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_policy_bandwidth_limit_rule": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_policy_bandwidth_limit_rule":"rule:regular_user" has been
# deprecated since W in favor of
-# "get_policy_bandwidth_limit_rule":"rule:admin_only or role:reader
-# and project_id:%(project_id)s".
+# "get_policy_bandwidth_limit_rule":"(rule:admin_only) or (role:reader
+# and project_id:%(project_id)s)".
# The QoS API now supports project scope and default roles.
# Create a QoS bandwidth limit rule
@@ -1660,7 +1655,7 @@
# GET /qos/policies/{policy_id}/packet_rate_limit_rules
# GET /qos/policies/{policy_id}/packet_rate_limit_rules/{rule_id}
# Intended scope(s): project
-#"get_policy_packet_rate_limit_rule": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_policy_packet_rate_limit_rule": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# Create a QoS packet rate limit rule
# POST /qos/policies/{policy_id}/packet_rate_limit_rules
@@ -1681,13 +1676,13 @@
# GET /qos/policies/{policy_id}/dscp_marking_rules
# GET /qos/policies/{policy_id}/dscp_marking_rules/{rule_id}
# Intended scope(s): project
-#"get_policy_dscp_marking_rule": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_policy_dscp_marking_rule": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_policy_dscp_marking_rule":"rule:regular_user" has been
# deprecated since W in favor of
-# "get_policy_dscp_marking_rule":"rule:admin_only or role:reader and
-# project_id:%(project_id)s".
+# "get_policy_dscp_marking_rule":"(rule:admin_only) or (role:reader
+# and project_id:%(project_id)s)".
# The QoS API now supports project scope and default roles.
# Create a QoS DSCP marking rule
@@ -1727,13 +1722,13 @@
# GET /qos/policies/{policy_id}/minimum_bandwidth_rules
# GET /qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}
# Intended scope(s): project
-#"get_policy_minimum_bandwidth_rule": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_policy_minimum_bandwidth_rule": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_policy_minimum_bandwidth_rule":"rule:regular_user" has been
# deprecated since W in favor of
-# "get_policy_minimum_bandwidth_rule":"rule:admin_only or role:reader
-# and project_id:%(project_id)s".
+# "get_policy_minimum_bandwidth_rule":"(rule:admin_only) or
+# (role:reader and project_id:%(project_id)s)".
# The QoS API now supports project scope and default roles.
# Create a QoS minimum bandwidth rule
@@ -1773,7 +1768,7 @@
# GET /qos/policies/{policy_id}/minimum_packet_rate_rules
# GET /qos/policies/{policy_id}/minimum_packet_rate_rules/{rule_id}
# Intended scope(s): project
-#"get_policy_minimum_packet_rate_rule": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_policy_minimum_packet_rate_rule": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# Create a QoS minimum packet rate rule
# POST /qos/policies/{policy_id}/minimum_packet_rate_rules
@@ -1793,13 +1788,13 @@
# Get a QoS bandwidth limit rule through alias
# GET /qos/alias_bandwidth_limit_rules/{rule_id}/
# Intended scope(s): project
-#"get_alias_bandwidth_limit_rule": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_alias_bandwidth_limit_rule": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_alias_bandwidth_limit_rule":"rule:regular_user" has been
# deprecated since W in favor of
-# "get_alias_bandwidth_limit_rule":"rule:admin_only or role:reader and
-# project_id:%(project_id)s".
+# "get_alias_bandwidth_limit_rule":"(rule:admin_only) or (role:reader
+# and project_id:%(project_id)s)".
# The QoS API now supports project scope and default roles.
# Update a QoS bandwidth limit rule through alias
@@ -1827,13 +1822,13 @@
# Get a QoS DSCP marking rule through alias
# GET /qos/alias_dscp_marking_rules/{rule_id}/
# Intended scope(s): project
-#"get_alias_dscp_marking_rule": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_alias_dscp_marking_rule": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_alias_dscp_marking_rule":"rule:regular_user" has been
# deprecated since W in favor of
-# "get_alias_dscp_marking_rule":"rule:admin_only or role:reader and
-# project_id:%(project_id)s".
+# "get_alias_dscp_marking_rule":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s)".
# The QoS API now supports project scope and default roles.
# Update a QoS DSCP marking rule through alias
@@ -1861,13 +1856,13 @@
# Get a QoS minimum bandwidth rule through alias
# GET /qos/alias_minimum_bandwidth_rules/{rule_id}/
# Intended scope(s): project
-#"get_alias_minimum_bandwidth_rule": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_alias_minimum_bandwidth_rule": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_alias_minimum_bandwidth_rule":"rule:regular_user" has been
# deprecated since W in favor of
-# "get_alias_minimum_bandwidth_rule":"rule:admin_only or role:reader
-# and project_id:%(project_id)s".
+# "get_alias_minimum_bandwidth_rule":"(rule:admin_only) or
+# (role:reader and project_id:%(project_id)s)".
# The QoS API now supports project scope and default roles.
# Update a QoS minimum bandwidth rule through alias
@@ -1944,12 +1939,12 @@
# Create an RBAC policy
# POST /rbac-policies
# Intended scope(s): project
-#"create_rbac_policy": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_rbac_policy": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_rbac_policy":"rule:regular_user" has been deprecated since W
-# in favor of "create_rbac_policy":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# in favor of "create_rbac_policy":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The RBAC API now supports system scope and default roles.
# Specify ``target_tenant`` when creating an RBAC policy
@@ -1968,12 +1963,12 @@
# Update an RBAC policy
# PUT /rbac-policies/{id}
# Intended scope(s): project
-#"update_rbac_policy": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"update_rbac_policy": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_rbac_policy":"rule:admin_or_owner" has been deprecated since
-# W in favor of "update_rbac_policy":"rule:admin_only or role:member
-# and project_id:%(project_id)s".
+# W in favor of "update_rbac_policy":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The RBAC API now supports system scope and default roles.
# Update ``target_tenant`` attribute of an RBAC policy
@@ -1993,34 +1988,34 @@
# GET /rbac-policies
# GET /rbac-policies/{id}
# Intended scope(s): project
-#"get_rbac_policy": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_rbac_policy": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_rbac_policy":"rule:admin_or_owner" has been deprecated since W
-# in favor of "get_rbac_policy":"rule:admin_only or role:reader and
-# project_id:%(project_id)s".
+# in favor of "get_rbac_policy":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s)".
# The RBAC API now supports system scope and default roles.
# Delete an RBAC policy
# DELETE /rbac-policies/{id}
# Intended scope(s): project
-#"delete_rbac_policy": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"delete_rbac_policy": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_rbac_policy":"rule:admin_or_owner" has been deprecated since
-# W in favor of "delete_rbac_policy":"rule:admin_only or role:member
-# and project_id:%(project_id)s".
+# W in favor of "delete_rbac_policy":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The RBAC API now supports system scope and default roles.
# Create a router
# POST /routers
# Intended scope(s): project
-#"create_router": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_router": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_router":"rule:regular_user" has been deprecated since W in
-# favor of "create_router":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# favor of "create_router":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Specify ``distributed`` attribute when creating a router
@@ -2046,26 +2041,26 @@
# Specify ``external_gateway_info`` information when creating a router
# POST /routers
# Intended scope(s): project
-#"create_router:external_gateway_info": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_router:external_gateway_info": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_router:external_gateway_info":"rule:admin_or_owner" has been
# deprecated since W in favor of
-# "create_router:external_gateway_info":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# "create_router:external_gateway_info":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Specify ``network_id`` in ``external_gateway_info`` information when
# creating a router
# POST /routers
# Intended scope(s): project
-#"create_router:external_gateway_info:network_id": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_router:external_gateway_info:network_id": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_router:external_gateway_info:network_id":"rule:admin_or_owne
# r" has been deprecated since W in favor of
-# "create_router:external_gateway_info:network_id":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# "create_router:external_gateway_info:network_id":"(rule:admin_only)
+# or (role:member and project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Specify ``enable_snat`` in ``external_gateway_info`` information
@@ -2096,12 +2091,12 @@
# GET /routers
# GET /routers/{id}
# Intended scope(s): project
-#"get_router": "rule:admin_only or role:reader and project_id:%(project_id)s"
+#"get_router": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_router":"rule:admin_or_owner" has been deprecated since W in
-# favor of "get_router":"rule:admin_only or role:reader and
-# project_id:%(project_id)s".
+# favor of "get_router":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Get ``distributed`` attribute of a router
@@ -2129,12 +2124,12 @@
# Update a router
# PUT /routers/{id}
# Intended scope(s): project
-#"update_router": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"update_router": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_router":"rule:admin_or_owner" has been deprecated since W in
-# favor of "update_router":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# favor of "update_router":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Update ``distributed`` attribute of a router
@@ -2160,26 +2155,26 @@
# Update ``external_gateway_info`` information of a router
# PUT /routers/{id}
# Intended scope(s): project
-#"update_router:external_gateway_info": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"update_router:external_gateway_info": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_router:external_gateway_info":"rule:admin_or_owner" has been
# deprecated since W in favor of
-# "update_router:external_gateway_info":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# "update_router:external_gateway_info":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Update ``network_id`` attribute of ``external_gateway_info``
# information of a router
# PUT /routers/{id}
# Intended scope(s): project
-#"update_router:external_gateway_info:network_id": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"update_router:external_gateway_info:network_id": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_router:external_gateway_info:network_id":"rule:admin_or_owne
# r" has been deprecated since W in favor of
-# "update_router:external_gateway_info:network_id":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# "update_router:external_gateway_info:network_id":"(rule:admin_only)
+# or (role:member and project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Update ``enable_snat`` attribute of ``external_gateway_info``
@@ -2209,56 +2204,56 @@
# Delete a router
# DELETE /routers/{id}
# Intended scope(s): project
-#"delete_router": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"delete_router": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_router":"rule:admin_or_owner" has been deprecated since W in
-# favor of "delete_router":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# favor of "delete_router":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Add an interface to a router
# PUT /routers/{id}/add_router_interface
# Intended scope(s): project
-#"add_router_interface": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"add_router_interface": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "add_router_interface":"rule:admin_or_owner" has been deprecated
-# since W in favor of "add_router_interface":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# since W in favor of "add_router_interface":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Remove an interface from a router
# PUT /routers/{id}/remove_router_interface
# Intended scope(s): project
-#"remove_router_interface": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"remove_router_interface": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "remove_router_interface":"rule:admin_or_owner" has been deprecated
-# since W in favor of "remove_router_interface":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# since W in favor of "remove_router_interface":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Add extra route to a router
# PUT /routers/{id}/add_extraroutes
# Intended scope(s): project
-#"add_extraroutes": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"add_extraroutes": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "add_extraroutes":"rule:admin_or_owner" has been deprecated since
-# Xena in favor of "add_extraroutes":"rule:admin_only or role:member
-# and project_id:%(project_id)s".
+# Xena in favor of "add_extraroutes":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Remove extra route from a router
# PUT /routers/{id}/remove_extraroutes
# Intended scope(s): project
-#"remove_extraroutes": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"remove_extraroutes": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "remove_extraroutes":"rule:admin_or_owner" has been deprecated since
-# Xena in favor of "remove_extraroutes":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# Xena in favor of "remove_extraroutes":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The router API now supports system scope and default roles.
# Rule for admin or security group owner access
@@ -2267,86 +2262,89 @@
# Rule for resource owner, admin or security group owner access
#"admin_owner_or_sg_owner": "rule:owner or rule:admin_or_sg_owner"
+# Definition of a shared security group
+#"shared_security_group": "field:security_groups:shared=True"
+
# Create a security group
# POST /security-groups
# Intended scope(s): project
-#"create_security_group": "role:member and project_id:%(project_id)s"
+#"create_security_group": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_security_group":"rule:admin_or_owner" has been deprecated
-# since W in favor of "create_security_group":"role:member and
-# project_id:%(project_id)s".
+# since W in favor of "create_security_group":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The security group API now supports system scope and default roles.
# Get a security group
# GET /security-groups
# GET /security-groups/{id}
# Intended scope(s): project
-#"get_security_group": "role:reader and project_id:%(project_id)s"
+#"get_security_group": "(rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared_security_group"
# DEPRECATED
# "get_security_group":"rule:regular_user" has been deprecated since W
-# in favor of "get_security_group":"role:reader and
-# project_id:%(project_id)s".
+# in favor of "get_security_group":"(rule:admin_only) or (role:reader
+# and project_id:%(project_id)s) or rule:shared_security_group".
# The security group API now supports system scope and default roles.
# Update a security group
# PUT /security-groups/{id}
# Intended scope(s): project
-#"update_security_group": "role:member and project_id:%(project_id)s"
+#"update_security_group": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_security_group":"rule:admin_or_owner" has been deprecated
-# since W in favor of "update_security_group":"role:member and
-# project_id:%(project_id)s".
+# since W in favor of "update_security_group":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The security group API now supports system scope and default roles.
# Delete a security group
# DELETE /security-groups/{id}
# Intended scope(s): project
-#"delete_security_group": "role:member and project_id:%(project_id)s"
+#"delete_security_group": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_security_group":"rule:admin_or_owner" has been deprecated
-# since W in favor of "delete_security_group":"role:member and
-# project_id:%(project_id)s".
+# since W in favor of "delete_security_group":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The security group API now supports system scope and default roles.
# Create a security group rule
# POST /security-group-rules
# Intended scope(s): project
-#"create_security_group_rule": "role:member and project_id:%(project_id)s"
+#"create_security_group_rule": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_security_group_rule":"rule:admin_or_owner" has been
# deprecated since W in favor of
-# "create_security_group_rule":"role:member and
-# project_id:%(project_id)s".
+# "create_security_group_rule":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The security group API now supports system scope and default roles.
# Get a security group rule
# GET /security-group-rules
# GET /security-group-rules/{id}
# Intended scope(s): project
-#"get_security_group_rule": "role:reader and project_id:%(project_id)s or rule:sg_owner"
+#"get_security_group_rule": "(rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:sg_owner"
# DEPRECATED
# "get_security_group_rule":"rule:admin_owner_or_sg_owner" has been
# deprecated since W in favor of
-# "get_security_group_rule":"role:reader and project_id:%(project_id)s
-# or rule:sg_owner".
+# "get_security_group_rule":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s) or rule:sg_owner".
# The security group API now supports system scope and default roles.
# Delete a security group rule
# DELETE /security-group-rules/{id}
# Intended scope(s): project
-#"delete_security_group_rule": "role:member and project_id:%(project_id)s"
+#"delete_security_group_rule": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_security_group_rule":"rule:admin_or_owner" has been
# deprecated since W in favor of
-# "delete_security_group_rule":"role:member and
-# project_id:%(project_id)s".
+# "delete_security_group_rule":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The security group API now supports system scope and default roles.
# Create a segment
@@ -2404,12 +2402,12 @@
# Create a subnet
# POST /subnets
# Intended scope(s): project
-#"create_subnet": "rule:admin_only or role:member and project_id:%(project_id)s or rule:network_owner"
+#"create_subnet": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:network_owner"
# DEPRECATED
# "create_subnet":"rule:admin_or_network_owner" has been deprecated
-# since W in favor of "create_subnet":"rule:admin_only or role:member
-# and project_id:%(project_id)s or rule:network_owner".
+# since W in favor of "create_subnet":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s) or rule:network_owner".
# The subnet API now supports system scope and default roles.
# Specify ``segment_id`` attribute when creating a subnet
@@ -2436,12 +2434,12 @@
# GET /subnets
# GET /subnets/{id}
# Intended scope(s): project
-#"get_subnet": "rule:admin_only or role:reader and project_id:%(project_id)s or rule:shared"
+#"get_subnet": "(rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared"
# DEPRECATED
# "get_subnet":"rule:admin_or_owner or rule:shared" has been
-# deprecated since W in favor of "get_subnet":"rule:admin_only or
-# role:reader and project_id:%(project_id)s or rule:shared".
+# deprecated since W in favor of "get_subnet":"(rule:admin_only) or
+# (role:reader and project_id:%(project_id)s) or rule:shared".
# The subnet API now supports system scope and default roles.
# Get ``segment_id`` attribute of a subnet
@@ -2458,12 +2456,12 @@
# Update a subnet
# PUT /subnets/{id}
# Intended scope(s): project
-#"update_subnet": "rule:admin_only or role:member and project_id:%(project_id)s or rule:network_owner"
+#"update_subnet": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:network_owner"
# DEPRECATED
# "update_subnet":"rule:admin_or_network_owner" has been deprecated
-# since W in favor of "update_subnet":"rule:admin_only or role:member
-# and project_id:%(project_id)s or rule:network_owner".
+# since W in favor of "update_subnet":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s) or rule:network_owner".
# The subnet API now supports system scope and default roles.
# Update ``segment_id`` attribute of a subnet
@@ -2489,12 +2487,12 @@
# Delete a subnet
# DELETE /subnets/{id}
# Intended scope(s): project
-#"delete_subnet": "rule:admin_only or role:member and project_id:%(project_id)s or rule:network_owner"
+#"delete_subnet": "(rule:admin_only) or (role:member and project_id:%(project_id)s) or rule:network_owner"
# DEPRECATED
# "delete_subnet":"rule:admin_or_network_owner" has been deprecated
-# since W in favor of "delete_subnet":"rule:admin_only or role:member
-# and project_id:%(project_id)s or rule:network_owner".
+# since W in favor of "delete_subnet":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s) or rule:network_owner".
# The subnet API now supports system scope and default roles.
# Definition of a shared subnetpool
@@ -2503,12 +2501,12 @@
# Create a subnetpool
# POST /subnetpools
# Intended scope(s): project
-#"create_subnetpool": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"create_subnetpool": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_subnetpool":"rule:regular_user" has been deprecated since W
-# in favor of "create_subnetpool":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# in favor of "create_subnetpool":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The subnet pool API now supports system scope and default roles.
# Create a shared subnetpool
@@ -2536,24 +2534,24 @@
# GET /subnetpools
# GET /subnetpools/{id}
# Intended scope(s): project
-#"get_subnetpool": "rule:admin_only or role:reader and project_id:%(project_id)s or rule:shared_subnetpools"
+#"get_subnetpool": "(rule:admin_only) or (role:reader and project_id:%(project_id)s) or rule:shared_subnetpools"
# DEPRECATED
# "get_subnetpool":"rule:admin_or_owner or rule:shared_subnetpools"
# has been deprecated since W in favor of
-# "get_subnetpool":"rule:admin_only or role:reader and
-# project_id:%(project_id)s or rule:shared_subnetpools".
+# "get_subnetpool":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s) or rule:shared_subnetpools".
# The subnet pool API now supports system scope and default roles.
# Update a subnetpool
# PUT /subnetpools/{id}
# Intended scope(s): project
-#"update_subnetpool": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"update_subnetpool": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_subnetpool":"rule:admin_or_owner" has been deprecated since
-# W in favor of "update_subnetpool":"rule:admin_only or role:member
-# and project_id:%(project_id)s".
+# W in favor of "update_subnetpool":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The subnet pool API now supports system scope and default roles.
# Update ``is_default`` attribute of a subnetpool
@@ -2570,116 +2568,122 @@
# Delete a subnetpool
# DELETE /subnetpools/{id}
# Intended scope(s): project
-#"delete_subnetpool": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"delete_subnetpool": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_subnetpool":"rule:admin_or_owner" has been deprecated since
-# W in favor of "delete_subnetpool":"rule:admin_only or role:member
-# and project_id:%(project_id)s".
+# W in favor of "delete_subnetpool":"(rule:admin_only) or (role:member
+# and project_id:%(project_id)s)".
# The subnet pool API now supports system scope and default roles.
# Onboard existing subnet into a subnetpool
# PUT /subnetpools/{id}/onboard_network_subnets
# Intended scope(s): project
-#"onboard_network_subnets": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"onboard_network_subnets": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "onboard_network_subnets":"rule:admin_or_owner" has been deprecated
-# since W in favor of "onboard_network_subnets":"rule:admin_only or
-# role:member and project_id:%(project_id)s".
+# since W in favor of "onboard_network_subnets":"(rule:admin_only) or
+# (role:member and project_id:%(project_id)s)".
# The subnet pool API now supports system scope and default roles.
# Add prefixes to a subnetpool
# PUT /subnetpools/{id}/add_prefixes
# Intended scope(s): project
-#"add_prefixes": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"add_prefixes": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "add_prefixes":"rule:admin_or_owner" has been deprecated since W in
-# favor of "add_prefixes":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# favor of "add_prefixes":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The subnet pool API now supports system scope and default roles.
# Remove unallocated prefixes from a subnetpool
# PUT /subnetpools/{id}/remove_prefixes
# Intended scope(s): project
-#"remove_prefixes": "rule:admin_only or role:member and project_id:%(project_id)s"
+#"remove_prefixes": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "remove_prefixes":"rule:admin_or_owner" has been deprecated since W
-# in favor of "remove_prefixes":"rule:admin_only or role:member and
-# project_id:%(project_id)s".
+# in favor of "remove_prefixes":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The subnet pool API now supports system scope and default roles.
# Create a trunk
# POST /trunks
# Intended scope(s): project
-#"create_trunk": "role:member and project_id:%(project_id)s"
+#"create_trunk": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "create_trunk":"rule:regular_user" has been deprecated since W in
-# favor of "create_trunk":"role:member and project_id:%(project_id)s".
+# favor of "create_trunk":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The trunks API now supports system scope and default roles.
# Get a trunk
# GET /trunks
# GET /trunks/{id}
# Intended scope(s): project
-#"get_trunk": "role:reader and project_id:%(project_id)s"
+#"get_trunk": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_trunk":"rule:admin_or_owner" has been deprecated since W in
-# favor of "get_trunk":"role:reader and project_id:%(project_id)s".
+# favor of "get_trunk":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s)".
# The trunks API now supports system scope and default roles.
# Update a trunk
# PUT /trunks/{id}
# Intended scope(s): project
-#"update_trunk": "role:member and project_id:%(project_id)s"
+#"update_trunk": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "update_trunk":"rule:admin_or_owner" has been deprecated since W in
-# favor of "update_trunk":"role:member and project_id:%(project_id)s".
+# favor of "update_trunk":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The trunks API now supports system scope and default roles.
# Delete a trunk
# DELETE /trunks/{id}
# Intended scope(s): project
-#"delete_trunk": "role:member and project_id:%(project_id)s"
+#"delete_trunk": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "delete_trunk":"rule:admin_or_owner" has been deprecated since W in
-# favor of "delete_trunk":"role:member and project_id:%(project_id)s".
+# favor of "delete_trunk":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The trunks API now supports system scope and default roles.
# List subports attached to a trunk
# GET /trunks/{id}/get_subports
# Intended scope(s): project
-#"get_subports": "role:reader and project_id:%(project_id)s"
+#"get_subports": "(rule:admin_only) or (role:reader and project_id:%(project_id)s)"
# DEPRECATED
# "get_subports":"rule:regular_user" has been deprecated since W in
-# favor of "get_subports":"role:reader and project_id:%(project_id)s".
+# favor of "get_subports":"(rule:admin_only) or (role:reader and
+# project_id:%(project_id)s)".
# The trunks API now supports system scope and default roles.
# Add subports to a trunk
# PUT /trunks/{id}/add_subports
# Intended scope(s): project
-#"add_subports": "role:member and project_id:%(project_id)s"
+#"add_subports": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "add_subports":"rule:admin_or_owner" has been deprecated since W in
-# favor of "add_subports":"role:member and project_id:%(project_id)s".
+# favor of "add_subports":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The trunks API now supports system scope and default roles.
# Delete subports from a trunk
# PUT /trunks/{id}/remove_subports
# Intended scope(s): project
-#"remove_subports": "role:member and project_id:%(project_id)s"
+#"remove_subports": "(rule:admin_only) or (role:member and project_id:%(project_id)s)"
# DEPRECATED
# "remove_subports":"rule:admin_or_owner" has been deprecated since W
-# in favor of "remove_subports":"role:member and
-# project_id:%(project_id)s".
+# in favor of "remove_subports":"(rule:admin_only) or (role:member and
+# project_id:%(project_id)s)".
# The trunks API now supports system scope and default roles.
diff --git a/openstack_dashboard/conf/nova_policy.yaml b/openstack_dashboard/conf/nova_policy.yaml
index 824854b54..46868868f 100644
--- a/openstack_dashboard/conf/nova_policy.yaml
+++ b/openstack_dashboard/conf/nova_policy.yaml
@@ -2119,12 +2119,12 @@
# This API is proxy calls to the Network service. This is deprecated.
# GET /os-tenant-networks
# Intended scope(s): project
-#"os_compute_api:os-tenant-networks:list": "rule:project_reader_api"
+#"os_compute_api:os-tenant-networks:list": "rule:project_reader_or_admin"
# DEPRECATED
# "os_compute_api:os-tenant-networks":"rule:admin_or_owner" has been
# deprecated since 22.0.0 in favor of "os_compute_api:os-tenant-
-# networks:list":"rule:project_reader_api".
+# networks:list":"rule:project_reader_or_admin".
# Nova API policies are introducing new default roles with scope_type
# capabilities. Old policies are deprecated and silently going to be
# ignored in nova 23.0.0 release.
@@ -2144,12 +2144,12 @@
# This API is proxy calls to the Network service. This is deprecated.
# GET /os-tenant-networks/{network_id}
# Intended scope(s): project
-#"os_compute_api:os-tenant-networks:show": "rule:project_reader_api"
+#"os_compute_api:os-tenant-networks:show": "rule:project_reader_or_admin"
# DEPRECATED
# "os_compute_api:os-tenant-networks":"rule:admin_or_owner" has been
# deprecated since 22.0.0 in favor of "os_compute_api:os-tenant-
-# networks:show":"rule:project_reader_api".
+# networks:show":"rule:project_reader_or_admin".
# Nova API policies are introducing new default roles with scope_type
# capabilities. Old policies are deprecated and silently going to be
# ignored in nova 23.0.0 release.
diff --git a/openstack_dashboard/dashboards/project/floating_ip_portforwardings/__init__.py b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/__init__.py
diff --git a/openstack_dashboard/dashboards/project/floating_ip_portforwardings/panel.py b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/panel.py
new file mode 100644
index 000000000..08c430fa6
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/panel.py
@@ -0,0 +1,37 @@
+# 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 logging
+
+from django.utils.translation import gettext_lazy as _
+
+import horizon
+
+from openstack_dashboard.api import neutron
+
+LOG = logging.getLogger(__name__)
+
+
+class FloatingIpPortforwardingRules(horizon.Panel):
+ name = _("Floating IP port forwarding rules")
+ slug = 'floating_ip_portforwardings'
+ permissions = ('openstack.services.network',)
+ nav = False
+
+ def allowed(self, context):
+ request = context['request']
+ return (
+ super().allowed(context) and
+ request.user.has_perms(self.permissions) and
+ neutron.is_extension_floating_ip_port_forwarding_supported(
+ request)
+ )
diff --git a/openstack_dashboard/dashboards/project/floating_ip_portforwardings/tables.py b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/tables.py
new file mode 100644
index 000000000..b2f5bed14
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/tables.py
@@ -0,0 +1,194 @@
+# 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 django import shortcuts
+from django.urls import reverse
+from django.utils.http import urlencode
+from django.utils.translation import gettext_lazy as _
+from django.utils.translation import ngettext_lazy
+
+from horizon import tables
+
+from openstack_dashboard import api
+from openstack_dashboard import policy
+
+PROTOCOL_CHOICES = (
+ ("Select a protocol", "Select a protocol"),
+ ("UDP", "UDP"),
+ ("TCP", "TCP"),
+)
+
+
+class CreateFloatingIpPortForwardingRule(tables.LinkAction):
+ name = "create"
+ verbose_name = _("Add floating IP port forwarding rule")
+ classes = ("ajax-modal",)
+ icon = "plus"
+ url = "horizon:project:floating_ip_portforwardings:create"
+ floating_ip_id = None
+
+ def allowed(self, request, fip=None):
+ policy_rules = (("network", "create_floatingip_port_forwarding"),)
+ return policy.check(policy_rules, request)
+
+ def single(self, data_table, request, *args):
+ return shortcuts.redirect(
+ 'horizon:project:floating_ip_portforwardings:show')
+
+ def get_url_params(self, datum=None):
+ return urlencode({"floating_ip_id": self.floating_ip_id})
+
+ def get_link_url(self, datum=None):
+ base_url = reverse(self.url)
+ join = "?".join([base_url, self.get_url_params(datum)])
+ return join
+
+
+class EditFloatingIpPortForwardingRule(CreateFloatingIpPortForwardingRule):
+ name = "edit"
+ verbose_name = _("Edit floating IP port forwarding rule")
+ classes = ("ajax-modal", "btn-edit")
+ url = "horizon:project:floating_ip_portforwardings:edit"
+
+ def allowed(self, request, fip=None):
+ policy_rules = (("network", "update_floatingip_port_forwarding"),)
+ return policy.check(policy_rules, request)
+
+ def get_url_params(self, datum=None):
+ portforwading_id = self.table.get_object_id(datum)
+ return urlencode({"floating_ip_id": self.floating_ip_id,
+ "pfwd_id": portforwading_id})
+
+
+class EditFloatingIpPortForwardingRuleFromAllPanel(
+ EditFloatingIpPortForwardingRule):
+ name = "edit-from-all"
+ url = "horizon:project:floating_ip_portforwardings:editToAll"
+
+ def single(self, data_table, request, *args):
+ return shortcuts.redirect(
+ 'horizon:project:floating_ip_portforwardings:index')
+
+ def get_url_params(self, datum=None):
+ portforwading_id = self.table.get_object_id(datum)
+ return urlencode({"floating_ip_id": datum.floating_ip_id,
+ "pfwd_id": portforwading_id})
+
+
+class DeleteRule(tables.DeleteAction):
+ name = "delete"
+ help_text = _(
+ "This action will delete the "
+ "selected floating IP port forwarding rule(s); "
+ "this process cannot be undone.")
+ floating_ip_id = None
+
+ @staticmethod
+ def action_present(count):
+ return ngettext_lazy(
+ u"Delete Rule",
+ u"Delete Rules",
+ count
+ )
+
+ @staticmethod
+ def action_past(count):
+ return ngettext_lazy(
+ u"Deleted Rule",
+ u"Deleted Rules",
+ count
+ )
+
+ def allowed(self, request, fip=None):
+ policy_rules = (("network", "delete_floatingip_port_forwarding"),)
+ return policy.check(policy_rules, request)
+
+ def action(self, request, obj_id):
+ api.neutron.floating_ip_port_forwarding_delete(request,
+ self.floating_ip_id,
+ obj_id)
+
+
+class DeleteRuleFromAllPanel(DeleteRule):
+ name = "delete-from-all"
+
+ def action(self, request, obj_id):
+ datum = self.table.get_object_by_id(obj_id)
+ api.neutron.floating_ip_port_forwarding_delete(request,
+ datum.floating_ip_id,
+ obj_id)
+
+
+class FloatingIpPortForwardingRulesTable(tables.DataTable):
+ protocol = tables.Column("protocol", verbose_name=_("Protocol"))
+ external_port_range = tables.Column("external_port_range",
+ verbose_name=_("External port"))
+ internal_port_range = tables.Column("internal_port_range",
+ verbose_name=_("Internal port"))
+ internal_ip_address = tables.Column("internal_ip_address",
+ verbose_name=_("Internal IP address"))
+ description = tables.Column("description", verbose_name=_("Description"))
+
+ def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
+ super().__init__(
+ request, data=data, needs_form_wrapper=needs_form_wrapper,
+ **kwargs)
+
+ floating_ip_id = request.GET.get('floating_ip_id')
+
+ for action in self.get_table_actions():
+ action.floating_ip_id = floating_ip_id
+
+ for action in self._meta.row_actions:
+ action.floating_ip_id = floating_ip_id
+
+ def get_object_display(self, datum):
+ return str(datum.internal_ip_address) + ':' + str(
+ datum.internal_port_range)
+
+ class Meta(object):
+ name = "floating_ip_portforwardings"
+ verbose_name = _("Floating IP port forwarding rules")
+ table_actions = (CreateFloatingIpPortForwardingRule, DeleteRule)
+ row_actions = (EditFloatingIpPortForwardingRule, DeleteRule)
+
+
+class AllFloatingIpPortForwardingRulesTable(tables.DataTable):
+ floating_ip_id = tables.Column("floating_ip_id",
+ verbose_name=_("floating_ip_id"),
+ hidden=True)
+ protocol = tables.Column("protocol", verbose_name=_("Protocol"))
+ external_port_range = tables.Column("external_port_range",
+ verbose_name=_("External port"))
+ internal_port_range = tables.Column("internal_port_range",
+ verbose_name=_("Internal port"))
+ external_ip_address = tables.Column("external_ip_address",
+ verbose_name=_("External IP address"))
+ internal_ip_address = tables.Column("internal_ip_address",
+ verbose_name=_("Internal IP address"))
+ description = tables.Column("description", verbose_name=_("Description"))
+
+ def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
+ super().__init__(
+ request, data=data, needs_form_wrapper=needs_form_wrapper, **kwargs)
+
+ def get_object_display(self, datum):
+ return str(datum.internal_ip_address) + ':' + str(
+ datum.internal_port_range)
+
+ class Meta(object):
+ name = "floating_ip_portforwardings"
+ verbose_name = _("Floating IP port forwarding rules")
+ table_actions = (DeleteRuleFromAllPanel,)
+ row_actions = (
+ EditFloatingIpPortForwardingRuleFromAllPanel,
+ DeleteRuleFromAllPanel)
diff --git a/openstack_dashboard/dashboards/project/floating_ip_portforwardings/tests.py b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/tests.py
new file mode 100644
index 000000000..f791b428f
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/tests.py
@@ -0,0 +1,262 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+from unittest import mock
+import uuid
+
+from django.urls import reverse
+from django.utils.http import urlencode
+
+from openstack_dashboard import api
+from openstack_dashboard.test import helpers as test
+
+from horizon.tables import views as table_views
+from horizon.workflows import views
+
+INDEX_URL = reverse('horizon:project:floating_ip_portforwardings:index')
+NAMESPACE = "horizon:project:floating_ip_portforwardings"
+
+
+class FloatingIpPortforwardingViewTests(test.TestCase):
+
+ def setUp(self):
+ super().setUp()
+ api_mock = mock.patch.object(
+ api.neutron,
+ 'is_extension_floating_ip_port_forwarding_supported').start()
+ api_mock.return_value = True
+
+ @test.create_mocks({api.neutron: ('tenant_floating_ip_get',
+ 'floating_ip_port_forwarding_list')})
+ def test_floating_ip_portforwarding(self):
+ fip = self._get_fip_targets()[0]
+ self.mock_tenant_floating_ip_get.return_value = fip
+ fip_id = fip.id
+ self.mock_floating_ip_port_forwarding_list.return_value = (
+ fip.port_forwardings)
+
+ params = urlencode({'floating_ip_id': fip_id})
+ url = '?'.join([reverse('%s:show' % NAMESPACE), params])
+ res = self.client.get(url)
+ self.assertTemplateUsed(res, table_views.DataTableView.template_name)
+ table_data = res.context_data['table'].data
+ self.assertEqual(len(table_data), 1)
+ self.assertEqual(fip.port_forwardings[0].id, table_data[0].id)
+
+ @test.create_mocks({api.neutron: ('floating_ip_port_forwarding_list',
+ 'tenant_floating_ip_list')})
+ def test_floating_ip_portforwarding_all(self):
+ fips = self._get_fip_targets()
+ self.mock_tenant_floating_ip_list.return_value = fips
+ fips_dict = {}
+ for f in fips:
+ fips_dict[f.id] = f.port_forwardings
+
+ def pfw_list(request, fip_id):
+ return fips_dict[fip_id]
+
+ self.mock_floating_ip_port_forwarding_list.side_effect = pfw_list
+
+ url = reverse('%s:index' % NAMESPACE)
+
+ res = self.client.get(url)
+ self.assertTemplateUsed(res, table_views.DataTableView.template_name)
+ table_data = res.context_data['table'].data
+ self.assertEqual(len(table_data), len(fips))
+ for pfw in table_data:
+ self.assertIn(pfw.id, list(map(lambda x: x.id,
+ fips_dict[pfw.floating_ip_id])))
+
+ def _get_compute_ports(self):
+ return [p for p in self.ports.list()
+ if not p.device_owner.startswith('network:')]
+
+ def _get_fip_targets(self):
+ server_dict = dict((s.id, s.name) for s in self.servers.list())
+ targets = []
+ port = 10
+ for p in self._get_compute_ports():
+ for ip in p.fixed_ips:
+ targets.append(api.neutron.FloatingIpTarget(
+ p, ip['ip_address'], server_dict.get(p.device_id)))
+ targets[-1].ip = ip['ip_address']
+ targets[-1].port_id = None
+ targets[-1].port_forwardings = [api.neutron.PortForwarding({
+ 'id': str(uuid.uuid4()),
+ 'floating_ip_id': targets[-1].id,
+ 'protocol': 'TCP',
+ 'internal_port_range': str(port),
+ 'external_port_range': str(port + 10),
+ 'internal_ip_address': ip['ip_address'],
+ 'description': '',
+ 'internal_port_id': '',
+ 'external_ip_address': ''}, targets[-1].id)]
+
+ port += 1
+ return targets
+
+ @test.create_mocks({api.neutron: ('tenant_floating_ip_get',
+ 'floating_ip_port_forwarding_list',
+ 'floating_ip_target_list')})
+ def test_create_floating_ip_portforwarding(self):
+ fip = self._get_fip_targets()[0]
+ self.mock_tenant_floating_ip_get.return_value = fip
+ fip_id = fip.id
+ self.mock_floating_ip_port_forwarding_list.return_value = (
+ fip.port_forwardings)
+ self.mock_floating_ip_target_list.return_value = [fip]
+
+ params = urlencode({'floating_ip_id': fip_id})
+ url = '?'.join([reverse('%s:create' % NAMESPACE), params])
+ res = self.client.get(url)
+ self.assertTemplateUsed(res, views.WorkflowView.template_name)
+ workflow = res.context['workflow']
+ choices = dict(
+ workflow.steps[0].action.fields[
+ 'internal_ip_address'].choices)
+ choices.pop('Select an IP-Address')
+
+ self.assertEqual({fip.id}, set(choices.keys()))
+
+ @test.create_mocks({api.neutron: ('tenant_floating_ip_get',
+ 'floating_ip_port_forwarding_list',
+ 'floating_ip_port_forwarding_create',
+ 'floating_ip_target_list')})
+ def test_create_floating_ip_portforwarding_post(self):
+ fip = self._get_fip_targets()[0]
+ self.mock_tenant_floating_ip_get.return_value = fip
+ fip_id = fip.id
+ self.mock_floating_ip_port_forwarding_list.return_value = (
+ fip.port_forwardings)
+ self.mock_floating_ip_target_list.return_value = [fip]
+
+ create_mock = self.mock_floating_ip_port_forwarding_create
+
+ params = urlencode({'floating_ip_id': fip_id})
+ url = '?'.join([reverse('%s:create' % NAMESPACE), params])
+ port = self.ports.get(id=fip.id.split('_')[0])
+ internal_ip = '%s_%s' % (port.id, port.fixed_ips[0]['ip_address'])
+ post_params = {
+ 'floating_ip_id': fip_id,
+ 'description': 'test',
+ 'internal_port': '10',
+ 'protocol': 'TCP',
+ 'internal_ip_address': internal_ip,
+ 'external_port': '123',
+ }
+ expected_params = {
+ 'description': 'test',
+ 'internal_port': '10',
+ 'protocol': 'TCP',
+ 'internal_port_id': internal_ip.split('_')[0],
+ 'internal_ip_address': internal_ip.split('_')[1],
+ 'external_port': '123',
+ }
+ self.client.post(url, post_params)
+ create_mock.assert_called_once_with(mock.ANY, fip_id,
+ **expected_params)
+
+ @test.create_mocks({api.neutron: ('tenant_floating_ip_get',
+ 'floating_ip_port_forwarding_list',
+ 'floating_ip_target_list',
+ 'floating_ip_port_forwarding_get')})
+ def test_update_floating_ip_portforwarding(self):
+ fip = self._get_fip_targets()[0]
+ self.mock_tenant_floating_ip_get.return_value = fip
+ fip_id = fip.id
+ self.mock_floating_ip_port_forwarding_list.return_value = (
+ fip.port_forwardings)
+ self.mock_floating_ip_target_list.return_value = [fip]
+ self.mock_floating_ip_port_forwarding_get.return_value = {
+ 'port_forwarding': fip.port_forwardings[0].to_dict()
+ }
+
+ params = urlencode({'floating_ip_id': fip_id,
+ 'pfwd_id': fip.port_forwardings[0]['id']})
+ url = '?'.join([reverse('%s:edit' % NAMESPACE), params])
+ res = self.client.get(url)
+ self.assertTemplateUsed(res, views.WorkflowView.template_name)
+ workflow = res.context['workflow']
+
+ self.assertEqual(workflow.steps[0].action.initial['floating_ip_id'],
+ fip.port_forwardings[0]['floating_ip_id'])
+ self.assertEqual(workflow.steps[0].action.initial['portforwading_id'],
+ fip.port_forwardings[0]['id'])
+ self.assertEqual(workflow.steps[0].action.initial['protocol'],
+ fip.port_forwardings[0]['protocol'])
+ self.assertEqual(workflow.steps[0].action.initial['internal_port'],
+ fip.port_forwardings[0]['internal_port_range'])
+ self.assertEqual(workflow.steps[0].action.initial['external_port'],
+ fip.port_forwardings[0]['external_port_range'])
+ self.assertEqual(workflow.steps[0].action.initial['description'],
+ fip.port_forwardings[0]['description'])
+
+ @test.create_mocks({api.neutron: ('tenant_floating_ip_get',
+ 'floating_ip_port_forwarding_list',
+ 'floating_ip_target_list',
+ 'floating_ip_port_forwarding_update',
+ 'floating_ip_port_forwarding_get')})
+ def test_update_floating_ip_portforwarding_post(self):
+ fip = self._get_fip_targets()[0]
+ self.mock_tenant_floating_ip_get.return_value = fip
+ fip_id = fip.id
+ self.mock_floating_ip_port_forwarding_list.return_value = (
+ fip.port_forwardings)
+ self.mock_floating_ip_target_list.return_value = [fip]
+ self.mock_floating_ip_port_forwarding_get.return_value = {
+ 'port_forwarding': fip.port_forwardings[0].to_dict()
+ }
+ update_mock = self.mock_floating_ip_port_forwarding_update
+ pfw_id = fip.port_forwardings[0]['id']
+ params = urlencode({'floating_ip_id': fip_id,
+ 'pfwd_id': pfw_id})
+ url = '?'.join([reverse('%s:edit' % NAMESPACE), params])
+ port = self.ports.get(id=fip.id.split('_')[0])
+ internal_ip = '%s_%s' % (port.id, port.fixed_ips[0]['ip_address'])
+
+ post_params = {
+ 'portforwading_id': pfw_id,
+ 'floating_ip_id': fip_id,
+ 'description': 'test',
+ 'internal_port': '10',
+ 'protocol': 'TCP',
+ 'internal_ip_address': internal_ip,
+ 'external_port': '123',
+ }
+ expected_params = {
+ 'portforwarding_id': pfw_id,
+ 'description': 'test',
+ 'internal_port': '10',
+ 'protocol': 'TCP',
+ 'internal_port_id': internal_ip.split('_')[0],
+ 'internal_ip_address': internal_ip.split('_')[1],
+ 'external_port': '123',
+ }
+ self.client.post(url, post_params)
+ update_mock.assert_called_once_with(mock.ANY, fip_id,
+ **expected_params)
+
+ @test.create_mocks({api.neutron: ('tenant_floating_ip_get',
+ 'floating_ip_port_forwarding_list',
+ 'floating_ip_port_forwarding_delete')})
+ def test_delete_floating_ip_portforwarding(self):
+ fip = self._get_fip_targets()[0]
+ self.mock_tenant_floating_ip_get.return_value = fip
+ fip_id = fip.id
+ self.mock_floating_ip_port_forwarding_list.return_value = (
+ fip.port_forwardings)
+ deletion_mock = self.mock_floating_ip_port_forwarding_delete
+ pf_id = fip.port_forwardings[0].id
+ params = urlencode({'floating_ip_id': fip_id})
+ url = '?'.join([reverse('%s:show' % NAMESPACE), params])
+ self.client.post(url, {
+ 'action': 'floating_ip_portforwardings__delete__%s' % pf_id})
+ deletion_mock.assert_called_once_with(mock.ANY, fip_id, pf_id)
diff --git a/openstack_dashboard/dashboards/project/floating_ip_portforwardings/urls.py b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/urls.py
new file mode 100644
index 000000000..6905e4a9a
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/urls.py
@@ -0,0 +1,24 @@
+# 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 django.urls import re_path
+
+from openstack_dashboard.dashboards.project.floating_ip_portforwardings import (
+ views)
+
+urlpatterns = [
+ re_path(r'^$', views.AllRulesView.as_view(), name='index'),
+ re_path(r'^show$', views.IndexView.as_view(), name='show'),
+ re_path(r'^create/$', views.CreateView.as_view(), name='create'),
+ re_path(r'^edit/$', views.EditView.as_view(), name='edit'),
+ re_path(r'^editToAll/$', views.EditToAllView.as_view(), name='editToAll')
+]
diff --git a/openstack_dashboard/dashboards/project/floating_ip_portforwardings/views.py b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/views.py
new file mode 100644
index 000000000..dc4a64eb1
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/views.py
@@ -0,0 +1,110 @@
+# 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.
+
+"""
+Views for managing floating IPs port forwardings
+"""
+import logging
+
+from django.utils.translation import gettext_lazy as _
+
+from neutronclient.common import exceptions as neutron_exc
+
+from horizon import exceptions
+from horizon import tables
+from horizon import workflows
+
+from openstack_dashboard import api
+from openstack_dashboard.dashboards.project.floating_ip_portforwardings import (
+ tables as project_tables)
+from openstack_dashboard.dashboards.project.floating_ip_portforwardings import (
+ workflows as project_workflows)
+
+LOG = logging.getLogger(__name__)
+
+
+class CreateView(workflows.WorkflowView):
+ workflow_class = (
+ project_workflows.FloatingIpPortForwardingRuleCreationWorkflow)
+
+
+class EditView(workflows.WorkflowView):
+ workflow_class = project_workflows.FloatingIpPortForwardingRuleEditWorkflow
+
+
+class EditToAllView(workflows.WorkflowView):
+ workflow_class = (
+ project_workflows.FloatingIpPortForwardingRuleEditWorkflowToAll)
+
+
+class IndexView(tables.DataTableView):
+ table_class = project_tables.FloatingIpPortForwardingRulesTable
+ page_title = _("Manage floating IP port forwarding rules")
+
+ def get_data(self):
+ try:
+ floating_ip_id = self.request.GET.get('floating_ip_id')
+ floating_ip = api.neutron.tenant_floating_ip_get(self.request,
+ floating_ip_id)
+ self.page_title = _(
+ "Manage floating IP port forwarding rules : " + str(
+ floating_ip.ip))
+ return self.get_floating_ip_rules(floating_ip)
+ except neutron_exc.ConnectionFailed:
+ exceptions.handle(self.request)
+ except Exception:
+ exceptions.handle(
+ self.request,
+ _('Unable to retrieve floating IP port forwarding rules.'))
+ return []
+
+ def get_floating_ip_rules(self, floating_ip):
+ if floating_ip.port_id:
+ return []
+
+ floating_ip_portforwarding_rules = []
+ external_ip_address = floating_ip.ip
+ floating_ip_id = floating_ip.id
+ port_forwarding_rules = api.neutron.floating_ip_port_forwarding_list(
+ self.request, floating_ip_id)
+
+ for port_forwarding_rule in port_forwarding_rules:
+ setattr(port_forwarding_rule, 'external_ip_address',
+ external_ip_address)
+
+ floating_ip_portforwarding_rules.extend(port_forwarding_rules)
+
+ return floating_ip_portforwarding_rules
+
+
+class AllRulesView(IndexView):
+ table_class = project_tables.AllFloatingIpPortForwardingRulesTable
+
+ def get_data(self):
+ try:
+ return self.get_all_floating_ip_rules()
+ except neutron_exc.ConnectionFailed:
+ exceptions.handle(self.request)
+ except Exception:
+ exceptions.handle(
+ self.request,
+ _('Unable to retrieve floating IP port forwarding rules.'))
+ return []
+
+ def get_all_floating_ip_rules(self):
+ floating_ip_portforwarding_rules = []
+ floating_ips = api.neutron.tenant_floating_ip_list(self.request)
+ for floating_ip in floating_ips:
+ floating_ip_portforwarding_rules.extend(
+ self.get_floating_ip_rules(floating_ip))
+
+ return floating_ip_portforwarding_rules
diff --git a/openstack_dashboard/dashboards/project/floating_ip_portforwardings/workflows.py b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/workflows.py
new file mode 100644
index 000000000..11c7b1b65
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/floating_ip_portforwardings/workflows.py
@@ -0,0 +1,270 @@
+# 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 logging
+
+from django.core.exceptions import ValidationError
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+
+from neutronclient.common import exceptions as neutron_exc
+
+from horizon import exceptions
+from horizon import forms
+from horizon import workflows
+
+from openstack_dashboard import api
+from openstack_dashboard.dashboards.project.floating_ip_portforwardings import (
+ tables as project_tables)
+
+LOG = logging.getLogger(__name__)
+
+
+class CommonMetaData(object):
+ name = _("Description")
+ help_text = _(
+ "Description:"
+ ""
+ "IP floating rules define external specific traffic that is bound "
+ "from a public IP to an internal address of a specific port.\n"
+ "Protocol: The protocol configured for the IP forwarding rule. "
+ "You can choose between TCP and UDP.\n"
+ "External port: The external port of the floating IP that"
+ " will be "
+ "bound to the internal port in the internal address. This field"
+ " allow values "
+ "between 1 and 65535 and also support ranges using the following"
+ " format:\n"
+ "InitialPort:FinalPort where InitialPort <= FinalPort.\n"
+ "Internal port: The internal port of the given internal IP "
+ "address that will be bound to the port that is exposed to the "
+ "internet via the public floating IP. This field allow values "
+ "between 1 and 65535 and also support ranges using the following"
+ " format:\n"
+ "InitialPort:FinalPort where InitialPort <= FinalPort.\n"
+ "Internal IP address: The internal IP address where the "
+ "internal ports will be running.\n"
+ "Description: Describes the reason why this rule is being "
+ "created.")
+
+
+class CreateFloatingIpPortForwardingRuleAction(workflows.Action):
+ protocol = forms.ThemableChoiceField(
+ required=True,
+ choices=project_tables.PROTOCOL_CHOICES,
+ label=_("Protocol"))
+ external_port = forms.CharField(max_length=11, label=_("External port"))
+ internal_port = forms.CharField(max_length=11, label=_("Internal port"))
+ internal_ip_address = forms.ThemableChoiceField(required=True, label=_(
+ "Internal IP address"))
+ description = forms.CharField(required=False, widget=forms.Textarea,
+ max_length=255, label=_("Description"))
+ floating_ip_id = forms.CharField(max_length=255,
+ widget=forms.HiddenInput())
+
+ class Meta(CommonMetaData):
+ pass
+
+ def ignore_validation(self, portforward=None):
+ return False
+
+ def validate_input_selects(self):
+ err_msg = "You must select a%s"
+ internal_ip_address = self.cleaned_data.get('internal_ip_address')
+ protocol = self.cleaned_data.get('protocol')
+
+ if protocol == "Select a protocol":
+ raise ValidationError(message=err_msg % " Protocol.")
+
+ if internal_ip_address in ('Select an IP-Address',
+ 'No ports available'):
+ raise ValidationError(message=err_msg % "n Ip-Address.")
+
+ def clean(self):
+ request = self.request
+ if request.method == "GET":
+ return self.cleaned_data
+
+ self.validate_input_selects()
+
+ return self.cleaned_data
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ floating_ip_id = self.request.GET.get('floating_ip_id')
+ self.initial['floating_ip_id'] = floating_ip_id
+
+ def populate_internal_ip_address_choices(self, request, context):
+ targets = api.neutron.floating_ip_target_list(self.request)
+ instances = sorted([(target.id, target.name) for target in targets],
+ key=lambda x: x[1])
+ if instances:
+ instances.insert(0, ("Select an IP-Address", _(
+ "Select an IP-Address")))
+ else:
+ instances = (("No ports available", _(
+ "No ports available")),)
+ return instances
+
+
+class EditFloatingIpPortForwardingRuleAction(
+ CreateFloatingIpPortForwardingRuleAction):
+ portforwading_id = forms.CharField(max_length=255,
+ widget=forms.HiddenInput())
+ instance_id = None
+
+ class Meta(CommonMetaData):
+ pass
+
+ def ignore_validation(self, portforward=None):
+ return (super().ignore_validation(portforward) or
+ portforward.id == self.cleaned_data.get(
+ 'portforwading_id'))
+
+ def __init__(self, *args, **kwargs):
+ request = args[0]
+ if request.method == 'POST':
+ super().__init__(
+ *args, **kwargs)
+ else:
+ floating_ip_id = request.GET.get('floating_ip_id')
+ port_forwarding_id = request.GET.get('pfwd_id')
+ port_forwarding = api.neutron.floating_ip_port_forwarding_get(
+ request, floating_ip_id, port_forwarding_id)
+ port_forwarding_rule = port_forwarding['port_forwarding']
+ self.instance_id = "%s_%s" % (
+ port_forwarding_rule['internal_port_id'],
+ port_forwarding_rule['internal_ip_address'])
+ super().__init__(
+ *args, **kwargs)
+ self.initial['portforwading_id'] = port_forwarding_id
+ self.initial['protocol'] = str(
+ port_forwarding_rule['protocol']).upper()
+ self.initial['internal_port'] = port_forwarding_rule[
+ 'internal_port_range']
+ self.initial['external_port'] = port_forwarding_rule[
+ 'external_port_range']
+ if 'description' in port_forwarding_rule.keys():
+ self.initial['description'] = port_forwarding_rule[
+ 'description']
+
+ def populate_internal_ip_address_choices(self, request, context):
+ targets = api.neutron.floating_ip_target_list(self.request)
+ instances = sorted([(target.id, target.name) for target in targets],
+ key=lambda x: '0'
+ if x[0] == self.instance_id else x[1])
+ return instances
+
+
+class CreateFloatingIpPortForwardingRule(workflows.Step):
+ action_class = CreateFloatingIpPortForwardingRuleAction
+ contributes = ("internal_port", "protocol", "external_port",
+ "internal_ip_address", "description", "floating_ip_id",
+ "portforwading_id")
+
+ def contribute(self, data, context):
+ context = super().contribute(data, context)
+ return context
+
+
+class EditFloatingIpPortForwardingRule(
+ CreateFloatingIpPortForwardingRule):
+ action_class = EditFloatingIpPortForwardingRuleAction
+
+ def contribute(self, data, context):
+ context = super().contribute(data, context)
+ return context
+
+
+class FloatingIpPortForwardingRuleCreationWorkflow(workflows.Workflow):
+ slug = "floating_ip_port_forwarding_rule_creation"
+ name = _("Add floating IP port forwarding rule")
+ finalize_button_name = _("Add")
+ success_message = _('Floating IP port forwarding rule %s created. '
+ 'It might take a few minutes to apply all rules.')
+ failure_message = _('Unable to create floating IP port forwarding rule'
+ ' %s.')
+ success_url = "horizon:project:floating_ip_portforwardings:show"
+ default_steps = (CreateFloatingIpPortForwardingRule,)
+
+ def format_status_message(self, message):
+ if "%s" in message:
+ return message % self.context.get('ip_address',
+ _('unknown IP address'))
+ return message
+
+ def handle_using_api_method(self, request, data, api_method,
+ **api_params):
+ try:
+ floating_ip_id = data['floating_ip_id']
+ self.success_url = reverse(
+ self.success_url) + "?floating_ip_id=" + str(
+ floating_ip_id)
+ port_id, internal_ip = data['internal_ip_address'].split('_')
+ self.context['ip_address'] = internal_ip
+ param = {}
+ if data['description']:
+ param['description'] = data['description']
+ if data['internal_port']:
+ param['internal_port'] = data['internal_port']
+ if data['external_port']:
+ param['external_port'] = data['external_port']
+ if internal_ip:
+ param['internal_ip_address'] = internal_ip
+ if data['protocol']:
+ param['protocol'] = data['protocol']
+ if port_id:
+ param['internal_port_id'] = port_id
+
+ param.update(**api_params)
+ api_method(request, floating_ip_id, **param)
+
+ except neutron_exc.Conflict as ex:
+ msg = _('The requested instance port is already'
+ ' associated with another floating IP.')
+ LOG.exception(msg, ex)
+ exceptions.handle(request, msg)
+ self.failure_message = msg
+ return False
+
+ except Exception:
+ exceptions.handle(request)
+ return False
+ return True
+
+ def handle(self, request, data):
+ return self.handle_using_api_method(
+ request, data, api.neutron.floating_ip_port_forwarding_create)
+
+
+class FloatingIpPortForwardingRuleEditWorkflow(
+ FloatingIpPortForwardingRuleCreationWorkflow):
+ slug = "floating_ip_port_forwarding_rule_edit"
+ name = _("Edit floating IP port forwarding rule")
+ finalize_button_name = _("Update")
+ success_message = _('Floating IP port forwarding rule %s updated. '
+ 'It might take a few minutes to apply all rules.')
+ failure_message = _('Unable to updated floating IP port forwarding'
+ ' rule %s.')
+ success_url = "horizon:project:floating_ip_portforwardings:show"
+ default_steps = (EditFloatingIpPortForwardingRule,)
+
+ def handle(self, request, data):
+ return self.handle_using_api_method(
+ request, data, api.neutron.floating_ip_port_forwarding_update,
+ portforwarding_id=data['portforwading_id'])
+
+
+class FloatingIpPortForwardingRuleEditWorkflowToAll(
+ FloatingIpPortForwardingRuleEditWorkflow):
+ slug = "floating_ip_port_forwarding_rule_edit_all"
+ success_url = "horizon:project:floating_ip_portforwardings:index"
diff --git a/openstack_dashboard/dashboards/project/floating_ips/tables.py b/openstack_dashboard/dashboards/project/floating_ips/tables.py
index 1fa3d579e..dbaedc08c 100644
--- a/openstack_dashboard/dashboards/project/floating_ips/tables.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/tables.py
@@ -90,12 +90,60 @@ class ReleaseIPs(tables.BatchAction):
def allowed(self, request, fip=None):
policy_rules = (("network", "delete_floatingip"),)
- return policy.check(policy_rules, request)
+
+ port_forwarding_occurrence = 0
+
+ if fip:
+ pwds = fip.port_forwardings
+ port_forwarding_occurrence = len(pwds)
+
+ return port_forwarding_occurrence == 0 and policy.check(policy_rules,
+ request)
def action(self, request, obj_id):
api.neutron.tenant_floating_ip_release(request, obj_id)
+class ReleaseIPsPortForwarding(ReleaseIPs):
+ name = "release_floating_ip_portforwarding_rule"
+ help_text = _(
+ "This floating IP has port forwarding rules configured to it."
+ " Therefore,"
+ " you will need to remove all of these rules before being able"
+ " to release it.")
+
+ def __init__(self, **kwargs):
+ attributes = {"title": "Release Floating IP with port forwarding rules",
+ "confirm-button-text": "Edit floating IP port"
+ " forwarding rules"}
+ super().__init__(attrs=attributes, **kwargs)
+
+ @staticmethod
+ def action_past(count):
+ return ngettext_lazy(
+ u"Successfully redirected",
+ u"Successfully redirected",
+ count
+ )
+
+ def allowed(self, request, fip=None):
+
+ policy_rules = (("network", "delete_floatingip_port_forwarding"),)
+ pwds = fip.port_forwardings
+ return (
+ len(pwds) > 0 and
+ policy.check(policy_rules, request) and
+ api.neutron.is_extension_floating_ip_port_forwarding_supported(
+ request)
+ )
+
+ def action(self, request, obj_id):
+ self.success_url = reverse(
+ 'horizon:project:floating_ip_portforwardings:show') \
+ + '?floating_ip_id=' \
+ + str(obj_id)
+
+
class AssociateIP(tables.LinkAction):
name = "associate"
verbose_name = _("Associate")
@@ -105,7 +153,9 @@ class AssociateIP(tables.LinkAction):
def allowed(self, request, fip):
policy_rules = (("network", "update_floatingip"),)
- return not fip.port_id and policy.check(policy_rules, request)
+ pwds = fip.port_forwardings
+ return len(pwds) == 0 and not fip.port_id and policy.check(policy_rules,
+ request)
def get_link_url(self, datum):
base_url = reverse(self.url)
@@ -113,6 +163,59 @@ class AssociateIP(tables.LinkAction):
return "?".join([base_url, params])
+class ListAllFloatingIpPortForwardingRules(tables.LinkAction):
+ name = "List floating_ip_portforwardings_rules"
+ verbose_name = _("List all floating IP port forwarding rules")
+ url = "horizon:project:floating_ip_portforwardings:index"
+ classes = ("btn-edit",)
+ icon = "link"
+
+ def exists_floating_ip_with_port_forwarding_rules_configurable(self,
+ request):
+ floating_ips = api.neutron.tenant_floating_ip_list(request)
+ for floating_ip in floating_ips:
+ if not floating_ip.port_id:
+ return True
+
+ return False
+
+ def allowed(self, request, fip):
+ policy_rules = (("network", "get_floatingip_port_forwarding"),)
+ return (self.exists_floating_ip_with_port_forwarding_rules_configurable(
+ request) and policy.check(policy_rules, request) and
+ api.neutron.is_extension_floating_ip_port_forwarding_supported(
+ request))
+
+
+class ConfigureFloatingIpPortForwarding(tables.Action):
+ name = "configure_floating_ip_portforwarding_rules"
+ verbose_name = _("Configure floating IP port forwarding rules")
+ classes = ("btn-edit",)
+ icon = "link"
+
+ def allowed(self, request, fip):
+ policy_rules = (("network", "get_floatingip_port_forwarding"),)
+ return (
+ not fip.port_id and
+ policy.check(policy_rules, request) and
+ api.neutron.is_extension_floating_ip_port_forwarding_supported(
+ request)
+ )
+
+ def single(self, table, request, obj_id):
+ fip = {}
+ try:
+ fip = table.get_object_by_id(filters.get_int_or_uuid(obj_id))
+ except Exception as ex:
+ err_msg = 'Unable to find a floating IP.'
+ LOG.debug(err_msg, ex)
+ exceptions.handle(request,
+ _('Unable to find a floating IP.'))
+ return shortcuts.redirect(
+ reverse('horizon:project:floating_ip_portforwardings:show') +
+ '?floating_ip_id=' + str(fip.id))
+
+
class DisassociateIP(tables.Action):
name = "disassociate"
verbose_name = _("Disassociate")
@@ -163,7 +266,6 @@ STATUS_DISPLAY_CHOICES = (
("error", pgettext_lazy("Current status of a Floating IP", "Error")),
)
-
FLOATING_IPS_FILTER_CHOICES = (
('floating_ip_address', _('Floating IP Address ='), True),
('network_id', _('Network ID ='), True),
@@ -224,5 +326,9 @@ class FloatingIPsTable(tables.DataTable):
class Meta(object):
name = "floating_ips"
verbose_name = _("Floating IPs")
- table_actions = (AllocateIP, ReleaseIPs, FloatingIPsFilterAction)
- row_actions = (AssociateIP, DisassociateIP, ReleaseIPs)
+ table_actions = (
+ ListAllFloatingIpPortForwardingRules, AllocateIP, ReleaseIPs,
+ FloatingIPsFilterAction)
+ row_actions = (AssociateIP, DisassociateIP, ReleaseIPs,
+ ReleaseIPsPortForwarding,
+ ConfigureFloatingIpPortForwarding)
diff --git a/openstack_dashboard/dashboards/project/floating_ips/tests.py b/openstack_dashboard/dashboards/project/floating_ips/tests.py
index 8c94c1f1f..ecaff9048 100644
--- a/openstack_dashboard/dashboards/project/floating_ips/tests.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/tests.py
@@ -35,6 +35,13 @@ NAMESPACE = "horizon:project:floating_ips"
class FloatingIpViewTests(test.TestCase):
+ def setUp(self):
+ super().setUp()
+ api_mock = mock.patch.object(
+ api.neutron,
+ 'is_extension_floating_ip_port_forwarding_supported').start()
+ api_mock.return_value = True
+
@test.create_mocks({api.neutron: ('floating_ip_target_list',
'tenant_floating_ip_list')})
def test_associate(self):
@@ -42,7 +49,6 @@ class FloatingIpViewTests(test.TestCase):
self._get_fip_targets()
self.mock_tenant_floating_ip_list.return_value = \
self.floating_ips.list()
-
url = reverse('%s:associate' % NAMESPACE)
res = self.client.get(url)
self.assertTemplateUsed(res, views.WorkflowView.template_name)
@@ -91,6 +97,7 @@ class FloatingIpViewTests(test.TestCase):
for ip in p.fixed_ips:
targets.append(api.neutron.FloatingIpTarget(
p, ip['ip_address'], server_dict.get(p.device_id)))
+ targets[-1].port_forwardings = []
return targets
@staticmethod
@@ -213,12 +220,14 @@ class FloatingIpViewTests(test.TestCase):
@test.create_mocks({api.nova: ('server_list',),
api.neutron: ('floating_ip_disassociate',
'floating_ip_pools_list',
+ 'floating_ip_port_forwarding_list',
'is_extension_supported',
'tenant_floating_ip_list')})
def test_disassociate_post(self):
floating_ip = self.floating_ips.first()
self.mock_is_extension_supported.return_value = False
+ self.mock_floating_ip_port_forwarding_list.return_value = []
self.mock_server_list.return_value = [self.servers.list(), False]
self.mock_tenant_floating_ip_list.return_value = \
self.floating_ips.list()
@@ -243,6 +252,7 @@ class FloatingIpViewTests(test.TestCase):
@test.create_mocks({api.nova: ('server_list',),
api.neutron: ('floating_ip_disassociate',
+ 'floating_ip_port_forwarding_list',
'floating_ip_pools_list',
'is_extension_supported',
'tenant_floating_ip_list')})
@@ -250,6 +260,7 @@ class FloatingIpViewTests(test.TestCase):
floating_ip = self.floating_ips.first()
self.mock_is_extension_supported.return_value = False
+ self.mock_floating_ip_port_forwarding_list.return_value = []
self.mock_server_list.return_value = [self.servers.list(), False]
self.mock_tenant_floating_ip_list.return_value = \
self.floating_ips.list()
@@ -273,6 +284,7 @@ class FloatingIpViewTests(test.TestCase):
@test.create_mocks({api.neutron: ('tenant_floating_ip_list',
'is_extension_supported',
+ 'floating_ip_port_forwarding_list',
'floating_ip_pools_list'),
api.nova: ('server_list',),
quotas: ('tenant_quota_usages',)})
@@ -283,6 +295,7 @@ class FloatingIpViewTests(test.TestCase):
self.mock_is_extension_supported.return_value = False
self.mock_tenant_floating_ip_list.return_value = floating_ips
+ self.mock_floating_ip_port_forwarding_list.return_value = []
self.mock_floating_ip_pools_list.return_value = floating_pools
self.mock_server_list.return_value = [self.servers.list(), False]
self.mock_tenant_quota_usages.return_value = quota_data
@@ -298,9 +311,9 @@ class FloatingIpViewTests(test.TestCase):
url = 'horizon:project:floating_ips:allocate'
self.assertEqual(url, allocate_action.url)
- self.mock_tenant_floating_ip_list.assert_called_once_with(
+ self.mock_tenant_floating_ip_list.assert_called_with(
test.IsHttpRequest())
- self.mock_floating_ip_pools_list.assert_called_once_with(
+ self.mock_floating_ip_pools_list.assert_called_with(
test.IsHttpRequest())
self.mock_server_list.assert_called_once_with(test.IsHttpRequest(),
detailed=False)
@@ -313,6 +326,7 @@ class FloatingIpViewTests(test.TestCase):
@test.create_mocks({api.neutron: ('tenant_floating_ip_list',
'is_extension_supported',
+ 'floating_ip_port_forwarding_list',
'floating_ip_pools_list'),
api.nova: ('server_list',),
quotas: ('tenant_quota_usages',)})
@@ -324,6 +338,7 @@ class FloatingIpViewTests(test.TestCase):
self.mock_is_extension_supported.return_value = False
self.mock_tenant_floating_ip_list.return_value = floating_ips
+ self.mock_floating_ip_port_forwarding_list.return_value = []
self.mock_floating_ip_pools_list.return_value = floating_pools
self.mock_server_list.return_value = [self.servers.list(), False]
self.mock_tenant_quota_usages.return_value = quota_data
@@ -337,9 +352,9 @@ class FloatingIpViewTests(test.TestCase):
self.assertEqual('Allocate IP To Project (Quota exceeded)',
allocate_action.verbose_name)
- self.mock_tenant_floating_ip_list.assert_called_once_with(
+ self.mock_tenant_floating_ip_list.assert_called_with(
test.IsHttpRequest())
- self.mock_floating_ip_pools_list.assert_called_once_with(
+ self.mock_floating_ip_pools_list.assert_called_with(
test.IsHttpRequest())
self.mock_server_list.assert_called_once_with(test.IsHttpRequest(),
detailed=False)
diff --git a/openstack_dashboard/dashboards/project/floating_ips/views.py b/openstack_dashboard/dashboards/project/floating_ips/views.py
index 879d4c5b5..e211181cd 100644
--- a/openstack_dashboard/dashboards/project/floating_ips/views.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/views.py
@@ -20,6 +20,7 @@
"""
Views for managing floating IPs.
"""
+import logging
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
@@ -41,6 +42,8 @@ from openstack_dashboard.dashboards.project.floating_ips \
from openstack_dashboard.dashboards.project.floating_ips \
import workflows as project_workflows
+LOG = logging.getLogger(__name__)
+
class AssociateView(workflows.WorkflowView):
workflow_class = project_workflows.IPAssociationWorkflow
@@ -129,8 +132,20 @@ class IndexView(tables.DataTableView):
instances_dict = dict((obj.id, obj.name) for obj in instances)
+ fip_pfw_enabled = (
+ api.neutron.is_extension_floating_ip_port_forwarding_supported(
+ self.request))
+
for ip in floating_ips:
ip.instance_name = instances_dict.get(ip.instance_id)
ip.pool_name = pool_dict.get(ip.pool, ip.pool)
+ if fip_pfw_enabled:
+ try:
+ pfws = api.neutron.floating_ip_port_forwarding_list(
+ self.request, ip.id)
+ ip.port_forwardings = pfws
+ except Exception as e:
+ LOG.info("Error fetching port forwardings for floating IP"
+ " %s: %s", ip.id, e)
return floating_ips
diff --git a/openstack_dashboard/dashboards/project/floating_ips/workflows.py b/openstack_dashboard/dashboards/project/floating_ips/workflows.py
index 5defa54d1..dcc13fcdb 100644
--- a/openstack_dashboard/dashboards/project/floating_ips/workflows.py
+++ b/openstack_dashboard/dashboards/project/floating_ips/workflows.py
@@ -94,7 +94,8 @@ class AssociateIPAction(workflows.Action):
exceptions.handle(self.request,
_('Unable to retrieve floating IP addresses.'),
redirect=redirect)
- options = sorted([(ip.id, ip.ip) for ip in ips if not ip.port_id])
+ options = sorted([(ip.id, ip.ip) for ip in ips if
+ not ip.port_id and len(ip.port_forwardings) == 0])
if options:
options.insert(0, ("", _("Select an IP address")))
else:
@@ -124,7 +125,7 @@ class AssociateIPAction(workflows.Action):
# The reason of specifying an empty tuple when q_instance_id is None
# is to make memoized_method _get_target_list work. Two calls of
# _get_target_list from here and __init__ must have a same arguments.
- params = (q_instance_id, ) if q_instance_id else ()
+ params = (q_instance_id,) if q_instance_id else ()
targets = self._get_target_list(*params)
instances = sorted([(target.id, target.name) for target in targets],
# Sort FIP targets by server name for easy browsing
diff --git a/openstack_dashboard/enabled/_1520_project_floating_ip_portforwardings_panel.py b/openstack_dashboard/enabled/_1520_project_floating_ip_portforwardings_panel.py
new file mode 100644
index 000000000..fc3c08916
--- /dev/null
+++ b/openstack_dashboard/enabled/_1520_project_floating_ip_portforwardings_panel.py
@@ -0,0 +1,7 @@
+PANEL_DASHBOARD = 'project'
+PANEL_GROUP = 'network'
+PANEL = 'floating_ip_portforwardings'
+
+ADD_PANEL = \
+ 'openstack_dashboard.dashboards.project.floating_ip_portforwardings.panel' \
+ '.FloatingIpPortforwardingRules'
diff --git a/openstack_dashboard/locale/as/LC_MESSAGES/django.po b/openstack_dashboard/locale/as/LC_MESSAGES/django.po
index 2ff82a74e..fc5bfb8b8 100644
--- a/openstack_dashboard/locale/as/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/as/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2260,6 +2260,9 @@ msgstr "প্ৰক্ষেপবোৰ:"
msgid "Protected"
msgstr "সুৰক্ষিত"
+msgid "Protocol"
+msgstr "প্ৰটকল"
+
msgid "Provider"
msgstr "আগবঢ়াওতা"
diff --git a/openstack_dashboard/locale/bn_IN/LC_MESSAGES/django.po b/openstack_dashboard/locale/bn_IN/LC_MESSAGES/django.po
index e40e1fbd6..30c25808f 100644
--- a/openstack_dashboard/locale/bn_IN/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/bn_IN/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2155,6 +2155,9 @@ msgstr "প্রোজেক্ট:"
msgid "Protected"
msgstr "সুরক্ষিত"
+msgid "Protocol"
+msgstr "প্রোটোকল"
+
msgid "Provider"
msgstr "সরবরাহকারী"
diff --git a/openstack_dashboard/locale/brx/LC_MESSAGES/django.po b/openstack_dashboard/locale/brx/LC_MESSAGES/django.po
index 5a01c886a..25da40518 100644
--- a/openstack_dashboard/locale/brx/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/brx/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2253,6 +2253,9 @@ msgstr "प्रजेक्ट:"
msgid "Protected"
msgstr "रैखाथि खालामबाय"
+msgid "Protocol"
+msgstr "प्र'ट'कल"
+
msgid "Provider"
msgstr "होग्रा"
diff --git a/openstack_dashboard/locale/cs/LC_MESSAGES/django.po b/openstack_dashboard/locale/cs/LC_MESSAGES/django.po
index 2f825ad23..d4c230625 100644
--- a/openstack_dashboard/locale/cs/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/cs/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-06-21 10:46+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -3891,6 +3891,9 @@ msgstr "Vlastnosti cíle:"
msgid "Protected"
msgstr "Chráněno"
+msgid "Protocol"
+msgstr "Protokol"
+
msgid "Protocol ID"
msgstr "ID protokolu"
diff --git a/openstack_dashboard/locale/de/LC_MESSAGES/django.po b/openstack_dashboard/locale/de/LC_MESSAGES/django.po
index 44d08e212..02a619fca 100644
--- a/openstack_dashboard/locale/de/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/de/LC_MESSAGES/django.po
@@ -19,7 +19,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-11-04 11:42+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4963,6 +4963,9 @@ msgstr "Eigenschaftsziel: "
msgid "Protected"
msgstr "Geschützt"
+msgid "Protocol"
+msgstr "Protokoll"
+
msgid "Protocol ID"
msgstr "Protokoll-ID"
diff --git a/openstack_dashboard/locale/en_AU/LC_MESSAGES/django.po b/openstack_dashboard/locale/en_AU/LC_MESSAGES/django.po
index 612dcdb84..168e4ffa6 100644
--- a/openstack_dashboard/locale/en_AU/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/en_AU/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-06-21 10:46+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4062,6 +4062,9 @@ msgstr "Properties Target: "
msgid "Protected"
msgstr "Protected"
+msgid "Protocol"
+msgstr "Protocol"
+
msgid "Protocol ID"
msgstr "Protocol ID"
diff --git a/openstack_dashboard/locale/en_GB/LC_MESSAGES/django.po b/openstack_dashboard/locale/en_GB/LC_MESSAGES/django.po
index 740213dd4..d0d064f34 100644
--- a/openstack_dashboard/locale/en_GB/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/en_GB/LC_MESSAGES/django.po
@@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2023-01-25 15:43+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4955,6 +4955,9 @@ msgstr "Properties Target: "
msgid "Protected"
msgstr "Protected"
+msgid "Protocol"
+msgstr "Protocol"
+
msgid "Protocol ID"
msgstr "Protocol ID"
diff --git a/openstack_dashboard/locale/es/LC_MESSAGES/django.po b/openstack_dashboard/locale/es/LC_MESSAGES/django.po
index 0dde555aa..685174f88 100644
--- a/openstack_dashboard/locale/es/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/es/LC_MESSAGES/django.po
@@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-11-04 11:42+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -5014,6 +5014,9 @@ msgstr "Propiedades del Objetivo:"
msgid "Protected"
msgstr "Protegida"
+msgid "Protocol"
+msgstr "Protocolo"
+
msgid "Protocol ID"
msgstr "ID de protocolo"
diff --git a/openstack_dashboard/locale/fr/LC_MESSAGES/django.po b/openstack_dashboard/locale/fr/LC_MESSAGES/django.po
index 8dba330e3..140e907b5 100644
--- a/openstack_dashboard/locale/fr/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/fr/LC_MESSAGES/django.po
@@ -27,7 +27,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2023-01-13 13:15+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4773,6 +4773,9 @@ msgstr "Propriétes cible:"
msgid "Protected"
msgstr "Protégée"
+msgid "Protocol"
+msgstr "Protocole"
+
msgid "Protocol ID"
msgstr "ID du protocole"
diff --git a/openstack_dashboard/locale/gu/LC_MESSAGES/django.po b/openstack_dashboard/locale/gu/LC_MESSAGES/django.po
index be115175d..115626aeb 100644
--- a/openstack_dashboard/locale/gu/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/gu/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2247,6 +2247,9 @@ msgstr "પ્રોજેક્ટો:"
msgid "Protected"
msgstr "સંરક્ષિત"
+msgid "Protocol"
+msgstr "પ્રોટોકોલ"
+
msgid "Provider"
msgstr "પૂરુ પાડનાર"
diff --git a/openstack_dashboard/locale/hi/LC_MESSAGES/django.po b/openstack_dashboard/locale/hi/LC_MESSAGES/django.po
index a405e9e5b..62439d1b1 100644
--- a/openstack_dashboard/locale/hi/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/hi/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2164,6 +2164,9 @@ msgstr "परियोजनाएँ:"
msgid "Protected"
msgstr "सुरक्षित"
+msgid "Protocol"
+msgstr "प्रोटोकाल"
+
msgid "Provider"
msgstr "प्रदाता"
diff --git a/openstack_dashboard/locale/id/LC_MESSAGES/django.po b/openstack_dashboard/locale/id/LC_MESSAGES/django.po
index 39fd03f21..521705dd2 100644
--- a/openstack_dashboard/locale/id/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/id/LC_MESSAGES/django.po
@@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-11-04 11:42+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4839,6 +4839,9 @@ msgstr "Properties Target: "
msgid "Protected"
msgstr "Dilindungi"
+msgid "Protocol"
+msgstr "Protocol (protokol)"
+
msgid "Protocol ID"
msgstr "Protocol ID"
diff --git a/openstack_dashboard/locale/it/LC_MESSAGES/django.po b/openstack_dashboard/locale/it/LC_MESSAGES/django.po
index a137fd522..0811ab814 100644
--- a/openstack_dashboard/locale/it/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/it/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -3581,6 +3581,9 @@ msgstr "Destinazione proprietà:"
msgid "Protected"
msgstr "Protetto"
+msgid "Protocol"
+msgstr "Protocollo"
+
msgid "Protocol ID"
msgstr "ID protocollo"
diff --git a/openstack_dashboard/locale/ja/LC_MESSAGES/django.po b/openstack_dashboard/locale/ja/LC_MESSAGES/django.po
index c39527cfc..0efa1ed00 100644
--- a/openstack_dashboard/locale/ja/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/ja/LC_MESSAGES/django.po
@@ -16,7 +16,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2023-02-04 14:47+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4906,6 +4906,9 @@ msgstr "属性ターゲット: "
msgid "Protected"
msgstr "保護"
+msgid "Protocol"
+msgstr "プロトコル"
+
msgid "Protocol ID"
msgstr "プロトコル ID"
diff --git a/openstack_dashboard/locale/kn/LC_MESSAGES/django.po b/openstack_dashboard/locale/kn/LC_MESSAGES/django.po
index 2dd5b3c41..112bff5ae 100644
--- a/openstack_dashboard/locale/kn/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/kn/LC_MESSAGES/django.po
@@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2289,6 +2289,9 @@ msgstr "ಪರಿಯೋಜನೆಗಳು:"
msgid "Protected"
msgstr "ಸಂರಕ್ಷಿತ"
+msgid "Protocol"
+msgstr "ಪ್ರೊಟೊಕಾಲ್"
+
msgid "Provider"
msgstr "ಪೂರೈಕೆದಾರ"
diff --git a/openstack_dashboard/locale/ko_KR/LC_MESSAGES/django.po b/openstack_dashboard/locale/ko_KR/LC_MESSAGES/django.po
index 706a335c5..808175aef 100644
--- a/openstack_dashboard/locale/ko_KR/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/ko_KR/LC_MESSAGES/django.po
@@ -30,7 +30,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-11-04 11:42+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4767,6 +4767,9 @@ msgstr "속성 타깃:"
msgid "Protected"
msgstr "보호됨"
+msgid "Protocol"
+msgstr "프로토콜"
+
msgid "Protocol ID"
msgstr "프로토콜 ID"
diff --git a/openstack_dashboard/locale/kok/LC_MESSAGES/django.po b/openstack_dashboard/locale/kok/LC_MESSAGES/django.po
index 819651990..c82409f4d 100644
--- a/openstack_dashboard/locale/kok/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/kok/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2241,6 +2241,9 @@ msgstr "प्रकल्प:"
msgid "Protected"
msgstr "सुरक्षित"
+msgid "Protocol"
+msgstr "शिष्टाचार"
+
msgid "Provider"
msgstr "पुरोवपी"
diff --git a/openstack_dashboard/locale/ks/LC_MESSAGES/django.po b/openstack_dashboard/locale/ks/LC_MESSAGES/django.po
index 1f37a2962..8e4121060 100644
--- a/openstack_dashboard/locale/ks/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/ks/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2159,6 +2159,9 @@ msgstr "پروجیکٹس:"
msgid "Protected"
msgstr "بَچآویتھ"
+msgid "Protocol"
+msgstr "پروٹوکال"
+
msgid "Provider"
msgstr "پرووایڈر"
diff --git a/openstack_dashboard/locale/mai/LC_MESSAGES/django.po b/openstack_dashboard/locale/mai/LC_MESSAGES/django.po
index 879cae6d5..aafa628f0 100644
--- a/openstack_dashboard/locale/mai/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/mai/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2254,6 +2254,9 @@ msgstr "परियोजना:"
msgid "Protected"
msgstr "सुरक्षित"
+msgid "Protocol"
+msgstr "प्रोटोकाल"
+
msgid "Provider"
msgstr "प्रदाता"
diff --git a/openstack_dashboard/locale/mni/LC_MESSAGES/django.po b/openstack_dashboard/locale/mni/LC_MESSAGES/django.po
index a935f0ed5..31b9de9f2 100644
--- a/openstack_dashboard/locale/mni/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/mni/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2164,6 +2164,9 @@ msgstr "প্রোজেক্তশীং:"
msgid "Protected"
msgstr "প্রোতেক্ত তৌরে"
+msgid "Protocol"
+msgstr "প্রোতোকোল"
+
msgid "Provider"
msgstr "প্রোভাইদর"
diff --git a/openstack_dashboard/locale/mr/LC_MESSAGES/django.po b/openstack_dashboard/locale/mr/LC_MESSAGES/django.po
index 9263cd619..9708b59fc 100644
--- a/openstack_dashboard/locale/mr/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/mr/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2253,6 +2253,9 @@ msgstr "प्रकल्प:"
msgid "Protected"
msgstr "सुरक्षित"
+msgid "Protocol"
+msgstr "प्रोटोकॉल"
+
msgid "Provider"
msgstr "पुरवठादार"
diff --git a/openstack_dashboard/locale/ne/LC_MESSAGES/django.po b/openstack_dashboard/locale/ne/LC_MESSAGES/django.po
index 0581cdaee..9be8ee2c0 100644
--- a/openstack_dashboard/locale/ne/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/ne/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2152,6 +2152,9 @@ msgstr "प्रोडेक्टहरबः"
msgid "Protected"
msgstr "संरक्षित"
+msgid "Protocol"
+msgstr "सन्धिपत्र"
+
msgid "Provider"
msgstr "प्रदाता"
diff --git a/openstack_dashboard/locale/pa_IN/LC_MESSAGES/django.po b/openstack_dashboard/locale/pa_IN/LC_MESSAGES/django.po
index 10d7ba7b7..d8e81323d 100644
--- a/openstack_dashboard/locale/pa_IN/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/pa_IN/LC_MESSAGES/django.po
@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2180,6 +2180,9 @@ msgstr "پروجیكٹ :"
msgid "Protected"
msgstr "محفوظ"
+msgid "Protocol"
+msgstr "پروٹوكول"
+
msgid "Provider"
msgstr "فراہم کنندہ"
diff --git a/openstack_dashboard/locale/pl_PL/LC_MESSAGES/django.po b/openstack_dashboard/locale/pl_PL/LC_MESSAGES/django.po
index fa5fcb75a..0f1642eab 100644
--- a/openstack_dashboard/locale/pl_PL/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/pl_PL/LC_MESSAGES/django.po
@@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -3347,6 +3347,9 @@ msgstr "Cel właściwości:"
msgid "Protected"
msgstr "Chroniony"
+msgid "Protocol"
+msgstr "Protokół"
+
msgid "Provider"
msgstr "Dostawca"
diff --git a/openstack_dashboard/locale/pt_BR/LC_MESSAGES/django.po b/openstack_dashboard/locale/pt_BR/LC_MESSAGES/django.po
index ac89bd3a3..adc912666 100644
--- a/openstack_dashboard/locale/pt_BR/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/pt_BR/LC_MESSAGES/django.po
@@ -17,7 +17,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-11-04 11:42+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4591,6 +4591,9 @@ msgstr "Propriedades dos Alvos:"
msgid "Protected"
msgstr "Protegido"
+msgid "Protocol"
+msgstr "Protocolo"
+
msgid "Protocol ID"
msgstr "ID do Protocolo"
diff --git a/openstack_dashboard/locale/ru/LC_MESSAGES/django.po b/openstack_dashboard/locale/ru/LC_MESSAGES/django.po
index b46174599..e986b403b 100644
--- a/openstack_dashboard/locale/ru/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/ru/LC_MESSAGES/django.po
@@ -41,7 +41,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-11-04 11:42+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -5104,6 +5104,9 @@ msgstr "Цель свойств:"
msgid "Protected"
msgstr "Защищенный"
+msgid "Protocol"
+msgstr "Протокол"
+
msgid "Protocol ID"
msgstr "Идентификатор протокола"
diff --git a/openstack_dashboard/locale/ta/LC_MESSAGES/django.po b/openstack_dashboard/locale/ta/LC_MESSAGES/django.po
index 9a0a3696a..9a447cb55 100644
--- a/openstack_dashboard/locale/ta/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/ta/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2274,6 +2274,9 @@ msgstr "பிராஜக்டுகள்:"
msgid "Protected"
msgstr "பாதுகாக்கப்பட்ட"
+msgid "Protocol"
+msgstr "வரைமுறை:"
+
msgid "Provider"
msgstr "வழங்குநர்"
diff --git a/openstack_dashboard/locale/tr_TR/LC_MESSAGES/django.po b/openstack_dashboard/locale/tr_TR/LC_MESSAGES/django.po
index 8fbf8bd80..c6c783bea 100644
--- a/openstack_dashboard/locale/tr_TR/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/tr_TR/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-06-21 10:46+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4087,6 +4087,9 @@ msgstr "Özellikler Hedefi: "
msgid "Protected"
msgstr "Korumalı"
+msgid "Protocol"
+msgstr "İletişim Kuralı"
+
msgid "Protocol ID"
msgstr "İletişim Kuralı ID'si"
diff --git a/openstack_dashboard/locale/ur/LC_MESSAGES/django.po b/openstack_dashboard/locale/ur/LC_MESSAGES/django.po
index c451e801d..9f89f09d4 100644
--- a/openstack_dashboard/locale/ur/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/ur/LC_MESSAGES/django.po
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-02-21 20:31+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -2164,6 +2164,9 @@ msgstr "پروجیكٹ :"
msgid "Protected"
msgstr "محفوظ"
+msgid "Protocol"
+msgstr "پروٹوكول"
+
msgid "Provider"
msgstr "فراہم کنندہ"
diff --git a/openstack_dashboard/locale/zh_Hans/LC_MESSAGES/django.po b/openstack_dashboard/locale/zh_Hans/LC_MESSAGES/django.po
index b6ee05bef..30e3699f5 100644
--- a/openstack_dashboard/locale/zh_Hans/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/zh_Hans/LC_MESSAGES/django.po
@@ -33,7 +33,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-11-04 11:42+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4566,6 +4566,9 @@ msgstr "属性目标:"
msgid "Protected"
msgstr "受保护的"
+msgid "Protocol"
+msgstr "协议"
+
msgid "Protocol ID"
msgstr "协议编号"
diff --git a/openstack_dashboard/locale/zh_Hant/LC_MESSAGES/django.po b/openstack_dashboard/locale/zh_Hant/LC_MESSAGES/django.po
index 444b28889..aaf7d8ec6 100644
--- a/openstack_dashboard/locale/zh_Hant/LC_MESSAGES/django.po
+++ b/openstack_dashboard/locale/zh_Hant/LC_MESSAGES/django.po
@@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: horizon VERSION\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
-"POT-Creation-Date: 2022-11-04 11:42+0000\n"
+"POT-Creation-Date: 2023-03-07 08:38+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -4351,6 +4351,9 @@ msgstr "屬性目標:"
msgid "Protected"
msgstr "保護"
+msgid "Protocol"
+msgstr "協定"
+
msgid "Protocol ID"
msgstr "通訊協定 ID"
diff --git a/openstack_dashboard/static/app/core/openstack-service-api/glance.service.js b/openstack_dashboard/static/app/core/openstack-service-api/glance.service.js
index db14f3f2f..a0b931982 100644
--- a/openstack_dashboard/static/app/core/openstack-service-api/glance.service.js
+++ b/openstack_dashboard/static/app/core/openstack-service-api/glance.service.js
@@ -47,6 +47,8 @@
getResourceTypes: getResourceTypes
};
+ var READONLY_PROPERTIES = ['os_hash_algo', 'os_hash_value'];
+
return service;
///////////////
@@ -278,6 +280,10 @@
* @returns {Object} The result of the API call
*/
function editImageProps(id, updated, removed) {
+ angular.forEach(READONLY_PROPERTIES, function(key) {
+ delete updated[key];
+ });
+
return apiService.patch(
'/api/glance/images/' + id + '/properties/',
{
diff --git a/openstack_dashboard/test/integration_tests/helpers.py b/openstack_dashboard/test/integration_tests/helpers.py
index 0e2ef5fde..17f9d60e7 100644
--- a/openstack_dashboard/test/integration_tests/helpers.py
+++ b/openstack_dashboard/test/integration_tests/helpers.py
@@ -332,10 +332,8 @@ class TestCase(BaseTestCase, AssertsMixin):
self.home_pg = self.login_pg.login(self.TEST_USER_NAME,
self.TEST_PASSWORD)
self.home_pg.change_project(self.HOME_PROJECT)
- self.assertTrue(
- self.home_pg.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.home_pg.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ self.home_pg.find_messages_and_dismiss(), {messages.SUCCESS})
def cleanup():
if self.home_pg.is_logged_in:
diff --git a/openstack_dashboard/test/integration_tests/horizon.conf b/openstack_dashboard/test/integration_tests/horizon.conf
index 52db2a69c..531f7e55d 100644
--- a/openstack_dashboard/test/integration_tests/horizon.conf
+++ b/openstack_dashboard/test/integration_tests/horizon.conf
@@ -12,7 +12,7 @@ help_url=https://docs.openstack.org/
[selenium]
# Timeout in seconds to wait for message confirmation modal
# (float value)
-message_implicit_wait=0.1
+message_implicit_wait=3
# Timeout in seconds to wait for a page to become available
# (integer value)
diff --git a/openstack_dashboard/test/integration_tests/pages/basepage.py b/openstack_dashboard/test/integration_tests/pages/basepage.py
index b3fdaae4f..62827f598 100644
--- a/openstack_dashboard/test/integration_tests/pages/basepage.py
+++ b/openstack_dashboard/test/integration_tests/pages/basepage.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import selenium.common.exceptions as Exceptions
from selenium.webdriver.common import by
from openstack_dashboard.test.integration_tests.pages import navigation
@@ -24,6 +25,7 @@ class BasePage(pageobject.PageObject):
_heading_locator = (by.By.CSS_SELECTOR, 'div.page-header > h2')
_help_page_brand = (by.By.CSS_SELECTOR, '.navbar-brand')
+ _default_message_locator = (by.By.CSS_SELECTOR, 'div.alert')
@property
def heading(self):
@@ -59,12 +61,25 @@ class BasePage(pageobject.PageObject):
def choose_theme(self, theme_name):
self.topbar.user_dropdown_menu.choose_theme(theme_name)
- def find_message_and_dismiss(self, message_level=messages.SUCCESS):
- message = messages.MessageRegion(self.driver, self.conf, message_level)
- is_message_present = message.exists()
- if is_message_present:
+ def find_all_messages(self):
+ self.driver.implicitly_wait(self.conf.selenium.message_implicit_wait)
+ try:
+ msg_elements = self.driver.find_elements(
+ *self._default_message_locator)
+ except Exceptions.NoSuchElementException:
+ msg_elements = []
+ finally:
+ self._turn_on_implicit_wait()
+ return msg_elements
+
+ def find_messages_and_dismiss(self):
+ messages_level_present = set()
+ for message_element in self.find_all_messages():
+ message = messages.MessageRegion(
+ self.driver, self.conf, message_element)
+ messages_level_present.add(message.message_class)
message.close()
- return is_message_present
+ return messages_level_present
def change_project(self, name):
self.topbar.user_dropdown_project.click_on_project(name)
diff --git a/openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py b/openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py
index 0c61bfcea..c3795a9ec 100644
--- a/openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py
+++ b/openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py
@@ -215,6 +215,7 @@ class ImagesPage(basepage.BaseNavigationPage):
confirm_edit_images_form.protected.pick('No')
confirm_edit_images_form.submit()
+ self.wait_till_element_disappears(self.wizard_getter)
def delete_image_via_row_action(self, name):
row = self._get_row_with_image_name(name)
@@ -289,6 +290,7 @@ class ImagesPage(basepage.BaseNavigationPage):
create_volume_form.availability_zone.value = \
self.conf.launch_instances.available_zone
create_volume_form.submit()
+ self.wait_till_element_disappears(self.wizard_getter)
def launch_instance_from_image(self, name, instance_name,
instance_count=1, flavor=None):
diff --git a/openstack_dashboard/test/integration_tests/regions/messages.py b/openstack_dashboard/test/integration_tests/regions/messages.py
index 68f50f11b..9860ec25d 100644
--- a/openstack_dashboard/test/integration_tests/regions/messages.py
+++ b/openstack_dashboard/test/integration_tests/regions/messages.py
@@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common import by
from openstack_dashboard.test.integration_tests.regions import baseregion
@@ -18,28 +17,31 @@ from openstack_dashboard.test.integration_tests.regions import baseregion
ERROR = 'alert-danger'
INFO = 'alert-info'
SUCCESS = 'alert-success'
+WARNING = 'alert-warning'
class MessageRegion(baseregion.BaseRegion):
_close_locator = (by.By.CSS_SELECTOR, 'a.close')
- def _msg_locator(self, level):
- return (by.By.CSS_SELECTOR, 'div.alert.%s' % level)
-
- def __init__(self, driver, conf, level=SUCCESS):
- self._default_src_locator = self._msg_locator(level)
- # NOTE(nhelgeson): Running selenium on remote servers
- # requires extra time to wait for message to pop up.
- driver.implicitly_wait(conf.selenium.message_implicit_wait)
- try:
- super().__init__(driver, conf)
- except NoSuchElementException:
- self.src_elem = None
- finally:
- self._turn_on_implicit_wait()
+ def __init__(self, driver, conf, src_elem):
+ self.src_elem = src_elem
+ self.message_class = self.get_message_class()
def exists(self):
return self._is_element_displayed(self.src_elem)
def close(self):
self._get_element(*self._close_locator).click()
+
+ def get_message_class(self):
+ message_class = self.src_elem.get_attribute("class")
+ if SUCCESS in message_class:
+ return SUCCESS
+ elif ERROR in message_class:
+ return ERROR
+ elif INFO in message_class:
+ return INFO
+ elif WARNING in message_class:
+ return WARNING
+ else:
+ return "Unknown"
diff --git a/openstack_dashboard/test/integration_tests/tests/test_defaults.py b/openstack_dashboard/test/integration_tests/tests/test_defaults.py
index 3a6d782b3..b4c887b5e 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_defaults.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_defaults.py
@@ -34,12 +34,8 @@ class TestDefaults(helpers.AdminTestCase):
"""
default_quota_values = self.defaults_page.compute_quota_values
self.defaults_page.update_compute_defaults(self.add_up)
-
- self.assertTrue(
- self.defaults_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.defaults_page.find_message_and_dismiss(messages.ERROR))
-
+ self.assertEqual(
+ self.defaults_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertGreater(len(default_quota_values), 0)
for quota_name in default_quota_values:
@@ -63,12 +59,8 @@ class TestDefaults(helpers.AdminTestCase):
self.defaults_page.go_to_volume_quotas_tab()
default_quota_values = self.defaults_page.volume_quota_values
self.defaults_page.update_volume_defaults(self.add_up)
-
- self.assertTrue(
- self.defaults_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.defaults_page.find_message_and_dismiss(messages.ERROR))
-
+ self.assertEqual(
+ self.defaults_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertGreater(len(default_quota_values), 0)
for quota_name in default_quota_values:
diff --git a/openstack_dashboard/test/integration_tests/tests/test_flavors.py b/openstack_dashboard/test/integration_tests/tests/test_flavors.py
index 2bd18051c..b541bb49e 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_flavors.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_flavors.py
@@ -43,19 +43,15 @@ class TestFlavors(helpers.AdminTestCase):
ephemeral_disk=0,
swap_disk=0
)
- self.assertTrue(
- self.flavors_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.flavors_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ self.flavors_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(
self.flavors_page.is_flavor_present(self.FLAVOR_NAME))
def _delete_flavor(self, flavor_name):
self.flavors_page.delete_flavor_by_row(flavor_name)
- self.assertTrue(
- self.flavors_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.flavors_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ self.flavors_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(
self.flavors_page.is_flavor_present(self.FLAVOR_NAME))
diff --git a/openstack_dashboard/test/integration_tests/tests/test_floatingips.py b/openstack_dashboard/test/integration_tests/tests/test_floatingips.py
index 29b57843d..ca6aaa5a3 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_floatingips.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_floatingips.py
@@ -25,17 +25,13 @@ class TestFloatingip(helpers.TestCase):
floatingip_page = \
self.home_pg.go_to_project_network_floatingipspage()
floating_ip = floatingip_page.allocate_floatingip()
- self.assertTrue(
- floatingip_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- floatingip_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ floatingip_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(floatingip_page.is_floatingip_present(floating_ip))
floatingip_page.release_floatingip(floating_ip)
- self.assertTrue(
- floatingip_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- floatingip_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ floatingip_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(floatingip_page.is_floatingip_present(floating_ip))
@@ -48,10 +44,8 @@ class TestFloatingipAssociateDisassociate(helpers.TestCase):
timestamp=False)
instances_page = self.home_pg.go_to_project_compute_instancespage()
instances_page.create_instance(instance_name)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- instances_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(instances_page.is_instance_active(instance_name))
instance_ipv4 = instances_page.get_fixed_ipv4(instance_name)
instance_info = "{} {}".format(instance_name, instance_ipv4)
@@ -59,40 +53,29 @@ class TestFloatingipAssociateDisassociate(helpers.TestCase):
floatingip_page = \
self.home_pg.go_to_project_network_floatingipspage()
floating_ip = floatingip_page.allocate_floatingip()
- self.assertTrue(
- floatingip_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- floatingip_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ floatingip_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(floatingip_page.is_floatingip_present(floating_ip))
-
self.assertEqual('-', floatingip_page.get_fixed_ip(floating_ip))
floatingip_page.associate_floatingip(floating_ip, instance_name,
instance_ipv4)
- self.assertTrue(
- floatingip_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- floatingip_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ floatingip_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertEqual(instance_info,
floatingip_page.get_fixed_ip(floating_ip))
floatingip_page.disassociate_floatingip(floating_ip)
- self.assertTrue(
- floatingip_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- floatingip_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ floatingip_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertEqual('-', floatingip_page.get_fixed_ip(floating_ip))
floatingip_page.release_floatingip(floating_ip)
- self.assertTrue(
- floatingip_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- floatingip_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ floatingip_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(floatingip_page.is_floatingip_present(floating_ip))
instances_page = self.home_pg.go_to_project_compute_instancespage()
instances_page.delete_instance(instance_name)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- instances_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ floatingip_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(instances_page.is_instance_deleted(instance_name))
diff --git a/openstack_dashboard/test/integration_tests/tests/test_groups.py b/openstack_dashboard/test/integration_tests/tests/test_groups.py
index 2e78807f1..9b3da22fb 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_groups.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_groups.py
@@ -31,18 +31,14 @@ class TestGroup(helpers.AdminTestCase):
def _test_create_group(self, group_name, group_desc=None):
self.groups_page.create_group(name=group_name, description=group_desc)
- self.assertTrue(
- self.groups_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.groups_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ self.groups_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(self.groups_page.is_group_present(group_name))
def _test_delete_group(self, group_name):
self.groups_page.delete_group(name=group_name)
- self.assertTrue(
- self.groups_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.groups_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ self.groups_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(self.groups_page.is_group_present(group_name))
def test_create_delete_group(self):
@@ -58,9 +54,7 @@ class TestGroup(helpers.AdminTestCase):
new_group_name = self.group_name
new_group_desc = self.group_description
self.groups_page.edit_group(group_name, new_group_name, new_group_desc)
- self.assertTrue(
- self.groups_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.groups_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ self.groups_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(self.groups_page.is_group_present(new_group_name))
self._test_delete_group(new_group_name)
diff --git a/openstack_dashboard/test/integration_tests/tests/test_grouptypes.py b/openstack_dashboard/test/integration_tests/tests/test_grouptypes.py
index 4e2d980fb..d94fcadd6 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_grouptypes.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_grouptypes.py
@@ -32,16 +32,12 @@ class TestAdminGroupTypes(helpers.AdminTestCase):
"""
group_types_page = self.home_pg.go_to_admin_volume_grouptypespage()
group_types_page.create_group_type(self.GROUP_TYPE_NAME)
- self.assertTrue(
- group_types_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- group_types_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ group_types_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(group_types_page.is_group_type_present(
self.GROUP_TYPE_NAME))
group_types_page.delete_group_type(self.GROUP_TYPE_NAME)
- self.assertTrue(
- group_types_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- group_types_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ group_types_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(group_types_page.is_group_type_deleted(
self.GROUP_TYPE_NAME))
diff --git a/openstack_dashboard/test/integration_tests/tests/test_host_aggregates.py b/openstack_dashboard/test/integration_tests/tests/test_host_aggregates.py
index f6f373dd0..4fc6b686e 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_host_aggregates.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_host_aggregates.py
@@ -34,17 +34,13 @@ class TestHostAggregates(helpers.AdminTestCase):
hostaggregates_page.create_host_aggregate(
name=self.HOST_AGGREGATE_NAME,
availability_zone=self.HOST_AGGREGATE_AVAILABILITY_ZONE)
- self.assertTrue(
- hostaggregates_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(hostaggregates_page.find_message_and_dismiss(
- messages.ERROR))
+ self.assertEqual(
+ hostaggregates_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(hostaggregates_page.is_host_aggregate_present(
self.HOST_AGGREGATE_NAME))
hostaggregates_page.delete_host_aggregate(self.HOST_AGGREGATE_NAME)
- self.assertTrue(
- hostaggregates_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(hostaggregates_page.find_message_and_dismiss(
- messages.ERROR))
+ self.assertEqual(
+ hostaggregates_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(hostaggregates_page.is_host_aggregate_present(
self.HOST_AGGREGATE_NAME))
diff --git a/openstack_dashboard/test/integration_tests/tests/test_images.py b/openstack_dashboard/test/integration_tests/tests/test_images.py
index 8065eaa92..ddf9db1f4 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_images.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_images.py
@@ -43,8 +43,8 @@ class TestImagesBasicAngular(helpers.TestCase):
images_page.create_image(self.IMAGE_NAME,
image_source_type='url',
**kwargs)
- self.assertTrue(images_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(images_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(images_page.is_image_present(self.IMAGE_NAME))
self.assertTrue(images_page.is_image_active(self.IMAGE_NAME))
return images_page
@@ -52,8 +52,8 @@ class TestImagesBasicAngular(helpers.TestCase):
def image_delete(self, image_name):
images_page = self.images_page
images_page.delete_image(image_name)
- self.assertTrue(images_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(images_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(images_page.is_image_present(self.IMAGE_NAME))
def test_image_create_delete_from_local_file(self):
@@ -111,8 +111,8 @@ class TestImagesBasicAngular(helpers.TestCase):
garbage = [i for i in image_list if i not in default_image_list]
if garbage:
images_page.delete_images(garbage)
- self.assertTrue(
- images_page.find_message_and_dismiss(messages.SUCCESS))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.SUCCESS})
items_per_page = 1
images_count = 2
@@ -121,10 +121,8 @@ class TestImagesBasicAngular(helpers.TestCase):
for image_name in images_names:
with helpers.gen_temporary_file() as file_name:
images_page.create_image(image_name, image_file=file_name)
- self.assertTrue(
- images_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- images_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(images_page.is_image_present(image_name))
first_page_definition = {'Next': True, 'Prev': False,
@@ -139,7 +137,7 @@ class TestImagesBasicAngular(helpers.TestCase):
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_pagesize(items_per_page)
- settings_page.find_message_and_dismiss(messages.SUCCESS)
+ settings_page.find_messages_and_dismiss()
images_page = self.images_page
if not images_page.is_image_present(default_image_list[0]):
@@ -160,13 +158,13 @@ class TestImagesBasicAngular(helpers.TestCase):
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_pagesize()
- settings_page.find_message_and_dismiss(messages.SUCCESS)
+ settings_page.find_messages_and_dismiss()
images_page = self.images_page
images_page.wait_until_image_present(default_image_list[0])
images_page.delete_images(images_names)
- self.assertTrue(images_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(images_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.SUCCESS})
class TestImagesAdminAngular(helpers.AdminTestCase, TestImagesBasicAngular):
@@ -220,8 +218,8 @@ class TestImagesAdminAngular(helpers.AdminTestCase, TestImagesBasicAngular):
with helpers.gen_temporary_file() as file_name:
images_page = self.image_create(local_file=file_name)
images_page.edit_image(self.IMAGE_NAME, protected=True)
- self.assertTrue(
- images_page.find_message_and_dismiss(messages.SUCCESS))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.SUCCESS})
# Check that Delete action is not available in the action list.
# The below action will generate exception since the bind fails.
@@ -235,15 +233,9 @@ class TestImagesAdminAngular(helpers.AdminTestCase, TestImagesBasicAngular):
images_page = self.images_page
images_page.edit_image(self.IMAGE_NAME, protected=False)
- self.assertTrue(
- images_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- images_page.find_message_and_dismiss(messages.ERROR))
-
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.image_delete(self.IMAGE_NAME)
- self.assertFalse(
- images_page.find_message_and_dismiss(messages.ERROR))
- self.assertFalse(images_page.is_image_present(self.IMAGE_NAME))
def test_edit_image_description_and_name(self):
"""tests that image description is editable
@@ -265,10 +257,8 @@ class TestImagesAdminAngular(helpers.AdminTestCase, TestImagesBasicAngular):
images_page = self.image_create(local_file=file_name)
images_page.edit_image(self.IMAGE_NAME,
description=new_description_text)
- self.assertTrue(
- images_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- images_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.SUCCESS})
results = images_page.check_image_details(self.IMAGE_NAME,
{'Description':
@@ -279,20 +269,14 @@ class TestImagesAdminAngular(helpers.AdminTestCase, TestImagesBasicAngular):
images_page = self.images_page
images_page.edit_image(self.IMAGE_NAME,
new_name=new_image_name)
- self.assertTrue(
- images_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- images_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.SUCCESS})
results = images_page.check_image_details(new_image_name,
{'Name':
new_image_name})
self.assertSequenceTrue(results)
-
self.image_delete(new_image_name)
- self.assertFalse(
- images_page.find_message_and_dismiss(messages.ERROR))
- self.assertFalse(images_page.is_image_present(self.IMAGE_NAME))
def test_filter_images(self):
"""This test checks filtering of images
@@ -354,10 +338,8 @@ class TestImagesAdvancedAngular(helpers.TestCase):
images_page.create_volume_from_image(
source_image, volume_name=target_volume)
- self.assertTrue(
- images_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- images_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.INFO})
volumes_page = self.volumes_page()
@@ -365,8 +347,7 @@ class TestImagesAdvancedAngular(helpers.TestCase):
self.assertTrue(volumes_page.is_volume_status(target_volume,
'Available'))
volumes_page.delete_volume(target_volume)
- volumes_page.find_message_and_dismiss(messages.SUCCESS)
- volumes_page.find_message_and_dismiss(messages.ERROR)
+ volumes_page.find_messages_and_dismiss()
volumes_page = self.volumes_page()
self.assertTrue(volumes_page.is_volume_deleted(target_volume))
@@ -387,10 +368,8 @@ class TestImagesAdvancedAngular(helpers.TestCase):
target_instance = "created_from_{0}".format(source_image)
images_page.launch_instance_from_image(source_image, target_instance)
- self.assertTrue(
- images_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- images_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.INFO})
instances_page = self.instances_page()
self.assertTrue(instances_page.is_instance_active(target_instance))
@@ -399,8 +378,6 @@ class TestImagesAdvancedAngular(helpers.TestCase):
self.assertEqual(source_image, actual_image_name)
instances_page.delete_instance(target_instance)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- instances_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(instances_page.is_instance_deleted(target_instance))
diff --git a/openstack_dashboard/test/integration_tests/tests/test_instances.py b/openstack_dashboard/test/integration_tests/tests/test_instances.py
index da510b268..ebf784714 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_instances.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_instances.py
@@ -33,16 +33,14 @@ class TestInstances(helpers.TestCase):
instances_page = self.home_pg.go_to_project_compute_instancespage()
instances_page.create_instance(self.INSTANCE_NAME)
- self.assertTrue(instances_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- instances_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(instances_page.is_instance_active(self.INSTANCE_NAME))
instances_page = self.instances_page
instances_page.delete_instance(self.INSTANCE_NAME)
- self.assertTrue(instances_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- instances_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(instances_page.is_instance_deleted(self.INSTANCE_NAME))
@@ -68,33 +66,33 @@ class TestInstancesPagination(helpers.TestCase):
name_column='Instance Name')
if garbage:
instances_page.delete_instances(garbage)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
instances_page.are_instances_deleted(garbage))
instances_page.create_instance(self.INSTANCE_NAME,
instance_count=self.INSTANCE_COUNT)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
instances_page.is_instance_active(self.instance_list[1]))
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_pagesize(self.ITEMS_PER_PAGE)
- self.assertTrue(
- settings_page.find_message_and_dismiss(messages.SUCCESS))
+ self.assertEqual(
+ settings_page.find_messages_and_dismiss(), {messages.SUCCESS})
def cleanup():
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_pagesize()
- self.assertTrue(
- settings_page.find_message_and_dismiss(messages.SUCCESS))
+ self.assertEqual(
+ settings_page.find_messages_and_dismiss(), {messages.SUCCESS})
instances_page = self.instances_page
instances_page.delete_instances(self.instance_list)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
instances_page.are_instances_deleted(self.instance_list))
@@ -207,23 +205,23 @@ class TestInstancesFilter(helpers.TestCase):
name_column='Instance Name')
if garbage:
instances_page.delete_instances(garbage)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
instances_page.are_instances_deleted(garbage))
instances_page.create_instance(self.INSTANCE_NAME,
instance_count=self.INSTANCE_COUNT)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
instances_page.is_instance_active(self.instance_list[1]))
def cleanup():
instances_page = self.instances_page
instances_page.delete_instances(self.instance_list)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
instances_page.are_instances_deleted(self.instance_list))
@@ -289,33 +287,33 @@ class TestAdminInstancesPagination(helpers.AdminTestCase, TestInstances):
name_column='Instance Name')
if garbage:
instances_page.delete_instances(garbage)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
instances_page.are_instances_deleted(garbage))
instances_page.create_instance(self.INSTANCE_NAME,
instance_count=self.INSTANCE_COUNT)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
instances_page.is_instance_active(self.instance_list[1]))
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_pagesize(self.ITEMS_PER_PAGE)
- self.assertTrue(
- settings_page.find_message_and_dismiss(messages.SUCCESS))
+ self.assertEqual(
+ settings_page.find_messages_and_dismiss(), {messages.SUCCESS})
def cleanup():
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_pagesize()
- self.assertTrue(
- settings_page.find_message_and_dismiss(messages.SUCCESS))
+ self.assertEqual(
+ settings_page.find_messages_and_dismiss(), {messages.SUCCESS})
instances_page = self.instances_page
instances_page.delete_instances(self.instance_list)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
instances_page.are_instances_deleted(self.instance_list))
diff --git a/openstack_dashboard/test/integration_tests/tests/test_keypairs.py b/openstack_dashboard/test/integration_tests/tests/test_keypairs.py
index c3f0f8397..62b5a32df 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_keypairs.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_keypairs.py
@@ -27,14 +27,14 @@ class TestKeypair(helpers.TestCase):
keypair_page = self.home_pg.\
go_to_project_compute_keypairspage()
keypair_page.create_keypair(self.KEYPAIR_NAME)
- self.assertFalse(keypair_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ keypair_page.find_messages_and_dismiss(), {messages.SUCCESS})
keypair_page = self.home_pg.\
go_to_project_compute_keypairspage()
self.assertTrue(keypair_page.is_keypair_present(self.KEYPAIR_NAME))
keypair_page.delete_keypair(self.KEYPAIR_NAME)
- self.assertTrue(
- keypair_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(keypair_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ keypair_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(keypair_page.is_keypair_present(self.KEYPAIR_NAME))
diff --git a/openstack_dashboard/test/integration_tests/tests/test_metadata_definitions.py b/openstack_dashboard/test/integration_tests/tests/test_metadata_definitions.py
index 4cf248663..be64edec5 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_metadata_definitions.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_metadata_definitions.py
@@ -54,8 +54,8 @@ class TestMetadataDefinitions(helpers.AdminTestCase):
is_protected=is_protected,
namespace_source_type=template_source_type)
# Checks
- self.assertTrue(page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(page.is_namespace_present(namespace_name))
row = page._get_row_with_namespace_name(namespace_name)
if checks:
@@ -79,8 +79,8 @@ class TestMetadataDefinitions(helpers.AdminTestCase):
"""
page.delete_namespace(name=namespace_name)
# Checks
- self.assertTrue(page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(page.is_namespace_present(namespace_name))
def test_namespace_create_delete(self):
diff --git a/openstack_dashboard/test/integration_tests/tests/test_networks.py b/openstack_dashboard/test/integration_tests/tests/test_networks.py
index 6c56fdb2a..a1bdf6fbc 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_networks.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_networks.py
@@ -37,18 +37,14 @@ class TestNetworks(helpers.TestCase):
networks_page = self.networks_page
networks_page.create_network(self.NETWORK_NAME, self.SUBNET_NAME)
- self.assertTrue(
- networks_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- networks_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ networks_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(networks_page.is_network_present(self.NETWORK_NAME))
self.assertTrue(networks_page.is_network_active(self.NETWORK_NAME))
networks_page.delete_network(self.NETWORK_NAME)
- self.assertTrue(
- networks_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- networks_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ networks_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(networks_page.is_network_present(self.NETWORK_NAME))
@@ -81,10 +77,8 @@ class TestNetworksPagination(helpers.TestCase):
networks_page = self.networks_page
for network_name in networks_names:
networks_page.create_network(network_name, self.SUBNET_NAME)
- self.assertTrue(
- networks_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- networks_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ networks_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(networks_page.is_network_present(network_name))
self.assertTrue(networks_page.is_network_active(network_name))
# we have to get this now, before we change page size
@@ -98,10 +92,9 @@ class TestNetworksPagination(helpers.TestCase):
networks_page = self.networks_page
for network_name in networks_names:
networks_page.delete_network(network_name)
- self.assertTrue(
- networks_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- networks_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ networks_page.find_messages_and_dismiss(),
+ {messages.SUCCESS})
self.assertFalse(networks_page.is_network_present(network_name))
self.addCleanup(cleanup)
@@ -166,4 +159,4 @@ class TestNetworksPagination(helpers.TestCase):
settings_page.change_pagesize(items_per_page)
else:
settings_page.change_pagesize()
- settings_page.find_message_and_dismiss(messages.SUCCESS)
+ settings_page.find_messages_and_dismiss()
diff --git a/openstack_dashboard/test/integration_tests/tests/test_projects.py b/openstack_dashboard/test/integration_tests/tests/test_projects.py
index 0b06c8528..f85958640 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_projects.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_projects.py
@@ -24,17 +24,13 @@ class TestCreateDeleteProject(helpers.AdminTestCase):
def test_create_delete_project(self):
self.projects_page.create_project(PROJECT_NAME)
- self.assertTrue(
- self.projects_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.projects_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ self.projects_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(self.projects_page.is_project_present(PROJECT_NAME))
self.projects_page.delete_project(PROJECT_NAME)
- self.assertTrue(
- self.projects_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.projects_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ self.projects_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(self.projects_page.is_project_present(PROJECT_NAME))
@@ -44,8 +40,8 @@ class TestModifyProject(helpers.AdminTestCase):
super().setUp()
self.projects_page = self.home_pg.go_to_identity_projectspage()
self.projects_page.create_project(PROJECT_NAME)
- self.assertTrue(
- self.projects_page.find_message_and_dismiss(messages.SUCCESS))
+ self.assertEqual(
+ self.projects_page.find_messages_and_dismiss(), {messages.SUCCESS})
def cleanup():
if not self.projects_page.is_the_current_page():
@@ -62,10 +58,8 @@ class TestModifyProject(helpers.AdminTestCase):
self.projects_page.allocate_user_to_project(
admin_name, roles2add, PROJECT_NAME)
- self.assertTrue(
- self.projects_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.projects_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ self.projects_page.find_messages_and_dismiss(), {messages.SUCCESS})
user_roles = self.projects_page.get_user_roles_at_project(
admin_name, PROJECT_NAME)
diff --git a/openstack_dashboard/test/integration_tests/tests/test_router.py b/openstack_dashboard/test/integration_tests/tests/test_router.py
index feba833e0..a462e3330 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_router.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_router.py
@@ -28,18 +28,16 @@ class TestRouters(helpers.TestCase):
routers_page = self.routers_page
routers_page.create_router(self.ROUTER_NAME)
- self.assertTrue(
- routers_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(routers_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ routers_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(routers_page.is_router_present(self.ROUTER_NAME))
self.assertTrue(routers_page.is_router_active(self.ROUTER_NAME))
def _delete_router(self):
routers_page = self.routers_page
routers_page.delete_router(self.ROUTER_NAME)
- self.assertTrue(
- routers_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(routers_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ routers_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(routers_page.is_router_present(self.ROUTER_NAME))
def test_router_create(self):
@@ -55,32 +53,28 @@ class TestRouters(helpers.TestCase):
def _create_interface(self, interfaces_page):
interfaces_page.create_interface(self.SUBNET_NAME)
- self.assertTrue(
- interfaces_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- interfaces_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ interfaces_page.find_messages_and_dismiss(), {messages.SUCCESS})
interface_name = interfaces_page.interface_name
self.assertTrue(interfaces_page.is_interface_present(interface_name))
def _delete_interface(self, interfaces_page, interface_name):
interfaces_page.delete_interface(interface_name)
- self.assertTrue(
- interfaces_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- interfaces_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ interfaces_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(interfaces_page.is_interface_present(interface_name))
def _create_subnet(self):
networks_page = self.home_pg.go_to_project_network_networkspage()
networks_page.create_network(self.NETWORK_NAME, self.SUBNET_NAME)
- self.assertTrue(
- networks_page.find_message_and_dismiss(messages.SUCCESS))
+ self.assertEqual(
+ networks_page.find_messages_and_dismiss(), {messages.SUCCESS})
def _delete_subnet(self):
networks_page = self.home_pg.go_to_project_network_networkspage()
networks_page.delete_network(self.NETWORK_NAME)
- self.assertTrue(
- networks_page.find_message_and_dismiss(messages.SUCCESS))
+ self.assertEqual(
+ networks_page.find_messages_and_dismiss(), {messages.SUCCESS})
def test_router_add_delete_interface(self):
"""Tests the router interface creation and deletion functionalities:
@@ -191,9 +185,8 @@ class TestAdminRouters(helpers.AdminTestCase):
routers_page = self.home_pg.go_to_project_network_routerspage()
routers_page.create_router(self.ROUTER_NAME)
- self.assertTrue(
- routers_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(routers_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ routers_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(routers_page.is_router_present(self.ROUTER_NAME))
self.assertTrue(routers_page.is_router_active(self.ROUTER_NAME))
@@ -204,18 +197,14 @@ class TestAdminRouters(helpers.AdminTestCase):
new_name = "edited_" + self.ROUTER_NAME
admin_routers_page.edit_router(self.ROUTER_NAME, new_name=new_name)
- self.assertTrue(
- admin_routers_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- admin_routers_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ admin_routers_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(
admin_routers_page.is_router_present(new_name))
self.assertTrue(
admin_routers_page.is_router_active(new_name))
admin_routers_page.delete_router(new_name)
- self.assertTrue(
- admin_routers_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- admin_routers_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ admin_routers_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(admin_routers_page.is_router_present(new_name))
diff --git a/openstack_dashboard/test/integration_tests/tests/test_router_gateway.py b/openstack_dashboard/test/integration_tests/tests/test_router_gateway.py
index 8f10c7589..8c17c44b6 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_router_gateway.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_router_gateway.py
@@ -45,26 +45,22 @@ class TestRouters(helpers.TestCase):
routers_page = self.home_pg.go_to_project_network_routerspage()
routers_page.create_router(self.ROUTER_NAME)
- self.assertTrue(
- routers_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(routers_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ routers_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(routers_page.is_router_present(self.ROUTER_NAME))
self.assertTrue(routers_page.is_router_active(self.ROUTER_NAME))
routers_page.clear_gateway(self.ROUTER_NAME)
- self.assertTrue(
- routers_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(routers_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ routers_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(routers_page.is_gateway_cleared(self.ROUTER_NAME))
routers_page.set_gateway(self.ROUTER_NAME)
- self.assertTrue(
- routers_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(routers_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ routers_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(routers_page.is_gateway_set(self.ROUTER_NAME))
routers_page.delete_router(self.ROUTER_NAME)
- self.assertTrue(
- routers_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(routers_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ routers_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(routers_page.is_router_present(self.ROUTER_NAME))
diff --git a/openstack_dashboard/test/integration_tests/tests/test_security_groups.py b/openstack_dashboard/test/integration_tests/tests/test_security_groups.py
index da6f8f248..6d3f99b54 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_security_groups.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_security_groups.py
@@ -29,45 +29,44 @@ class TestSecuritygroup(helpers.TestCase):
page = self.securitygroup_page
rule_page = page.create_securitygroup(self.SEC_GROUP_NAME)
if rule_page:
- self.assertTrue(
- rule_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- rule_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ rule_page.find_messages_and_dismiss(), {messages.SUCCESS})
page = self.securitygroup_page
self.assertTrue(page.is_securitygroup_present(self.SEC_GROUP_NAME))
else:
- self.assertTrue(page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(page.is_securitygroup_present(self.SEC_GROUP_NAME))
def _delete_securitygroup(self):
page = self.securitygroup_page
page.delete_securitygroup(self.SEC_GROUP_NAME)
- self.assertTrue(page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(page.is_securitygroup_present(self.SEC_GROUP_NAME))
def _add_rule(self):
page = self.securitygroup_page
page = page.go_to_manage_rules(self.SEC_GROUP_NAME)
page.create_rule(self.RULE_PORT)
- self.assertTrue(page.find_message_and_dismiss(messages.SUCCESS))
+ self.assertEqual(
+ page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(page.is_port_present(self.RULE_PORT))
def _delete_rule_by_table_action(self):
page = self.securitygroup_page
page = page.go_to_manage_rules(self.SEC_GROUP_NAME)
page.delete_rules(self.RULE_PORT)
- self.assertTrue(page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(page.is_port_present(self.RULE_PORT))
def _delete_rule_by_row_action(self):
page = self.securitygroup_page
page = page.go_to_manage_rules(self.SEC_GROUP_NAME)
page.delete_rule(self.RULE_PORT)
- self.assertTrue(page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(page.is_port_present(self.RULE_PORT))
def test_securitygroup_create_delete(self):
diff --git a/openstack_dashboard/test/integration_tests/tests/test_user_settings.py b/openstack_dashboard/test/integration_tests/tests/test_user_settings.py
index f48ae6da7..161fbf239 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_user_settings.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_user_settings.py
@@ -135,28 +135,20 @@ class TestUserSettings(helpers.TestCase):
"""
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_language("es")
- self.assertTrue(
- settings_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- settings_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ settings_page.find_messages_and_dismiss(), {messages.SUCCESS})
settings_page.change_timezone("Asia/Jerusalem")
- self.assertTrue(
- settings_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- settings_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ settings_page.find_messages_and_dismiss(), {messages.SUCCESS})
settings_page.change_pagesize("30")
- self.assertTrue(
- settings_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- settings_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ settings_page.find_messages_and_dismiss(), {messages.SUCCESS})
settings_page.change_loglines("50")
- self.assertTrue(
- settings_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- settings_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ settings_page.find_messages_and_dismiss(), {messages.SUCCESS})
changed_settings = {
"language": "es",
diff --git a/openstack_dashboard/test/integration_tests/tests/test_users.py b/openstack_dashboard/test/integration_tests/tests/test_users.py
index e1b09b14b..d7fad4a58 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_users.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_users.py
@@ -23,11 +23,11 @@ class TestUser(helpers.AdminTestCase):
users_page.create_user(self.USER_NAME, password=password,
project='admin', role='admin')
- self.assertTrue(users_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(users_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ users_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(users_page.is_user_present(self.USER_NAME))
users_page.delete_user(self.USER_NAME)
- self.assertTrue(users_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(users_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ users_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(users_page.is_user_present(self.USER_NAME))
diff --git a/openstack_dashboard/test/integration_tests/tests/test_volume_snapshots.py b/openstack_dashboard/test/integration_tests/tests/test_volume_snapshots.py
index 172b47962..e218ac165 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_volume_snapshots.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_volume_snapshots.py
@@ -32,14 +32,14 @@ class TestVolumeSnapshotsBasic(helpers.TestCase):
super().setUp()
volumes_page = self.home_pg.go_to_project_volumes_volumespage()
volumes_page.create_volume(self.VOLUME_NAME)
- volumes_page.find_message_and_dismiss(messages.INFO)
+ volumes_page.find_messages_and_dismiss()
self.assertTrue(volumes_page.is_volume_status(self.VOLUME_NAME,
'Available'))
def cleanup():
volumes_page = self.home_pg.go_to_project_volumes_volumespage()
volumes_page.delete_volume(self.VOLUME_NAME)
- volumes_page.find_message_and_dismiss(messages.INFO)
+ volumes_page.find_messages_and_dismiss()
self.assertTrue(volumes_page.is_volume_deleted(self.VOLUME_NAME))
self.addCleanup(cleanup)
@@ -61,8 +61,8 @@ class TestVolumeSnapshotsBasic(helpers.TestCase):
volumes_page = self.home_pg.go_to_project_volumes_volumespage()
volumes_snapshot_page = volumes_page.create_volume_snapshot(
self.VOLUME_NAME, self.VOLUME_SNAPSHOT_NAME)
- self.assertTrue(volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_snapshot_page.is_volume_snapshot_available(
self.VOLUME_SNAPSHOT_NAME))
actual_volume_name = volumes_snapshot_page.get_volume_name(
@@ -74,18 +74,15 @@ class TestVolumeSnapshotsBasic(helpers.TestCase):
self.home_pg.go_to_project_volumes_snapshotspage()
volumes_snapshot_page.edit_snapshot(self.VOLUME_SNAPSHOT_NAME,
new_name, "description")
- self.assertTrue(
- volumes_snapshot_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- volumes_snapshot_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_snapshot_page.
is_volume_snapshot_available(new_name))
volumes_snapshot_page.delete_volume_snapshot(new_name)
- self.assertTrue(
- volumes_snapshot_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- volumes_snapshot_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_snapshot_page.find_messages_and_dismiss(),
+ {messages.SUCCESS})
self.assertTrue(volumes_snapshot_page.is_volume_snapshot_deleted(
new_name))
@@ -120,7 +117,7 @@ class TestVolumeSnapshotsBasic(helpers.TestCase):
for i, name in enumerate(snapshot_names):
volumes_snapshot_page = volumes_page.create_volume_snapshot(
self.VOLUME_NAME, name)
- volumes_page.find_message_and_dismiss(messages.INFO)
+ volumes_page.find_messages_and_dismiss()
self.assertTrue(
volumes_snapshot_page.is_volume_snapshot_available(name))
if i < count - 1:
@@ -138,7 +135,7 @@ class TestVolumeSnapshotsBasic(helpers.TestCase):
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_pagesize(items_per_page)
- settings_page.find_message_and_dismiss(messages.SUCCESS)
+ settings_page.find_messages_and_dismiss()
volumes_snapshot_page = self.volumes_snapshot_page
volumes_snapshot_page.volumesnapshots_table.assert_definition(
@@ -162,11 +159,11 @@ class TestVolumeSnapshotsBasic(helpers.TestCase):
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_pagesize()
- settings_page.find_message_and_dismiss(messages.SUCCESS)
+ settings_page.find_messages_and_dismiss()
volumes_snapshot_page = self.volumes_snapshot_page
volumes_snapshot_page.delete_volume_snapshots(snapshot_names)
- volumes_snapshot_page.find_message_and_dismiss(messages.SUCCESS)
+ volumes_snapshot_page.find_messages_and_dismiss()
for name in snapshot_names:
volumes_snapshot_page.is_volume_snapshot_deleted(name)
@@ -202,17 +199,15 @@ class TestVolumeSnapshotsAdvanced(helpers.TestCase):
super().setUp()
volumes_page = self.home_pg.go_to_project_volumes_volumespage()
volumes_page.create_volume(self.VOLUME_NAME)
- volumes_page.find_message_and_dismiss(messages.INFO)
+ volumes_page.find_messages_and_dismiss()
self.assertTrue(volumes_page.is_volume_status(self.VOLUME_NAME,
'Available'))
def cleanup():
volumes_page = self.home_pg.go_to_project_volumes_volumespage()
volumes_page.delete_volume(self.VOLUME_NAME)
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_deleted(self.VOLUME_NAME))
self.addCleanup(cleanup)
@@ -221,8 +216,8 @@ class TestVolumeSnapshotsAdvanced(helpers.TestCase):
volumes_page = self.home_pg.go_to_project_volumes_volumespage()
volumes_snapshot_page = volumes_page.create_volume_snapshot(
self.VOLUME_NAME, self.VOLUME_SNAPSHOT_NAME)
- self.assertTrue(volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_snapshot_page.is_volume_snapshot_available(
self.VOLUME_SNAPSHOT_NAME))
@@ -236,19 +231,17 @@ class TestVolumeSnapshotsAdvanced(helpers.TestCase):
def delete_snapshot(self):
volumes_snapshot_page = self.volumes_snapshot_page
volumes_snapshot_page.delete_volume_snapshot(self.VOLUME_SNAPSHOT_NAME)
- self.assertTrue(
- volumes_snapshot_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- volumes_snapshot_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_snapshot_page.find_messages_and_dismiss(),
+ {messages.SUCCESS})
self.assertTrue(volumes_snapshot_page.is_volume_snapshot_deleted(
self.VOLUME_SNAPSHOT_NAME))
def delete_volume(self, new_volume):
volumes_page = self.home_pg.go_to_project_volumes_volumespage()
volumes_page.delete_volume(new_volume)
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_deleted(new_volume))
@pytest.mark.skipif(
diff --git a/openstack_dashboard/test/integration_tests/tests/test_volumes.py b/openstack_dashboard/test/integration_tests/tests/test_volumes.py
index 3d8727b70..52f8a715b 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_volumes.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_volumes.py
@@ -44,29 +44,23 @@ class TestVolumesBasic(helpers.TestCase):
"""
volumes_page = self.home_pg.go_to_project_volumes_volumespage()
volumes_page.create_volume(self.VOLUME_NAME)
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_present(self.VOLUME_NAME))
self.assertTrue(volumes_page.is_volume_status(self.VOLUME_NAME,
'Available'))
new_name = "edited_" + self.VOLUME_NAME
volumes_page.edit_volume(self.VOLUME_NAME, new_name, "description")
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_present(new_name))
self.assertTrue(volumes_page.is_volume_status(new_name, 'Available'))
volumes_page = self.volumes_page
volumes_page.delete_volume(new_name)
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_deleted(new_name))
# NOTE(tsufiev): A short regression test on bug 1553314: we try to
# re-open 'Create Volume' button after the volume was deleted. If the
@@ -107,8 +101,8 @@ class TestVolumesBasic(helpers.TestCase):
name_column='Name')
if garbage:
volumes_page.delete_volumes(garbage)
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
count = 3
items_per_page = 1
@@ -116,10 +110,8 @@ class TestVolumesBasic(helpers.TestCase):
range(count)]
for volume_name in volumes_names:
volumes_page.create_volume(volume_name)
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_present(volume_name))
self.assertTrue(volumes_page.is_volume_status(volume_name,
'Available'))
@@ -135,7 +127,7 @@ class TestVolumesBasic(helpers.TestCase):
'Names': [volumes_names[0]]}
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_pagesize(items_per_page)
- settings_page.find_message_and_dismiss(messages.SUCCESS)
+ settings_page.find_messages_and_dismiss()
volumes_page = self.volumes_page
volumes_page.volumes_table.assert_definition(first_page_definition)
@@ -154,14 +146,12 @@ class TestVolumesBasic(helpers.TestCase):
settings_page = self.home_pg.go_to_settings_usersettingspage()
settings_page.change_pagesize()
- settings_page.find_message_and_dismiss(messages.SUCCESS)
+ settings_page.find_messages_and_dismiss()
volumes_page = self.volumes_page
volumes_page.delete_volumes(volumes_names)
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.are_volumes_deleted(volumes_names))
@@ -200,21 +190,20 @@ class TestVolumesAdvanced(helpers.TestCase):
instance_name = helpers.gen_random_resource_name('instance')
instances_page = self.home_pg.go_to_project_compute_instancespage()
instances_page.create_instance(instance_name)
- instances_page.find_message_and_dismiss(messages.INFO)
- self.assertFalse(
- instances_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(instances_page.is_instance_active(instance_name))
volumes_page = self.volumes_page
volumes_page.create_volume(self.VOLUME_NAME)
- volumes_page.find_message_and_dismiss(messages.INFO)
- self.assertFalse(volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_status(self.VOLUME_NAME,
'Available'))
volumes_page.attach_volume_to_instance(self.VOLUME_NAME, instance_name)
- volumes_page.find_message_and_dismiss(messages.INFO)
- self.assertFalse(volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_status(self.VOLUME_NAME,
'In-use'))
self.assertTrue(
@@ -223,21 +212,20 @@ class TestVolumesAdvanced(helpers.TestCase):
volumes_page.detach_volume_from_instance(self.VOLUME_NAME,
instance_name)
- volumes_page.find_message_and_dismiss(messages.SUCCESS)
- self.assertFalse(volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(volumes_page.is_volume_status(self.VOLUME_NAME,
'Available'))
volumes_page.delete_volume(self.VOLUME_NAME)
- volumes_page.find_message_and_dismiss(messages.SUCCESS)
- self.assertFalse(volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_deleted(self.VOLUME_NAME))
instances_page = self.home_pg.go_to_project_compute_instancespage()
instances_page.delete_instance(instance_name)
- instances_page.find_message_and_dismiss(messages.INFO)
- self.assertFalse(
- instances_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(instances_page.is_instance_deleted(instance_name))
@@ -258,10 +246,8 @@ class TestVolumesActions(helpers.TestCase):
super().setUp()
volumes_page = self.volumes_page
volumes_page.create_volume(self.VOLUME_NAME)
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_present(self.VOLUME_NAME))
self.assertTrue(
volumes_page.is_volume_status(self.VOLUME_NAME, 'Available'))
@@ -269,10 +255,8 @@ class TestVolumesActions(helpers.TestCase):
def cleanup():
volumes_page = self.volumes_page
volumes_page.delete_volume(self.VOLUME_NAME)
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
volumes_page.is_volume_deleted(self.VOLUME_NAME))
@@ -291,10 +275,8 @@ class TestVolumesActions(helpers.TestCase):
volumes_page = self.volumes_page
orig_size = volumes_page.get_size(self.VOLUME_NAME)
volumes_page.extend_volume(self.VOLUME_NAME, orig_size + 1)
- self.assertTrue(
- volumes_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(
volumes_page.is_volume_status(self.VOLUME_NAME, 'Available'))
new_size = volumes_page.get_size(self.VOLUME_NAME)
@@ -316,8 +298,8 @@ class TestVolumesActions(helpers.TestCase):
for disk_format in all_formats:
volumes_page.upload_volume_to_image(
self.VOLUME_NAME, self.IMAGE_NAME, disk_format)
- self.assertFalse(
- volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volumes_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(volumes_page.is_volume_status(
self.VOLUME_NAME, 'Available'))
images_page = self.images_page
@@ -326,10 +308,8 @@ class TestVolumesActions(helpers.TestCase):
self.assertEqual(images_page.get_image_format(self.IMAGE_NAME),
all_formats[disk_format])
images_page.delete_image(self.IMAGE_NAME)
- self.assertTrue(images_page.find_message_and_dismiss(
- messages.SUCCESS))
- self.assertFalse(images_page.find_message_and_dismiss(
- messages.ERROR))
+ self.assertEqual(
+ images_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(images_page.is_image_present(self.IMAGE_NAME))
volumes_page = \
self.home_pg.go_to_project_volumes_volumespage()
@@ -347,10 +327,8 @@ class TestVolumesActions(helpers.TestCase):
6. Delete instance
"""
self.volumes_page.launch_instance(self.VOLUME_NAME, self.INSTANCE_NAME)
- self.assertTrue(
- self.volumes_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- self.volumes_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ self.volumes_page.find_messages_and_dismiss(), {messages.SUCCESS})
instances_page = self.home_pg.go_to_project_compute_instancespage()
self.assertTrue(instances_page.is_instance_active(self.INSTANCE_NAME))
self.volumes_page = self.home_pg.go_to_project_volumes_volumespage()
@@ -360,9 +338,7 @@ class TestVolumesActions(helpers.TestCase):
self.volumes_page.get_attach_instance(self.VOLUME_NAME))
instances_page = self.home_pg.go_to_project_compute_instancespage()
instances_page.delete_instance(self.INSTANCE_NAME)
- self.assertTrue(
- instances_page.find_message_and_dismiss(messages.INFO))
- self.assertFalse(
- instances_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ instances_page.find_messages_and_dismiss(), {messages.INFO})
self.assertTrue(instances_page.is_instance_deleted(self.INSTANCE_NAME))
self.volumes_page = self.home_pg.go_to_project_volumes_volumespage()
diff --git a/openstack_dashboard/test/integration_tests/tests/test_volumetypes.py b/openstack_dashboard/test/integration_tests/tests/test_volumetypes.py
index 8c3cc3e7c..37b243a84 100644
--- a/openstack_dashboard/test/integration_tests/tests/test_volumetypes.py
+++ b/openstack_dashboard/test/integration_tests/tests/test_volumetypes.py
@@ -33,20 +33,14 @@ class TestAdminVolumeTypes(helpers.AdminTestCase):
volume_types_page = self.home_pg.go_to_admin_volume_volumetypespage()
volume_types_page.create_volume_type(self.VOLUME_TYPE_NAME)
-
- self.assertTrue(
- volume_types_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- volume_types_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volume_types_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(volume_types_page.is_volume_type_present(
self.VOLUME_TYPE_NAME))
volume_types_page.delete_volume_type(self.VOLUME_TYPE_NAME)
-
- self.assertTrue(
- volume_types_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- volume_types_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ volume_types_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(volume_types_page.is_volume_type_deleted(
self.VOLUME_TYPE_NAME))
@@ -65,17 +59,13 @@ class TestQoSSpec(helpers.AdminTestCase):
qos_spec_page = self.home_pg.go_to_admin_volume_volumetypespage()
qos_spec_page.create_qos_spec(self.QOS_SPEC_NAME)
- self.assertTrue(
- qos_spec_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- qos_spec_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ qos_spec_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(qos_spec_page.is_qos_spec_present(self.QOS_SPEC_NAME))
qos_spec_page.delete_qos_specs(self.QOS_SPEC_NAME)
- self.assertTrue(
- qos_spec_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- qos_spec_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ qos_spec_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(qos_spec_page.is_qos_spec_present(self.QOS_SPEC_NAME))
def test_qos_spec_edit_consumer(self):
@@ -96,39 +86,29 @@ class TestQoSSpec(helpers.AdminTestCase):
cinder_consumer = 'back-end'
qos_spec_page.create_qos_spec(qos_spec_name)
- self.assertTrue(
- qos_spec_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- qos_spec_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ qos_spec_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertTrue(qos_spec_page.is_qos_spec_present(qos_spec_name))
qos_spec_page.edit_consumer(qos_spec_name, nova_compute_consumer)
- self.assertTrue(
- qos_spec_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- qos_spec_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ qos_spec_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertEqual(
qos_spec_page.get_consumer(qos_spec_name), nova_compute_consumer)
qos_spec_page.edit_consumer(qos_spec_name, both_consumers)
- self.assertTrue(
- qos_spec_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- qos_spec_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ qos_spec_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertEqual(
qos_spec_page.get_consumer(qos_spec_name), both_consumers)
qos_spec_page.edit_consumer(qos_spec_name, cinder_consumer)
- self.assertTrue(
- qos_spec_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- qos_spec_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ qos_spec_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertEqual(
qos_spec_page.get_consumer(qos_spec_name), cinder_consumer)
qos_spec_page.delete_qos_specs(qos_spec_name)
- self.assertTrue(
- qos_spec_page.find_message_and_dismiss(messages.SUCCESS))
- self.assertFalse(
- qos_spec_page.find_message_and_dismiss(messages.ERROR))
+ self.assertEqual(
+ qos_spec_page.find_messages_and_dismiss(), {messages.SUCCESS})
self.assertFalse(qos_spec_page.is_qos_spec_present(qos_spec_name))
diff --git a/openstack_dashboard/test/test_data/neutron_data.py b/openstack_dashboard/test/test_data/neutron_data.py
index 78bd93df3..024ec79bc 100644
--- a/openstack_dashboard/test/test_data/neutron_data.py
+++ b/openstack_dashboard/test/test_data/neutron_data.py
@@ -34,6 +34,7 @@ def data(TEST):
TEST.routers_with_rules = utils.TestDataContainer()
TEST.routers_with_routes = utils.TestDataContainer()
TEST.floating_ips = utils.TestDataContainer()
+ TEST.port_forwardings = utils.TestDataContainer()
TEST.security_groups = utils.TestDataContainer()
TEST.security_group_rules = utils.TestDataContainer()
TEST.providers = utils.TestDataContainer()
@@ -63,6 +64,7 @@ def data(TEST):
TEST.api_routers = utils.TestDataContainer()
TEST.api_routers_with_routes = utils.TestDataContainer()
TEST.api_floating_ips = utils.TestDataContainer()
+ TEST.api_port_forwardings = utils.TestDataContainer()
TEST.api_security_groups = utils.TestDataContainer()
TEST.api_security_group_rules = utils.TestDataContainer()
TEST.api_pools = utils.TestDataContainer()
@@ -647,6 +649,7 @@ def data(TEST):
'id': '9012cd70-cfae-4e46-b71e-6a409e9e0063',
'fixed_ip_address': None,
'port_id': None,
+ 'port_forwardings': [],
'router_id': None}
TEST.api_floating_ips.add(fip_dict)
fip_with_instance = copy.deepcopy(fip_dict)
@@ -659,6 +662,7 @@ def data(TEST):
'floating_ip_address': '172.16.88.228',
'floating_network_id': ext_net['id'],
'id': 'a97af8f2-3149-4b97-abbd-e49ad19510f7',
+ 'port_forwardings': [],
'fixed_ip_address': assoc_port['fixed_ips'][0]['ip_address'],
'port_id': assoc_port['id'],
'router_id': router_dict['id']}
@@ -668,6 +672,46 @@ def data(TEST):
'instance_type': 'compute'})
TEST.floating_ips.add(neutron.FloatingIp(fip_with_instance))
+ # port forwardings
+
+ TEST.api_port_forwardings.add({
+ "protocol": "tcp",
+ "internal_ip_address": "10.0.0.11",
+ "internal_port": 25,
+ "internal_port_id": "1238be08-a2a8-4b8d-addf-fb5e2250e480",
+ "external_port": 2230,
+ "internal_port_range": "25:25",
+ "external_port_range": "2230:2230",
+ "description": "",
+ "id": "e0a0274e-4d19-4eab-9e12-9e77a8caf3ea"
+ })
+ TEST.api_port_forwardings.add({
+ "protocol": "tcp",
+ "internal_port": 80,
+ "external_port": 8080,
+ "internal_ip_address": "10.0.0.12",
+ "internal_port_range": "80:90",
+ "internal_port_id": "2057ec54-8be2-11eb-8dcd-0242ac130003",
+ "external_port_range": "8080:8090",
+ "description": "using port ranges",
+ "id": "0f23a90a-8be2-11eb-8dcd-0242ac130003"
+ })
+ TEST.api_port_forwardings.add({
+ "protocol": "tcp",
+ "internal_ip_address": "10.0.0.24",
+ "internal_port": 25,
+ "internal_port_id": "070ef0b2-0175-4299-be5c-01fea8cca522",
+ "external_port": 2229,
+ "internal_port_range": "25:25",
+ "external_port_range": "2229:2229",
+ "description": "Some description",
+ "id": "1798dc82-c0ed-4b79-b12d-4c3c18f90eb2"
+ })
+
+ TEST.port_forwardings.add(neutron.PortForwarding(
+ TEST.api_port_forwardings.first(), fip_dict['id']
+ ))
+
# Security group.
sec_group_1 = {'tenant_id': '1',
diff --git a/openstack_dashboard/test/unit/api/test_neutron.py b/openstack_dashboard/test/unit/api/test_neutron.py
index 19bde8df1..4d9d1e8d0 100644
--- a/openstack_dashboard/test/unit/api/test_neutron.py
+++ b/openstack_dashboard/test/unit/api/test_neutron.py
@@ -2321,6 +2321,81 @@ class NeutronApiSecurityGroupTests(test.APIMockTestCase):
self.qclient.update_port.assert_has_calls(expected_calls)
+class NeutronApiFloatingIpPortForwardingTest(test.APIMockTestCase):
+ def setUp(self):
+ super().setUp()
+ neutronclient = mock.patch.object(api.neutron, 'neutronclient').start()
+ self.client_mock = neutronclient.return_value
+
+ def test_port_forwarding_list(self):
+ pfws = {'port_forwardings': self.api_port_forwardings.list()}
+ self.client_mock.list_port_forwardings.return_value = pfws
+ response = api.neutron.floating_ip_port_forwarding_list(
+ self.request, 'fip')
+ for i in range(len(response)):
+ resp_val = response[i]
+ expected_val = pfws['port_forwardings'][i]
+ for attr in resp_val.to_dict():
+ self.assertEqual(getattr(resp_val, attr), expected_val[attr])
+
+ self.client_mock.list_port_forwardings.assert_called_once_with('fip')
+
+ def test_port_forwarding_get(self):
+ pfw = self.api_port_forwardings.first()
+ pfw_id = pfw['id']
+ self.client_mock.show_port_forwarding.return_value = pfw
+ response = api.neutron.floating_ip_port_forwarding_get(
+ self.request, 'fip', pfw_id)
+ for attr in response.to_dict():
+ self.assertEqual(getattr(response, attr), pfw[attr])
+ self.client_mock.show_port_forwarding.assert_called_once_with(
+ 'fip', pfw_id)
+
+ def test_port_forwarding_create(self):
+ pfw_resp_mock = {'port_forwarding': self.api_port_forwardings.first()}
+ pfw_expected = self.port_forwardings.get().to_dict()
+ pfw = {
+ "protocol": "tcp",
+ "internal_ip_address": "10.0.0.24",
+ "internal_port": 25,
+ "internal_port_id": "070ef0b2-0175-4299-be5c-01fea8cca522",
+ "external_port": 2229,
+ "description": "Some description",
+ }
+ self.client_mock.create_port_forwarding.return_value = pfw_resp_mock
+ response = api.neutron.floating_ip_port_forwarding_create(
+ self.request, 'fip', **pfw)
+ for attr in response.to_dict():
+ self.assertEqual(getattr(response, attr), pfw_expected[attr])
+ self.client_mock.create_port_forwarding.assert_called_once_with(
+ 'fip', {'port_forwarding': pfw})
+
+ def test_port_forwarding_update(self):
+ pfw_resp_mock = {'port_forwarding': self.api_port_forwardings.first()}
+ pfw_expected = self.port_forwardings.get().to_dict()
+ pfw_id = pfw_resp_mock['port_forwarding']['id']
+ pfw = {
+ "protocol": "tcp",
+ "internal_port": 25,
+ "description": "Some description",
+ }
+ self.client_mock.update_port_forwarding.return_value = pfw_resp_mock
+ response = api.neutron.floating_ip_port_forwarding_update(
+ self.request, 'fip', portforwarding_id=pfw_id, **pfw)
+ for attr in response.to_dict():
+ self.assertEqual(getattr(response, attr), pfw_expected[attr])
+ self.client_mock.update_port_forwarding.assert_called_once_with(
+ 'fip', pfw_id, {'port_forwarding': pfw})
+
+ def test_port_forwarding_delete(self):
+ pfw_id = self.api_port_forwardings.first()['id']
+ self.client_mock.delete_port_forwarding.return_value = None
+ api.neutron.floating_ip_port_forwarding_delete(
+ self.request, 'fip', pfw_id)
+ self.client_mock.delete_port_forwarding.assert_called_once_with(
+ 'fip', pfw_id)
+
+
class NeutronApiFloatingIpTests(test.APIMockTestCase):
def setUp(self):