diff --git a/ci/roles/router/defaults/main.yml b/ci/roles/router/defaults/main.yml
index c6c56c45a90b5d005dd9e7761c304ffa16e13744..fceb3955a7bdecd01841f463be1d811691bc88fa 100644
--- a/ci/roles/router/defaults/main.yml
+++ b/ci/roles/router/defaults/main.yml
@@ -6,7 +6,6 @@ expected_fields:
   - external_gateway_info
   - flavor_id
   - id
-  - interfaces_info
   - is_admin_state_up
   - is_distributed
   - is_ha
@@ -18,6 +17,28 @@ expected_fields:
   - tags
   - tenant_id
   - updated_at
+network_name: ansible_net
 external_network_name: ansible_external_net
 network_external: true
 router_name: ansible_router
+test_subnets:
+  - cloud: "{{ cloud }}"
+    state: present
+    network_name: "{{ network_name }}"
+    name: shade_subnet1
+    cidr: 10.7.7.0/24
+  - cloud: "{{ cloud }}"
+    state: present
+    network_name: "{{ network_name }}"
+    name: shade_subnet2
+    cidr: 10.8.8.0/24
+  - cloud: "{{ cloud }}"
+    state: present
+    network_name: "{{ network_name }}"
+    name: shade_subnet3
+    cidr: 10.9.9.0/24
+  - cloud: "{{ cloud }}"
+    state: present
+    network_name: "{{ network_name }}"
+    name: shade_subnet4
+    cidr: 10.10.10.0/24
diff --git a/ci/roles/router/tasks/main.yml b/ci/roles/router/tasks/main.yml
index 991b82ce53e5f3229ec41909a904a6589b9ff798..74aaed3fedc4e69b566af6892fd284ed3d40974f 100644
--- a/ci/roles/router/tasks/main.yml
+++ b/ci/roles/router/tasks/main.yml
@@ -6,44 +6,51 @@
      state: present
      name: "{{ network_name }}"
      external: false
+  register: internal_net
 
-- name: Create subnet1
-  openstack.cloud.subnet:
+- name: Ensure router doesn't exist before tests
+  openstack.cloud.router:
      cloud: "{{ cloud }}"
-     state: present
-     network_name: "{{ network_name }}"
-     name: shade_subnet1
-     cidr: 10.7.7.0/24
+     state: absent
+     name: "{{ router_name }}"
 
-- name: Create subnet2
+- name: Get ports in internal network
+  openstack.cloud.port_info:
+    cloud: "{{ cloud }}"
+    filters:
+      network_id: "{{ internal_net.id }}"
+  register: existing_ports
+
+- name: Ensure ports don't exist before tests
+  openstack.cloud.port:
+    cloud: "{{ cloud }}"
+    name: "{{ item.id }}"
+    state: absent
+  loop: "{{ existing_ports.ports }}"
+
+- name: Delete subnets before tests
   openstack.cloud.subnet:
-     cloud: "{{ cloud }}"
-     state: present
-     network_name: "{{ network_name }}"
-     name: shade_subnet2
-     cidr: 10.8.8.0/24
+    cloud: "{{ cloud }}"
+    state: absent
+    name: "{{ item.name }}"
+  loop: "{{ test_subnets }}"
 
-- name: Create subnet3
-  openstack.cloud.subnet:
-     cloud: "{{ cloud }}"
-     state: present
-     network_name: "{{ network_name }}"
-     name: shade_subnet3
-     cidr: 10.9.9.0/24
-
-- name: Create subnet4
-  openstack.cloud.subnet:
-     cloud: "{{ cloud }}"
-     state: present
-     network_name: "{{ network_name }}"
-     name: shade_subnet4
-     cidr: 10.10.10.0/24
+- name: Create subnets 1-4
+  openstack.cloud.subnet: "{{ item }}"
+  loop: "{{ test_subnets }}"
 
 - name: Create router
   openstack.cloud.router:
      cloud: "{{ cloud }}"
      state: present
      name: "{{ router_name }}"
+  register: result
+
+- name: Verify returned values
+  assert:
+    that:
+      - item in result.router
+  loop: "{{ expected_fields }}"
 
 - name: Update router (add interface)
   openstack.cloud.router:
@@ -80,6 +87,12 @@
       - "result.routers.0.name == router_name"
       - (result.routers.0.interfaces_info|length) == 1
 
+- name: Verify existence of return values
+  assert:
+    that:
+      - item in result.routers[0]
+  loop: "{{ expected_fields + ['interfaces_info'] }}"
+
 - name: Gather routers info with filters
   openstack.cloud.routers_info:
       cloud: "{{ cloud }}"
