2013-05-23 04:25:05 +02:00
|
|
|
#!/usr/bin/python
|
|
|
|
#coding: utf-8 -*-
|
|
|
|
|
2014-01-28 17:20:36 +01:00
|
|
|
# (c) 2013, Benno Joy <benno@ansible.com>
|
2013-11-23 06:37:47 +01:00
|
|
|
# (c) 2013, John Dewey <john@dewey.ws>
|
2013-05-23 04:25:05 +02:00
|
|
|
#
|
|
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
try:
|
|
|
|
from novaclient.v1_1 import client as nova_client
|
2013-11-23 06:37:47 +01:00
|
|
|
from novaclient import exceptions
|
2013-05-23 04:25:05 +02:00
|
|
|
import time
|
|
|
|
except ImportError:
|
|
|
|
print("failed=True msg='novaclient is required for this module'")
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: nova_compute
|
2013-11-19 00:55:49 +01:00
|
|
|
version_added: "1.2"
|
2013-06-14 11:53:43 +02:00
|
|
|
short_description: Create/Delete VMs from OpenStack
|
2013-05-23 04:25:05 +02:00
|
|
|
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
|
2013-06-01 17:52:28 +02:00
|
|
|
default: 'yes'
|
2013-05-23 04:25:05 +02:00
|
|
|
login_tenant_name:
|
|
|
|
description:
|
|
|
|
- The tenant name of the login user
|
|
|
|
required: true
|
2013-06-01 17:52:28 +02:00
|
|
|
default: 'yes'
|
2013-05-23 04:25:05 +02:00
|
|
|
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:
|
2013-10-31 11:34:04 +01:00
|
|
|
- Name that has to be given to the instance
|
2013-05-23 04:25:05 +02:00
|
|
|
required: true
|
|
|
|
default: None
|
|
|
|
image_id:
|
|
|
|
description:
|
|
|
|
- The id of the image that has to be cloned
|
|
|
|
required: true
|
|
|
|
default: None
|
|
|
|
flavor_id:
|
|
|
|
description:
|
2013-06-14 11:53:43 +02:00
|
|
|
- The id of the flavor in which the new VM has to be created
|
2013-05-23 04:25:05 +02:00
|
|
|
required: false
|
|
|
|
default: 1
|
|
|
|
key_name:
|
|
|
|
description:
|
2013-06-14 11:53:43 +02:00
|
|
|
- The key pair name to be used when creating a VM
|
2013-05-23 04:25:05 +02:00
|
|
|
required: false
|
|
|
|
default: None
|
|
|
|
security_groups:
|
|
|
|
description:
|
2013-06-14 11:53:43 +02:00
|
|
|
- The name of the security group to which the VM should be added
|
2013-05-23 04:25:05 +02:00
|
|
|
required: false
|
|
|
|
default: None
|
|
|
|
nics:
|
|
|
|
description:
|
2013-11-23 06:37:47 +01:00
|
|
|
- A list of network id's to which the VM's interface should be attached
|
2013-05-23 04:25:05 +02:00
|
|
|
required: false
|
|
|
|
default: None
|
|
|
|
meta:
|
|
|
|
description:
|
2013-06-14 11:53:43 +02:00
|
|
|
- A list of key value pairs that should be provided as a metadata to the new VM
|
2013-05-23 04:25:05 +02:00
|
|
|
required: false
|
|
|
|
default: None
|
|
|
|
wait:
|
|
|
|
description:
|
2013-06-14 11:53:43 +02:00
|
|
|
- If the module should wait for the VM to be created.
|
2013-05-23 04:25:05 +02:00
|
|
|
required: false
|
|
|
|
default: 'yes'
|
|
|
|
wait_for:
|
|
|
|
description:
|
2013-06-14 11:53:43 +02:00
|
|
|
- The amount of time the module should wait for the VM to get into active state
|
2013-05-23 04:25:05 +02:00
|
|
|
required: false
|
|
|
|
default: 180
|
2014-02-08 15:45:03 +01:00
|
|
|
user_data:
|
|
|
|
description:
|
|
|
|
- Opaque blob of data which is made available to the instance
|
|
|
|
required: false
|
|
|
|
default: None
|
2014-03-11 19:45:04 +01:00
|
|
|
version_added: "1.6"
|
2013-06-14 11:53:43 +02:00
|
|
|
requirements: ["novaclient"]
|
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
# Creates a new VM and attaches to a network and passes metadata to the instance
|
|
|
|
- nova_compute:
|
2013-05-23 04:25:05 +02:00
|
|
|
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
|
2013-06-14 11:53:43 +02:00
|
|
|
group: uge_master
|
2013-05-23 04:25:05 +02:00
|
|
|
'''
|
2013-06-14 11:53:43 +02:00
|
|
|
|
2013-05-23 04:25:05 +02:00
|
|
|
def _delete_server(module, nova):
|
|
|
|
name = None
|
2013-10-10 08:14:30 +02:00
|
|
|
server_list = None
|
2013-05-23 04:25:05 +02:00
|
|
|
try:
|
2013-10-10 08:14:30 +02:00
|
|
|
server_list = nova.servers.list(True, {'name': module.params['name']})
|
|
|
|
if server_list:
|
|
|
|
server = [x for x in server_list if x.name == module.params['name']]
|
|
|
|
nova.servers.delete(server.pop())
|
2014-02-02 18:33:27 +01:00
|
|
|
except Exception, e:
|
2013-05-25 05:34:48 +02:00
|
|
|
module.fail_json( msg = "Error in deleting vm: %s" % e.message)
|
2013-05-23 04:25:05 +02:00
|
|
|
if module.params['wait'] == 'no':
|
2013-05-25 05:34:48 +02:00
|
|
|
module.exit_json(changed = True, result = "deleted")
|
2013-09-08 23:36:37 +02:00
|
|
|
expire = time.time() + int(module.params['wait_for'])
|
2013-05-23 04:25:05 +02:00
|
|
|
while time.time() < expire:
|
2013-05-31 14:09:00 +02:00
|
|
|
name = nova.servers.list(True, {'name': module.params['name']})
|
2013-05-25 05:34:48 +02:00
|
|
|
if not name:
|
|
|
|
module.exit_json(changed = True, result = "deleted")
|
|
|
|
time.sleep(5)
|
2013-05-23 04:25:05 +02:00
|
|
|
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 = {
|
2013-05-25 05:34:48 +02:00
|
|
|
'nics' : module.params['nics'],
|
|
|
|
'meta' : module.params['meta'],
|
|
|
|
'key_name': module.params['key_name'],
|
|
|
|
'security_groups': module.params['security_groups'].split(','),
|
2014-02-08 15:45:03 +01:00
|
|
|
#userdata is unhyphenated in novaclient, but hyphenated here for consistency with the ec2 module:
|
|
|
|
'userdata': module.params['user_data'],
|
2013-05-23 04:25:05 +02:00
|
|
|
}
|
|
|
|
if not module.params['key_name']:
|
2013-05-25 05:34:48 +02:00
|
|
|
del bootkwargs['key_name']
|
2013-05-23 04:25:05 +02:00
|
|
|
try:
|
2013-05-25 05:46:23 +02:00
|
|
|
server = nova.servers.create(*bootargs, **bootkwargs)
|
2013-05-25 05:34:48 +02:00
|
|
|
server = nova.servers.get(server.id)
|
2014-02-02 18:33:27 +01:00
|
|
|
except Exception, e:
|
2013-05-25 05:34:48 +02:00
|
|
|
module.fail_json( msg = "Error in creating instance: %s " % e.message)
|
2013-05-23 04:25:05 +02:00
|
|
|
if module.params['wait'] == 'yes':
|
2013-09-08 23:36:37 +02:00
|
|
|
expire = time.time() + int(module.params['wait_for'])
|
2013-05-25 05:34:48 +02:00
|
|
|
while time.time() < expire:
|
|
|
|
try:
|
2013-05-23 04:25:05 +02:00
|
|
|
server = nova.servers.get(server.id)
|
2014-02-02 18:33:27 +01:00
|
|
|
except Exception, e:
|
2013-05-23 04:25:05 +02:00
|
|
|
module.fail_json( msg = "Error in getting info from instance: %s " % e.message)
|
2013-05-25 05:34:48 +02:00
|
|
|
if server.status == 'ACTIVE':
|
Fix traceback when openstack do not have OS-EXT-IPS:type attached to address
failed: [127.0.0.1] => {"failed": true, "parsed": false}
invalid output was: Traceback (most recent call last):
File "/tmp/ansible-1376083321.99-111209413777779/nova_compute", line 1176, in <module>
main()
File "/tmp/ansible-1376083321.99-111209413777779/nova_compute", line 239, in main
_get_server_state(module, nova)
File "/tmp/ansible-1376083321.99-111209413777779/nova_compute", line 198, in _get_server_state
private = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'fixed']
KeyError: 'OS-EXT-IPS:type'
This extension was added less than 6 month ago, and so cannot be used on a release
older than Grizzly ( like Folsom ).
Commit of the extension : https://review.openstack.org/#/c/21453/
See https://wiki.openstack.org/wiki/ReleaseNotes/Grizzly#Key_New_Features_2
2013-08-09 23:25:15 +02:00
|
|
|
private = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if 'OS-EXT-IPS:type' in x and x['OS-EXT-IPS:type'] == 'fixed']
|
|
|
|
public = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if 'OS-EXT-IPS:type' in x and x['OS-EXT-IPS:type'] == 'floating']
|
2013-05-25 05:34:48 +02:00
|
|
|
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)
|
2013-11-23 06:37:47 +01:00
|
|
|
|
2013-05-25 05:34:48 +02:00
|
|
|
module.fail_json(msg = "Timeout waiting for the server to come up.. Please check manually")
|
2013-05-23 04:25:05 +02:00
|
|
|
if server.status == 'ERROR':
|
2013-05-25 05:34:48 +02:00
|
|
|
module.fail_json(msg = "Error in creating the server.. Please check manually")
|
2013-05-23 04:25:05 +02:00
|
|
|
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)
|
|
|
|
|
2013-11-23 06:37:47 +01:00
|
|
|
|
2013-05-23 04:25:05 +02:00
|
|
|
def _get_server_state(module, nova):
|
|
|
|
server = None
|
|
|
|
try:
|
2013-05-31 14:09:00 +02:00
|
|
|
servers = nova.servers.list(True, {'name': module.params['name']})
|
2013-05-25 05:34:48 +02:00
|
|
|
if servers:
|
2014-03-01 03:05:52 +01:00
|
|
|
# the {'name': module.params['name']} will also return servers
|
|
|
|
# with names that partially match the server name, so we have to
|
|
|
|
# strictly filter here
|
|
|
|
servers = [x for x in servers if x.name == module.params['name']]
|
2014-03-03 19:55:28 +01:00
|
|
|
if servers:
|
|
|
|
server = servers[0]
|
2014-02-02 18:33:27 +01:00
|
|
|
except Exception, e:
|
2013-05-25 05:34:48 +02:00
|
|
|
module.fail_json(msg = "Error in getting the server list: %s" % e.message)
|
2013-05-23 04:25:05 +02:00
|
|
|
if server and module.params['state'] == 'present':
|
2013-05-25 05:34:48 +02:00
|
|
|
if server.status != 'ACTIVE':
|
|
|
|
module.fail_json( msg="The VM is available but not Active. state:" + server.status)
|
Fix traceback when openstack do not have OS-EXT-IPS:type attached to address
failed: [127.0.0.1] => {"failed": true, "parsed": false}
invalid output was: Traceback (most recent call last):
File "/tmp/ansible-1376083321.99-111209413777779/nova_compute", line 1176, in <module>
main()
File "/tmp/ansible-1376083321.99-111209413777779/nova_compute", line 239, in main
_get_server_state(module, nova)
File "/tmp/ansible-1376083321.99-111209413777779/nova_compute", line 198, in _get_server_state
private = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if x['OS-EXT-IPS:type'] == 'fixed']
KeyError: 'OS-EXT-IPS:type'
This extension was added less than 6 month ago, and so cannot be used on a release
older than Grizzly ( like Folsom ).
Commit of the extension : https://review.openstack.org/#/c/21453/
See https://wiki.openstack.org/wiki/ReleaseNotes/Grizzly#Key_New_Features_2
2013-08-09 23:25:15 +02:00
|
|
|
private = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if 'OS-EXT-IPS:type' in x and x['OS-EXT-IPS:type'] == 'fixed']
|
|
|
|
public = [ x['addr'] for x in getattr(server, 'addresses').itervalues().next() if 'OS-EXT-IPS:type' in x and x['OS-EXT-IPS:type'] == 'floating']
|
2013-11-23 06:37:47 +01:00
|
|
|
module.exit_json(changed = False, id = server.id, public_ip = ''.join(public), private_ip = ''.join(private), info = server._info)
|
2013-05-23 04:25:05 +02:00
|
|
|
if server and module.params['state'] == 'absent':
|
2013-05-25 05:34:48 +02:00
|
|
|
return True
|
2013-05-23 04:25:05 +02:00
|
|
|
if module.params['state'] == 'absent':
|
2013-05-25 05:34:48 +02:00
|
|
|
module.exit_json(changed = False, result = "not present")
|
2013-05-23 04:25:05 +02:00
|
|
|
return True
|
|
|
|
|
2013-11-23 06:37:47 +01:00
|
|
|
|
|
|
|
|
2013-05-23 04:25:05 +02:00
|
|
|
def main():
|
|
|
|
module = AnsibleModule(
|
2013-05-25 05:34:48 +02:00
|
|
|
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),
|
2013-11-23 06:37:47 +01:00
|
|
|
image_id = dict(default=None),
|
2013-05-25 05:34:48 +02:00
|
|
|
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']),
|
2013-09-08 23:38:48 +02:00
|
|
|
wait_for = dict(default=180),
|
2014-02-08 15:45:03 +01:00
|
|
|
state = dict(default='present', choices=['absent', 'present']),
|
|
|
|
user_data = dict(default=None)
|
2013-05-23 04:25:05 +02:00
|
|
|
),
|
|
|
|
)
|
2013-11-23 06:37:47 +01:00
|
|
|
|
|
|
|
nova = nova_client.Client(module.params['login_username'],
|
|
|
|
module.params['login_password'],
|
|
|
|
module.params['login_tenant_name'],
|
|
|
|
module.params['auth_url'],
|
2014-03-20 14:07:49 +01:00
|
|
|
region_name=module.params['region_name'],
|
2013-11-23 06:37:47 +01:00
|
|
|
service_type='compute')
|
2013-05-23 04:25:05 +02:00
|
|
|
try:
|
2013-11-23 06:37:47 +01:00
|
|
|
nova.authenticate()
|
2014-03-04 17:30:15 +01:00
|
|
|
except exceptions.Unauthorized, e:
|
2013-11-23 06:37:47 +01:00
|
|
|
module.fail_json(msg = "Invalid OpenStack Nova credentials.: %s" % e.message)
|
2014-03-04 17:30:15 +01:00
|
|
|
except exceptions.AuthorizationFailure, e:
|
2013-11-23 06:37:47 +01:00
|
|
|
module.fail_json(msg = "Unable to authorize user: %s" % e.message)
|
|
|
|
|
2013-05-23 04:25:05 +02:00
|
|
|
if module.params['state'] == 'present':
|
2013-08-17 17:56:19 +02:00
|
|
|
if not module.params['image_id']:
|
|
|
|
module.fail_json( msg = "Parameter 'image_id' is required if state == 'present'")
|
|
|
|
else:
|
|
|
|
_get_server_state(module, nova)
|
|
|
|
_create_server(module, nova)
|
2013-05-23 04:25:05 +02:00
|
|
|
if module.params['state'] == 'absent':
|
2013-05-25 05:34:48 +02:00
|
|
|
_get_server_state(module, nova)
|
|
|
|
_delete_server(module, nova)
|
2013-11-23 06:37:47 +01:00
|
|
|
|
2013-05-23 04:25:05 +02:00
|
|
|
# this is magic, see lib/ansible/module.params['common.py
|
2013-12-02 21:11:23 +01:00
|
|
|
from ansible.module_utils.basic import *
|
2013-05-23 04:25:05 +02:00
|
|
|
main()
|
|
|
|
|