From ff0fc73d64cd2467246435097bf25416e4e1cc7e Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 31 Mar 2015 16:37:07 -0400 Subject: [PATCH 1/4] Add OpenStack Floating IP Module Also deprecate the two old quantum floating ip modules. --- ...floating_ip.py => _quantum_floating_ip.py} | 1 + ...e.py => _quantum_floating_ip_associate.py} | 1 + cloud/openstack/os_floating_ip.py | 245 ++++++++++++++++++ 3 files changed, 247 insertions(+) rename cloud/openstack/{quantum_floating_ip.py => _quantum_floating_ip.py} (99%) rename cloud/openstack/{quantum_floating_ip_associate.py => _quantum_floating_ip_associate.py} (99%) create mode 100644 cloud/openstack/os_floating_ip.py diff --git a/cloud/openstack/quantum_floating_ip.py b/cloud/openstack/_quantum_floating_ip.py similarity index 99% rename from cloud/openstack/quantum_floating_ip.py rename to cloud/openstack/_quantum_floating_ip.py index b7599da0725..5220d307844 100644 --- a/cloud/openstack/quantum_floating_ip.py +++ b/cloud/openstack/_quantum_floating_ip.py @@ -36,6 +36,7 @@ version_added: "1.2" author: - "Benno Joy (@bennojoy)" - "Brad P. Crochet (@bcrochet)" +deprecated: Deprecated in 2.0. Use os_floating_ip instead short_description: Add/Remove floating IP from an instance description: - Add or Remove a floating IP to an instance diff --git a/cloud/openstack/quantum_floating_ip_associate.py b/cloud/openstack/_quantum_floating_ip_associate.py similarity index 99% rename from cloud/openstack/quantum_floating_ip_associate.py rename to cloud/openstack/_quantum_floating_ip_associate.py index a5f39dec133..8960e247b0f 100644 --- a/cloud/openstack/quantum_floating_ip_associate.py +++ b/cloud/openstack/_quantum_floating_ip_associate.py @@ -33,6 +33,7 @@ DOCUMENTATION = ''' module: quantum_floating_ip_associate version_added: "1.2" author: "Benno Joy (@bennojoy)" +deprecated: Deprecated in 2.0. Use os_floating_ip instead short_description: Associate or disassociate a particular floating IP with an instance description: - Associates or disassociates a specific floating IP with a particular instance diff --git a/cloud/openstack/os_floating_ip.py b/cloud/openstack/os_floating_ip.py new file mode 100644 index 00000000000..2d939a9bcd7 --- /dev/null +++ b/cloud/openstack/os_floating_ip.py @@ -0,0 +1,245 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# Copyright (c) 2013, Benno Joy +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +DOCUMENTATION = ''' +--- +module: os_floating_ip +version_added: "2.0" +short_description: Add/Remove floating IP from an instance +extends_documentation_fragment: openstack +description: + - Add or Remove a floating IP to an instance +options: + server: + description: + - The name or ID of the instance to which the IP address + should be assigned. + required: true + network_name: + description: + - Name of the network from which IP has to be assigned to VM. + Please make sure the network is an external network. + - Required if ip_address is not given. + required: true + default: None + internal_network_name: + description: + - Name of the network of the port to associate with the floating ip. + Necessary when VM multiple networks. + required: false + default: None + state: + description: + - Should the resource be present or absent. + choices: [present, absent] + required: false + default: present +requirements: ["shade"] +''' + +EXAMPLES = ''' +# Assign a floating ip to the instance from an external network +- os_floating_ip: + cloud: mordred + state: present + server: vm1 + network_name: external_network + internal_network_name: internal_network +''' + + +def _get_server_state(module, cloud): + info = None + server = cloud.get_server(module.params['server']) + if server: + info = server._info + status = info['status'] + if status != 'ACTIVE' and module.params['state'] == 'present': + module.fail_json( + msg="The VM is available but not Active. State: %s" % status + ) + return info, server + + +def _get_port_info(neutron, module, instance_id, internal_network_name=None): + subnet_id = None + if internal_network_name: + kwargs = {'name': internal_network_name} + networks = neutron.list_networks(**kwargs) + network_id = networks['networks'][0]['id'] + kwargs = { + 'network_id': network_id, + 'ip_version': 4 + } + subnets = neutron.list_subnets(**kwargs) + subnet_id = subnets['subnets'][0]['id'] + + kwargs = { + 'device_id': instance_id, + } + try: + ports = neutron.list_ports(**kwargs) + except Exception, e: + module.fail_json(msg="Error in listing ports: %s" % e.message) + + if subnet_id: + port = next(port for port in ports['ports'] if port['fixed_ips'][0]['subnet_id'] == subnet_id) + port_id = port['id'] + fixed_ip_address = port['fixed_ips'][0]['ip_address'] + else: + port_id = ports['ports'][0]['id'] + fixed_ip_address = ports['ports'][0]['fixed_ips'][0]['ip_address'] + + if not ports['ports']: + return None, None + return fixed_ip_address, port_id + + +def _get_floating_ip(neutron, module, fixed_ip_address): + kwargs = { + 'fixed_ip_address': fixed_ip_address + } + try: + ips = neutron.list_floatingips(**kwargs) + except Exception, e: + module.fail_json( + msg="Error in fetching the floatingips's %s" % e.message + ) + + if not ips['floatingips']: + return None, None + + return (ips['floatingips'][0]['id'], + ips['floatingips'][0]['floating_ip_address']) + + +def _create_and_associate_floating_ip(neutron, module, port_id, + net_id, fixed_ip): + kwargs = { + 'port_id': port_id, + 'floating_network_id': net_id, + 'fixed_ip_address': fixed_ip + } + + try: + result = neutron.create_floatingip({'floatingip': kwargs}) + except Exception, e: + module.fail_json( + msg="Error in updating the floating ip address: %s" % e.message + ) + + module.exit_json( + changed=True, + result=result, + public_ip=result['floatingip']['floating_ip_address'] + ) + + +def _get_public_net_id(neutron, module): + kwargs = { + 'name': module.params['network_name'], + } + try: + networks = neutron.list_networks(**kwargs) + except Exception, e: + module.fail_json("Error in listing neutron networks: %s" % e.message) + if not networks['networks']: + return None + return networks['networks'][0]['id'] + + +def _update_floating_ip(neutron, module, port_id, floating_ip_id): + kwargs = { + 'port_id': port_id + } + try: + result = neutron.update_floatingip(floating_ip_id, + {'floatingip': kwargs}) + except Exception, e: + module.fail_json( + msg="Error in updating the floating ip address: %s" % e.message + ) + module.exit_json(changed=True, result=result) + + +def main(): + argument_spec = openstack_full_argument_spec( + server = dict(required=True), + network_name = dict(required=True), + internal_network_name = dict(default=None), + state = dict(default='present', choices=['absent', 'present']), + ) + + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + state = module.params['state'] + internal_network_name = module.params['internal_network_name'] + + try: + cloud = shade.openstack_cloud(**module.params) + neutron = cloud.neutron_client + + server_info, server_obj = _get_server_state(module, cloud) + if not server_info: + module.fail_json(msg="The server provided cannot be found") + + fixed_ip, port_id = _get_port_info( + neutron, module, server_info['id'], internal_network_name) + if not port_id: + module.fail_json(msg="Cannot find a port for this instance," + " maybe fixed ip is not assigned") + + floating_id, floating_ip = _get_floating_ip(neutron, module, fixed_ip) + + if state == 'present': + if floating_ip: + # This server already has a floating IP assigned + module.exit_json(changed=False, public_ip=floating_ip) + + pub_net_id = _get_public_net_id(neutron, module) + if not pub_net_id: + module.fail_json( + msg="Cannot find the public network specified" + ) + _create_and_associate_floating_ip(neutron, module, port_id, + pub_net_id, fixed_ip) + + elif state == 'absent': + if floating_ip: + _update_floating_ip(neutron, module, None, floating_id) + module.exit_json(changed=False) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=e.message) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +main() From e3d9b51cbb954a43f138ea02a9f9311ff7555ca9 Mon Sep 17 00:00:00 2001 From: Davide Guerri Date: Fri, 26 Jun 2015 12:53:20 +0100 Subject: [PATCH 2/4] Update os_floating_ip with new shade methods --- cloud/openstack/os_floating_ip.py | 283 +++++++++++++----------------- 1 file changed, 118 insertions(+), 165 deletions(-) diff --git a/cloud/openstack/os_floating_ip.py b/cloud/openstack/os_floating_ip.py index 2d939a9bcd7..9755b1d4159 100644 --- a/cloud/openstack/os_floating_ip.py +++ b/cloud/openstack/os_floating_ip.py @@ -1,8 +1,6 @@ #!/usr/bin/python -# coding: utf-8 -*- - -# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. -# Copyright (c) 2013, Benno Joy +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# Author: Davide Guerri # # This module is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,9 +15,10 @@ # You should have received a copy of the GNU General Public License # along with this software. If not, see . - try: import shade + from shade import meta + HAS_SHADE = True except ImportError: HAS_SHADE = False @@ -38,19 +37,39 @@ options: - The name or ID of the instance to which the IP address should be assigned. required: true - network_name: + network: description: - - Name of the network from which IP has to be assigned to VM. - Please make sure the network is an external network. - - Required if ip_address is not given. - required: true - default: None - internal_network_name: + - The name or ID of a neutron external network or a nova pool name. + required: false + floating_ip_address: + description: + - A floating IP address to attach or to detach. Required only if state + is absent. When state is present can be used to specify a IP address + to attach. + required: false + reuse: + description: + - When state is present, and 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. + required: false + default: false + fixed_address: + description: + - To which fixed IP of server the floating IP address should be + attached to. + required: false + wait: description: - - Name of the network of the port to associate with the floating ip. - Necessary when VM multiple networks. + - When attaching a floating IP address, specify whether we should + wait for it to appear as attached. required: false - default: None + default false + timeout: + description: + - Time to wait for an IP address to appear as attached. See wait. + required: false + default 60 state: description: - Should the resource be present or absent. @@ -61,136 +80,54 @@ requirements: ["shade"] ''' EXAMPLES = ''' -# Assign a floating ip to the instance from an external network +# Assign a floating IP to the fist interface of `cattle001` from an exiting +# external network or nova pool. If a free floating IP is already allocated to +# the project, it is reused; if not, a new one is created. - os_floating_ip: - cloud: mordred + cloud: dguerri + server: cattle001 + +# Assign a new floating IP to the instance fixed ip `192.0.2.3` of +# `cattle001`. A new floating IP from the external network (or nova pool) +# ext_net is created. +- os_floating_ip: + cloud: dguerri state: present - server: vm1 - network_name: external_network - internal_network_name: internal_network + reuse: false + server: cattle001 + network: ext_net + fixed_address: 192.0.2.3 + wait: true + timeout: 180 + +# Detach a floating IP address from a server +- os_floating_ip: + cloud: dguerri + state: absent + floating_ip_address: 203.0.113.2 + server: cattle001 ''' -def _get_server_state(module, cloud): - info = None - server = cloud.get_server(module.params['server']) - if server: - info = server._info - status = info['status'] - if status != 'ACTIVE' and module.params['state'] == 'present': - module.fail_json( - msg="The VM is available but not Active. State: %s" % status - ) - return info, server - - -def _get_port_info(neutron, module, instance_id, internal_network_name=None): - subnet_id = None - if internal_network_name: - kwargs = {'name': internal_network_name} - networks = neutron.list_networks(**kwargs) - network_id = networks['networks'][0]['id'] - kwargs = { - 'network_id': network_id, - 'ip_version': 4 - } - subnets = neutron.list_subnets(**kwargs) - subnet_id = subnets['subnets'][0]['id'] - - kwargs = { - 'device_id': instance_id, - } - try: - ports = neutron.list_ports(**kwargs) - except Exception, e: - module.fail_json(msg="Error in listing ports: %s" % e.message) - - if subnet_id: - port = next(port for port in ports['ports'] if port['fixed_ips'][0]['subnet_id'] == subnet_id) - port_id = port['id'] - fixed_ip_address = port['fixed_ips'][0]['ip_address'] - else: - port_id = ports['ports'][0]['id'] - fixed_ip_address = ports['ports'][0]['fixed_ips'][0]['ip_address'] - - if not ports['ports']: - return None, None - return fixed_ip_address, port_id - - -def _get_floating_ip(neutron, module, fixed_ip_address): - kwargs = { - 'fixed_ip_address': fixed_ip_address - } - try: - ips = neutron.list_floatingips(**kwargs) - except Exception, e: - module.fail_json( - msg="Error in fetching the floatingips's %s" % e.message - ) - - if not ips['floatingips']: - return None, None - - return (ips['floatingips'][0]['id'], - ips['floatingips'][0]['floating_ip_address']) - - -def _create_and_associate_floating_ip(neutron, module, port_id, - net_id, fixed_ip): - kwargs = { - 'port_id': port_id, - 'floating_network_id': net_id, - 'fixed_ip_address': fixed_ip - } - - try: - result = neutron.create_floatingip({'floatingip': kwargs}) - except Exception, e: - module.fail_json( - msg="Error in updating the floating ip address: %s" % e.message - ) - - module.exit_json( - changed=True, - result=result, - public_ip=result['floatingip']['floating_ip_address'] - ) - - -def _get_public_net_id(neutron, module): - kwargs = { - 'name': module.params['network_name'], - } - try: - networks = neutron.list_networks(**kwargs) - except Exception, e: - module.fail_json("Error in listing neutron networks: %s" % e.message) - if not networks['networks']: +def _get_floating_ip(cloud, floating_ip_address): + f_ips = cloud.search_floating_ips( + filters={'floating_ip_address': floating_ip_address}) + if not f_ips: return None - return networks['networks'][0]['id'] - -def _update_floating_ip(neutron, module, port_id, floating_ip_id): - kwargs = { - 'port_id': port_id - } - try: - result = neutron.update_floatingip(floating_ip_id, - {'floatingip': kwargs}) - except Exception, e: - module.fail_json( - msg="Error in updating the floating ip address: %s" % e.message - ) - module.exit_json(changed=True, result=result) + return f_ips[0] def main(): argument_spec = openstack_full_argument_spec( - server = dict(required=True), - network_name = dict(required=True), - internal_network_name = dict(default=None), - state = dict(default='present', choices=['absent', 'present']), + server=dict(required=True), + state=dict(default='present', choices=['absent', 'present']), + network=dict(required=False), + floating_ip_address=dict(required=False), + reuse=dict(required=False, type='bool', default=False), + fixed_address=dict(required=False), + wait=dict(required=False, type='bool', default=False), + timeout=dict(required=False, type='int', default=60), ) module_kwargs = openstack_module_kwargs() @@ -199,47 +136,63 @@ def main(): if not HAS_SHADE: module.fail_json(msg='shade is required for this module') + server_name_or_id = module.params['server'] state = module.params['state'] - internal_network_name = module.params['internal_network_name'] + network = module.params['network'] + floating_ip_address = module.params['floating_ip_address'] + reuse = module.params['reuse'] + fixed_address = module.params['fixed_address'] + wait = module.params['wait'] + timeout = module.params['timeout'] - try: - cloud = shade.openstack_cloud(**module.params) - neutron = cloud.neutron_client - - server_info, server_obj = _get_server_state(module, cloud) - if not server_info: - module.fail_json(msg="The server provided cannot be found") - - fixed_ip, port_id = _get_port_info( - neutron, module, server_info['id'], internal_network_name) - if not port_id: - module.fail_json(msg="Cannot find a port for this instance," - " maybe fixed ip is not assigned") + cloud = shade.openstack_cloud(**module.params) - floating_id, floating_ip = _get_floating_ip(neutron, module, fixed_ip) + try: + server = cloud.get_server(server_name_or_id) + if server is None: + module.fail_json( + msg="server {0} not found".format(server_name_or_id)) if state == 'present': - if floating_ip: - # This server already has a floating IP assigned - module.exit_json(changed=False, public_ip=floating_ip) - - pub_net_id = _get_public_net_id(neutron, module) - if not pub_net_id: - module.fail_json( - msg="Cannot find the public network specified" - ) - _create_and_associate_floating_ip(neutron, module, port_id, - pub_net_id, fixed_ip) + if floating_ip_address is None: + if reuse: + f_ip = cloud.available_floating_ip(network=network) + else: + f_ip = cloud.create_floating_ip(network=network) + else: + f_ip = _get_floating_ip(cloud, floating_ip_address) + if f_ip is None: + module.fail_json( + msg="floating IP {0} not found".format( + floating_ip_address)) + + cloud.attach_ip_to_server( + server_id=server['id'], floating_ip_id=f_ip['id'], + fixed_address=fixed_address, wait=wait, timeout=timeout) + # Update the floating IP status + f_ip = cloud.get_floating_ip(id=f_ip['id']) + module.exit_json(changed=True, floating_ip=f_ip) elif state == 'absent': - if floating_ip: - _update_floating_ip(neutron, module, None, floating_id) - module.exit_json(changed=False) + if floating_ip_address is None: + module.fail_json(msg="floating_ip_address is required") + + f_ip = _get_floating_ip(cloud, floating_ip_address) + + cloud.detach_ip_from_server( + server_id=server['id'], floating_ip_id=f_ip['id']) + # Update the floating IP status + f_ip = cloud.get_floating_ip(id=f_ip['id']) + module.exit_json(changed=True, floating_ip=f_ip) except shade.OpenStackCloudException as e: - module.fail_json(msg=e.message) + module.fail_json(msg=e.message, extra_data=e.extra_data) + # this is magic, see lib/ansible/module_common.py from ansible.module_utils.basic import * from ansible.module_utils.openstack import * -main() + + +if __name__ == '__main__': + main() From 304e187a52abd165afaf5c4dd88ac28b66bfc149 Mon Sep 17 00:00:00 2001 From: Davide Guerri Date: Fri, 26 Jun 2015 14:53:48 +0100 Subject: [PATCH 3/4] Fix reuse argument documentation --- cloud/openstack/os_floating_ip.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cloud/openstack/os_floating_ip.py b/cloud/openstack/os_floating_ip.py index 9755b1d4159..5bd29240a67 100644 --- a/cloud/openstack/os_floating_ip.py +++ b/cloud/openstack/os_floating_ip.py @@ -81,19 +81,19 @@ requirements: ["shade"] EXAMPLES = ''' # Assign a floating IP to the fist interface of `cattle001` from an exiting -# external network or nova pool. If a free floating IP is already allocated to -# the project, it is reused; if not, a new one is created. +# external network or nova pool. A new floating IP from the first available +# external network is allocated to the project. - os_floating_ip: cloud: dguerri server: cattle001 # Assign a new floating IP to the instance fixed ip `192.0.2.3` of -# `cattle001`. A new floating IP from the external network (or nova pool) -# ext_net is created. +# `cattle001`. If a free floating IP is already allocated to the project, it is +# reused; if not, a new one is created. - os_floating_ip: cloud: dguerri state: present - reuse: false + reuse: yes server: cattle001 network: ext_net fixed_address: 192.0.2.3 From 76398781bac86caf6006e67a77a917155a02f3b4 Mon Sep 17 00:00:00 2001 From: Jesse Keating Date: Tue, 7 Jul 2015 15:29:47 -0700 Subject: [PATCH 4/4] Fix up docs --- cloud/openstack/os_floating_ip.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/openstack/os_floating_ip.py b/cloud/openstack/os_floating_ip.py index 5bd29240a67..10827012ae8 100644 --- a/cloud/openstack/os_floating_ip.py +++ b/cloud/openstack/os_floating_ip.py @@ -64,12 +64,12 @@ options: - When attaching a floating IP address, specify whether we should wait for it to appear as attached. required: false - default false + default: false timeout: description: - Time to wait for an IP address to appear as attached. See wait. required: false - default 60 + default: 60 state: description: - Should the resource be present or absent.