@@ -94,12 +107,6 @@
       - "result_filter.routers.0.name == router_name"
       - (result_filter.routers.0.interfaces_info|length) == 1
 
-- name: Verify returned values
-  assert:
-    that:
-      - item in result.routers[0]
-  loop: '{{ expected_fields }}'
-
 - name: Update router (change interfaces)
   openstack.cloud.router:
      cloud: "{{ cloud }}"
@@ -270,12 +277,13 @@
      name: "{{ router_name }}"
      interfaces:
          - shade_subnet1
-     network: "{{ external_network_name }}"
-     external_fixed_ips:
-        - subnet: shade_subnet5
-          ip: 10.6.6.100
-        - subnet: shade_subnet5
-          ip: 10.6.6.101
+     external_gateway_info:
+        network: "{{ external_network_name }}"
+        external_fixed_ips:
+           - subnet: shade_subnet5
+             ip: 10.6.6.100
+           - subnet: shade_subnet5
+             ip: 10.6.6.101
   when:
     - network_external
 
@@ -368,35 +376,145 @@
   when:
     - network_external
 
+- name: Update router (disable external snat)
+  openstack.cloud.router:
+     cloud: "{{ cloud }}"
+     state: present
+     name: "{{ router_name }}"
+     enable_snat: false
+     interfaces:
+         - shade_subnet1
+     network: "{{ external_network_name }}"
+     external_fixed_ips:
+        - subnet: shade_subnet5
+          ip: 10.6.6.101
+  when:
+    - network_external
+  register: result
+
+- name: Gather routers info
+  openstack.cloud.routers_info:
+     cloud: "{{ cloud }}"
+     name: "{{ router_name }}"
+     filters:
+       admin_state_up: true
+  register: result
+- name: Verify routers info
+  assert:
+    that:
+      - "result.routers.0.name == router_name"
+      - "not result.routers.0.external_gateway_info.enable_snat"
+  when:
+    - network_external
+
+- name: Update router (disable external snat) again
+  openstack.cloud.router:
+     cloud: "{{ cloud }}"
+     state: present
+     name: "{{ router_name }}"
+     enable_snat: false
+     interfaces:
+         - shade_subnet1
+     network: "{{ external_network_name }}"
+     external_fixed_ips:
+        - subnet: shade_subnet5
+          ip: 10.6.6.101
+  when:
+    - network_external
+  register: result
+
+- name: Assert idempotent module
+  assert:
+    that: result is not changed
+
 - name: Delete router
   openstack.cloud.router:
      cloud: "{{ cloud }}"
      state: absent
      name: "{{ router_name }}"
 
-- name: Delete subnet1
-  openstack.cloud.subnet:
+- name: Delete router again
+  openstack.cloud.router:
      cloud: "{{ cloud }}"
      state: absent
-     name: shade_subnet1
+     name: "{{ router_name }}"
+  register: result
 
-- name: Delete subnet2
-  openstack.cloud.subnet:
+- name: Assert idempotent module
+  assert:
+    that: result is not changed
+
+- name: Create router with simple interface
+  openstack.cloud.router:
      cloud: "{{ cloud }}"
-     state: absent
-     name: shade_subnet2
+     state: present
+     name: "{{ router_name }}"
+     interfaces:
+         - shade_subnet1
+  register: result
 
-- name: Delete subnet3
-  openstack.cloud.subnet:
+- name: Assert changed
+  assert:
+    that: result is changed
+
+- name: Set portip in already assigned subnet
+  openstack.cloud.router:
      cloud: "{{ cloud }}"
-     state: absent
-     name: shade_subnet3
+     state: present
+     name: "{{ router_name }}"
+     interfaces:
+         - subnet: shade_subnet1
+           net: "{{ network_name }}"
+           portip: 10.7.7.42
+  register: result
 
-- name: Delete subnet4
-  openstack.cloud.subnet:
+- name: Assert changed
+  assert:
+    that: result is changed
+
+- name: Gather routers info
+  openstack.cloud.routers_info:
+     cloud: "{{ cloud }}"
+     name: "{{ router_name }}"
+  register: result
+
+- name: Verify routers info
+  assert:
+    that: "'10.7.7.42' in result.routers[0].interfaces_info|map(attribute='ip_address')"
+
+- name: Unset portip in already assigned subnet
+  openstack.cloud.router:
+     cloud: "{{ cloud }}"
+     state: present
+     name: "{{ router_name }}"
+     interfaces:
+         - subnet: shade_subnet1
+           net: "{{ network_name }}"
+  register: result
+
+- name: Assert not changed
+  assert:
+    that: result is not changed
+
+- name: Delete router
+  openstack.cloud.router:
      cloud: "{{ cloud }}"
      state: absent
