diff options
Diffstat (limited to 'openstack_dashboard')
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): |