From 34b0abb4cae37f7d96ca2fadeaac25ff93089d63 Mon Sep 17 00:00:00 2001 From: Jakob Meng Date: Wed, 28 Sep 2022 15:49:04 +0200 Subject: [PATCH] Refactored floating_ip and floating_ip_info modules Use service proxies from openstacksdk wherever reasonable in order to reduce calls to OpenStack API. Renamed floating_ip_info's attribute 'project_id' to 'project' to be consistent with other attributes and added the former as an alias to keep backward compatibility. The latter can now also be used to search for floating ips by project names, not only project ids. Sorted argument specs and documentation of both modules. Reworked integration tests, e.g. replaced references to server's 'addresses' attribute with calls to our port_info and floating_ip_info modules. Also reformatted tests and added assertion on return values. Merged integration tests of floating_ip_info module into floating_ip module, because the former does not create any floating ips and assumes that they have been created earlier. For Zuul CI job ansible-collections-openstack-functional-devstack-\ releases to pass, the minimum required openstacksdk release must be 0.102.0 because [1],[2],[3],[4] are available since that release only. [1] https://review.opendev.org/c/openstack/openstacksdk/+/851976 [2] https://github.com/openstack/openstacksdk/commit/0ded7ac398843b6b1ce46668eb3b45ce02628428 [3] https://review.opendev.org/c/openstack/openstacksdk/+/859672 [4] https://github.com/openstack/openstacksdk/commit/2535ba7a28ae081f064ae20f9ddb19f48a973625 Change-Id: I129f866e7ed8d5c0499c93e78ebbe2c424e09423 --- .zuul.yaml | 3 +- ci/roles/floating_ip/defaults/main.yml | 21 + ci/roles/floating_ip/tasks/main.yml | 584 +++++++++++++---------- ci/roles/floating_ip_info/tasks/main.yml | 21 - ci/run-collection.yml | 3 +- plugins/modules/floating_ip.py | 573 ++++++++++++++-------- plugins/modules/floating_ip_info.py | 94 ++-- 7 files changed, 774 insertions(+), 525 deletions(-) create mode 100644 ci/roles/floating_ip/defaults/main.yml delete mode 100644 ci/roles/floating_ip_info/tasks/main.yml diff --git a/.zuul.yaml b/.zuul.yaml index 1479e9ba..6d13ea50 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -71,7 +71,7 @@ dns dns_zone_info endpoint - floating_ip_info + floating_ip host_aggregate identity_domain_info identity_group @@ -112,7 +112,6 @@ user_role volume # failing tags - # floating_ip # neutron_rbac - job: diff --git a/ci/roles/floating_ip/defaults/main.yml b/ci/roles/floating_ip/defaults/main.yml new file mode 100644 index 00000000..638b179f --- /dev/null +++ b/ci/roles/floating_ip/defaults/main.yml @@ -0,0 +1,21 @@ +--- +expected_fields: + - created_at + - description + - dns_domain + - dns_name + - fixed_ip_address + - floating_ip_address + - floating_network_id + - id + - name + - port_details + - port_id + - project_id + - qos_policy_id + - revision_number + - router_id + - status + - subnet_id + - tags + - updated_at diff --git a/ci/roles/floating_ip/tasks/main.yml b/ci/roles/floating_ip/tasks/main.yml index ebb41ec7..d846e28e 100644 --- a/ci/roles/floating_ip/tasks/main.yml +++ b/ci/roles/floating_ip/tasks/main.yml @@ -1,5 +1,4 @@ --- -# Prepare environment - name: Gather information about public network openstack.cloud.networks_info: cloud: "{{ cloud }}" @@ -12,109 +11,112 @@ - name: Create external network openstack.cloud.network: - cloud: "{{ cloud }}" - state: present - name: ansible_external - external: true + cloud: "{{ cloud }}" + state: present + name: ansible_external + external: true - name: Create external subnet openstack.cloud.subnet: - cloud: "{{ cloud }}" - state: present - network_name: ansible_external - name: ansible_external_subnet - cidr: 10.6.6.0/24 + cloud: "{{ cloud }}" + state: present + network_name: ansible_external + name: ansible_external_subnet + cidr: 10.6.6.0/24 - name: Create external port 1 openstack.cloud.port: - cloud: "{{ cloud }}" - state: present - name: ansible_external_port1 - network: ansible_external - fixed_ips: - - ip_address: 10.6.6.50 + cloud: "{{ cloud }}" + state: present + name: ansible_external_port1 + network: ansible_external + fixed_ips: + - ip_address: 10.6.6.50 - name: Create external port 2 openstack.cloud.port: - cloud: "{{ cloud }}" - state: present - name: ansible_external_port2 - network: ansible_external - fixed_ips: - - ip_address: 10.6.6.51 + cloud: "{{ cloud }}" + state: present + name: ansible_external_port2 + network: ansible_external + fixed_ips: + - ip_address: 10.6.6.51 - name: Create internal network openstack.cloud.network: - cloud: "{{ cloud }}" - state: present - name: ansible_internal - external: false + cloud: "{{ cloud }}" + state: present + name: ansible_internal + external: false - name: Create internal subnet openstack.cloud.subnet: - cloud: "{{ cloud }}" - state: present - network_name: ansible_internal - name: ansible_internal_subnet - cidr: 10.7.7.0/24 + cloud: "{{ cloud }}" + state: present + network_name: ansible_internal + name: ansible_internal_subnet + cidr: 10.7.7.0/24 - name: Create internal port 1 openstack.cloud.port: - cloud: "{{ cloud }}" - state: present - name: ansible_internal_port1 - network: ansible_internal - fixed_ips: - - ip_address: 10.7.7.100 + cloud: "{{ cloud }}" + state: present + name: ansible_internal_port1 + network: ansible_internal + fixed_ips: + - ip_address: 10.7.7.100 + register: port1 - name: Create internal port 2 openstack.cloud.port: - cloud: "{{ cloud }}" - state: present - name: ansible_internal_port2 - network: ansible_internal - fixed_ips: - - ip_address: 10.7.7.101 + cloud: "{{ cloud }}" + state: present + name: ansible_internal_port2 + network: ansible_internal + fixed_ips: + - ip_address: 10.7.7.101 + register: port2 - name: Create internal port 3 openstack.cloud.port: - cloud: "{{ cloud }}" - state: present - name: ansible_internal_port3 - network: ansible_internal - fixed_ips: - - ip_address: 10.7.7.102 + cloud: "{{ cloud }}" + state: present + name: ansible_internal_port3 + network: ansible_internal + fixed_ips: + - ip_address: 10.7.7.102 + register: port3 - name: Create router 1 openstack.cloud.router: - cloud: "{{ cloud }}" - state: present - name: ansible_router1 - network: ansible_external - external_fixed_ips: - - subnet: ansible_external_subnet - ip: 10.6.6.10 - interfaces: - - net: ansible_internal - subnet: ansible_internal_subnet - portip: 10.7.7.1 + cloud: "{{ cloud }}" + state: present + name: ansible_router1 + network: ansible_external + external_fixed_ips: + - subnet: ansible_external_subnet + ip: 10.6.6.10 + interfaces: + - net: ansible_internal + subnet: ansible_internal_subnet + portip: 10.7.7.1 # Router 2 is required for the simplest, first test that assigns a new floating IP to server # from first available external network or nova pool which is DevStack's public network - name: Create router 2 openstack.cloud.router: - cloud: "{{ cloud }}" - state: present - name: ansible_router2 - network: public - interfaces: - - net: ansible_internal - subnet: ansible_internal_subnet - portip: 10.7.7.10 + cloud: "{{ cloud }}" + state: present + name: ansible_router2 + network: public + interfaces: + - net: ansible_internal + subnet: ansible_internal_subnet + portip: 10.7.7.10 - name: Get all floating ips openstack.cloud.floating_ip_info: - cloud: "{{ cloud }}" + cloud: "{{ cloud }}" register: fips - name: Check if public network has any floating ips @@ -138,232 +140,286 @@ when: fips.floating_ips|length == 0 or "10.6.6.150" not in fips.floating_ips|map(attribute="floating_ip_address")|list -- name: Create server with one nic +- name: Create server 1 with one nic openstack.cloud.server: - cloud: "{{ cloud }}" - state: present - name: ansible_server1 - image: "{{ image }}" - flavor: m1.tiny - nics: - # one nic only else simple, first floating ip test does not work - - port-name: ansible_internal_port1 - auto_ip: false - wait: true - -- name: Get info about server - openstack.cloud.server_info: cloud: "{{ cloud }}" - server: ansible_server1 - register: info + state: present + name: ansible_server1 + image: "{{ image }}" + flavor: m1.tiny + nics: + # one nic only else simple, first floating ip test does not work + - port-name: ansible_internal_port1 + auto_ip: false + wait: true + register: server1 -- name: Assert one internal port and no floating ips on server 1 +- name: Get server 1 ports + openstack.cloud.port_info: + cloud: "{{ cloud }}" + filters: + device_id: "{{ server1.server.id }}" + register: server1_ports + +- name: Assert one fixed ip on server 1 # If this assertion fails because server has an public ipv4 address (public_v4) then make sure # that no floating ip on public network is associated with "10.7.7.100" before running this role assert: that: - - info.servers|length == 1 - - info.servers.0.public_v4|length == 0 - - info.servers.0.public_v6|length == 0 - - info.servers.0.addresses.ansible_internal|length == 1 - - info.servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list == ["10.7.7.100"] + - server1_ports.ports|length == 1 + - server1_ports.ports|sum(attribute='fixed_ips', start=[])|map(attribute='ip_address')|sort|list == + ["10.7.7.100"] -- name: Create server with two nics +- name: Create server 2 with two nics openstack.cloud.server: - cloud: "{{ cloud }}" - state: present - name: ansible_server2 - image: "{{ image }}" - flavor: m1.tiny - nics: - - port-name: ansible_internal_port2 - - port-name: ansible_internal_port3 - auto_ip: false - wait: true - -- name: Get info about server - openstack.cloud.server_info: cloud: "{{ cloud }}" - server: ansible_server2 - register: info + state: present + name: ansible_server2 + image: "{{ image }}" + flavor: m1.tiny + nics: + - port-name: ansible_internal_port2 + - port-name: ansible_internal_port3 + auto_ip: false + wait: true + register: server2 -- name: Assert two internal ports and no floating ips on server 2 +- name: Get server 2 ports + openstack.cloud.port_info: + cloud: "{{ cloud }}" + filters: + device_id: "{{ server2.server.id }}" + register: server2_ports + +- name: Assert two fixed ips on server 2 assert: that: - - info.servers|length == 1 - - info.servers.0.public_v4|length == 0 - - info.servers.0.public_v6|length == 0 - - info.servers.0.addresses.ansible_internal|length == 2 - - info.servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list == + - server2_ports.ports|length == 2 + - server2_ports.ports|sum(attribute='fixed_ips', start=[])|map(attribute='ip_address')|sort|list == ["10.7.7.101", "10.7.7.102"] -# Tests - name: Assign new floating IP to server from first available external network or nova pool openstack.cloud.floating_ip: - cloud: "{{ cloud }}" - state: present - server: ansible_server1 - wait: true - -- name: Get info about server - openstack.cloud.server_info: cloud: "{{ cloud }}" + state: present server: ansible_server1 - register: info + wait: yes -- name: Assert one internal port and one floating ip on server 1 +- name: Get floating ip attached to server 1 + openstack.cloud.floating_ip_info: + cloud: "{{ cloud }}" + port: "{{ port1.port.id }}" + register: server1_fips + # openstacksdk has issues with waiting hence we simply retry + retries: 10 + delay: 3 + until: server1_fips.floating_ips|length == 1 + +- name: Assert fixed ip and floating ip attached to server 1 assert: that: - - info.servers.0.addresses.ansible_internal|length == 2 - - info.servers.0.addresses.ansible_internal|map(attribute="OS-EXT-IPS:type")|sort|list == - ["fixed", "floating"] + - server1_ports.ports|length == 1 + - server1_ports.ports|sum(attribute='fixed_ips', start=[])|map(attribute='ip_address')|sort|list == + ["10.7.7.100"] + - server1_fips.floating_ips|length == 1 + - server1_fips.floating_ips|map(attribute='fixed_ip_address')|sort|list == + ["10.7.7.100"] -- name: Detach floating IP from server +- name: Assert return values of floating_ip_info module + assert: + that: + - server1_fips is success + - server1_fips is not changed + - server1_fips.floating_ips + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(server1_fips.floating_ips[0].keys())|length == 0 + +- name: Assign floating ip to server 1 again openstack.cloud.floating_ip: - cloud: "{{ cloud }}" - state: absent - server: ansible_server1 - network: public - floating_ip_address: "{{ (info.servers.0.addresses.ansible_internal| - selectattr('OS-EXT-IPS:type', '==', 'floating')|map(attribute='addr')|list)[0] }}" - -- name: Get info about server - openstack.cloud.server_info: cloud: "{{ cloud }}" + state: present server: ansible_server1 - register: info - # When detaching a floating ip from an instance there might be a delay until openstack.cloud.server_info - # does not list it any more in info.servers.0.addresses.ansible_internal, so retry if necessary. + wait: true + register: floating_ip + +- name: Assert floating ip on server 1 has not changed + assert: + that: floating_ip is not changed + +- name: Assert return values of floating_ip module + assert: + that: + - floating_ip.floating_ip + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(floating_ip.floating_ip.keys())|length == 0 + +- name: Detach floating ip from server 1 + openstack.cloud.floating_ip: + cloud: "{{ cloud }}" + state: absent + server: ansible_server1 + network: public + floating_ip_address: "{{ server1_fips.floating_ips.0.floating_ip_address }}" + +- name: Wait until floating ip is detached from server 1 + openstack.cloud.floating_ip_info: + cloud: "{{ cloud }}" + port: "{{ port1.port.id }}" + register: server1_fips + # When detaching a floating ip from an instance there might be a delay until it is not listed anymore retries: 10 delay: 3 - until: info.servers.0.addresses.ansible_internal|length == 1 + until: server1_fips.floating_ips|length == 0 -- name: Assert one internal port on server 1 - assert: - that: - - info.servers.0.addresses.ansible_internal|length == 1 - - info.servers.0.addresses.ansible_internal|map(attribute="addr")|list == ["10.7.7.100"] +- name: Find all floating ips for debugging + openstack.cloud.floating_ip_info: + cloud: "{{ cloud }}" + register: fips -- name: Assign floating IP to server - openstack.cloud.floating_ip: - cloud: "{{ cloud }}" - state: present - reuse: yes - server: ansible_server2 - network: public - fixed_address: 10.7.7.101 - wait: true +- name: Print all floating ips for debugging + debug: var=fips -- name: Get info about server +- name: Find all servers for debugging openstack.cloud.server_info: cloud: "{{ cloud }}" - server: ansible_server2 - register: info + register: servers -- name: Assert two internal ports and one floating ip on server 2 +- name: Print all servers for debugging + debug: var=servers + +- name: Assign floating ip to server 2 + openstack.cloud.floating_ip: + cloud: "{{ cloud }}" + state: present + reuse: no # else fixed_address will be ignored + server: ansible_server2 + network: public + fixed_address: "{{ port2.port.fixed_ips[0].ip_address }}" + wait: true + register: server2_fip + +- name: Assert floating ip attached to server 2 assert: that: - - info.servers.0.addresses.ansible_internal|length == 3 - - info.servers.0.addresses.ansible_internal|map(attribute="OS-EXT-IPS:type")|sort|list == - ["fixed", "fixed", "floating"] + - server2_fip.floating_ip -- name: Assign a second, specific floating IP to server - openstack.cloud.floating_ip: - cloud: "{{ cloud }}" - state: present - reuse: yes - server: ansible_server2 - network: ansible_external - fixed_address: 10.7.7.102 - floating_ip_address: "10.6.6.150" +- name: Find all floating ips for debugging + openstack.cloud.floating_ip_info: + cloud: "{{ cloud }}" + register: fips -# We cannot wait for second floating ip to be attached because OpenStackSDK checks only for first floating ip -# Ref.: https://github.com/openstack/openstacksdk/blob/e0372b72af8c5f471fc17e53434d7a814ca958bd/openstack/cloud/_floating_ip.py#L733 +- name: Print all floating ips for debugging + debug: var=fips -- name: Get info about server +- name: Find all servers for debugging openstack.cloud.server_info: cloud: "{{ cloud }}" + register: servers + +- name: Print all servers for debugging + debug: var=servers + +- name: Get floating ip attached to server 2 + openstack.cloud.floating_ip_info: + cloud: "{{ cloud }}" + port: "{{ port2.port.id }}" + register: server2_fips + +- name: Assert floating ip attached to server 2 + assert: + that: + - server2_fips.floating_ips|length == 1 + - server2_fips.floating_ips|map(attribute='fixed_ip_address')|sort|list == + ["10.7.7.101"] + +- name: Assign a second, specific floating ip to server 2 + openstack.cloud.floating_ip: + cloud: "{{ cloud }}" + state: present + reuse: no # else fixed_address will be ignored server: ansible_server2 - register: info - # retry because we cannot wait for second floating ip + network: ansible_external + fixed_address: "{{ port3.port.fixed_ips[0].ip_address }}" + floating_ip_address: "10.6.6.150" + wait: no # does not work anyway and causes issues in local testing + +- name: Get floating ip attached to server 2 + openstack.cloud.floating_ip_info: + cloud: "{{ cloud }}" + port: "{{ port3.port.id }}" + register: server2_fips + # We cannot wait for second floating ip to be attached because OpenStackSDK checks only for first floating ip + # Ref.: https://github.com/openstack/openstacksdk/blob/e0372b72af8c5f471fc17e53434d7a814ca958bd/openstack/cloud/_floating_ip.py#L733 retries: 10 delay: 3 - until: info.servers.0.addresses.ansible_internal|length == 4 + until: server2_fips.floating_ips|length == 1 -- name: Assert two internal ports and two floating ips on server 2 +- name: Assert second floating ip attached to server 2 assert: that: - - info.servers.0.addresses.ansible_internal|length == 4 - - ("10.6.6.150" in info.servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list) + - server2_fips.floating_ips|length == 1 + - server2_fips.floating_ips|map(attribute='fixed_ip_address')|sort|list == + ["10.7.7.102"] -- name: Detach second floating IP from server +- name: Detach second floating ip from server 2 openstack.cloud.floating_ip: - cloud: "{{ cloud }}" - state: absent - server: ansible_server2 - network: ansible_external - floating_ip_address: "10.6.6.150" - -- name: Get info about server - openstack.cloud.server_info: cloud: "{{ cloud }}" + state: absent server: ansible_server2 - register: info - # When detaching a floating ip from an instance there might be a delay until openstack.cloud.server_info - # does not list it any more in info.servers.0.addresses.ansible_internal, so retry if necessary. + network: ansible_external + floating_ip_address: "10.6.6.150" + +- name: Wait until second floating ip is detached from server 2 + openstack.cloud.floating_ip_info: + cloud: "{{ cloud }}" + port: "{{ port3.port.id }}" + register: server2_fips + # When detaching a floating ip from an instance there might be a delay until it is not listed anymore retries: 10 delay: 3 - until: info.servers.0.addresses.ansible_internal|length == 3 + until: server2_fips.floating_ips|length == 0 -- name: Assert two internal ports and one floating ip on server 2 - assert: - that: - - info.servers.0.addresses.ansible_internal|length == 3 - -- name: Detach remaining floating IP from server - openstack.cloud.floating_ip: - cloud: "{{ cloud }}" - state: absent - server: ansible_server2 - network: public - floating_ip_address: "{{ (info.servers.0.addresses.ansible_internal| - selectattr('OS-EXT-IPS:type', '==', 'floating')|map(attribute='addr')|list)[0] }}" - -- name: Get info about server - openstack.cloud.server_info: +- name: Get first floating ip attached to server 2 + openstack.cloud.floating_ip_info: cloud: "{{ cloud }}" + port: "{{ port2.port.id }}" + register: server2_fips + +- name: Detach remaining floating ip from server 2 + openstack.cloud.floating_ip: + cloud: "{{ cloud }}" + state: absent server: ansible_server2 - register: info - # When detaching a floating ip from an instance there might be a delay until openstack.cloud.server_info - # does not list it any more in info.servers.0.addresses.ansible_internal, so retry if necessary. + network: public + floating_ip_address: "{{ server2_fips.floating_ips.0.floating_ip_address }}" + +- name: Wait until first floating ip is detached from server 2 + openstack.cloud.floating_ip_info: + cloud: "{{ cloud }}" + port: "{{ port2.port.id }}" + register: server2_fips + # When detaching a floating ip from an instance there might be a delay until it is not listed anymore retries: 10 delay: 3 - until: info.servers.0.addresses.ansible_internal|length == 2 + until: server2_fips.floating_ips|length == 0 -- name: Assert two internal ports on server 2 - assert: - that: - - info.servers.0.addresses.ansible_internal|length == 2 - - info.servers.0.addresses.ansible_internal|map(attribute="addr")|list == ["10.7.7.101", "10.7.7.102"] - -# Clean environment - name: Delete server with two nics openstack.cloud.server: - cloud: "{{ cloud }}" - state: absent - name: ansible_server2 - wait: true + cloud: "{{ cloud }}" + state: absent + name: ansible_server2 + wait: true - name: Delete server with one nic openstack.cloud.server: - cloud: "{{ cloud }}" - state: absent - name: ansible_server1 - wait: true + cloud: "{{ cloud }}" + state: absent + name: ansible_server1 + wait: true - name: Get all floating ips openstack.cloud.floating_ip_info: - cloud: "{{ cloud }}" + cloud: "{{ cloud }}" register: fips # TODO: Replace with appropriate Ansible module once available @@ -381,8 +437,8 @@ - name: Get remaining floating ips on external network openstack.cloud.floating_ip_info: - cloud: "{{ cloud }}" - floating_network: ansible_external + cloud: "{{ cloud }}" + floating_network: ansible_external register: fips # TODO: Replace with appropriate Ansible module once available @@ -396,71 +452,71 @@ # Remove routers after floating ips have been detached and disassociated else removal fails with # Error detaching interface from router ***: Client Error for url: ***, -# Router interface for subnet *** on router *** cannot be deleted, +# Router interface for subnet *** on router *** cannot be deleted, # as it is required by one or more floating IPs. - name: Delete router 2 openstack.cloud.router: - cloud: "{{ cloud }}" - state: absent - name: ansible_router2 + cloud: "{{ cloud }}" + state: absent + name: ansible_router2 - name: Delete router 1 openstack.cloud.router: - cloud: "{{ cloud }}" - state: absent - name: ansible_router1 + cloud: "{{ cloud }}" + state: absent + name: ansible_router1 - name: Delete internal port 3 openstack.cloud.port: - cloud: "{{ cloud }}" - state: absent - name: ansible_internal_port3 + cloud: "{{ cloud }}" + state: absent + name: ansible_internal_port3 - name: Delete internal port 2 openstack.cloud.port: - cloud: "{{ cloud }}" - state: absent - name: ansible_internal_port2 + cloud: "{{ cloud }}" + state: absent + name: ansible_internal_port2 - name: Delete internal port 1 openstack.cloud.port: - cloud: "{{ cloud }}" - state: absent - name: ansible_internal_port1 + cloud: "{{ cloud }}" + state: absent + name: ansible_internal_port1 - name: Delete internal subnet openstack.cloud.subnet: - cloud: "{{ cloud }}" - state: absent - name: ansible_internal_subnet + cloud: "{{ cloud }}" + state: absent + name: ansible_internal_subnet - name: Delete internal network openstack.cloud.network: - cloud: "{{ cloud }}" - state: absent - name: ansible_internal + cloud: "{{ cloud }}" + state: absent + name: ansible_internal - name: Delete external port 2 openstack.cloud.port: - cloud: "{{ cloud }}" - state: absent - name: ansible_external_port2 + cloud: "{{ cloud }}" + state: absent + name: ansible_external_port2 - name: Delete external port 1 openstack.cloud.port: - cloud: "{{ cloud }}" - state: absent - name: ansible_external_port1 + cloud: "{{ cloud }}" + state: absent + name: ansible_external_port1 - name: Delete external subnet openstack.cloud.subnet: - cloud: "{{ cloud }}" - state: absent - name: ansible_external_subnet + cloud: "{{ cloud }}" + state: absent + name: ansible_external_subnet - name: Delete external network openstack.cloud.network: - cloud: "{{ cloud }}" - state: absent - name: ansible_external + cloud: "{{ cloud }}" + state: absent + name: ansible_external diff --git a/ci/roles/floating_ip_info/tasks/main.yml b/ci/roles/floating_ip_info/tasks/main.yml deleted file mode 100644 index 916c2cb2..00000000 --- a/ci/roles/floating_ip_info/tasks/main.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -- name: Getting info about allocated ips - openstack.cloud.floating_ip_info: - cloud: "{{ cloud }}" - register: fips - -- name: assert result - assert: - that: - - fips is success - - fips is not changed - -- name: assert fields - when: fips.floating_ips|length > 0 - assert: - that: - # allow new fields to be introduced but prevent fields from being removed - - '["created_at", "description", "dns_domain", "dns_name", "fixed_ip_address", "floating_ip_address", - "floating_network_id", "id", "name", "port_details", "port_id", "project_id", "qos_policy_id", - "revision_number", "router_id", "status", "subnet_id", "tags", "updated_at"]| - difference(fips.floating_ips.0.keys())|length == 0' diff --git a/ci/run-collection.yml b/ci/run-collection.yml index 31b8e99a..70c37784 100644 --- a/ci/run-collection.yml +++ b/ci/run-collection.yml @@ -17,7 +17,7 @@ tags: dns when: sdk_version is version(0.28, '>=') - { role: endpoint, tags: endpoint } - - { role: floating_ip_info, tags: floating_ip_info } + - { role: floating_ip, tags: floating_ip } - { role: host_aggregate, tags: host_aggregate } - { role: identity_domain_info, tags: identity_domain_info } - { role: identity_group, tags: identity_group } @@ -67,5 +67,4 @@ - { role: volume, tags: volume } - role: loadbalancer tags: loadbalancer - - { role: floating_ip, tags: floating_ip } - { role: quota, tags: quota } diff --git a/plugins/modules/floating_ip.py b/plugins/modules/floating_ip.py index 29e18a78..fbfda7f1 100644 --- a/plugins/modules/floating_ip.py +++ b/plugins/modules/floating_ip.py @@ -12,66 +12,59 @@ short_description: Add/Remove floating IP from an instance description: - Add or Remove a floating IP to an instance. - Returns the floating IP when attaching only if I(wait=true). - - When detaching a floating IP there might be a delay until an instance does not list the floating IP any more. + - When detaching a floating IP there might be a delay until an instance + does not list the floating IP any more. options: - server: - description: - - The name or ID of the instance to which the IP address - should be assigned. - required: true - type: str - network: - description: - - The name or ID of a neutron external network or a nova pool name. - type: str - floating_ip_address: - description: - - A floating IP address to attach or to detach. When I(state) is present - can be used to specify a IP address to attach. I(floating_ip_address) - requires I(network) to be set. - type: str - reuse: - description: - - When I(state) is present, and I(floating_ip_address) is not present, - this parameter can be used to specify whether we should try to reuse - a floating IP address already allocated to the project. - type: bool - default: 'no' fixed_address: description: - To which fixed IP of server the floating IP address should be attached to. type: str + floating_ip_address: + description: + - A floating IP address to attach or to detach. When I(state) is + present can be used to specify a IP address to attach. + I(floating_ip_address) requires I(network) to be set. + type: str nat_destination: description: - The name or id of a neutron private network that the fixed IP to attach floating IP is on aliases: ["fixed_network", "internal_network"] type: str - wait: + network: description: - - When attaching a floating IP address, specify whether to wait for it to appear as attached. - - Must be set to C(yes) for the module to return the value of the floating IP when attaching. + - The name or ID of a neutron external network or a nova pool name. + type: str + purge: + description: + - When I(state) is absent, indicates whether or not to delete the + floating IP completely, or only detach it from the server. + Default is to detach only. type: bool default: 'no' - timeout: + reuse: description: - - Time to wait for an IP address to appear as attached. See wait. - required: false - default: 60 - type: int + - When I(state) is present, and I(floating_ip_address) is not present, + this parameter can be used to specify whether we should try to reuse + a floating IP address already allocated to the project. + - When I(reuse) is C(true), I(network) is defined and + I(floating_ip_address) is undefined, then C(nat_destination) and + C(fixed_address) will be ignored. + type: bool + default: 'no' + server: + description: + - The name or ID of the instance to which the IP address + should be assigned. + required: true + type: str state: description: - Should the resource be present or absent. choices: [present, absent] default: present type: str - purge: - description: - - When I(state) is absent, indicates whether or not to delete the floating - IP completely, or only detach it from the server. Default is to detach only. - type: bool - default: 'no' requirements: - "python >= 3.6" - "openstacksdk" @@ -120,182 +113,384 @@ EXAMPLES = ''' server: cattle001 ''' +RETURN = ''' +floating_ip: + description: Dictionary describing the floating ip address. + type: dict + returned: success + contains: + created_at: + description: Timestamp at which the floating IP was assigned. + type: str + description: + description: The description of a floating IP. + type: str + dns_domain: + description: The DNS domain. + type: str + dns_name: + description: The DNS name. + type: str + fixed_ip_address: + description: The fixed IP address associated with a floating IP address. + type: str + floating_ip_address: + description: The IP address of a floating IP. + type: str + floating_network_id: + description: The id of the network associated with a floating IP. + type: str + id: + description: Id of the floating ip. + type: str + name: + description: Name of the floating ip. + type: str + port_details: + description: | + The details of the port that this floating IP associates + with. Present if C(fip-port-details) extension is loaded. + type: dict + port_id: + description: The port ID floating ip associated with. + type: str + project_id: + description: The ID of the project this floating IP is associated with. + type: str + qos_policy_id: + description: The ID of the QoS policy attached to the floating IP. + type: str + revision_number: + description: Revision number. + type: str + router_id: + description: The id of the router floating ip associated with. + type: str + status: + description: | + The status of a floating IP, which can be 'ACTIVE' or 'DOWN'. + type: str + subnet_id: + description: The id of the subnet the floating ip associated with. + type: str + tags: + description: List of tags. + type: list + elements: str + updated_at: + description: Timestamp at which the floating IP was last updated. + type: str +''' + from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule -import itertools class NetworkingFloatingIPModule(OpenStackModule): argument_spec = dict( + fixed_address=dict(), + floating_ip_address=dict(), + nat_destination=dict(aliases=['fixed_network', 'internal_network']), + network=dict(), + purge=dict(type='bool', default=False), + reuse=dict(type='bool', default=False), server=dict(required=True), state=dict(default='present', choices=['absent', 'present']), - network=dict(), - floating_ip_address=dict(), - reuse=dict(type='bool', default=False), - fixed_address=dict(), - nat_destination=dict(aliases=['fixed_network', 'internal_network']), - wait=dict(type='bool', default=False), - timeout=dict(type='int', default=60), - purge=dict(type='bool', default=False), ) module_kwargs = dict( required_if=[ ['state', 'absent', ['floating_ip_address']] ], - required_by=dict( - floating_ip_address=('network',) - ) + required_by={ + 'floating_ip_address': ('network'), + } ) - def _get_floating_ip(self, floating_ip_address): - f_ips = self.conn.search_floating_ips( - filters={'floating_ip_address': floating_ip_address}) + def run(self): + self._init() + if self.params['state'] == 'present': + self._create_and_attach() - if not f_ips: - return None + else: # self.params['state'] == 'absent' + self._detach_and_delete() - return f_ips[0] + def _create_and_attach(self): + changed = False + fixed_address = self.params['fixed_address'] + floating_ip_address = self.params['floating_ip_address'] + nat_destination_name_or_id = self.params['nat_destination'] + network_id = self.network['id'] if self.network else None - def _list_floating_ips(self, server): - return itertools.chain.from_iterable([ - (addr['addr'] for addr in server.addresses[net] if addr['OS-EXT-IPS:type'] == 'floating') - for net in server.addresses - ]) + ips = self._find_ips( + server=self.server, + floating_ip_address=floating_ip_address, + network_id=network_id, + fixed_address=fixed_address, + nat_destination_name_or_id=nat_destination_name_or_id) - def _match_floating_ip(self, server, - floating_ip_address, - network_id, - fixed_address, - nat_destination): + # First floating ip satisfies our requirements + ip = ips[0] if ips else None if floating_ip_address: - return self._get_floating_ip(floating_ip_address) - elif not fixed_address and nat_destination: - nat_destination_name = self.conn.get_network(nat_destination)['name'] - return next( - (self._get_floating_ip(addr['addr']) - for addr in server.addresses.get(nat_destination_name, []) - if addr['OS-EXT-IPS:type'] == 'floating'), - None) - else: - # not floating_ip_address and (fixed_address or not nat_destination) + # A specific floating ip address has been requested - # get any of the floating ips that matches fixed_address and/or network - f_ip_addrs = self._list_floating_ips(server) - f_ips = [f_ip for f_ip in self.conn.list_floating_ips() if f_ip['floating_ip_address'] in f_ip_addrs] - return next( - (f_ip for f_ip in f_ips - if ((fixed_address and f_ip.fixed_ip_address == fixed_address) or not fixed_address) - and ((network_id and f_ip.network == network_id) or not network_id)), - None) + if not ip: + # If a specific floating ip address has been requested + # and it does not exist yet then create it - def run(self): - server_name_or_id = self.params['server'] - state = self.params['state'] - network = self.params['network'] - floating_ip_address = self.params['floating_ip_address'] - reuse = self.params['reuse'] - fixed_address = self.params['fixed_address'] - nat_destination = self.params['nat_destination'] - wait = self.params['wait'] - timeout = self.params['timeout'] - purge = self.params['purge'] - - server = self.conn.get_server(server_name_or_id) - if not server: - self.fail_json( - msg="server {0} not found".format(server_name_or_id)) - - # Extract floating ips from server - f_ip_addrs = self._list_floating_ips(server) - - # Get details about requested floating ip - f_ip = self._get_floating_ip(floating_ip_address) if floating_ip_address else None - - if network: - network_id = self.conn.get_network(name_or_id=network)["id"] - else: - network_id = None - - if state == 'present': - if floating_ip_address and f_ip and floating_ip_address in f_ip_addrs: - # Floating ip address has been assigned to server - self.exit_json(changed=False, floating_ip=f_ip) - - if f_ip and f_ip['attached'] and floating_ip_address not in f_ip_addrs: - # Requested floating ip has been attached to different server - self.fail_json(msg="floating-ip {floating_ip_address} already has been attached to different server" - .format(floating_ip_address=floating_ip_address)) - - if not floating_ip_address: - # No specific floating ip requested, i.e. if any floating ip is already assigned to server, - # check that it matches requirements. - - if not fixed_address and nat_destination: - # Check if we have any floating ip on the given nat_destination network - nat_destination_name = self.conn.get_network(nat_destination)['name'] - for addr in server.addresses.get(nat_destination_name, []): - if addr['OS-EXT-IPS:type'] == 'floating': - # A floating ip address has been assigned to the requested nat_destination - f_ip = self._get_floating_ip(addr['addr']) - self.exit_json(changed=False, floating_ip=f_ip) - # else fixed_address or not nat_destination, hence an - # analysis of all floating ips of server is required - f_ips = [f_ip for f_ip in self.conn.list_floating_ips() if f_ip['floating_ip_address'] in f_ip_addrs] - for f_ip in f_ips: - if network_id and f_ip.network != network_id: - # requested network does not match network of floating ip - continue - - if not fixed_address and not nat_destination: - # any floating ip will fullfil these requirements - self.exit_json(changed=False, floating_ip=f_ip) - - if fixed_address and f_ip.fixed_ip_address == fixed_address: - # a floating ip address has been assigned that points to the requested fixed_address - self.exit_json(changed=False, floating_ip=f_ip) - - if floating_ip_address and not f_ip: - # openstacksdk's create_ip requires floating_ip_address and floating_network_id to be set - self.conn.network.create_ip(floating_ip_address=floating_ip_address, floating_network_id=network_id) - # Else floating ip either does not exist or has not been attached yet - - # Both floating_ip_address and network are mutually exclusive in add_ips_to_server, i.e. - # add_ips_to_server will ignore floating_ip_address if network is set - # Ref.: https://github.com/openstack/openstacksdk/blob/a6b0ece2821ea79330c4067100295f6bdcbe456e/openstack/cloud/_floating_ip.py#L987 - server = self.conn.add_ips_to_server( - server=server, - ips=floating_ip_address, - ip_pool=network if not floating_ip_address else None, - reuse=reuse, - fixed_address=fixed_address, - wait=wait, - timeout=timeout, nat_destination=nat_destination) - - # Update the floating ip status - f_ip = self._match_floating_ip(server, floating_ip_address, network_id, fixed_address, nat_destination) - self.exit_json(changed=True, floating_ip=f_ip) - - elif state == 'absent': - f_ip = self._match_floating_ip(server, floating_ip_address, network_id, fixed_address, nat_destination) - if not f_ip: - # Nothing to detach - self.exit_json(changed=False) - changed = False - - if f_ip["fixed_ip_address"]: - self.conn.detach_ip_from_server(server_id=server['id'], floating_ip_id=f_ip['id']) - # OpenStackSDK sets {"port_id": None} to detach a floating ip from an instance, - # but there might be a delay until a server does not list it in addresses any more. - - # Update the floating IP status - f_ip = self.conn.get_floating_ip(id=f_ip['id']) + # openstacksdk's create_ip requires floating_ip_address + # and floating_network_id to be set + self.conn.network.create_ip( + floating_ip_address=floating_ip_address, + floating_network_id=network_id) changed = True - if purge: - self.conn.delete_floating_ip(f_ip['id']) - self.exit_json(changed=True) - self.exit_json(changed=changed, floating_ip=f_ip) + else: # ip + # Requested floating ip address exists already + + if ip.port_details and (ip.port_details.status == 'ACTIVE') \ + and (floating_ip_address not in self._filter_ips( + self.server)): + # Floating ip address exists and has been attached + # but to a different server + + # Requested ip has been attached to different server + self.fail_json( + msg="Floating ip {0} has been attached to different " + "server".format(floating_ip_address)) + + if not ip \ + or floating_ip_address not in self._filter_ips(self.server): + # Requested floating ip address does not exist or has not been + # assigned to server + + self.conn.add_ip_list( + server=self.server, + ips=[floating_ip_address], + wait=self.params['wait'], + timeout=self.params['timeout'], + fixed_address=fixed_address) + changed = True + else: + # Requested floating ip address has been assigned to server + pass + + elif not ips: # and not floating_ip_address + # No specific floating ip has been requested and none of the + # floating ips which have been assigned to the server matches + # requirements + + # add_ips_to_server() will handle several scenarios: + # + # If a specific floating ip address has been requested then it + # will be attached to the server. The floating ip address has + # either been created in previous steps or it already existed. + # Ref.: https://github.com/openstack/openstacksdk/blob/ + # 9d3ee1d32149ba2a8bb3dc894295e180746cdddc/openstack/cloud + # /_floating_ip.py#L985 + # + # If no specific floating ip address has been requested, reuse + # is allowed and a network has been given (with ip_pool) from + # which floating ip addresses will be drawn, then any existing + # floating ip address from ip_pool=network which is not + # attached to any other server will be attached to the server. + # If no such floating ip address exists or if reuse is not + # allowed, then a new floating ip address will be created + # within ip_pool=network and attached to the server. + # Ref.: https://github.com/openstack/openstacksdk/blob/ + # 9d3ee1d32149ba2a8bb3dc894295e180746cdddc/openstack/cloud/ + # _floating_ip.py#L981 + # + # If no specific floating ip address has been requested and no + # network has been given (with ip_pool) from which floating ip + # addresses will be taken, then a floating ip address might be + # added to the server, refer to _needs_floating_ip() for + # details. + # Ref.: + # * https://github.com/openstack/openstacksdk/blob/ + # 9d3ee1d32149ba2a8bb3dc894295e180746cdddc/openstack/cloud/\ + # _floating_ip.py#L989 + # * https://github.com/openstack/openstacksdk/blob/ + # 9d3ee1d32149ba2a8bb3dc894295e180746cdddc/openstack/cloud/ + # _floating_ip.py#L995 + # + # Both floating_ip_address and network are mutually exclusive + # in add_ips_to_server(), i.e.add_ips_to_server will ignore + # floating_ip_address if network is not None. To prefer + # attaching a specific floating ip address over assigning any + # fip, ip_pool is only defined if floating_ip_address is None. + # Ref.: https://github.com/openstack/openstacksdk/blob/ + # a6b0ece2821ea79330c4067100295f6bdcbe456e/openstack/cloud/ + # _floating_ip.py#L987 + self.conn.add_ips_to_server( + server=self.server, + ip_pool=network_id, + ips=None, # No specific floating ip requested + reuse=self.params['reuse'], + fixed_address=fixed_address, + wait=self.params['wait'], + timeout=self.params['timeout'], + nat_destination=nat_destination_name_or_id) + changed = True + else: + # Found one or more floating ips which satisfy requirements + pass + + if changed: + # update server details such as addresses + self.server = self.conn.compute.get_server(self.server) + + # Update the floating ip resource + ips = self._find_ips( + self.server, floating_ip_address, network_id, + fixed_address, nat_destination_name_or_id) + + # ips can be empty, e.g. when server has no private ipv4 + # address to which a floating ip address can be attached + + self.exit_json( + changed=changed, + floating_ip=ips[0].to_dict(computed=False) if ips else None) + + def _detach_and_delete(self): + ips = self._find_ips( + server=self.server, + floating_ip_address=self.params['floating_ip_address'], + network_id=self.network['id'] if self.network else None, + fixed_address=self.params['fixed_address'], + nat_destination_name_or_id=self.params['nat_destination']) + + if not ips: + # Nothing to detach + self.exit_json(changed=False) + + changed = False + for ip in ips: + if ip['fixed_ip_address']: + # Silently ignore that ip might not be attached to server + self.conn.compute.remove_floating_ip_from_server( + self.server, ip['floating_ip_address']) + + # OpenStackSDK sets {"port_id": None} to detach a floating + # ip from an instance, but there might be a delay until a + # server does not list it in addresses any more. + changed = True + + if self.params['purge']: + self.conn.network.delete_ip(ip['id']) + changed = True + + self.exit_json(changed=changed) + + def _filter_ips(self, server): + # Extract floating ips from server + + def _flatten(lists): + return [item for sublist in lists for item in sublist] + + if server['addresses'] is None: + # fetch server with details + server = self.conn.compute.get_server(server) + + if not server['addresses']: + return [] + + # Returns a list not an iterator here because + # it is iterated several times below + return [address['addr'] + for address in _flatten(server['addresses'].values()) + if address['OS-EXT-IPS:type'] == 'floating'] + + def _find_ips(self, + server, + floating_ip_address, + network_id, + fixed_address, + nat_destination_name_or_id): + # Check which floating ips matches our requirements. + # They might or might not be attached to our server. + if floating_ip_address: + # A specific floating ip address has been requested + ip = self.conn.network.find_ip(floating_ip_address) + return [ip] if ip else [] + elif (not fixed_address and nat_destination_name_or_id): + # No specific floating ip and no specific fixed ip have been + # requested but a private network (nat_destination) has been + # given where the floating ip should be attached to. + return self._find_ips_by_nat_destination( + server, nat_destination_name_or_id) + else: + # not floating_ip_address + # and (fixed_address or not nat_destination_name_or_id) + + # An analysis of all floating ips of server is required + return self._find_ips_by_network_id_and_fixed_address( + server, fixed_address, network_id) + + def _find_ips_by_nat_destination(self, + server, + nat_destination_name_or_id): + + if not server['addresses']: + return None + + # Check if we have any floating ip on + # the given nat_destination network + nat_destination = self.conn.network.find_network( + nat_destination_name_or_id, ignore_missing=False) + + fips_with_nat_destination = [ + addr for addr + in server['addresses'].get(nat_destination['name'], []) + if addr['OS-EXT-IPS:type'] == 'floating'] + + if not fips_with_nat_destination: + return None + + # One or more floating ip addresses have been assigned + # to the requested nat_destination; return the first. + return [self.conn.network.find_ip(fip['addr'], ignore_missing=False) + for fip in fips_with_nat_destination] + + def _find_ips_by_network_id_and_fixed_address(self, + server, + fixed_address=None, + network_id=None): + # Get any of the floating ips that matches fixed_address and/or network + ips = [ip for ip in self.conn.network.ips() + if ip['floating_ip_address'] in self._filter_ips(server)] + + matching_ips = [] + for ip in ips: + if network_id and ip['floating_network_id'] != network_id: + # Requested network does not + # match network of floating ip + continue + + if not fixed_address: # and not nat_destination_name_or_id + # Any floating ip will fullfil these requirements + matching_ips.append(ip) + + if (fixed_address and ip['fixed_ip_address'] == fixed_address): + # A floating ip address has been assigned that + # points to the requested fixed_address + matching_ips.append(ip) + + return matching_ips + + def _init(self): + server_name_or_id = self.params['server'] + server = self.conn.compute.find_server(server_name_or_id, + ignore_missing=False) + # fetch server details such as addresses + self.server = self.conn.compute.get_server(server) + + network_name_or_id = self.params['network'] + if network_name_or_id: + self.network = self.conn.network.find_network( + name_or_id=network_name_or_id, ignore_missing=False) + else: + self.network = None def main(): diff --git a/plugins/modules/floating_ip_info.py b/plugins/modules/floating_ip_info.py index e108a814..f15eaef1 100644 --- a/plugins/modules/floating_ip_info.py +++ b/plugins/modules/floating_ip_info.py @@ -32,17 +32,18 @@ options: description: - The name or id of the port to which a floating IP is associated. type: str - project_id: + project: description: - - The ID of the project a floating IP is associated with. + - The name or ID of the project a floating IP is associated with. type: str + aliases: ['project_id'] router: description: - The name or id of an associated router. type: str status: description: - - The status of a floating IP, which can be ``ACTIVE``or ``DOWN``. + - The status of a floating IP. choices: ['active', 'down'] type: str requirements: @@ -56,8 +57,9 @@ extends_documentation_fragment: RETURN = ''' floating_ips: description: The floating ip objects list. - type: complex - returned: On Success. + type: list + elements: dict + returned: success contains: created_at: description: Timestamp at which the floating IP was assigned. @@ -87,9 +89,10 @@ floating_ips: description: Name of the floating ip. type: str port_details: - description: The details of the port that this floating IP associates \ - with. Present if ``fip-port-details`` extension is loaded. - type: str + description: | + The details of the port that this floating IP associates + with. Present if C(fip-port-details) extension is loaded. + type: dict port_id: description: The port ID floating ip associated with. type: str @@ -106,15 +109,16 @@ floating_ips: description: The id of the router floating ip associated with. type: str status: - description: The status of a floating IP, which can be ``ACTIVE``or ``DOWN``.\ - Can be 'ACTIVE' and 'DOWN'. + description: | + The status of a floating IP, which can be 'ACTIVE' or 'DOWN'. type: str subnet_id: description: The id of the subnet the floating ip associated with. type: str tags: description: List of tags. - type: str + type: list + elements: str updated_at: description: Timestamp at which the floating IP was last updated. type: str @@ -146,7 +150,7 @@ class FloatingIPInfoModule(OpenStackModule): floating_ip_address=dict(), floating_network=dict(), port=dict(), - project_id=dict(), + project=dict(aliases=['project_id']), router=dict(), status=dict(choices=['active', 'down']), ) @@ -155,46 +159,42 @@ class FloatingIPInfoModule(OpenStackModule): ) def run(self): + query = dict((k, self.params[k]) + for k in ['description', 'fixed_ip_address', + 'floating_ip_address'] + if self.params[k] is not None) + + for k in ['port', 'router']: + if self.params[k]: + k_id = '{0}_id'.format(k) + find_name = 'find_{0}'.format(k) + query[k_id] = getattr(self.conn.network, find_name)( + name_or_id=self.params[k], ignore_missing=False)['id'] + + floating_network_name_or_id = self.params['floating_network'] + if floating_network_name_or_id: + query['floating_network_id'] = self.conn.network.find_network( + name_or_id=floating_network_name_or_id, + ignore_missing=False)['id'] + + project_name_or_id = self.params['project'] + if project_name_or_id: + project = self.conn.identity.find_project(project_name_or_id) + if project: + query['project_id'] = project['id'] + else: + # caller might not have permission to query projects + # so assume she gave a project id + query['project_id'] = project_name_or_id - description = self.params['description'] - fixed_ip_address = self.params['fixed_ip_address'] - floating_ip_address = self.params['floating_ip_address'] - floating_network = self.params['floating_network'] - port = self.params['port'] - project_id = self.params['project_id'] - router = self.params['router'] status = self.params['status'] - - query = {} - if description: - query['description'] = description - if fixed_ip_address: - query['fixed_ip_address'] = fixed_ip_address - if floating_ip_address: - query['floating_ip_address'] = floating_ip_address - if floating_network: - try: - query['floating_network_id'] = self.conn.network.find_network(name_or_id=floating_network, - ignore_missing=False).id - except self.sdk.exceptions.ResourceNotFound: - self.fail_json(msg="floating_network not found") - if port: - try: - query['port_id'] = self.conn.network.find_port(name_or_id=port, ignore_missing=False).id - except self.sdk.exceptions.ResourceNotFound: - self.fail_json(msg="port not found") - if project_id: - query['project_id'] = project_id - if router: - try: - query['router_id'] = self.conn.network.find_router(name_or_id=router, ignore_missing=False).id - except self.sdk.exceptions.ResourceNotFound: - self.fail_json(msg="router not found") if status: query['status'] = status.upper() - ips = [ip.to_dict(computed=False) for ip in self.conn.network.ips(**query)] - self.exit_json(changed=False, floating_ips=ips) + self.exit_json( + changed=False, + floating_ips=[ip.to_dict(computed=False) + for ip in self.conn.network.ips(**query)]) def main():