-     name: shade_subnet4
+     name: "{{ router_name }}"
+
+- name: Get ports in internal network
+  openstack.cloud.port_info:
+    cloud: "{{ cloud }}"
+    filters:
+      network_id: "{{ internal_net.id }}"
+  register: existing_ports
+
+- name: Clean up ports
+  openstack.cloud.port:
+    cloud: "{{ cloud }}"
+    name: "{{ item.id }}"
+    state: absent
+  loop: "{{ existing_ports.ports }}"
 
 - name: Delete subnet5
   openstack.cloud.subnet:
@@ -406,6 +524,13 @@
   when:
     - network_external
 
+- name: Delete subnets 1-4
+  openstack.cloud.subnet:
+    cloud: "{{ cloud }}"
+    state: absent
+    name: "{{ item.name }}"
+  loop: "{{ test_subnets }}"
+
 - name: Delete internal network
   openstack.cloud.network:
      cloud: "{{ cloud }}"
diff --git a/plugins/modules/router.py b/plugins/modules/router.py
index 74a9839ebf14a9e1af6b97f4acba4016b8c45e38..3041ae12ee3531ebe7d959c9b111fd02b9475e87 100644
--- a/plugins/modules/router.py
+++ b/plugins/modules/router.py
@@ -25,15 +25,16 @@ options:
         - Name to be give to the router
      required: true
      type: str
-   admin_state_up:
-     description:
-        - Desired admin state of the created or existing router.
-     type: bool
-     default: 'yes'
    enable_snat:
      description:
         - Enable Source NAT (SNAT) attribute.
      type: bool
+   is_admin_state_up:
+     description:
+        - Desired admin state of the created or existing router.
+     type: bool
+     default: 'yes'
+     aliases: [admin_state_up]
    network:
      description:
         - Unique name or ID of the external gateway network.
@@ -43,6 +44,39 @@ options:
      description:
         - Unique name or ID of the project.
      type: str
+   external_gateway_info:
+     description:
+       - Information about the router's external gateway
+     type: dict
+     suboptions:
+       network:
+         description:
+            - Unique name or ID of the external gateway network.
+            - required I(interfaces) or I(enable_snat) are provided.
+         type: str
+       enable_snat:
+         description:
+            - Unique name or ID of the external gateway network.
+            - required I(interfaces) or I(enable_snat) are provided.
+         type: bool
+       external_fixed_ips:
+         description:
+            - The IP address parameters for the external gateway network. Each
+              is a dictionary with the subnet name or ID (subnet) and the IP
+              address to assign on the subnet (ip). If no IP is specified,
+              one is automatically assigned from that subnet.
+         type: list
+         elements: dict
+         suboptions:
+            ip_address:
+               description: The fixed IP address to attempt to allocate.
+               type: str
+               aliases: [ip]
+            subnet_id:
+               description: The subnet to attach the IP address to.
+               required: true
+               type: str
+               aliases: [subnet]
    external_fixed_ips:
      description:
         - The IP address parameters for the external gateway network. Each
@@ -52,13 +86,15 @@ options:
      type: list
      elements: dict
      suboptions:
-        ip:
+        ip_address:
            description: The fixed IP address to attempt to allocate.
-           required: true
            type: str
-        subnet:
+           aliases: [ip]
+        subnet_id:
            description: The subnet to attach the IP address to.
+           required: true
            type: str
+           aliases: [subnet]
    interfaces:
      description:
         - List of subnets to attach to the router internal interface. Default
@@ -172,7 +208,7 @@ RETURN = '''
 router:
     description: Dictionary describing the router.
     returned: On success when I(state) is 'present'
-    type: complex
+    type: dict
     contains:
         id:
             description: Router ID.
@@ -212,256 +248,327 @@ router:
 '''
 
 from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
-import itertools
+from collections import defaultdict
+
+
+ext_ips_spec = dict(type='list', elements='dict', options=dict(
+    ip_address=dict(aliases=["ip"]),
+    subnet_id=dict(required=True, aliases=["subnet"]),
+))
 
 
 class RouterModule(OpenStackModule):
     argument_spec = dict(
         state=dict(default='present', choices=['absent', 'present']),
         name=dict(required=True),
-        admin_state_up=dict(type='bool', default=True),
+        is_admin_state_up=dict(type='bool', default=True,
+                               aliases=['admin_state_up']),
         enable_snat=dict(type='bool'),
         network=dict(),
         interfaces=dict(type='list', elements='raw'),
-        external_fixed_ips=dict(type='list', elements='dict'),
+        external_fixed_ips=ext_ips_spec,
+        external_gateway_info=dict(type='dict', options=dict(
+            network=dict(),
+            enable_snat=dict(type='bool'),
+            external_fixed_ips=ext_ips_spec,
+        )),
         project=dict()
     )
 
