diff options
Diffstat (limited to 'ansible_collections/openstack/cloud/plugins/modules/router.py')
-rw-r--r-- | ansible_collections/openstack/cloud/plugins/modules/router.py | 796 |
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(): |