diff --git a/library/cloud/glance_image b/library/cloud/glance_image new file mode 100755 index 0000000000..f3646abd7a --- /dev/null +++ b/library/cloud/glance_image @@ -0,0 +1,240 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (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 . + +DOCUMENTATION = ''' +--- +module: glance_image +short_description: Add/Delete images from glance +description: + - Add or Remove images from the glance repository. +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: True + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: True + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + name: + description: + - Name that has to be given to the image + required: true + default: None + disk_format: + description: + - The format of the disk that is getting uploaded + required: false + default: qcow2 + container_format: + description: + - The format of the container + required: false + default: bare + owner: + description: + - The owner of the image + required: false + default: None + min_disk: + description: + - The minimum disk space required to deploy this image + required: false + default: None + min_ram: + description: + - The minimum ram required to deploy this image + required: false + default: None + is_public: + description: + - Wether the image can be accesed publically + required: false + default: yes + copy_from: + description: + - A url from where the image can be downloaded, mutually exculsive with file parameter + required: false + default: None + timeout: + description: + - The time to wait for the image process to complete in seconds + required: false + default: 180 + file: + description: + - The path to the file which has to be uploaded, mutually exclusive with copy_from + required: false + default: None +examples: + - code: "glance_image: login_username=admin login_password=passme login_tenant_name=admin name=cirros container_format=bare + disk_format=qcow2 state=present copy_from=http:launchpad.net/cirros/trunk/0.3.0/+download/cirros-0.3.0-x86_64-disk.img" + description: "Uploads an image from an http url" +requirements: ["glanceclient", "keystoneclient"] + +''' + +import time +try: + import glanceclient + from keystoneclient.v2_0 import client as ksclient +except ImportError: + print("failed=True msg='glanceclient and keystone client are required'") + +def _get_ksclient(module, kwargs): + try: + client = ksclient.Client(username=kwargs.get('login_username'), + password=kwargs.get('login_password'), + tenant_name=kwargs.get('login_tenant_name'), + auth_url=kwargs.get('auth_url')) + except Exception as e: + module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message) + return client + + +def _get_endpoint(module, client): + try: + endpoint = client.service_catalog.url_for(service_type='image', endpoint_type='publicURL') + except Exception as e: + module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + return endpoint + +def _get_glance_client(module, kwargs): + _ksclient = _get_ksclient(module, kwargs) + token = _ksclient.auth_token + endpoint =_get_endpoint(module, _ksclient) + kwargs = { + 'token': token, + } + try: + client = glanceclient.Client('1', endpoint, **kwargs) + except Exception as e: + module.fail_json(msg = "Error in connecting to glance: %s" %e.message) + return client + +def _glance_image_present(module, params, client): + try: + for image in client.images.list(): + if image.name == params['name']: + return image.id + return None + except Exception as e: + module.fail_json(msg = "Error in fetching image list: %s" %e.message) + +def _glance_image_create(module, params, client): + kwargs = { + 'name': params.get('name'), + 'disk_format': params.get('disk_format'), + 'container_format': params.get('container_format'), + 'owner': params.get('owner'), + 'is_public': params.get('is_public'), + 'copy_from': params.get('copy_from'), + } + try: + timeout = params.get('timeout') + expire = time.time() + timeout + image = client.images.create(**kwargs) + if not params['copy_from']: + image.update(data=open(params['file'], 'rb')) + while time.time() < expire: + image = client.images.get(image.id) + if image.status == 'active': + break + time.sleep(5) + except Exception as e: + module.fail_json(msg = "Error in creating image: %s" %e.message ) + if image.status == 'active': + module.exit_json(changed = True, result = image.status, id=image.id) + else: + module.fail_json(msg = " The module timed out, please check manually " + image.status) + +def _glance_delete_image(module, params, client): + try: + for image in client.images.list(): + if image.name == params['name']: + client.images.delete(image) + except Exception as e: + module.fail_json(msg = "Error in deleting image: %s" %e.message) + module.exit_json(changed = True, result = "Deleted") + +def main(): + + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin'), + login_password = dict(required=True), + login_tenant_name = dict(required=True), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + name = dict(required=True), + disk_format = dict(default='qcow2', choices=['aki', 'vhd', 'vmdk', 'raw', 'qcow2', 'vdi', 'iso']), + container_format = dict(default='bare', choices=['aki', 'ari', 'bare', 'ovf']), + owner = dict(default=None), + min_disk = dict(default=None), + min_ram = dict(default=None), + is_public = dict(default=True), + copy_from = dict(default= None), + timeout = dict(default=180), + file = dict(default=None), + state = dict(default='present', choices=['absent', 'present']) + ), + mutually_exclusive = [['file','copy_from']], + ) + if module.params['state'] == 'present': + if not module.params['file'] and not module.params['copy_from']: + module.fail_json(msg = "Either file or copy_from variable should be set to create the image") + client = _get_glance_client(module, module.params) + id = _glance_image_present(module, module.params, client) + if not id: + _glance_image_create(module, module.params, client) + module.exit_json(changed = False, id = id, result = "success") + + if module.params['state'] == 'absent': + client = _get_glance_client(module, module.params) + id = _glance_image_present(module, module.params, client) + if not id: + module.exit_json(changed = False, result = "Success") + else: + _glance_delete_image(module, module.params, client) + +# this is magic, see lib/ansible/module.params['common.py +#<> +main() + diff --git a/library/cloud/nova_compute b/library/cloud/nova_compute new file mode 100755 index 0000000000..aaa7cfd5f7 --- /dev/null +++ b/library/cloud/nova_compute @@ -0,0 +1,243 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (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: + from novaclient.v1_1 import client as nova_client + import time +except ImportError: + print("failed=True msg='novaclient is required for this module'") + +DOCUMENTATION = ''' +--- +module: nova_compute +short_description: Create/Delete VM's from OpenStack +description: + - Create or Remove virtual machines from Openstack. +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: True + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: True + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + name: + description: + - Name that has to be given to the image + required: true + default: None + image_id: + description: + - The id of the image that has to be cloned + required: true + default: None + flavor_id: + description: + - The id of the flavor in which the new vm has to be created + required: false + default: 1 + key_name: + description: + - The keypair name to be used when creating a vm + required: false + default: None + security_groups: + description: + - The name of the security group to which the vm should be added + required: false + default: None + nics: + description: + - A list of network id's to which the vm's interface should be attached + required: false + default: None + meta: + description: + - A list of key value pairs that should be provided as a metadata to the new vm + required: false + default: None + wait: + description: + - If the module should wait for the vm to be created. + required: false + default: 'yes' + wait_for: + description: + - The amount of time the module should wait for the vm to get into active state + required: false + default: 180 +examples: + - code: "nova_compute: + state: present + login_username: admin + login_password: admin + login_tenant_name: admin + name: vm1 + image_id: 4f905f38-e52a-43d2-b6ec-754a13ffb529 + key_name: ansible_key + wait_for: 200 + flavor_id: 4 + nics: + - net-id: 34605f38-e52a-25d2-b6ec-754a13ffb723 + meta: + hostname: test1 + group: uge_master" + description: "Creates a new VM and attaches to a network and passes metadata to the instance" +requirements: ["novaclient"] +''' +def _delete_server(module, nova): + name = None + try: + server = nova.servers.list({'name': module.params['name']}).pop() + nova.servers.delete(server) + except Exception as e: + module.fail_json( msg = "Error in deleting vm: %s" % e.message) + if module.params['wait'] == 'no': + module.exit_json(changed = True, result = "deleted") + expire = time.time() + module.params['wait_for'] + while time.time() < expire: + name = nova.servers.list({'name': module.params['name']}) + if not name: + module.exit_json(changed = True, result = "deleted") + time.sleep(5) + module.fail_json(msg = "Timed out waiting for server to get deleted, please check manually") + + +def _create_server(module, nova): + bootargs = [module.params['name'], module.params['image_id'], module.params['flavor_id']] + bootkwargs = { + 'nics' : module.params['nics'], + 'meta' : module.params['meta'], + 'key_name': module.params['key_name'], + 'security_groups': module.params['security_groups'].split(','), + } + if not module.params['key_name']: + del bootkwargs['key_name'] + try: + server = nova.servers.create(*bootargs, **bootkwargs ) + server = nova.servers.get(server.id) + except Exception as e: + module.fail_json( msg = "Error in creating instance: %s " % e.message) + if module.params['wait'] == 'yes': + expire = time.time() + module.params['wait_for'] + while time.time() < expire: + try: + server = nova.servers.get(server.id) + except Exception as e: + module.fail_json( msg = "Error in getting info from instance: %s " % e.message) + if server.status == 'ACTIVE': + private = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'fixed'] + public = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'floating'] + module.exit_json(changed = True, id = server.id, private_ip=''.join(private), public_ip=''.join(public), status = server.status, info = server._info) + if server.status == 'ERROR': + module.fail_json(msg = "Error in creating the server, please check logs") + time.sleep(2) + + module.fail_json(msg = "Timeout waiting for the server to come up.. Please check manually") + if server.status == 'ERROR': + module.fail_json(msg = "Error in creating the server.. Please check manually") + private = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'fixed'] + public = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'floating'] + module.exit_json(changed = True, id = info['id'], private_ip=''.join(private), public_ip=''.join(public), status = server.status, info = server._info) + + +def _get_server_state(module, nova): + server = None + try: + servers = nova.servers.list({'name': module.params['name']}) + if servers: + server = servers.pop() + except Exception as e: + module.fail_json(msg = "Error in getting the server list: %s" % e.message) + if server and module.params['state'] == 'present': + if server.status != 'ACTIVE': + module.fail_json( msg="The VM is available but not Active. state:" + server.status) + private = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'fixed'] + public = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'floating'] + module.exit_json(changed = False, id = server.id, public_ip = ''.join(public), private_ip = ''.join(private), info = server._info) + if server and module.params['state'] == 'absent': + return True + if module.params['state'] == 'absent': + module.exit_json(changed = False, result = "not present") + return True + + + +def main(): + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin'), + login_password = dict(required=True), + login_tenant_name = dict(required='True'), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + name = dict(required=True), + image_id = dict(required=True), + flavor_id = dict(default=1), + key_name = dict(default=None), + security_groups = dict(default='default'), + nics = dict(default=None), + meta = dict(default=None), + wait = dict(default='yes', choices=['yes', 'no']), + wait_for = dict(default=120), + state = dict(default='present', choices=['absent', 'present']) + ), + ) + + try: + nova = nova_client.Client( module.params['login_username'], + module.params['login_password'], + module.params['login_tenant_name'], + module.params['auth_url'], + service_type='compute') + except Exception as e: + module.fail_json( msg = "Error in authenticating to nova: %s" % e.message) + if module.params['state'] == 'present': + _get_server_state(module, nova) + _create_server(module, nova) + if module.params['state'] == 'absent': + _get_server_state(module, nova) + _delete_server(module, nova) + +# this is magic, see lib/ansible/module.params['common.py +#<> +main() + diff --git a/library/cloud/nova_keypair b/library/cloud/nova_keypair new file mode 100755 index 0000000000..bd6baaa3e8 --- /dev/null +++ b/library/cloud/nova_keypair @@ -0,0 +1,125 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (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: + from novaclient.v1_1 import client + import time +except ImportError: + print("failed=True msg='novaclient is required for this module to work'") + +DOCUMENTATION = ''' +--- +module: nova_keypair +short_description: Add/Delete keypair from nova +description: + - Add or Remove keypair from nova . +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: True + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: True + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + name: + description: + - Name that has to be given to the keypair + required: true + default: None + public_key: + description: + - The public key that would be uploaded to nova and injected to vm's upon creation + required: false + default: None + +examples: + - code: "nova_keypair: state=present login_username=admin login_password=admin login_tenant_name=admin name=ansible_key + public_key={{ lookup('file','~/.ssh/id_rsa.pub') }}" + description: "Creates a keypair with the running users public key" + - code: "nova_keypair: state=present login_username=admin login_password=admin login_tenant_name=admin name=ansible_key" + description: "Creates a new keypair and the private key returned after the run." +requirements: ["novaclient"] +''' + +def main(): + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin'), + login_password = dict(required=True), + login_tenant_name = dict(required='True'), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + name = dict(required=True), + public_key = dict(default=None), + state = dict(default='present', choices=['absent', 'present']) + ), + ) + + try: + nova = client.Client(module.params['login_username'], module.params['login_password'], + module.params['login_tenant_name'], module.params['auth_url'], service_type='compute') + except Exception as e: + module.fail_json( msg = " Error in authenticating to nova: %s" % e.message) + if module.params['state'] == 'present': + for key in nova.keypairs.list(): + if key.name == module.params['name']: + module.exit_json(changed = False, result = "Key present") + try: + key = nova.keypairs.create(module.params['name'], module.params['public_key']) + except Exception as e: + module.exit_json(msg = "Error in creating the keypair: %s" % e.message) + if not module.params['public_key']: + module.exit_json(changed = True, key = key.private_key) + module.exit_json(changed = True, key = None) + if module.params['state'] == 'absent': + for key in nova.keypairs.list(): + if key.name == module.params['name']: + try: + nova.keypairs.delete(module.params['name']) + except Exception as e: + module.fail_json(msg = "The keypair deletion has failed: %s" % e.message) + module.exit_json( changed = True, result = "deleted") + module.exit_json(changed = False, result = "not present") + +# this is magic, see lib/ansible/module.params['common.py +#<> +main() + diff --git a/library/cloud/quantum_floating_ip b/library/cloud/quantum_floating_ip new file mode 100755 index 0000000000..bf613877e1 --- /dev/null +++ b/library/cloud/quantum_floating_ip @@ -0,0 +1,233 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (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: + from novaclient.v1_1 import client as nova_client + from quantumclient.quantum import client + from keystoneclient.v2_0 import client as ksclient + import time +except ImportError: + print("failed=True msg='glanceclient,keystoneclient and quantumclient client are required'") + +DOCUMENTATION = ''' +--- +module: quantum_floating_ip +short_description: Add/Remove floating ip from an instance +description: + - Add or Remove a floating ip to an instance +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: True + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: True + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + network_name: + description: + - Name of the nework from which ip has to be assigned to vm. Please make sure the network is an external network + required: true + default: None + instance_name: + description: + - The name of the instance to which the ip address should be assigned + required: true + default: None +examples: + - code: "quantum_floating_ip: state=present login_username=admin login_password=admin login_tenant_name=admin + network_name=external_network instance_name=vm1" + description: "Assigns a floating ip to the instance from an external network" +requirements: ["novaclient", "quantumclient", "keystoneclient"] +''' + +def _get_ksclient(module, kwargs): + try: + kclient = ksclient.Client(username=kwargs.get('login_username'), + password=kwargs.get('login_password'), + tenant_name=kwargs.get('login_tenant_name'), + auth_url=kwargs.get('auth_url')) + except Exception as e: + module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message) + global _os_keystone + _os_keystone = kclient + return kclient + + +def _get_endpoint(module, ksclient): + try: + endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') + except Exception as e: + module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + return endpoint + +def _get_quantum_client(module, kwargs): + _ksclient = _get_ksclient(module, kwargs) + token = _ksclient.auth_token + endpoint = _get_endpoint(module, _ksclient) + kwargs = { + 'token': token, + 'endpoint_url': endpoint + } + try: + quantum = client.Client('2.0', **kwargs) + except Exception as e: + module.fail_json(msg = "Error in connecting to quantum: %s " % e.message) + return quantum + +def _get_server_state(module, nova): + server_info = None + server = None + try: + for server in nova.servers.list(): + if server: + info = server._info + if info['name'] == module.params['instance_name']: + if info['status'] != 'ACTIVE' and module.params['state'] == 'present': + module.fail_json( msg="The VM is available but not Active. state:" + info['status']) + server_info = info + break + except Exception as e: + module.fail_json(msg = "Error in getting the server list: %s" % e.message) + return server_info, server + +def _get_port_info(quantum, module, instance_id): + kwargs = { + 'device_id': instance_id, + } + try: + ports = quantum.list_ports(**kwargs) + except Exception as e: + module.fail_json( msg = "Error in listing ports: %s" % e.message) + if not ports['ports']: + return None, None + return ports['ports'][0]['fixed_ips'][0]['ip_address'], ports['ports'][0]['id'] + +def _get_floating_ip(module, quantum, fixed_ip_address): + kwargs = { + 'fixed_ip_address': fixed_ip_address + } + try: + ips = quantum.list_floatingips(**kwargs) + except Exception as 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_floating_ip(quantum, module, port_id, net_id): + kwargs = { + 'port_id': port_id, + 'floating_network_id': net_id + } + try: + result = quantum.create_floatingip({'floatingip': kwargs}) + except Exception as e: + module.fail_json( msg = "There was an 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_net_id(quantum, module): + kwargs = { + 'name': module.params['network_name'], + } + try: + networks = quantum.list_networks(**kwargs) + except Exception as e: + module.fail_json("Error in listing quantum networks: %s" % e.message) + if not networks['networks']: + return None + return networks['networks'][0]['id'] + +def _update_floating_ip(quantum, module, port_id, floating_ip_id): + kwargs = { + 'port_id': port_id + } + try: + result = quantum.update_floatingip(floating_ip_id, {'floatingip': kwargs}) + except Exception as e: + module.fail_json( msg = "There was an error in updating the floating ip address: %s" % e.message) + module.exit_json( changed = True, result = result) + + +def main(): + + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin'), + login_password = dict(required=True), + login_tenant_name = dict(required='True'), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + network_name = dict(required=True), + instance_name = dict(required=True), + state = dict(default='present', choices=['absent', 'present']) + ), + ) + + try: + nova = nova_client.Client(module.params['login_username'], module.params['login_password'], + module.params['login_tenant_name'], module.params['auth_url'], service_type='compute') + quantum = _get_quantum_client(module, module.params) + except Exception as e: + module.fail_json( msg = " Error in authenticating to nova: %s" % e.message) + + server_info, server_obj = _get_server_state(module, nova) + if not server_info: + module.fail_json( msg = " The instance name provided cannot be found") + fixed_ip, port_id = _get_port_info(quantum, module, server_info['id']) + 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(module, quantum, fixed_ip) + if module.params['state'] == 'present': + if floating_ip: + module.exit_json(changed = False, public_ip=floating_ip) + net_id = _get_net_id(quantum, module) + if not net_id: + module.fail_json(msg = "cannot find the network specified, please check") + _create_floating_ip(quantum, module, port_id, net_id) + + if module.params['state'] == 'absent': + if floating_ip: + _update_floating_ip(quantum, module, None, floating_id) + module.exit_json(changed=False) +# this is magic, see lib/ansible/module.params['common.py +#<> +main() + diff --git a/library/cloud/quantum_floating_ip_associate b/library/cloud/quantum_floating_ip_associate new file mode 100755 index 0000000000..b46037ed82 --- /dev/null +++ b/library/cloud/quantum_floating_ip_associate @@ -0,0 +1,211 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (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: + from novaclient.v1_1 import client as nova_client + from quantumclient.quantum import client + from keystoneclient.v2_0 import client as ksclient + import time +except ImportError: + print("failed=True msg='glanceclient,novaclient and keystone client are required'") + +DOCUMENTATION = ''' +--- +module: quantum_floating_ip_associate +short_description: Associate/Disassociate a particular floating ip with an instance +description: + - Associates or disassociate's a specific floating ip with a particular instance +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: True + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: True + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + instance_name: + description: + - Name of the instance to which the public ip should be assigned + required: true + default: None + ip_address: + description: + - Floating ip that should be assigned to the instance + required: true + default: None +examples: + - code: "quantum_floating_ip_associate: state=present login_username=admin login_password=admin login_tenant_name=admin + ip_address=1.1.1.1 instance_name=vm1" + description: "Associate a specific floating ip with an Instance" +requirements: ["quantumclient", "keystoneclient"] +''' + +def _get_ksclient(module, kwargs): + try: + kclient = ksclient.Client(username=kwargs.get('login_username'), + password=kwargs.get('login_password'), + tenant_name=kwargs.get('login_tenant_name'), + auth_url=kwargs.get('auth_url')) + except Exception as e: + module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message) + global _os_keystone + _os_keystone = kclient + return kclient + + +def _get_endpoint(module, ksclient): + try: + endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') + except Exception as e: + module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + return endpoint + +def _get_quantum_client(module, kwargs): + _ksclient = _get_ksclient(module, kwargs) + token = _ksclient.auth_token + endpoint = _get_endpoint(module, _ksclient) + kwargs = { + 'token': token, + 'endpoint_url': endpoint + } + try: + quantum = client.Client('2.0', **kwargs) + except Exception as e: + module.fail_json(msg = "Error in connecting to quantum: %s " % e.message) + return quantum + +def _get_server_state(module, nova): + server_info = None + server = None + try: + for server in nova.servers.list(): + if server: + info = server._info + if info['name'] == module.params['instance_name']: + if info['status'] != 'ACTIVE' and module.params['state'] == 'present': + module.fail_json( msg="The VM is available but not Active. state:" + info['status']) + server_info = info + break + except Exception as e: + module.fail_json(msg = "Error in getting the server list: %s" % e.message) + return server_info, server + +def _get_port_id(quantum, module, instance_id): + kwargs = { + device_id': instance_id, + } + try: + ports = quantum.list_ports(**kwargs) + except Exception as e: + module.fail_json( msg = "Error in listing ports: %s" % e.message) + if not ports['ports']: + return None + return ports['ports'][0]['id'] + +def _get_floating_ip_id(module, quantum): + kwargs = { + 'floating_ip_address': module.params['ip_address'] + } + try: + ips = quantum.list_floatingips(**kwargs) + except Exception as e: + module.fail_json(msg = "error in fetching the floatingips's %s" % e.message) + if not ips['floatingips']: + module.fail_json(msg = "Could find the ip specified in parameter, Please check") + ip = ips['floatingips'][0]['id'] + if not ips['floatingips'][0]['port_id']: + state = "detached" + else: + state = "attached" + return state, ip + +def _update_floating_ip(quantum, module, port_id, floating_ip_id): + kwargs = { + 'port_id': port_id + } + try: + result = quantum.update_floatingip(floating_ip_id, {'floatingip': kwargs}) + except Exception as e: + module.fail_json( msg = "There was an error in updating the floating ip address: %s" % e.message) + module.exit_json( changed = True, result = result, public_ip=module.params['ip_address']) + +def main(): + + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin'), + login_password = dict(required=True), + login_tenant_name = dict(required='True'), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + ip_address = dict(required=True), + instance_name = dict(required=True), + state = dict(default='present', choices=['absent', 'present']) + ), + ) + + try: + nova = nova_client.Client(module.params['login_username'], module.params['login_password'], module.params['login_tenant_name'], module.params['auth_url'], service_type='compute') + except Exception as e: + module.fail_json( msg = " Error in authenticating to nova: %s" % e.message) + quantum = _get_quantum_client(module, module.params) + state, floating_ip_id = _get_floating_ip_id(module, quantum) + if module.params['state'] == 'present': + if state == 'attached': + module.exit_json(changed = False, result = 'attached', public_ip=module.params['ip_address']) + server_info, server_obj = _get_server_state(module, nova) + if not server_info: + module.fail_json( msg = " The instance name provided cannot be found") + port_id = _get_port_id(quantum, module, server_info['id']) + if not port_id: + module.fail_json(msg = "Cannot find a port for this instance, maybe fixed ip is not assigned") + _update_floating_ip(quantum, module, port_id, floating_ip_id) + + if module.params['state'] == 'absent': + if state == 'detached': + module.exit_json(changed = False, result = 'detached') + if state == 'attached': + _update_floating_ip(quantum, module, None, floating_ip_id) + module.exit_json( changed = True, result = "detached") +# this is magic, see lib/ansible/module.params['common.py +#<> +main() + diff --git a/library/cloud/quantum_network b/library/cloud/quantum_network new file mode 100755 index 0000000000..690c425781 --- /dev/null +++ b/library/cloud/quantum_network @@ -0,0 +1,257 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (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: + from quantumclient.quantum import client + from keystoneclient.v2_0 import client as ksclient +except ImportError: + print("failed=True msg='quantunclient and keystone client are required'") + +DOCUMENTATION = ''' +--- +module: quantum_network +short_description: Creates/Removes networks from OpenStack +description: + - Add or Remove network from OpenStack. +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: True + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: True + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + name: + description: + - Name to be assigned to the nework + required: true + default: None + network_type: + description: + - The type of the network to be created, gre, vlan, local. + required: false + default: local + provider_physical_network: + description: + - The physical network which would realize the virtual network for flat and vlan networks. + required: false + default: None + provider_segmentation_id: + description: + - The id that has to be assigned to the network, in case of vlan networks that would be vlan id and for gre the tunnel id + required: false + default: None + router_external: + description: + - A value of true specifies that the virtual network is a external network (public). + required: false + default: false + shared: + description: + - Wether this network is shared or not + required: false + default: false + admin_state_up: + description: + - Wether the state should be marked as up or down + required: false + default: true +examples: + - code: "quantum_network: state=present login_username=admin login_password=admin + provider_network_type=gre login_tenant_name=admin + provider_segmentation_id=1 tenant_name=tenant1 name=t1network" + description: "Create's a GRE nework with tunnel id of 1 for tenant 1" + - code: "quantum_network: state=present login_username=admin login_password=admin + provider_network_type=local login_tenant_name=admin + provider_segmentation_id=1 router_external=true name=external_network" + description: "Create's an external,public network" +requirements: ["quantumclient", "keystoneclient"] + +''' + +_os_keystone = None +_os_tenant_id = None + +def _get_ksclient(module, kwargs): + try: + kclient = ksclient.Client(username=kwargs.get('login_username'), + password=kwargs.get('login_password'), + tenant_name=kwargs.get('login_tenant_name'), + auth_url=kwargs.get('auth_url')) + except Exception as e: + module.fail_json(msg = "Error authenticating to the keystone: %s" %e.message) + global _os_keystone + _os_keystone = kclient + return kclient + + +def _get_endpoint(module, ksclient): + try: + endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') + except Exception as e: + module.fail_json(msg = "Error getting endpoint for glance: %s " %e.message) + return endpoint + +def _get_quantum_client(module, kwargs): + _ksclient = _get_ksclient(module, kwargs) + token = _ksclient.auth_token + endpoint = _get_endpoint(module, _ksclient) + kwargs = { + 'token': token, + 'endpoint_url': endpoint + } + try: + quantum = client.Client('2.0', **kwargs) + except Exception as e: + module.fail_json(msg = " Error in connecting to quantum: %s " %e.message) + return quantum + +def _set_tenant_id(module): + global _os_tenant_id + if not module.params['tenant_name']: + tenant_name = module.params['login_tenant_name'] + else: + tenant_name = module.params['tenant_name'] + + for tenant in _os_keystone.tenants.list(): + if tenant.name == tenant_name: + _os_tenant_id = tenant.id + break; + if not _os_tenant_id: + module.fail_json(msg = "The tenant id cannot be found, please check the paramters") + + +def _get_net_id(quantum, module): + kwargs = { + 'tenant_id': _os_tenant_id, + 'name': module.params['name'], + } + try: + networks = quantum.list_networks(**kwargs) + except Exception as e: + module.fail_json("Error in listing quantum networks: %s" % e.message) + if not networks['networks']: + return None + return networks['networks'][0]['id'] + +def _create_network(module, quantum): + quantum.format = 'json' + network = { + 'name': module.params.get('name'), + 'tenant_id': _os_tenant_id, + 'provider:network_type': module.params.get('provider_network_type'), + 'provider:physical_network': module.params.get('provider_physical_network'), + 'provider:segmentation_id': module.params.get('provider_segmentation_id'), + 'router:external': module.params.get('router_external'), + 'shared': module.params.get('shared'), + 'admin_state_up': module.params.get('admin_state_up'), + } + if module.params['provider_network_type'] == 'local': + network.pop('provider:physical_network', None) + network.pop('provider:segmentation_id', None) + if module.params['provider_network_type'] == 'flat': + network.pop('provider:segmentation_id', None) + if module.params['provider_network_type'] == 'gre': + network.pop('provider:physical_network', None) + + try: + net = quantum.create_network({'network':network}) + except Exception as e: + module.fail_json(msg = "Error in creating network: %s" % e.message) + return net['network']['id'] + +def _delete_network(module, net_id, quantum): + try: + id = quantum.delete_network(net_id) + except Exception as e: + module.fail_json(msg = "Error in deleting the network: %s" % e.message) + return True + +def main(): + + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin'), + login_password = dict(required=True), + login_tenant_name = dict(required='True'), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + name = dict(required=True), + tenant_name = dict(default=None), + provider_network_type = dict(default='local', choices=['local', 'vlan', 'flat', 'gre']), + provider_physical_network = dict(default=None), + provider_segmentation_id = dict(default=None), + router_external = dict(default='false', choices=BOOLEANS), + shared = dict(default='false', choices=BOOLEANS), + admin_state_up = dict(default='true', choices=BOOLEANS), + state = dict(default='present', choices=['absent', 'present']) + ), + ) + if module.params['provider_network_type'] in ['vlan' , 'flat']: + if not module.params['provider_physical_network']: + module.fail_json(msg = " for vlan and flat networks, variable provider_physical_network should be set.") + if module.params['provider_network_type'] in ['vlan', 'gre']: + if not module.params['provider_segmentation_id']: + module.fail_json(msg = " for vlan & gre networks, variable provider_segmentation_id should be set.") + quantum = _get_quantum_client(module, module.params) + _set_tenant_id(module) + if module.params['state'] == 'present': + network_id = _get_net_id(quantum, module) + if not network_id: + network_id = _create_network(module, quantum) + module.exit_json(changed = True, result = "Created", id = network_id) + else: + module.exit_json(changed = False, result = "Success", id = network_id) + if module.params['state'] == 'absent': + network_id = _get_net_id(quantum, module) + if not network_id: + module.exit_json(changed = False, result = "Success") + else: + _delete_network(module, network_id, quantum) + module.exit_json(changed = True, result = "Deleted") + + + + +# this is magic, see lib/ansible/module.params['common.py +#<> +main() + diff --git a/library/cloud/quantum_router b/library/cloud/quantum_router new file mode 100755 index 0000000000..bccb29f626 --- /dev/null +++ b/library/cloud/quantum_router @@ -0,0 +1,205 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (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: + from quantumclient.quantum import client + from keystoneclient.v2_0 import client as ksclient +except ImportError: + print("failed=True msg='quantumclient and keystone client are required'") + +DOCUMENTATION = ''' +--- +module: quantum_router +short_description: Create or Remove router from openstack +description: + - Create or Delete routers from OpenStack +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: True + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: True + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + name: + description: + - Name to be give to the router + required: true + default: None + tenant_name: + description: + - Name of the tenant for which the router has to be created, if none router would be created for the login tenant. + required: false + default: None + admin_state_up: + description: + - desired admin state of the created router . + required: false + default: true +examples: + - code: "quantum_router: state=present login_username=admin login_password=admin login_tenant_name=admin name=router1" + description: "Creates a router for tenant admin" +requirements: ["quantumclient", "keystoneclient"] +''' + +_os_keystone = None +_os_tenant_id = None + +def _get_ksclient(module, kwargs): + try: + kclient = ksclient.Client(username=kwargs.get('login_username'), + password=kwargs.get('login_password'), + tenant_name=kwargs.get('login_tenant_name'), + auth_url=kwargs.get('auth_url')) + except Exception as e: + module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message) + global _os_keystone + _os_keystone = kclient + return kclient + + +def _get_endpoint(module, ksclient): + try: + endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') + except Exception as e: + module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + return endpoint + +def _get_quantum_client(module, kwargs): + _ksclient = _get_ksclient(module, kwargs) + token = _ksclient.auth_token + endpoint = _get_endpoint(module, _ksclient) + kwargs = { + 'token': token, + 'endpoint_url': endpoint + } + try: + quantum = client.Client('2.0', **kwargs) + except Exception as e: + module.fail_json(msg = "Error in connecting to quantum: %s " % e.message) + return quantum + +def _set_tenant_id(module): + global _os_tenant_id + if not module.params['tenant_name']: + login_tenant_name = module.params['login_tenant_name'] + else: + login_tenant_name = module.params['tenant_name'] + + for tenant in _os_keystone.tenants.list(): + if tenant.name == login_tenant_name: + _os_tenant_id = tenant.id + break; + if not _os_tenant_id: + module.fail_json(msg = "The tenant id cannot be found, please check the paramters") + + +def _get_router_id(module, quantum): + kwargs = { + 'name': module.params['name'], + 'tenant_id': _os_tenant_id, + } + try: + routers = quantum.list_routers(**kwargs) + except Exception as e: + module.fail_json(msg = "Error in getting the router list: %s " % e.message) + if not routers['routers']: + return None + return routers['routers'][0]['id'] + +def _create_router(module, quantum): + router = { + 'name': module.params['name'], + 'tenant_id': _os_tenant_id, + 'admin_state_up': module.params['admin_state_up'], + } + try: + new_router = quantum.create_router({'router': router }) + except Exception as e: + module.fail_json( msg = "Error in creating router: %s" % e.message) + return new_router['router']['id'] + +def _delete_router(module, quantum, router_id): + try: + quantum.delete_router(router_id) + except: + module.fail_json("Error in deleting the router") + return True + +def main(): + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin'), + login_password = dict(required=True), + login_tenant_name = dict(required='True'), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + name = dict(required=True), + tenant_name = dict(default=None), + state = dict(default='present', choices=['absent', 'present']), + admin_state_up = dict(default='true', choices=BOOLEANS), + ), + ) + + quantum = _get_quantum_client(module, module.params) + _set_tenant_id(module) + if module.params['state'] == 'present': + router_id = _get_router_id(module, quantum) + if not router_id: + router_id = _create_router(module, quantum) + module.exit_json(changed = True, result = "Created" , id = router_id) + else: + module.exit_json(changed = False, result = "success" , id = router_id) + else: + router_id = _get_router_id(module, quantum) + if not router_id: + module.exit_json(changed = False, result = "success" ) + else: + _delete_router(module, quantum, router_id) + module.exit_json(changed = True, result = "deleted" ) + + + + +# this is magic, see lib/ansible/module.params['common.py +#<> +main() + diff --git a/library/cloud/quantum_router_gateway b/library/cloud/quantum_router_gateway new file mode 100755 index 0000000000..325379f927 --- /dev/null +++ b/library/cloud/quantum_router_gateway @@ -0,0 +1,210 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (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: + from quantumclient.quantum import client + from keystoneclient.v2_0 import client as ksclient +except ImportError: + print("failed=True msg='quantumclient and keystone client are required'") +DOCUMENTATION = ''' +--- +module: quantum_router_gateway +short_description: set/unset a gateway interface for the router with the specified external network +description: + - Creates/Removes a gateway interface from the router, used to associate a external network with a router to route external traffic. +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: True + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: True + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + router_name: + description: + - Name of the router to which the gateway should be attached. + required: true + default: None + network_name: + description: + - Name of the external network which should be attached to the router. + required: true + default: None +examples: + - code: "quantum_router_gateway: state=present login_username=admin login_password=admin + login_tenant_name=admin router_name=external_router network_name=external_network" + description: "Attaches an external network with a router to allow flow of external traffic" +requirements: ["quantumclient", "keystoneclient"] +''' + +_os_keystone = None +def _get_ksclient(module, kwargs): + try: + kclient = ksclient.Client(username=kwargs.get('login_username'), + password=kwargs.get('login_password'), + tenant_name=kwargs.get('login_tenant_name'), + auth_url=kwargs.get('auth_url')) + except Exception as e: + module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message) + global _os_keystone + _os_keystone = kclient + return kclient + + +def _get_endpoint(module, ksclient): + try: + endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') + except Exception as e: + module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + return endpoint + +def _get_quantum_client(module, kwargs): + _ksclient = _get_ksclient(module, kwargs) + token = _ksclient.auth_token + endpoint = _get_endpoint(module, _ksclient) + kwargs = { + 'token': token, + 'endpoint_url': endpoint + } + try: + quantum = client.Client('2.0', **kwargs) + except Exception as e: + module.fail_json(msg = "Error in connecting to quantum: %s " % e.message) + return quantum + +def _get_router_id(module, quantum): + kwargs = { + 'name': module.params['router_name'], + } + try: + routers = quantum.list_routers(**kwargs) + except Exception as e: + module.fail_json(msg = "Error in getting the router list: %s " % e.message) + if not routers['routers']: + return None + return routers['routers'][0]['id'] + +def _get_net_id(quantum, module): + kwargs = { + 'name': module.params['network_name'], + 'router:external': True + } + try: + networks = quantum.list_networks(**kwargs) + except Exception as e: + module.fail_json("Error in listing quantum networks: %s" % e.message) + if not networks['networks']: + return None + return networks['networks'][0]['id'] + + +def _get_port_id(quantum, module, router_id, network_id): + kwargs = { + 'device_id': router_id, + 'network_id': network_id, + } + try: + ports = quantum.list_ports(**kwargs) + except Exception as e: + module.fail_json( msg = "Error in listing ports: %s" % e.message) + if not ports['ports']: + return None + return ports['ports'][0]['id'] + +def _add_gateway_router(quantum, module, router_id, network_id): + kwargs = { + 'network_id': network_id + } + try: + quantum.add_gateway_router(router_id, kwargs) + except Exception as e: + module.fail_json(msg = "Error in adding gateway to router: %s" % e.message) + return True + +def _remove_gateway_router(quantum, module, router_id): + try: + quantum.remove_gateway_router(router_id) + except Exception as e: + module.fail_json(msg = "Error in removing gateway to router: %s" % e.message) + return True + +def main(): + + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin'), + login_password = dict(required=True), + login_tenant_name = dict(required='True'), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + router_name = dict(required=True), + network_name = dict(required=True), + state = dict(default='present', choices=['absent', 'present']), + ), + ) + + quantum = _get_quantum_client(module, module.params) + router_id = _get_router_id(module, quantum) + if not router_id: + module.fail_json(msg = "failed to get the router id, please check the router name") + network_id = _get_net_id(quantum, module) + if not network_id: + module.fail_json(msg = "failed to get the network id, please check the network name and make sure it is external") + + if module.params['state'] == 'present': + port_id = _get_port_id(quantum, module, router_id, network_id) + if not port_id: + _add_gateway_router(quantum, module, router_id, network_id) + module.exit_json(changed = True, result = "created") + module.exit_json(changed = False, result = "success") + if module.params['state'] == 'absent': + port_id = _get_port_id(quantum, module, router_id, network_id) + if not port_id: + module.exit_json(changed = False, result = "Success") + _remove_gateway_router(quantum, module, router_id) + module.exit_json(changed = True, result = "Deleted") + + + +# this is magic, see lib/ansible/module.params['common.py +#<> +main() + diff --git a/library/cloud/quantum_router_interface b/library/cloud/quantum_router_interface new file mode 100755 index 0000000000..e7d3f180ed --- /dev/null +++ b/library/cloud/quantum_router_interface @@ -0,0 +1,241 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (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: + from quantumclient.quantum import client + from keystoneclient.v2_0 import client as ksclient +except ImportError: + print("failed=True msg='quantumclient and keystone client are required'") +DOCUMENTATION = ''' +--- +module: quantum_router_interface +short_description: Attach/Dettach a subnet's interface to a router +description: + - Attach/Dettach a subnet interface to a router, to provide a gateway for the subnet. +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: True + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: True + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + router_name: + description: + - Name of the router to which the subnet's interface should be attached. + required: true + default: None + subnet_name: + description: + - Name of the subnet to whose interface should be attached to the router. + required: true + default: None + tenant_name: + description: + - Name of the tenant whose subnet has to be attached. + required: false + default: None +examples: + - code: "quantum_router_interface: state=present login_username=admin login_password=admin login_tenant_name=admin + tenant_name=tenant1 router_name=external_route subnet_name=t1subnet" + description: "Attach tenant1's subnet to the external router" +requirements: ["quantumclient", "keystoneclient"] +''' + +_os_keystone = None +_os_tenant_id = None + +def _get_ksclient(module, kwargs): + try: + kclient = ksclient.Client(username=kwargs.get('login_username'), + password=kwargs.get('login_password'), + tenant_name=kwargs.get('login_tenant_name'), + auth_url=kwargs.get('auth_url')) + except Exception as e: + module.fail_json(msg = "Error authenticating to the keystone: %s " % e.message) + global _os_keystone + _os_keystone = kclient + return kclient + + +def _get_endpoint(module, ksclient): + try: + endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') + except Exception as e: + module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + return endpoint + +def _get_quantum_client(module, kwargs): + _ksclient = _get_ksclient(module, kwargs) + token = _ksclient.auth_token + endpoint = _get_endpoint(module, _ksclient) + kwargs = { + 'token': token, + 'endpoint_url': endpoint + } + try: + quantum = client.Client('2.0', **kwargs) + except Exception as e: + module.fail_json(msg = "Error in connecting to quantum: %s " % e.message) + return quantum + +def _set_tenant_id(module): + global _os_tenant_id + if not module.params['tenant_name']: + login_tenant_name = module.params['login_tenant_name'] + else: + login_tenant_name = module.params['tenant_name'] + + for tenant in _os_keystone.tenants.list(): + if tenant.name == login_tenant_name: + _os_tenant_id = tenant.id + break; + if not _os_tenant_id: + module.fail_json(msg = "The tenant id cannot be found, please check the paramters") + + +def _get_router_id(module, quantum): + kwargs = { + 'name': module.params['router_name'], + } + try: + routers = quantum.list_routers(**kwargs) + except Exception as e: + module.fail_json(msg = "Error in getting the router list: %s " % e.message) + if not routers['routers']: + return None + return routers['routers'][0]['id'] + + +def _get_subnet_id(module, quantum): + subnet_id = None + kwargs = { + 'tenant_id': _os_tenant_id, + 'name': module.params['subnet_name'], + } + try: + subnets = quantum.list_subnets(**kwargs) + except Exception as e: + module.fail_json( msg = " Error in getting the subnet list:%s " % e.message) + if not subnets['subnets']: + return None + return subnets['subnets'][0]['id'] + +def _get_port_id(quantum, module, router_id, subnet_id): + kwargs = { + 'tenant_id': _os_tenant_id, + 'device_id': router_id, + } + try: + ports = quantum.list_ports(**kwargs) + except Exception as e: + module.fail_json( msg = "Error in listing ports: %s" % e.message) + if not ports['ports']: + return None + for port in ports['ports']: + for subnet in port['fixed_ips']: + if subnet['subnet_id'] == subnet_id: + return port['id'] + return None + +def _add_interface_router(quantum, module, router_id, subnet_id): + kwargs = { + 'subnet_id': subnet_id + } + try: + quantum.add_interface_router(router_id, kwargs) + except Exception as e: + module.fail_json(msg = "Error in adding interface to router: %s" % e.message) + return True + +def _remove_interface_router(quantum, module, router_id, subnet_id): + kwargs = { + 'subnet_id': subnet_id + } + try: + quantum.remove_interface_router(router_id, kwargs) + except Exception as e: + module.fail_json(msg = "Error in removing interface from router: %s" % e.message) + return True + +def main(): + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin'), + login_password = dict(required=True), + login_tenant_name = dict(required='True'), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + router_name = dict(required=True), + subnet_name = dict(required=True), + tenant_name = dict(default=None), + state = dict(default='present', choices=['absent', 'present']), + ), + ) + + quantum = _get_quantum_client(module, module.params) + _set_tenant_id(module) + router_id = _get_router_id(module, quantum) + if not router_id: + module.fail_json(msg = "failed to get the router id, please check the router name") + subnet_id = _get_subnet_id(module, quantum) + if not subnet_id: + module.fail_json(msg = "failed to get the subnet id, please check the subnet name") + + if module.params['state'] == 'present': + port_id = _get_port_id(quantum, module, router_id, subnet_id) + if not port_id: + _add_interface_router(quantum, module, router_id, subnet_id) + module.exit_json(changed = True, result = "created", id = port_id) + module.exit_json(changed = False, result = "success", id = port_id) + if module.params['state'] == 'absent': + port_id = _get_port_id(quantum, module, router_id, subnet_id) + if not port_id: + module.exit_json(changed = False, result = "Sucess") + _remove_interface_router(quantum, module, router_id, subnet_id) + module.exit_json(changed = True, result = "Deleted") + + + +# this is magic, see lib/ansible/module.params['common.py +#<> +main() + diff --git a/library/cloud/quantum_subnet b/library/cloud/quantum_subnet new file mode 100755 index 0000000000..69c5d41277 --- /dev/null +++ b/library/cloud/quantum_subnet @@ -0,0 +1,270 @@ +#!/usr/bin/python +#coding: utf-8 -*- + +# (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: + from quantumclient.quantum import client + from keystoneclient.v2_0 import client as ksclient +except ImportError: + print("failed=True msg='quantum and keystone client are required'") + +DOCUMENTATION = ''' +--- +module: quantum_floating_ip +short_description: Add/Remove floating ip from an instance +description: + - Add or Remove a floating ip to an instance +options: + login_username: + description: + - login username to authenticate to keystone + required: true + default: admin + login_password: + description: + - Password of login user + required: true + default: True + login_tenant_name: + description: + - The tenant name of the login user + required: true + default: True + auth_url: + description: + - The keystone url for authentication + required: false + default: 'http://127.0.0.1:35357/v2.0/' + region_name: + description: + - Name of the region + required: false + default: None + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + network_name: + description: + - Name of the nework to which the subnet should be attached + required: true + default: None + cidr: + description: + - The cidr representation of the subnet that should be assigned to the subnet + required: true + default: None + tenant_name: + description: + - The name of the tenant for whom the subnet should be created + required: false + default: None + ip_version: + description: + - The IP version of the subnet 4 or 6 + required: false + default: 4 + enable_dhcp: + description: + - Wether DHCP should be enabled for this subnet. + required: false + default: true + gateway_ip: + description: + - The ip that would be assigned to the gateway for this subnet + required: false + default: None + allocation_pool_start: + description: + - From the subnet pool the starting address from which the ip should be allocated + required: false + default: None + allocation_pool_end: + description: + - From the subnet pool the last ip that should be assigned to the virtual machines + required: false + default: None +examples: + - code: "quantum_subnet: state=present login_username=admin login_password=admin login_tenant_name=admin tenant_name=tenant1 + network_name=network1 name=net1subnet cidr=192.168.0.0/24" + description: "Create a subnet for a tenant with the specified subnet" + +requirements: ["quantum", "keystoneclient"] +''' + +_os_keystone = None +_os_tenant_id = None +_os_network_id = None + +def _get_ksclient(module, kwargs): + try: + kclient = ksclient.Client(username=kwargs.get('login_username'), + password=kwargs.get('login_password'), + tenant_name=kwargs.get('login_tenant_name'), + auth_url=kwargs.get('auth_url')) + except Exception as e: + module.fail_json(msg = "Error authenticating to the keystone: %s" %e.message) + global _os_keystone + _os_keystone = kclient + return kclient + + +def _get_endpoint(module, ksclient): + try: + endpoint = ksclient.service_catalog.url_for(service_type='network', endpoint_type='publicURL') + except Exception as e: + module.fail_json(msg = "Error getting endpoint for glance: %s" % e.message) + return endpoint + +def _get_quantum_client(module, kwargs): + _ksclient = _get_ksclient(module, kwargs) + token = _ksclient.auth_token + endpoint = _get_endpoint(module, _ksclient) + kwargs = { + 'token': token, + 'endpoint_url': endpoint + } + try: + quantum = client.Client('2.0', **kwargs) + except Exception as e: + module.fail_json(msg = " Error in connecting to quantum: %s" % e.message) + return quantum + +def _set_tenant_id(module): + global _os_tenant_id + if not module.params['tenant_name']: + tenant_name = module.params['login_tenant_name'] + else: + tenant_name = module.params['tenant_name'] + + for tenant in _os_keystone.tenants.list(): + if tenant.name == tenant_name: + _os_tenant_id = tenant.id + break; + if not _os_tenant_id: + module.fail_json(msg = "The tenant id cannot be found, please check the paramters") + +def _get_net_id(quantum, module): + kwargs = { + 'tenant_id': _os_tenant_id, + 'name': module.params['network_name'], + } + try: + networks = quantum.list_networks(**kwargs) + except Exception as e: + module.fail_json("Error in listing quantum networks: %s" % e.message) + if not networks['networks']: + return None + return networks['networks'][0]['id'] + + +def _get_subnet_id(module, quantum): + global _os_network_id + subnet_id = None + _os_network_id = _get_net_id(quantum, module) + if not _os_network_id: + module.fail_json(msg = "network id of network not found.") + else: + kwargs = { + 'tenant_id': _os_tenant_id, + 'name': module.params['name'], + } + try: + subnets = quantum.list_subnets(**kwargs) + except Exception as e: + module.fail_json( msg = " Error in getting the subnet list:%s " % e.message) + if not subnets['subnets']: + return None + return subnets['subnets'][0]['id'] + +def _create_subnet(module, quantum): + quantum.format = 'json' + subnet = { + 'name': module.params['name'], + 'ip_version': module.params['ip_version'], + 'enable_dhcp': module.params['enable_dhcp'], + 'tenant_id': _os_tenant_id, + 'gateway_ip': module.params['gateway_ip'], + 'network_id': _os_network_id, + 'cidr': module.params['cidr'], + } + if module.params['allocation_pool_start'] and module.params['allocation_pool_end']: + allocation_pools = [ + {'start': module.params['allocation_pool_start'], + 'end': module.params['allocation_pool_end']} + ] + subnet.update({'allocation_pools': allocation_pools}) + if not module.params['gateway_ip']: + subnet.pop('gateway_ip') + try: + new_subnet = quantum.create_subnet({'subnet': subnet }) + except Exception as e: + module.fail_json( msg = "Failure in creating subnet: %s" %e.message) + return new_subnet['subnet']['id'] + + +def _delete_subnet(module, quantum, subnet_id): + try: + quantum.delete_subnet(subnet_id) + except Exception as e: + module.fail_json( msg = "Error in deleting subnet: %s" % e.message) + return True + + +def main(): + + module = AnsibleModule( + argument_spec = dict( + login_username = dict(default='admin'), + login_password = dict(required=True), + login_tenant_name = dict(required='True'), + auth_url = dict(default='http://127.0.0.1:35357/v2.0/'), + region_name = dict(default=None), + name = dict(required=True), + network_name = dict(required=True), + cidr = dict(required=True), + tenant_name = dict(default=None), + state = dict(default='present', choices=['absent', 'present']), + ip_version = dict(default='4', choices=['4', '6']), + enable_dhcp = dict(default='true', choices=BOOLEANS), + gateway_ip = dict(default=None), + allocation_pool_start = dict(default=None), + allocation_pool_end = dict(default=None), + ), + ) + quantum = _get_quantum_client(module, module.params) + _set_tenant_id(module) + if module.params['state'] == 'present': + subnet_id = _get_subnet_id(module, quantum) + if not subnet_id: + subnet_id = _create_subnet(module, quantum) + module.exit_json(changed = True, result = "Created" , id = subnet_id) + else: + module.exit_json(changed = False, result = "success" , id = subnet_id) + else: + subnet_id = _get_subnet_id(module, quantum) + if not subnet_id: + module.exit_json(changed = False, result = "success" ) + else: + _delete_subnet(module, quantum, subnet_id) + module.exit_json(changed = True, result = "deleted" ) + +# this is magic, see lib/ansible/module.params['common.py +#<> +main() + diff --git a/plugins/inventory/nova.py b/plugins/inventory/nova.py index 26613056dd..747300fe06 100755 --- a/plugins/inventory/nova.py +++ b/plugins/inventory/nova.py @@ -177,7 +177,10 @@ if len(sys.argv) == 2 and (sys.argv[1] == '--list'): # Cycle on servers for f in client.servers.list(): - # Define group (or set to empty string) + private = [ x['addr'] for x in getattr(f, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'fixed'] + public = [ x['addr'] for x in getattr(f, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'floating'] + + # Define group (or set to empty string) group = f.metadata['group'] if f.metadata.has_key('group') else 'undefined' # Create group if not exist @@ -185,7 +188,15 @@ if len(sys.argv) == 2 and (sys.argv[1] == '--list'): groups[group] = [] # Append group to list - groups[group].append(f.accessIPv4) + if f.accessIPv4: + groups[group].append(f.accessIPv4) + continue + if public: + groups[group].append(''.join(public)) + continue + if private: + groups[group].append(''.join(private)) + continue # Return server list print json.dumps(groups) @@ -197,8 +208,14 @@ if len(sys.argv) == 2 and (sys.argv[1] == '--list'): elif len(sys.argv) == 3 and (sys.argv[1] == '--host'): results = {} + ips = [] for instance in client.servers.list(): - if instance.accessIPv4 == sys.argv[2]: + private = [ x['addr'] for x in getattr(instance, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'fixed'] + public = [ x['addr'] for x in getattr(instance, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'floating'] + ips.append( instance.accessIPv4) + ips.append(''.join(private)) + ips.append(''.join(public)) + if sys.argv[2] in ips: for key in vars(instance): # Extract value value = getattr(instance, key)