-    def _get_subnet_ids_from_ports(self, ports):
-        return [fixed_ip['subnet_id'] for fixed_ip in
-                itertools.chain.from_iterable(port['fixed_ips'] for port in ports if 'fixed_ips' in port)]
+    module_kwargs = dict(
+        mutually_exclusive=[
+            ('external_gateway_info', 'network'),
+            ('external_gateway_info', 'external_fixed_ips'),
+            ('external_gateway_info', 'enable_snat'),
+        ],
+        required_by=dict(
+            external_fixed_ips='network',
+        ),
+    )
 
-    def _needs_update(self, router, net,
-                      missing_port_ids,
-                      requested_subnet_ids,
-                      existing_subnet_ids,
-                      router_ifs_cfg):
+    def _needs_update(self, router, kwargs, external_fixed_ips, to_add,
+                      to_remove, missing_port_ids):
         """Decide if the given router needs an update."""
-        if router['admin_state_up'] != self.params['admin_state_up']:
+        if router['is_admin_state_up'] != self.params['is_admin_state_up']:
             return True
-        if router['external_gateway_info']:
-            # check if enable_snat is set in module params
-            if self.params['enable_snat'] is not None:
-                if router['external_gateway_info'].get('enable_snat', True) != self.params['enable_snat']:
-                    return True
-        if net:
-            if not router['external_gateway_info']:
-                return True
-            elif router['external_gateway_info']['network_id'] != net['id']:
-                return True
-
-        # check if external_fixed_ip has to be added
-        for external_fixed_ip in router_ifs_cfg['external_fixed_ips']:
-            exists = False
-
-            # compare the requested interface with existing, looking for an existing match
-            for existing_if in router['external_gateway_info']['external_fixed_ips']:
-                if existing_if['subnet_id'] == external_fixed_ip['subnet_id']:
-                    if 'ip' in external_fixed_ip:
-                        if existing_if['ip_address'] == external_fixed_ip['ip']:
-                            # both subnet id and ip address match
-                            exists = True
-                            break
-                    else:
-                        # only the subnet was given, so ip doesn't matter
-                        exists = True
-                        break
 
-            # this interface isn't present on the existing router
-            if not exists:
+        cur_ext_gw_info = router['external_gateway_info']
+        if 'external_gateway_info' in kwargs:
+            if cur_ext_gw_info is None:
                 return True
+            update = kwargs['external_gateway_info']
+            for attr in ('enable_snat', 'network_id'):
+                if attr in update and cur_ext_gw_info[attr] != update[attr]:
+                    return True
 
-        # check if external_fixed_ip has to be removed
-        if router_ifs_cfg['external_fixed_ips']:
-            for external_fixed_ip in router['external_gateway_info']['external_fixed_ips']:
-                obsolete = True
-
-                # compare the existing interface with requested, looking for an requested match
-                for requested_if in router_ifs_cfg['external_fixed_ips']:
-                    if external_fixed_ip['subnet_id'] == requested_if['subnet_id']:
-                        if 'ip' in requested_if:
-                            if external_fixed_ip['ip_address'] == requested_if['ip']:
-                                # both subnet id and ip address match
-                                obsolete = False
-                                break
-                        else:
-                            # only the subnet was given, so ip doesn't matter
-                            obsolete = False
-                            break
-
-                # this interface isn't present on the existing router
-                if obsolete:
+        cur_ext_gw_info = router['external_gateway_info']
+        cur_ext_fips = (cur_ext_gw_info or {}) \
+            .get('external_fixed_ips', [])
+        # map of external fixed ip subnets to addresses
+        cur_fip_map = defaultdict(set)
+        for p in cur_ext_fips:
+            if 'ip_address' in p:
+                cur_fip_map[p['subnet_id']].add(p['ip_address'])
+        req_fip_map = defaultdict(set)
+        for p in external_fixed_ips:
+            if 'ip_address' in p:
+                req_fip_map[p['subnet_id']].add(p['ip_address'])
+
+        # Check if external ip addresses need to be added
+        for fip in external_fixed_ips:
+            subnet = fip['subnet_id']
+            ip = fip.get('ip_address', None)
+            if subnet in cur_fip_map:
+                if ip is not None and ip not in cur_fip_map[subnet]:
                     return True
-        else:
-            # no external fixed ips requested
-            if router['external_gateway_info'] \
-               and router['external_gateway_info']['external_fixed_ips'] \
-               and len(router['external_gateway_info']['external_fixed_ips']) > 1:
-                # but router has several external fixed ips
+            else:
+                return True
+        # Check if external ip addresses need to be removed
+        for fip in cur_ext_fips:
+            subnet = fip['subnet_id']
+            ip = fip['ip_address']
+            if subnet in req_fip_map:
+                if ip not in req_fip_map[subnet]:
+                    return True
+            else:
                 return True
 
