497f020100
Story: 2010610 Task: 47505 Change-Id: I9e138d8f282de9adfb3d7e1142c10ab77c22578e
503 lines
18 KiB
Python
503 lines
18 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright: (c) 2015, Hewlett-Packard Development Company, L.P.
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: floating_ip
|
|
author: OpenStack Ansible SIG
|
|
short_description: Manage floating IP addresses for OpenStack servers
|
|
description:
|
|
- Add or remove an floating ip address to/from an OpenStack server.
|
|
- Returns the floating IP when attaching only if I(wait) is C(true).
|
|
- When detaching a floating IP there might be a delay until an server
|
|
does not list the floating IP any more.
|
|
options:
|
|
fixed_address:
|
|
description:
|
|
- To which fixed IP of server the floating IP address should be
|
|
attached to.
|
|
type: str
|
|
floating_ip_address:
|
|
description:
|
|
- A floating IP address to attach or to detach. When I(state) is
|
|
present can be used to specify a IP address to attach.
|
|
I(floating_ip_address) requires I(network) to be set.
|
|
type: str
|
|
nat_destination:
|
|
description:
|
|
- The name or id of a neutron private network that the fixed IP to
|
|
attach floating IP is on
|
|
aliases: ["fixed_network", "internal_network"]
|
|
type: str
|
|
network:
|
|
description:
|
|
- The name or ID of a neutron external network or a nova pool name.
|
|
type: str
|
|
purge:
|
|
description:
|
|
- When I(state) is absent, indicates whether or not to delete the
|
|
floating IP completely, or only detach it from the server.
|
|
Default is to detach only.
|
|
type: bool
|
|
default: 'false'
|
|
reuse:
|
|
description:
|
|
- When I(state) is present, and I(floating_ip_address) is not present,
|
|
this parameter can be used to specify whether we should try to reuse
|
|
a floating IP address already allocated to the project.
|
|
- When I(reuse) is C(true), I(network) is defined and
|
|
I(floating_ip_address) is undefined, then C(nat_destination) and
|
|
C(fixed_address) will be ignored.
|
|
type: bool
|
|
default: 'false'
|
|
server:
|
|
description:
|
|
- The name or ID of the server to which the IP address
|
|
should be assigned.
|
|
required: true
|
|
type: str
|
|
state:
|
|
description:
|
|
- Should the resource be present or absent.
|
|
choices: [present, absent]
|
|
default: present
|
|
type: str
|
|
extends_documentation_fragment:
|
|
- openstack.cloud.openstack
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
# Assign a floating IP to the first interface of `cattle001` from an existing
|
|
# external network or nova pool. A new floating IP from the first available
|
|
# external network is allocated to the project.
|
|
- openstack.cloud.floating_ip:
|
|
cloud: dguerri
|
|
server: cattle001
|
|
|
|
# Assign a new floating IP to the server fixed ip `192.0.2.3` of
|
|
# `cattle001`. If a free floating IP is already allocated to the project, it is
|
|
# reused; if not, a new one is created.
|
|
- openstack.cloud.floating_ip:
|
|
cloud: dguerri
|
|
state: present
|
|
reuse: true
|
|
server: cattle001
|
|
network: ext_net
|
|
fixed_address: 192.0.2.3
|
|
wait: true
|
|
timeout: 180
|
|
|
|
# Assign a new floating IP from the network `ext_net` to the server fixed
|
|
# ip in network `private_net` of `cattle001`.
|
|
- openstack.cloud.floating_ip:
|
|
cloud: dguerri
|
|
state: present
|
|
server: cattle001
|
|
network: ext_net
|
|
nat_destination: private_net
|
|
wait: true
|
|
timeout: 180
|
|
|
|
# Detach a floating IP address from a server
|
|
- openstack.cloud.floating_ip:
|
|
cloud: dguerri
|
|
state: absent
|
|
floating_ip_address: 203.0.113.2
|
|
server: cattle001
|
|
'''
|
|
|
|
RETURN = '''
|
|
floating_ip:
|
|
description: Dictionary describing the floating ip address.
|
|
type: dict
|
|
returned: success
|
|
contains:
|
|
created_at:
|
|
description: Timestamp at which the floating IP was assigned.
|
|
type: str
|
|
description:
|
|
description: The description of a floating IP.
|
|
type: str
|
|
dns_domain:
|
|
description: The DNS domain.
|
|
type: str
|
|
dns_name:
|
|
description: The DNS name.
|
|
type: str
|
|
fixed_ip_address:
|
|
description: The fixed IP address associated with a floating IP address.
|
|
type: str
|
|
floating_ip_address:
|
|
description: The IP address of a floating IP.
|
|
type: str
|
|
floating_network_id:
|
|
description: The id of the network associated with a floating IP.
|
|
type: str
|
|
id:
|
|
description: Id of the floating ip.
|
|
type: str
|
|
name:
|
|
description: Name of the floating ip.
|
|
type: str
|
|
port_details:
|
|
description: |
|
|
The details of the port that this floating IP associates
|
|
with. Present if C(fip-port-details) extension is loaded.
|
|
type: dict
|
|
port_id:
|
|
description: The port ID floating ip associated with.
|
|
type: str
|
|
project_id:
|
|
description: The ID of the project this floating IP is associated with.
|
|
type: str
|
|
qos_policy_id:
|
|
description: The ID of the QoS policy attached to the floating IP.
|
|
type: str
|
|
revision_number:
|
|
description: Revision number.
|
|
type: str
|
|
router_id:
|
|
description: The id of the router floating ip associated with.
|
|
type: str
|
|
status:
|
|
description: |
|
|
The status of a floating IP, which can be 'ACTIVE' or 'DOWN'.
|
|
type: str
|
|
subnet_id:
|
|
description: The id of the subnet the floating ip associated with.
|
|
type: str
|
|
tags:
|
|
description: List of tags.
|
|
type: list
|
|
elements: str
|
|
updated_at:
|
|
description: Timestamp at which the floating IP was last updated.
|
|
type: str
|
|
'''
|
|
|
|
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
|
|
|
|
|
class NetworkingFloatingIPModule(OpenStackModule):
|
|
argument_spec = dict(
|
|
fixed_address=dict(),
|
|
floating_ip_address=dict(),
|
|
nat_destination=dict(aliases=['fixed_network', 'internal_network']),
|
|
network=dict(),
|
|
purge=dict(type='bool', default=False),
|
|
reuse=dict(type='bool', default=False),
|
|
server=dict(required=True),
|
|
state=dict(default='present', choices=['absent', 'present']),
|
|
)
|
|
|
|
module_kwargs = dict(
|
|
required_if=[
|
|
['state', 'absent', ['floating_ip_address']]
|
|
],
|
|
required_by={
|
|
'floating_ip_address': ('network'),
|
|
}
|
|
)
|
|
|
|
def run(self):
|
|
self._init()
|
|
if self.params['state'] == 'present':
|
|
self._create_and_attach()
|
|
|
|
else: # self.params['state'] == 'absent'
|
|
self._detach_and_delete()
|
|
|
|
def _create_and_attach(self):
|
|
changed = False
|
|
fixed_address = self.params['fixed_address']
|
|
floating_ip_address = self.params['floating_ip_address']
|
|
nat_destination_name_or_id = self.params['nat_destination']
|
|
network_id = self.network['id'] if self.network else None
|
|
|
|
ips = self._find_ips(
|
|
server=self.server,
|
|
floating_ip_address=floating_ip_address,
|
|
network_id=network_id,
|
|
fixed_address=fixed_address,
|
|
nat_destination_name_or_id=nat_destination_name_or_id)
|
|
|
|
# First floating ip satisfies our requirements
|
|
ip = ips[0] if ips else None
|
|
|
|
if floating_ip_address:
|
|
# A specific floating ip address has been requested
|
|
|
|
if not ip:
|
|
# If a specific floating ip address has been requested
|
|
# and it does not exist yet then create it
|
|
|
|
# openstacksdk's create_ip requires floating_ip_address
|
|
# and floating_network_id to be set
|
|
self.conn.network.create_ip(
|
|
floating_ip_address=floating_ip_address,
|
|
floating_network_id=network_id)
|
|
changed = True
|
|
|
|
else: # ip
|
|
# Requested floating ip address exists already
|
|
|
|
if ip.port_details and (ip.port_details['status'] == 'ACTIVE') \
|
|
and (floating_ip_address not in self._filter_ips(
|
|
self.server)):
|
|
# Floating ip address exists and has been attached
|
|
# but to a different server
|
|
|
|
# Requested ip has been attached to different server
|
|
self.fail_json(
|
|
msg="Floating ip {0} has been attached to different "
|
|
"server".format(floating_ip_address))
|
|
|
|
if not ip \
|
|
or floating_ip_address not in self._filter_ips(self.server):
|
|
# Requested floating ip address does not exist or has not been
|
|
# assigned to server
|
|
|
|
self.conn.add_ip_list(
|
|
server=self.server,
|
|
ips=[floating_ip_address],
|
|
wait=self.params['wait'],
|
|
timeout=self.params['timeout'],
|
|
fixed_address=fixed_address)
|
|
changed = True
|
|
else:
|
|
# Requested floating ip address has been assigned to server
|
|
pass
|
|
|
|
elif not ips: # and not floating_ip_address
|
|
# No specific floating ip has been requested and none of the
|
|
# floating ips which have been assigned to the server matches
|
|
# requirements
|
|
|
|
# add_ips_to_server() will handle several scenarios:
|
|
#
|
|
# If a specific floating ip address has been requested then it
|
|
# will be attached to the server. The floating ip address has
|
|
# either been created in previous steps or it already existed.
|
|
# Ref.: https://github.com/openstack/openstacksdk/blob/
|
|
# 9d3ee1d32149ba2a8bb3dc894295e180746cdddc/openstack/cloud
|
|
# /_floating_ip.py#L985
|
|
#
|
|
# If no specific floating ip address has been requested, reuse
|
|
# is allowed and a network has been given (with ip_pool) from
|
|
# which floating ip addresses will be drawn, then any existing
|
|
# floating ip address from ip_pool=network which is not
|
|
# attached to any other server will be attached to the server.
|
|
# If no such floating ip address exists or if reuse is not
|
|
# allowed, then a new floating ip address will be created
|
|
# within ip_pool=network and attached to the server.
|
|
# Ref.: https://github.com/openstack/openstacksdk/blob/
|
|
# 9d3ee1d32149ba2a8bb3dc894295e180746cdddc/openstack/cloud/
|
|
# _floating_ip.py#L981
|
|
#
|
|
# If no specific floating ip address has been requested and no
|
|
# network has been given (with ip_pool) from which floating ip
|
|
# addresses will be taken, then a floating ip address might be
|
|
# added to the server, refer to _needs_floating_ip() for
|
|
# details.
|
|
# Ref.:
|
|
# * https://github.com/openstack/openstacksdk/blob/
|
|
# 9d3ee1d32149ba2a8bb3dc894295e180746cdddc/openstack/cloud/\
|
|
# _floating_ip.py#L989
|
|
# * https://github.com/openstack/openstacksdk/blob/
|
|
# 9d3ee1d32149ba2a8bb3dc894295e180746cdddc/openstack/cloud/
|
|
# _floating_ip.py#L995
|
|
#
|
|
# Both floating_ip_address and network are mutually exclusive
|
|
# in add_ips_to_server(), i.e.add_ips_to_server will ignore
|
|
# floating_ip_address if network is not None. To prefer
|
|
# attaching a specific floating ip address over assigning any
|
|
# fip, ip_pool is only defined if floating_ip_address is None.
|
|
# Ref.: https://github.com/openstack/openstacksdk/blob/
|
|
# a6b0ece2821ea79330c4067100295f6bdcbe456e/openstack/cloud/
|
|
# _floating_ip.py#L987
|
|
self.conn.add_ips_to_server(
|
|
server=self.server,
|
|
ip_pool=network_id,
|
|
ips=None, # No specific floating ip requested
|
|
reuse=self.params['reuse'],
|
|
fixed_address=fixed_address,
|
|
wait=self.params['wait'],
|
|
timeout=self.params['timeout'],
|
|
nat_destination=nat_destination_name_or_id)
|
|
changed = True
|
|
else:
|
|
# Found one or more floating ips which satisfy requirements
|
|
pass
|
|
|
|
if changed:
|
|
# update server details such as addresses
|
|
self.server = self.conn.compute.get_server(self.server)
|
|
|
|
# Update the floating ip resource
|
|
ips = self._find_ips(
|
|
self.server, floating_ip_address, network_id,
|
|
fixed_address, nat_destination_name_or_id)
|
|
|
|
# ips can be empty, e.g. when server has no private ipv4
|
|
# address to which a floating ip address can be attached
|
|
|
|
self.exit_json(
|
|
changed=changed,
|
|
floating_ip=ips[0].to_dict(computed=False) if ips else None)
|
|
|
|
def _detach_and_delete(self):
|
|
ips = self._find_ips(
|
|
server=self.server,
|
|
floating_ip_address=self.params['floating_ip_address'],
|
|
network_id=self.network['id'] if self.network else None,
|
|
fixed_address=self.params['fixed_address'],
|
|
nat_destination_name_or_id=self.params['nat_destination'])
|
|
|
|
if not ips:
|
|
# Nothing to detach
|
|
self.exit_json(changed=False)
|
|
|
|
changed = False
|
|
for ip in ips:
|
|
if ip['fixed_ip_address']:
|
|
# Silently ignore that ip might not be attached to server
|
|
#
|
|
# self.conn.network.update_ip(ip_id, port_id=None) does not
|
|
# handle nova network but self.conn.detach_ip_from_server()
|
|
# does so
|
|
self.conn.detach_ip_from_server(server_id=self.server['id'],
|
|
floating_ip_id=ip['id'])
|
|
|
|
# OpenStackSDK sets {"port_id": None} to detach a floating
|
|
# ip from a device, but there might be a delay until a
|
|
# server does not list it in addresses any more.
|
|
changed = True
|
|
|
|
if self.params['purge']:
|
|
self.conn.network.delete_ip(ip['id'])
|
|
changed = True
|
|
|
|
self.exit_json(changed=changed)
|
|
|
|
def _filter_ips(self, server):
|
|
# Extract floating ips from server
|
|
|
|
def _flatten(lists):
|
|
return [item for sublist in lists for item in sublist]
|
|
|
|
if server['addresses'] is None:
|
|
# fetch server with details
|
|
server = self.conn.compute.get_server(server)
|
|
|
|
if not server['addresses']:
|
|
return []
|
|
|
|
# Returns a list not an iterator here because
|
|
# it is iterated several times below
|
|
return [address['addr']
|
|
for address in _flatten(server['addresses'].values())
|
|
if address['OS-EXT-IPS:type'] == 'floating']
|
|
|
|
def _find_ips(self,
|
|
server,
|
|
floating_ip_address,
|
|
network_id,
|
|
fixed_address,
|
|
nat_destination_name_or_id):
|
|
# Check which floating ips matches our requirements.
|
|
# They might or might not be attached to our server.
|
|
if floating_ip_address:
|
|
# A specific floating ip address has been requested
|
|
ip = self.conn.network.find_ip(floating_ip_address)
|
|
return [ip] if ip else []
|
|
elif (not fixed_address and nat_destination_name_or_id):
|
|
# No specific floating ip and no specific fixed ip have been
|
|
# requested but a private network (nat_destination) has been
|
|
# given where the floating ip should be attached to.
|
|
return self._find_ips_by_nat_destination(
|
|
server, nat_destination_name_or_id)
|
|
else:
|
|
# not floating_ip_address
|
|
# and (fixed_address or not nat_destination_name_or_id)
|
|
|
|
# An analysis of all floating ips of server is required
|
|
return self._find_ips_by_network_id_and_fixed_address(
|
|
server, fixed_address, network_id)
|
|
|
|
def _find_ips_by_nat_destination(self,
|
|
server,
|
|
nat_destination_name_or_id):
|
|
|
|
if not server['addresses']:
|
|
return None
|
|
|
|
# Check if we have any floating ip on
|
|
# the given nat_destination network
|
|
nat_destination = self.conn.network.find_network(
|
|
nat_destination_name_or_id, ignore_missing=False)
|
|
|
|
fips_with_nat_destination = [
|
|
addr for addr
|
|
in server['addresses'].get(nat_destination['name'], [])
|
|
if addr['OS-EXT-IPS:type'] == 'floating']
|
|
|
|
if not fips_with_nat_destination:
|
|
return None
|
|
|
|
# One or more floating ip addresses have been assigned
|
|
# to the requested nat_destination; return the first.
|
|
return [self.conn.network.find_ip(fip['addr'], ignore_missing=False)
|
|
for fip in fips_with_nat_destination]
|
|
|
|
def _find_ips_by_network_id_and_fixed_address(self,
|
|
server,
|
|
fixed_address=None,
|
|
network_id=None):
|
|
# Get any of the floating ips that matches fixed_address and/or network
|
|
ips = [ip for ip in self.conn.network.ips()
|
|
if ip['floating_ip_address'] in self._filter_ips(server)]
|
|
|
|
matching_ips = []
|
|
for ip in ips:
|
|
if network_id and ip['floating_network_id'] != network_id:
|
|
# Requested network does not
|
|
# match network of floating ip
|
|
continue
|
|
|
|
if not fixed_address: # and not nat_destination_name_or_id
|
|
# Any floating ip will fullfil these requirements
|
|
matching_ips.append(ip)
|
|
|
|
if (fixed_address and ip['fixed_ip_address'] == fixed_address):
|
|
# A floating ip address has been assigned that
|
|
# points to the requested fixed_address
|
|
matching_ips.append(ip)
|
|
|
|
return matching_ips
|
|
|
|
def _init(self):
|
|
server_name_or_id = self.params['server']
|
|
server = self.conn.compute.find_server(server_name_or_id,
|
|
ignore_missing=False)
|
|
# fetch server details such as addresses
|
|
self.server = self.conn.compute.get_server(server)
|
|
|
|
network_name_or_id = self.params['network']
|
|
if network_name_or_id:
|
|
self.network = self.conn.network.find_network(
|
|
name_or_id=network_name_or_id, ignore_missing=False)
|
|
else:
|
|
self.network = None
|
|
|
|
|
|
def main():
|
|
module = NetworkingFloatingIPModule()
|
|
module()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|