diff --git a/.zuul.yaml b/.zuul.yaml
index 1479e9ba456534b712f9d662dc7fb0684d4aef25..6d13ea50c395c2eda41fc906d0cebbce7af49c3c 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 0000000000000000000000000000000000000000..638b179f5a5d43119c5b8f9259ef7a858a9c6eab
--- /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 ebb41ec72ccf1e53152d41648ba99f8a4deaf449..d846e28ef83dbe83128498d9479c7ff04c27d092 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: Get server 1 ports
+  openstack.cloud.port_info:
+    cloud: "{{ cloud }}"
+    filters:
+      device_id: "{{ server1.server.id }}"
+  register: server1_ports
 
-- name: Assert one internal port and no floating ips on server 1
+- 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: Get server 2 ports
+  openstack.cloud.port_info:
+    cloud: "{{ cloud }}"
+    filters:
+      device_id: "{{ server2.server.id }}"
+  register: server2_ports
 
-- name: Assert two internal ports and no floating ips on server 2
+- 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
+    cloud: "{{ cloud }}"
+    state: present
+    server: ansible_server1
+    wait: yes
 
-- name: Get info about server
-  openstack.cloud.server_info:
+- 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:
+      - 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: 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: present
     server: ansible_server1
-  register: info
+    wait: true
+  register: floating_ip
+
+- name: Assert floating ip on server 1 has not changed
+  assert:
+    that: floating_ip is not changed
 
-- name: Assert one internal port and one floating ip on server 1
+- name: Assert return values of floating_ip module
   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"]
+      - 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
+- name: Detach floating ip from server 1
   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: absent
     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.
+    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: Find all floating ips for debugging
+  openstack.cloud.floating_ip_info:
+    cloud: "{{ cloud }}"
+  register: fips
+
+- name: Print all floating ips for debugging
+  debug: var=fips
 
-- name: Assert one internal port on server 1
+- name: Find all servers for debugging
+  openstack.cloud.server_info:
+    cloud: "{{ cloud }}"
+  register: servers
+
+- 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 == 1
-      - info.servers.0.addresses.ansible_internal|map(attribute="addr")|list == ["10.7.7.100"]
+      - server2_fip.floating_ip
 
-- 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: Get info about server
+- name: Find all floating ips for debugging
+  openstack.cloud.floating_ip_info:
+    cloud: "{{ cloud }}"
+  register: fips
+
+- name: Print all floating ips for debugging
+  debug: var=fips
+
+- name: Find all servers for debugging
   openstack.cloud.server_info:
     cloud: "{{ cloud }}"
-    server: ansible_server2
-  register: info
+  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 two internal ports and one floating ip on server 2
+- 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_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
+- name: Assign a second, specific floating ip to server 2
   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"
-
-# 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: Get info about server
-  openstack.cloud.server_info:
     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: 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
+- name: Detach remaining floating ip from server 2
   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:
     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 916c2cb239e703e9b20cd2366258816f45d8e8d9..0000000000000000000000000000000000000000
--- 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 31b8e99ab74c4aa3f54726b3262fffbf6c88c3d8..70c37784445c920e95f816ef8d9a6024397d7c72 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 29e18a7843d4ad8c0d6d3c900407899ca105b140..fbfda7f1a42656df7176930a95dff0943296bc27 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:
+   fixed_address:
      description:
-        - The name or ID of the instance to which the IP address
-          should be assigned.
-     required: true
+        - 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
    network:
      description:
         - The name or ID of a neutron external network or a nova pool name.
      type: str
-   floating_ip_address:
+   purge:
      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
+        - 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'
    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.
+        - 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'
-   fixed_address:
-     description:
-        - To which fixed IP of server the floating IP address should be
-          attached to.
-     type: str
-   nat_destination:
+   server:
      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"]
+        - The name or ID of the instance to which the IP address
+          should be assigned.
+     required: true
      type: str
-   wait:
-     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.
-     type: bool
-     default: 'no'
-   timeout:
-     description:
-        - Time to wait for an IP address to appear as attached. See wait.
-     required: false
-     default: 60
-     type: int
    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(
-        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(),
+        floating_ip_address=dict(),
         nat_destination=dict(aliases=['fixed_network', 'internal_network']),
-        wait=dict(type='bool', default=False),
-        timeout=dict(type='int', default=60),
+        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']),
     )
 
     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']
+                # 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
 
-        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))
+            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
 
-        # Extract floating ips from server
-        f_ip_addrs = self._list_floating_ips(server)
+            if self.params['purge']:
+                self.conn.network.delete_ip(ip['id'])
+                changed = True
 
-        # Get details about requested floating ip
-        f_ip = self._get_floating_ip(floating_ip_address) if floating_ip_address else None
+        self.exit_json(changed=changed)
 
-        if network:
-            network_id = self.conn.get_network(name_or_id=network)["id"]
+    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:
-            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'])
-                changed = True
+            # not floating_ip_address
+            # and (fixed_address or not nat_destination_name_or_id)
 
-            if purge:
-                self.conn.delete_floating_ip(f_ip['id'])
-                self.exit_json(changed=True)
-            self.exit_json(changed=changed, floating_ip=f_ip)
+            # 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 e108a814b35a2669f9e55e81696c7e2f40abee66..f15eaef1a15b3d1ac864466b0a58f60dad2d848d 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():