-        # check if internal port has to be added
-        if router_ifs_cfg['internal_ports_missing']:
-            return True
-
-        if missing_port_ids:
+        if not external_fixed_ips and len(cur_ext_fips) > 1:
+            # no external fixed ips requested but router has several external
+            # fixed ips
             return True
 
-        # check if internal subnet has to be added or removed
-        if set(requested_subnet_ids) != set(existing_subnet_ids):
+        # check if internal interfaces need update
+        if to_add or to_remove or missing_port_ids:
             return True
 
         return False
 
-    def _build_kwargs(self, router, net):
+    def _build_kwargs(self, router, network, ext_fixed_ips):
         kwargs = {
-            'admin_state_up': self.params['admin_state_up'],
+            'is_admin_state_up': self.params['is_admin_state_up'],
         }
 
-        if router:
-            kwargs['name_or_id'] = router['id']
-        else:
+        if not router:
             kwargs['name'] = self.params['name']
 
-        if net:
-            kwargs['ext_gateway_net_id'] = net['id']
+        curr_ext_gw_info = None
+        if router:
+            curr_ext_gw_info = router['external_gateway_info']
+        curr_ext_fixed_ips = []
+        if curr_ext_gw_info:
+            curr_ext_fixed_ips = curr_ext_gw_info.get('external_fixed_ips', [])
+
+        external_gateway_info = {}
+        if network:
+            external_gateway_info['network_id'] = network.id
             # can't send enable_snat unless we have a network
-            if self.params.get('enable_snat') is not None:
-                kwargs['enable_snat'] = self.params['enable_snat']
-
-        if self.params['external_fixed_ips']:
-            kwargs['ext_fixed_ips'] = []
-            for iface in self.params['external_fixed_ips']:
-                subnet = self.conn.get_subnet(iface['subnet'])
-                d = {'subnet_id': subnet['id']}
-                if 'ip' in iface:
-                    d['ip_address'] = iface['ip']
-                kwargs['ext_fixed_ips'].append(d)
-        else:
-            # no external fixed ips requested
-            if router \
-               and router['external_gateway_info'] \
-               and router['external_gateway_info']['external_fixed_ips'] \
-               and len(router['external_gateway_info']['external_fixed_ips']) > 1:
-                # but router has several external fixed ips
-                # keep first external fixed ip only
-                fip = router['external_gateway_info']['external_fixed_ips'][0]
-                kwargs['ext_fixed_ips'] = [fip]
+            if self.params['enable_snat'] is not None:
+                external_gateway_info['enable_snat'] = \
+                    self.params['enable_snat']
+        if ext_fixed_ips:
+            external_gateway_info['external_fixed_ips'] = ext_fixed_ips
+        if external_gateway_info:
+            kwargs['external_gateway_info'] = external_gateway_info
+
+        if 'external_fixed_ips' not in external_gateway_info:
+            if len(curr_ext_fixed_ips) > 1:
+                fip = curr_ext_fixed_ips[0]
+                external_gateway_info['external_fixed_ips'] = [fip]
 
         return kwargs
 
     def _build_router_interface_config(self, filters=None):
+        if filters is None:
+            filters = {}
         external_fixed_ips = []
-        internal_subnets = []
-        internal_ports = []
         internal_ports_missing = []
+        internal_ifaces = []
 
         # Build external interface configuration
-        if self.params['external_fixed_ips']:
-            for iface in self.params['external_fixed_ips']:
-                subnet = self.conn.get_subnet(iface['subnet'], filters)
+        ext_fixed_ips = None
+        if self.params['external_gateway_info']:
+            ext_fixed_ips = self.params['external_gateway_info'] \
+                .get('external_fixed_ips')
+        ext_fixed_ips = ext_fixed_ips or self.params['external_fixed_ips']
+        if ext_fixed_ips:
+            for iface in ext_fixed_ips:
+                subnet = self.conn.network.find_subnet(
+                    iface['subnet'], **filters)
                 if not subnet:
                     self.fail(msg='subnet %s not found' % iface['subnet'])
-                new_external_fixed_ip = {'subnet_name': subnet.name, 'subnet_id': subnet.id}
-                if 'ip' in iface:
-                    new_external_fixed_ip['ip'] = iface['ip']
-                external_fixed_ips.append(new_external_fixed_ip)
+                fip = dict(subnet_id=subnet.id)
+                if 'ip_address' in iface:
+                    fip['ip_address'] = iface['ip_address']
+                external_fixed_ips.append(fip)
 
         # Build internal interface configuration
         if self.params['interfaces']:
             internal_ips = []
             for iface in self.params['interfaces']:
                 if isinstance(iface, str):
