1#!/usr/bin/python
2
3# Copyright: (c) 2013, Vincent Van der Kussen <vincent at vanderkussen.org>
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6from __future__ import absolute_import, division, print_function
7__metaclass__ = type
8
9ANSIBLE_METADATA = {'metadata_version': '1.1',
10                    'status': ['preview'],
11                    'supported_by': 'community'}
12
13DOCUMENTATION = '''
14---
15module: ovirt
16author:
17- Vincent Van der Kussen (@vincentvdk)
18short_description: oVirt/RHEV platform management
19description:
20    - This module only supports oVirt/RHEV version 3. A newer module M(ovirt_vm) supports oVirt/RHV version 4.
21    - Allows you to create new instances, either from scratch or an image, in addition to deleting or stopping instances on the oVirt/RHEV platform.
22version_added: "1.4"
23options:
24  user:
25    description:
26     - The user to authenticate with.
27    required: true
28  url:
29    description:
30     - The url of the oVirt instance.
31    required: true
32  instance_name:
33    description:
34     - The name of the instance to use.
35    required: true
36    aliases: [ vmname ]
37  password:
38    description:
39     - Password of the user to authenticate with.
40    required: true
41  image:
42    description:
43     - The template to use for the instance.
44  resource_type:
45    description:
46     - Whether you want to deploy an image or create an instance from scratch.
47    choices: [ new, template ]
48  zone:
49    description:
50     - Deploy the image to this oVirt cluster.
51  instance_disksize:
52    description:
53     - Size of the instance's disk in GB.
54    aliases: [ vm_disksize]
55  instance_cpus:
56    description:
57     - The instance's number of CPUs.
58    default: 1
59    aliases: [ vmcpus ]
60  instance_nic:
61    description:
62     - The name of the network interface in oVirt/RHEV.
63    aliases: [ vmnic  ]
64  instance_network:
65    description:
66     - The logical network the machine should belong to.
67    default: rhevm
68    aliases: [ vmnetwork ]
69  instance_mem:
70    description:
71     - The instance's amount of memory in MB.
72    aliases: [ vmmem ]
73  instance_type:
74    description:
75     - Define whether the instance is a server, desktop or high_performance.
76     - I(high_performance) is supported since Ansible 2.5 and oVirt/RHV 4.2.
77    choices: [ desktop, server, high_performance ]
78    default: server
79    aliases: [ vmtype ]
80  disk_alloc:
81    description:
82     - Define whether disk is thin or preallocated.
83    choices: [ preallocated, thin ]
84    default: thin
85  disk_int:
86    description:
87     - Interface type of the disk.
88    choices: [ ide, virtio ]
89    default: virtio
90  instance_os:
91    description:
92     - Type of Operating System.
93    aliases: [ vmos ]
94  instance_cores:
95    description:
96     - Define the instance's number of cores.
97    default: 1
98    aliases: [ vmcores ]
99  sdomain:
100    description:
101     - The Storage Domain where you want to create the instance's disk on.
102  region:
103    description:
104     - The oVirt/RHEV datacenter where you want to deploy to.
105  instance_dns:
106    description:
107     - Define the instance's Primary DNS server.
108    aliases: [ dns ]
109    version_added: "2.1"
110  instance_domain:
111    description:
112     - Define the instance's Domain.
113    aliases: [ domain ]
114    version_added: "2.1"
115  instance_hostname:
116    description:
117     - Define the instance's Hostname.
118    aliases: [ hostname ]
119    version_added: "2.1"
120  instance_ip:
121    description:
122     - Define the instance's IP.
123    aliases: [ ip ]
124    version_added: "2.1"
125  instance_netmask:
126    description:
127     - Define the instance's Netmask.
128    aliases: [ netmask ]
129    version_added: "2.1"
130  instance_rootpw:
131    description:
132     - Define the instance's Root password.
133    aliases: [ rootpw ]
134    version_added: "2.1"
135  instance_key:
136    description:
137     - Define the instance's Authorized key.
138    aliases: [ key ]
139    version_added: "2.1"
140  state:
141    description:
142     - Create, terminate or remove instances.
143    choices: [ absent, present, restarted, shutdown, started ]
144    default: present
145requirements:
146  - ovirt-engine-sdk-python
147'''
148
149EXAMPLES = '''
150- name: Basic example to provision from image
151  ovirt:
152    user: admin@internal
153    url: https://ovirt.example.com
154    instance_name: ansiblevm04
155    password: secret
156    image: centos_64
157    zone: cluster01
158    resource_type: template
159
160- name: Full example to create new instance from scratch
161  ovirt:
162    instance_name: testansible
163    resource_type: new
164    instance_type: server
165    user: admin@internal
166    password: secret
167    url: https://ovirt.example.com
168    instance_disksize: 10
169    zone: cluster01
170    region: datacenter1
171    instance_cpus: 1
172    instance_nic: nic1
173    instance_network: rhevm
174    instance_mem: 1000
175    disk_alloc: thin
176    sdomain: FIBER01
177    instance_cores: 1
178    instance_os: rhel_6x64
179    disk_int: virtio
180
181- name: Stopping an existing instance
182  ovirt:
183    instance_name: testansible
184    state: stopped
185    user: admin@internal
186    password: secret
187    url: https://ovirt.example.com
188
189- name: Start an existing instance
190  ovirt:
191    instance_name: testansible
192    state: started
193    user: admin@internal
194    password: secret
195    url: https://ovirt.example.com
196
197- name: Start an instance with cloud init information
198  ovirt:
199    instance_name: testansible
200    state: started
201    user: admin@internal
202    password: secret
203    url: https://ovirt.example.com
204    hostname: testansible
205    domain: ansible.local
206    ip: 192.0.2.100
207    netmask: 255.255.255.0
208    gateway: 192.0.2.1
209    rootpw: bigsecret
210'''
211
212import time
213
214try:
215    from ovirtsdk.api import API
216    from ovirtsdk.xml import params
217    HAS_OVIRTSDK = True
218except ImportError:
219    HAS_OVIRTSDK = False
220
221from ansible.module_utils.basic import AnsibleModule
222
223
224# ------------------------------------------------------------------- #
225# create connection with API
226#
227def conn(url, user, password):
228    api = API(url=url, username=user, password=password, insecure=True)
229    try:
230        value = api.test()
231    except Exception:
232        raise Exception("error connecting to the oVirt API")
233    return api
234
235
236# ------------------------------------------------------------------- #
237# Create VM from scratch
238def create_vm(conn, vmtype, vmname, zone, vmdisk_size, vmcpus, vmnic, vmnetwork, vmmem, vmdisk_alloc, sdomain, vmcores, vmos, vmdisk_int):
239    if vmdisk_alloc == 'thin':
240        # define VM params
241        vmparams = params.VM(name=vmname, cluster=conn.clusters.get(name=zone), os=params.OperatingSystem(type_=vmos),
242                             template=conn.templates.get(name="Blank"), memory=1024 * 1024 * int(vmmem),
243                             cpu=params.CPU(topology=params.CpuTopology(cores=int(vmcores))), type_=vmtype)
244        # define disk params
245        vmdisk = params.Disk(size=1024 * 1024 * 1024 * int(vmdisk_size), wipe_after_delete=True, sparse=True, interface=vmdisk_int, type_="System",
246                             format='cow',
247                             storage_domains=params.StorageDomains(storage_domain=[conn.storagedomains.get(name=sdomain)]))
248        # define network parameters
249        network_net = params.Network(name=vmnetwork)
250        nic_net1 = params.NIC(name='nic1', network=network_net, interface='virtio')
251    elif vmdisk_alloc == 'preallocated':
252        # define VM params
253        vmparams = params.VM(name=vmname, cluster=conn.clusters.get(name=zone), os=params.OperatingSystem(type_=vmos),
254                             template=conn.templates.get(name="Blank"), memory=1024 * 1024 * int(vmmem),
255                             cpu=params.CPU(topology=params.CpuTopology(cores=int(vmcores))), type_=vmtype)
256        # define disk params
257        vmdisk = params.Disk(size=1024 * 1024 * 1024 * int(vmdisk_size), wipe_after_delete=True, sparse=False, interface=vmdisk_int, type_="System",
258                             format='raw', storage_domains=params.StorageDomains(storage_domain=[conn.storagedomains.get(name=sdomain)]))
259        # define network parameters
260        network_net = params.Network(name=vmnetwork)
261        nic_net1 = params.NIC(name=vmnic, network=network_net, interface='virtio')
262
263    try:
264        conn.vms.add(vmparams)
265    except Exception:
266        raise Exception("Error creating VM with specified parameters")
267    vm = conn.vms.get(name=vmname)
268    try:
269        vm.disks.add(vmdisk)
270    except Exception:
271        raise Exception("Error attaching disk")
272    try:
273        vm.nics.add(nic_net1)
274    except Exception:
275        raise Exception("Error adding nic")
276
277
278# create an instance from a template
279def create_vm_template(conn, vmname, image, zone):
280    vmparams = params.VM(name=vmname, cluster=conn.clusters.get(name=zone), template=conn.templates.get(name=image), disks=params.Disks(clone=True))
281    try:
282        conn.vms.add(vmparams)
283    except Exception:
284        raise Exception('error adding template %s' % image)
285
286
287# start instance
288def vm_start(conn, vmname, hostname=None, ip=None, netmask=None, gateway=None,
289             domain=None, dns=None, rootpw=None, key=None):
290    vm = conn.vms.get(name=vmname)
291    use_cloud_init = False
292    nics = None
293    nic = None
294    if hostname or ip or netmask or gateway or domain or dns or rootpw or key:
295        use_cloud_init = True
296    if ip and netmask and gateway:
297        ipinfo = params.IP(address=ip, netmask=netmask, gateway=gateway)
298        nic = params.GuestNicConfiguration(name='eth0', boot_protocol='STATIC', ip=ipinfo, on_boot=True)
299        nics = params.Nics()
300    nics = params.GuestNicsConfiguration(nic_configuration=[nic])
301    initialization = params.Initialization(regenerate_ssh_keys=True, host_name=hostname, domain=domain, user_name='root',
302                                           root_password=rootpw, nic_configurations=nics, dns_servers=dns,
303                                           authorized_ssh_keys=key)
304    action = params.Action(use_cloud_init=use_cloud_init, vm=params.VM(initialization=initialization))
305    vm.start(action=action)
306
307
308# Stop instance
309def vm_stop(conn, vmname):
310    vm = conn.vms.get(name=vmname)
311    vm.stop()
312
313
314# restart instance
315def vm_restart(conn, vmname):
316    state = vm_status(conn, vmname)
317    vm = conn.vms.get(name=vmname)
318    vm.stop()
319    while conn.vms.get(vmname).get_status().get_state() != 'down':
320        time.sleep(5)
321    vm.start()
322
323
324# remove an instance
325def vm_remove(conn, vmname):
326    vm = conn.vms.get(name=vmname)
327    vm.delete()
328
329
330# ------------------------------------------------------------------- #
331# VM statuses
332#
333# Get the VMs status
334def vm_status(conn, vmname):
335    status = conn.vms.get(name=vmname).status.state
336    return status
337
338
339# Get VM object and return it's name if object exists
340def get_vm(conn, vmname):
341    vm = conn.vms.get(name=vmname)
342    if vm is None:
343        name = "empty"
344    else:
345        name = vm.get_name()
346    return name
347
348# ------------------------------------------------------------------- #
349# Hypervisor operations
350#
351# not available yet
352# ------------------------------------------------------------------- #
353# Main
354
355
356def main():
357    module = AnsibleModule(
358        argument_spec=dict(
359            state=dict(type='str', default='present', choices=['absent', 'present', 'restart', 'shutdown', 'started']),
360            user=dict(type='str', required=True),
361            url=dict(type='str', required=True),
362            instance_name=dict(type='str', required=True, aliases=['vmname']),
363            password=dict(type='str', required=True, no_log=True),
364            image=dict(type='str'),
365            resource_type=dict(type='str', choices=['new', 'template']),
366            zone=dict(type='str'),
367            instance_disksize=dict(type='str', aliases=['vm_disksize']),
368            instance_cpus=dict(type='str', default=1, aliases=['vmcpus']),
369            instance_nic=dict(type='str', aliases=['vmnic']),
370            instance_network=dict(type='str', default='rhevm', aliases=['vmnetwork']),
371            instance_mem=dict(type='str', aliases=['vmmem']),
372            instance_type=dict(type='str', default='server', aliases=['vmtype'], choices=['desktop', 'server', 'high_performance']),
373            disk_alloc=dict(type='str', default='thin', choices=['preallocated', 'thin']),
374            disk_int=dict(type='str', default='virtio', choices=['ide', 'virtio']),
375            instance_os=dict(type='str', aliases=['vmos']),
376            instance_cores=dict(type='str', default=1, aliases=['vmcores']),
377            instance_hostname=dict(type='str', aliases=['hostname']),
378            instance_ip=dict(type='str', aliases=['ip']),
379            instance_netmask=dict(type='str', aliases=['netmask']),
380            instance_gateway=dict(type='str', aliases=['gateway']),
381            instance_domain=dict(type='str', aliases=['domain']),
382            instance_dns=dict(type='str', aliases=['dns']),
383            instance_rootpw=dict(type='str', aliases=['rootpw'], no_log=True),
384            instance_key=dict(type='str', aliases=['key']),
385            sdomain=dict(type='str'),
386            region=dict(type='str'),
387        ),
388    )
389
390    if not HAS_OVIRTSDK:
391        module.fail_json(msg='ovirtsdk required for this module')
392
393    state = module.params['state']
394    user = module.params['user']
395    url = module.params['url']
396    vmname = module.params['instance_name']
397    password = module.params['password']
398    image = module.params['image']  # name of the image to deploy
399    resource_type = module.params['resource_type']  # template or from scratch
400    zone = module.params['zone']  # oVirt cluster
401    vmdisk_size = module.params['instance_disksize']  # disksize
402    vmcpus = module.params['instance_cpus']  # number of cpu
403    vmnic = module.params['instance_nic']  # network interface
404    vmnetwork = module.params['instance_network']  # logical network
405    vmmem = module.params['instance_mem']  # mem size
406    vmdisk_alloc = module.params['disk_alloc']  # thin, preallocated
407    vmdisk_int = module.params['disk_int']  # disk interface virtio or ide
408    vmos = module.params['instance_os']  # Operating System
409    vmtype = module.params['instance_type']  # server, desktop or high_performance
410    vmcores = module.params['instance_cores']  # number of cores
411    sdomain = module.params['sdomain']  # storage domain to store disk on
412    region = module.params['region']  # oVirt Datacenter
413    hostname = module.params['instance_hostname']
414    ip = module.params['instance_ip']
415    netmask = module.params['instance_netmask']
416    gateway = module.params['instance_gateway']
417    domain = module.params['instance_domain']
418    dns = module.params['instance_dns']
419    rootpw = module.params['instance_rootpw']
420    key = module.params['instance_key']
421    # initialize connection
422    try:
423        c = conn(url + "/api", user, password)
424    except Exception as e:
425        module.fail_json(msg='%s' % e)
426
427    if state == 'present':
428        if get_vm(c, vmname) == "empty":
429            if resource_type == 'template':
430                try:
431                    create_vm_template(c, vmname, image, zone)
432                except Exception as e:
433                    module.fail_json(msg='%s' % e)
434                module.exit_json(changed=True, msg="deployed VM %s from template %s" % (vmname, image))
435            elif resource_type == 'new':
436                # FIXME: refactor, use keyword args.
437                try:
438                    create_vm(c, vmtype, vmname, zone, vmdisk_size, vmcpus, vmnic, vmnetwork, vmmem, vmdisk_alloc, sdomain, vmcores, vmos, vmdisk_int)
439                except Exception as e:
440                    module.fail_json(msg='%s' % e)
441                module.exit_json(changed=True, msg="deployed VM %s from scratch" % vmname)
442            else:
443                module.exit_json(changed=False, msg="You did not specify a resource type")
444        else:
445            module.exit_json(changed=False, msg="VM %s already exists" % vmname)
446
447    if state == 'started':
448        if vm_status(c, vmname) == 'up':
449            module.exit_json(changed=False, msg="VM %s is already running" % vmname)
450        else:
451            # vm_start(c, vmname)
452            vm_start(c, vmname, hostname, ip, netmask, gateway, domain, dns, rootpw, key)
453            module.exit_json(changed=True, msg="VM %s started" % vmname)
454
455    if state == 'shutdown':
456        if vm_status(c, vmname) == 'down':
457            module.exit_json(changed=False, msg="VM %s is already shutdown" % vmname)
458        else:
459            vm_stop(c, vmname)
460            module.exit_json(changed=True, msg="VM %s is shutting down" % vmname)
461
462    if state == 'restart':
463        if vm_status(c, vmname) == 'up':
464            vm_restart(c, vmname)
465            module.exit_json(changed=True, msg="VM %s is restarted" % vmname)
466        else:
467            module.exit_json(changed=False, msg="VM %s is not running" % vmname)
468
469    if state == 'absent':
470        if get_vm(c, vmname) == "empty":
471            module.exit_json(changed=False, msg="VM %s does not exist" % vmname)
472        else:
473            vm_remove(c, vmname)
474            module.exit_json(changed=True, msg="VM %s removed" % vmname)
475
476
477if __name__ == '__main__':
478    main()
479