summaryrefslogtreecommitdiffstats
path: root/ansible_collections/openstack/cloud/plugins/modules/router.py
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/openstack/cloud/plugins/modules/router.py')
-rw-r--r--ansible_collections/openstack/cloud/plugins/modules/router.py796
1 files changed, 465 insertions, 331 deletions
diff --git a/ansible_collections/openstack/cloud/plugins/modules/router.py b/ansible_collections/openstack/cloud/plugins/modules/router.py
index 58c5c124e..7002a4110 100644
--- a/ansible_collections/openstack/cloud/plugins/modules/router.py
+++ b/ansible_collections/openstack/cloud/plugins/modules/router.py
@@ -1,5 +1,6 @@
#!/usr/bin/python
-#
+# -*- coding: utf-8 -*-
+
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@@ -13,53 +14,63 @@ description:
routers to share the same name, this module enforces name uniqueness
to be more user friendly.
options:
- state:
- description:
- - Indicate desired state of the resource
- choices: ['present', 'absent']
- default: present
- type: str
- name:
- description:
- - 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_snat:
+ description:
- Enable Source NAT (SNAT) attribute.
- type: bool
- network:
- description:
- - Unique name or ID of the external gateway network.
- - required I(interfaces) or I(enable_snat) are provided.
- type: str
- project:
- description:
- - Unique name or ID of the project.
- type: str
- external_fixed_ips:
- description:
+ 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,
+ address to assign on the subnet (ip_address). If no IP is specified,
one is automatically assigned from that subnet.
- type: list
- elements: dict
- suboptions:
- ip:
+ type: list
+ elements: dict
+ suboptions:
+ 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
- interfaces:
- description:
+ aliases: ['subnet']
+ 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_address). 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']
+ interfaces:
+ description:
- List of subnets to attach to the router internal interface. Default
gateway associated with the subnet will be automatically attached
with the router's internal interface.
@@ -70,12 +81,37 @@ options:
User defined portip is often required when a multiple router need
to be connected to a single subnet for which the default gateway has
been already used.
- type: list
- elements: raw
-requirements:
- - "python >= 3.6"
- - "openstacksdk"
-
+ type: list
+ elements: raw
+ is_admin_state_up:
+ description:
+ - Desired admin state of the created or existing router.
+ type: bool
+ default: 'true'
+ aliases: ['admin_state_up']
+ name:
+ description:
+ - Name to be give to the router.
+ - This router attribute cannot be updated.
+ required: true
+ type: str
+ network:
+ description:
+ - Unique name or ID of the external gateway network.
+ - Required if I(external_fixed_ips) or I(enable_snat) are provided.
+ - This router attribute cannot be updated.
+ type: str
+ project:
+ description:
+ - Unique name or ID of the project.
+ - This router attribute cannot be updated.
+ type: str
+ state:
+ description:
+ - Indicate desired state of the resource
+ choices: ['present', 'absent']
+ default: present
+ type: str
extends_documentation_fragment:
- openstack.cloud.openstack
'''
@@ -87,14 +123,14 @@ EXAMPLES = '''
state: present
name: simple_router
-# Create a simple router, not attached to a gateway or subnets for a given project.
+# Create a router, not attached to a gateway or subnets for a given project.
- openstack.cloud.router:
cloud: mycloud
state: present
name: simple_router
project: myproj
-# Creates a router attached to ext_network1 on an IPv4 subnet and one
+# Creates a router attached to ext_network1 on an IPv4 subnet and with one
# internal subnet interface.
- openstack.cloud.router:
cloud: mycloud
@@ -103,11 +139,11 @@ EXAMPLES = '''
network: ext_network1
external_fixed_ips:
- subnet: public-subnet
- ip: 172.24.4.2
+ ip_address: 172.24.4.2
interfaces:
- private-subnet
-# Create another router with two internal subnet interfaces.One with user defined port
+# Create a router with two internal subnet interfaces and a user defined port
# ip and another with default gateway.
- openstack.cloud.router:
cloud: mycloud
@@ -120,8 +156,8 @@ EXAMPLES = '''
portip: 10.1.1.10
- project-subnet
-# Create another router with two internal subnet interface.One with user defined port
-# ip and and another with default gateway.
+# Create a router with two internal subnet interface. One with user defined
+# port ip and and another with default gateway.
- openstack.cloud.router:
cloud: mycloud
state: present
@@ -133,8 +169,8 @@ EXAMPLES = '''
portip: 10.1.1.10
- project-subnet
-# Create another router with two internal subnet interface. one with user defined port
-# ip and and another with default gateway.
+# Create a router with two internal subnet interface. One with user defined
+# port ip and and another with default gateway.
- openstack.cloud.router:
cloud: mycloud
state: present
@@ -156,9 +192,9 @@ EXAMPLES = '''
network: ext_network1
external_fixed_ips:
- subnet: public-subnet
- ip: 172.24.4.2
+ ip_address: 172.24.4.2
- subnet: ipv6-public-subnet
- ip: 2001:db8::3
+ ip_address: 2001:db8::3
# Delete router1
- openstack.cloud.router:
@@ -171,296 +207,431 @@ RETURN = '''
router:
description: Dictionary describing the router.
returned: On success when I(state) is 'present'
- type: complex
+ type: dict
contains:
+ availability_zones:
+ description: Availability zones
+ returned: success
+ type: list
+ availability_zone_hints:
+ description: Availability zone hints
+ returned: success
+ type: list
+ created_at:
+ description: Date and time when the router was created
+ returned: success
+ type: str
+ description:
+ description: Description notes of the router
+ returned: success
+ type: str
+ external_gateway_info:
+ description: The external gateway information of the router.
+ returned: success
+ type: dict
+ sample: |
+ {
+ "enable_snat": true,
+ "external_fixed_ips": [
+ {
+ "ip_address": "10.6.6.99",
+ "subnet_id": "4272cb52-a456-4c20-8f3c-c26024ecfa81"
+ }
+ ]
+ }
+ flavor_id:
+ description: ID of the flavor of the router
+ returned: success
+ type: str
id:
- description: Router ID.
+ description: Unique UUID.
+ returned: success
type: str
sample: "474acfe5-be34-494c-b339-50f06aa143e4"
+ is_admin_state_up:
+ description: Network administrative state
+ returned: success
+ type: bool
+ is_distributed:
+ description: Indicates a distributed router.
+ returned: success
+ type: bool
+ is_ha:
+ description: Indicates a highly-available router.
+ returned: success
+ type: bool
name:
- description: Router name.
+ description: Name given to the router.
+ returned: success
type: str
sample: "router1"
- admin_state_up:
- description: Administrative state of the router.
- type: bool
- sample: true
+ project_id:
+ description: Project id associated with this router.
+ returned: success
+ type: str
+ revision_number:
+ description: Revision number
+ returned: success
+ type: int
+ routes:
+ description: The extra routes configuration for L3 router.
+ returned: success
+ type: list
status:
- description: The router status.
+ description: Router status.
+ returned: success
type: str
sample: "ACTIVE"
+ tags:
+ description: List of tags
+ returned: success
+ type: list
tenant_id:
- description: The tenant ID.
+ description: Owner tenant ID
+ returned: success
+ type: str
+ updated_at:
+ description: Date of last update on the router
+ returned: success
type: str
- sample: "861174b82b43463c9edc5202aadc60ef"
- external_gateway_info:
- description: The external gateway parameters.
- type: dict
- sample: {
- "enable_snat": true,
- "external_fixed_ips": [
- {
- "ip_address": "10.6.6.99",
- "subnet_id": "4272cb52-a456-4c20-8f3c-c26024ecfa81"
- }
- ]
- }
- routes:
- description: The extra routes configuration for L3 router.
- type: list
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
-import itertools
+from collections import defaultdict
class RouterModule(OpenStackModule):
+
+ external_fixed_ips_spec = dict(
+ type='list',
+ elements='dict',
+ options=dict(
+ ip_address=dict(aliases=["ip"]),
+ subnet_id=dict(required=True, aliases=["subnet"]),
+ ))
+
argument_spec = dict(
- state=dict(default='present', choices=['absent', 'present']),
- name=dict(required=True),
- admin_state_up=dict(type='bool', default=True),
enable_snat=dict(type='bool'),
- network=dict(default=None),
- interfaces=dict(type='list', default=None, elements='raw'),
- external_fixed_ips=dict(type='list', default=None, elements='dict'),
- project=dict(default=None)
+ external_fixed_ips=external_fixed_ips_spec,
+ external_gateway_info=dict(type='dict', options=dict(
+ network=dict(),
+ enable_snat=dict(type='bool'),
+ external_fixed_ips=external_fixed_ips_spec,
+ )),
+ interfaces=dict(type='list', elements='raw'),
+ is_admin_state_up=dict(type='bool',
+ default=True,
+ aliases=['admin_state_up']),
+ name=dict(required=True),
+ network=dict(),
+ project=dict(),
+ state=dict(default='present', choices=['absent', 'present']),
)
- 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={
+ 'external_fixed_ips': 'network',
+ 'enable_snat': '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:
+ # added external gateway info
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:
+ 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
- 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
- return True
- # check if internal port has to be added
- if router_ifs_cfg['internal_ports_missing']:
- return True
+ 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)
+ if external_fixed_ips is not None:
+ # User passed expected external_fixed_ips configuration.
+ # Build map of requested ips/subnets.
+ 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]:
+ # mismatching ip for subnet
+ return True
+ else:
+ # adding ext ip with subnet 'subnet'
+ return True
- if missing_port_ids:
- 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]:
+ # removing ext ip with subnet (ip clash)
+ return True
+ else:
+ # removing ext ip with subnet
+ 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:
+ # need to change interfaces
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']
+ # We cannot update a router name because name is used to find routers
+ # by name so only any router with an already matching name will be
+ # considered for updates
- if net:
- kwargs['ext_gateway_net_id'] = net['id']
+ 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:
+ 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:
# 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:
+
+ # get current external fixed ips
+ curr_ext_gw_info = \
+ router['external_gateway_info'] if router else None
+ curr_ext_fixed_ips = \
+ curr_ext_gw_info.get('external_fixed_ips', []) \
+ if curr_ext_gw_info else []
+
+ if len(curr_ext_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]
+ external_gateway_info['external_fixed_ips'] = [
+ curr_ext_fixed_ips[0]]
return kwargs
- def _build_router_interface_config(self, filters=None):
- external_fixed_ips = []
- internal_subnets = []
- internal_ports = []
+ def _build_router_interface_config(self, filters):
+ # Undefine external_fixed_ips to have possibility to unset them
+ external_fixed_ips = None
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)
- 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)
+ 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:
+ # User passed external_fixed_ips configuration. Initialize ips list
+ external_fixed_ips = []
+ for iface in ext_fixed_ips:
+ subnet = self.conn.network.find_subnet(
+ iface['subnet_id'], ignore_missing=False, **filters)
+ 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)
- if not subnet:
- self.fail(msg='subnet %s not found' % iface)
- internal_subnets.append(subnet)
+ subnet = self.conn.network.find_subnet(
+ iface, ignore_missing=False, **filters)
+ internal_ifaces.append(dict(subnet_id=subnet.id))
elif isinstance(iface, dict):
- subnet = self.conn.get_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'])
-
- if "portip" not in iface:
+ subnet = self.conn.network.find_subnet(
+ iface['subnet'], ignore_missing=False, **filters)
+
+ # 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'],
+ ignore_missing=False)
+
+ 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:
- # no port with portip exists hence create a new port
+ # 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
+ # portip exists in net already
+ 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,
+ ignore_missing=False)
+ 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,
+ ignore_missing=False,
+ **query_filters)
# 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
@@ -471,82 +642,44 @@ 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)
+ router = self.conn.network.create_router(**kwargs)
- # 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)
+ 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:
@@ -557,9 +690,10 @@ 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.exit_json(changed=True, router=router)
+ self.conn.network.remove_interface_from_router(
+ router, port_id=port['id'])
+ self.conn.network.delete_router(router)
+ self.exit_json(changed=True)
def main():