-                    subnet = self.conn.get_subnet(iface, filters)
+                    subnet = self.conn.network.find_subnet(iface, **filters)
                     if not subnet:
                         self.fail(msg='subnet %s not found' % iface)
-                    internal_subnets.append(subnet)
+                    internal_ifaces.append(dict(subnet_id=subnet.id))
 
                 elif isinstance(iface, dict):
-                    subnet = self.conn.get_subnet(iface['subnet'], filters)
+                    subnet = self.conn.network.find_subnet(iface['subnet'],
+                                                           **filters)
                     if not subnet:
                         self.fail(msg='subnet %s not found' % iface['subnet'])
 
-                    net = self.conn.get_network(iface['net'])
-                    if not net:
-                        self.fail(msg='net %s not found' % iface['net'])
+                    # TODO: We allow passing a subnet without specifing a
+                    #       network in case iface is a string, hence we
+                    #       should allow to omit the network here as well.
+                    if 'net' not in iface:
+                        self.fail(
+                            "Network name missing from interface definition")
+                    net = self.conn.network.find_network(iface['net'])
 
-                    if "portip" not in iface:
+                    if 'portip' not in iface:
                         # portip not set, add any ip from subnet
-                        internal_subnets.append(subnet)
+                        internal_ifaces.append(dict(subnet_id=subnet.id))
                     elif not iface['portip']:
                         # portip is set but has invalid value
-                        self.fail(msg='put an ip in portip or remove it from list to assign default port to router')
+                        self.fail(msg='put an ip in portip or remove it'
+                                  'from list to assign default port to router')
                     else:
                         # portip has valid value
-                        # look for ports whose fixed_ips.ip_address matchs portip
-                        for existing_port in self.conn.list_ports(filters={'network_id': net.id}):
-                            for fixed_ip in existing_port['fixed_ips']:
-                                if iface['portip'] == fixed_ip['ip_address']:
-                                    # portip exists in net already
-                                    internal_ports.append(existing_port)
-                                    internal_ips.append(fixed_ip['ip_address'])
-                        if iface['portip'] not in internal_ips:
+                        # look for ports whose fixed_ips.ip_address matchs
+                        # portip
+                        portip = iface['portip']
+                        port_kwargs = ({'network_id': net.id}
+                                       if net is not None else {})
+                        existing_ports = self.conn.network.ports(**port_kwargs)
+                        for port in existing_ports:
+                            for fip in port['fixed_ips']:
+                                if (fip['subnet_id'] != subnet.id
+                                        or fip['ip_address'] != portip):
+                                    continue
+                                internal_ips.append(fip['ip_address'])
+                                internal_ifaces.append(
+                                    dict(port_id=port.id,
+                                         subnet_id=subnet.id,
+                                         ip_address=portip))
+                        if portip not in internal_ips:
                             # no port with portip exists hence create a new port
                             internal_ports_missing.append({
-                                'network_id': net.id,
-                                'fixed_ips': [{'ip_address': iface['portip'], 'subnet_id': subnet.id}]
+                                'network_id': subnet.network_id,
+                                'fixed_ips': [{'ip_address': portip,
+                                               'subnet_id': subnet.id}]
                             })
 
         return {
             'external_fixed_ips': external_fixed_ips,
-            'internal_subnets': internal_subnets,
-            'internal_ports': internal_ports,
-            'internal_ports_missing': internal_ports_missing
+            'internal_ports_missing': internal_ports_missing,
+            'internal_ifaces': internal_ifaces,
         }
 
-    def run(self):
+    def _update_ifaces(self, router, to_add, to_remove, missing_ports):
+        for port in to_remove:
+            self.conn.network.remove_interface_from_router(router,
+                                                           port_id=port.id)
+        # create ports that are missing
+        for port in missing_ports:
+            p = self.conn.network.create_port(**port)
+            if p:
+                to_add.append(dict(port_id=p.id))
+        for iface in to_add:
+            self.conn.network.add_interface_to_router(router, **iface)
+
+    def _get_external_gateway_network_name(self):
+        network_name_or_id = self.params['network']
+        if self.params['external_gateway_info']:
+            network_name_or_id = \
+                self.params['external_gateway_info']['network']
+        return network_name_or_id
+
+    def _get_port_changes(self, router, ifs_cfg):
+        requested_subnet_ids = [iface['subnet_id'] for iface
+                                in ifs_cfg['internal_ifaces']]
+
+        router_ifs_internal = []
+        if router:
+            router_ifs_internal = self.conn.list_router_interfaces(
+                router, 'internal')
+
+        existing_subnet_ips = {}
+        for iface in router_ifs_internal:
+            if 'fixed_ips' not in iface:
+                continue
+            for fip in iface['fixed_ips']:
+                existing_subnet_ips[fip['subnet_id']] = (fip['ip_address'],
+                                                         iface)
+
+        obsolete_subnet_ids = (set(existing_subnet_ips.keys())
+                               - set(requested_subnet_ids))
+
+        internal_ifaces = ifs_cfg['internal_ifaces']
+        to_add = []
+        to_remove = []
+        for iface in internal_ifaces:
+            subnet_id = iface['subnet_id']
+            if subnet_id not in existing_subnet_ips:
+                iface.pop('ip_address', None)
+                to_add.append(iface)
+                continue
+            ip, existing_port = existing_subnet_ips[subnet_id]
+            if 'ip_address' in iface and ip != iface['ip_address']:
+                # Port exists for subnet but has the wrong ip. Schedule it for
+                # deletion
+                to_remove.append(existing_port)
+
+        for port in router_ifs_internal:
+            if 'fixed_ips' not in port:
+                continue
+            if any(fip['subnet_id'] in obsolete_subnet_ids
+                   for fip in port['fixed_ips']):
+                to_remove.append(port)
+        return dict(to_add=to_add, to_remove=to_remove,
+                    router_ifs_internal=router_ifs_internal)
 
+    def run(self):
         state = self.params['state']
         name = self.params['name']
-        network = self.params['network']
-        project = self.params['project']
-
-        if self.params['external_fixed_ips'] and not network:
-            self.fail(msg='network is required when supplying external_fixed_ips')
-
-        if project is not None:
-            proj = self.conn.get_project(project)
-            if proj is None:
-                self.fail(msg='Project %s could not be found' % project)
-            project_id = proj['id']
-            filters = {'tenant_id': project_id}
-        else:
-            project_id = None
-            filters = None
-
-        router = self.conn.get_router(name, filters=filters)
-        net = None
-        if network:
-            net = self.conn.get_network(network)
-            if not net:
-                self.fail(msg='network %s not found' % network)
+        network_name_or_id = self._get_external_gateway_network_name()
+        project_name_or_id = self.params['project']
+
+        if self.params['external_fixed_ips'] and not network_name_or_id:
+            self.fail(
+                msg='network is required when supplying external_fixed_ips')
+
+        query_filters = {}
+        project = None
+        project_id = None
+        if project_name_or_id is not None:
+            project = self.conn.identity.find_project(project_name_or_id)
+            if project is None:
+                self.fail(
+                    msg='Project %s could not be found' % project_name_or_id)
+            project_id = project['id']
+            query_filters['project_id'] = project_id
+
+        router = self.conn.network.find_router(name, **query_filters)
+        network = None
+        if network_name_or_id:
+            network = self.conn.network.find_network(network_name_or_id,
+                                                     **query_filters)
+            if not network:
+                self.fail(msg='network %s not found' % network_name_or_id)
 
         # Validate and cache the subnet IDs so we can avoid duplicate checks
         # and expensive API calls.
-        router_ifs_cfg = self._build_router_interface_config(filters)
-        requested_subnet_ids = [subnet.id for subnet in router_ifs_cfg['internal_subnets']] + \
-            self._get_subnet_ids_from_ports(router_ifs_cfg['internal_ports'])
-        requested_port_ids = [i['id'] for i in router_ifs_cfg['internal_ports']]
+        router_ifs_cfg = self._build_router_interface_config(query_filters)
 
-        if router:
-            router_ifs_internal = self.conn.list_router_interfaces(router, 'internal')
-            existing_subnet_ids = self._get_subnet_ids_from_ports(router_ifs_internal)
-            obsolete_subnet_ids = set(existing_subnet_ids) - set(requested_subnet_ids)
-            existing_port_ids = [i['id'] for i in router_ifs_internal]
+        missing_internal_ports = router_ifs_cfg['internal_ports_missing']
 
-        else:
-            router_ifs_internal = []
-            existing_subnet_ids = []
-            obsolete_subnet_ids = []
-            existing_port_ids = []
+        port_changes = self._get_port_changes(router, router_ifs_cfg)
+        to_add = port_changes['to_add']
+        to_remove = port_changes['to_remove']
+        router_ifs_internal = port_changes['router_ifs_internal']
 
-        missing_port_ids = set(requested_port_ids) - set(existing_port_ids)
+        external_fixed_ips = router_ifs_cfg['external_fixed_ips']
 
         if self.ansible.check_mode:
             # Check if the system state would be changed
@@ -472,82 +579,42 @@ class RouterModule(OpenStackModule):
             elif state == 'present' and not router:
                 changed = True
             else:  # if state == 'present' and router
-                changed = self._needs_update(router, net,
-                                             missing_port_ids,
-                                             requested_subnet_ids,
-                                             existing_subnet_ids,
-                                             router_ifs_cfg)
+                kwargs = self._build_kwargs(router, network,
+                                            external_fixed_ips)
+                changed = self._needs_update(
+                    router, kwargs, external_fixed_ips, to_add, to_remove,
+                    missing_internal_ports)
             self.exit_json(changed=changed)
 
         if state == 'present':
             changed = False
+            external_fixed_ips = router_ifs_cfg['external_fixed_ips']
+            internal_ifaces = router_ifs_cfg['internal_ifaces']
+            kwargs = self._build_kwargs(router, network,
+                                        external_fixed_ips)
 
             if not router:
                 changed = True
-
-                kwargs = self._build_kwargs(router, net)
                 if project_id:
                     kwargs['project_id'] = project_id
-                router = self.conn.create_router(**kwargs)
-
-                # add interface by subnet id, because user did not specify a port id
-                for subnet in router_ifs_cfg['internal_subnets']:
-                    self.conn.add_router_interface(router, subnet_id=subnet.id)
-
-                # add interface by port id if user did specify a valid port id
-                for port in router_ifs_cfg['internal_ports']:
-                    self.conn.add_router_interface(router, port_id=port.id)
-
-                # add port and interface if user did specify an ip address but port is missing yet
-                for missing_internal_port in router_ifs_cfg['internal_ports_missing']:
-                    p = self.conn.create_port(**missing_internal_port)
-                    if p:
-                        self.conn.add_router_interface(router, port_id=p.id)
+                router = self.conn.network.create_router(**kwargs)
 
+                self._update_ifaces(router, internal_ifaces, [],
+                                    missing_internal_ports)
             else:
-                if self._needs_update(router, net,
-                                      missing_port_ids,
-                                      requested_subnet_ids,
-                                      existing_subnet_ids,
-                                      router_ifs_cfg):
+
+                if self._needs_update(router, kwargs, external_fixed_ips,
+                                      to_add, to_remove,
+                                      missing_internal_ports):
                     changed = True
-                    kwargs = self._build_kwargs(router, net)
-                    updated_router = self.conn.update_router(**kwargs)
+                    router = self.conn.network.update_router(router, **kwargs)
 
-                    # Protect against update_router() not actually updating the router.
-                    if not updated_router:
-                        changed = False
-                    else:
-                        router = updated_router
-
-                    # delete internal subnets i.e. ports
-                    if obsolete_subnet_ids:
-                        for port in router_ifs_internal:
-                            if 'fixed_ips' in port:
-                                for fip in port['fixed_ips']:
-                                    if fip['subnet_id'] in obsolete_subnet_ids:
-                                        self.conn.remove_router_interface(router, port_id=port['id'])
-                                        changed = True
-
-                    # add new internal interface by subnet id, because user did not specify a port id
-                    for subnet in router_ifs_cfg['internal_subnets']:
-                        if subnet.id not in existing_subnet_ids:
-                            self.conn.add_router_interface(router, subnet_id=subnet.id)
-                            changed = True
-
-                    # add new internal interface by port id if user did specify a valid port id
-                    for port_id in missing_port_ids:
-                        self.conn.add_router_interface(router, port_id=port_id)
-                        changed = True
-
-                    # add new port and new internal interface if user did specify an ip address but port is missing yet
-                    for missing_internal_port in router_ifs_cfg['internal_ports_missing']:
-                        p = self.conn.create_port(**missing_internal_port)
-                        if p:
-                            self.conn.add_router_interface(router, port_id=p.id)
-                            changed = True
-
-            self.exit_json(changed=changed, router=router)
+                    if to_add or to_remove or missing_internal_ports:
+                        self._update_ifaces(router, to_add, to_remove,
+                                            missing_internal_ports)
+
+            self.exit_json(changed=changed,
+                           router=router.to_dict(computed=False))
 
         elif state == 'absent':
             if not router:
@@ -558,8 +625,9 @@ class RouterModule(OpenStackModule):
                 # still fail if e.g. floating ips are attached to the
                 # router.
                 for port in router_ifs_internal:
-                    self.conn.remove_router_interface(router, port_id=port['id'])
-                self.conn.delete_router(router['id'])
+                    self.conn.network.remove_interface_from_router(router,
+                                                                   port_id=port['id'])
+                self.conn.network.delete_router(router)
                 self.exit_json(changed=True, router=router)