blob: f10b3bc10de1c8a3504675c43c99e53f6f4d8151 [file] [log] [blame]
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001# -*- coding: utf-8 -*-
2'''
3Module for handling maas calls.
4
5:optdepends: pyapi-maas Python adapter
6:configuration: This module is not usable until the following are specified
7 either in a pillar or in the minion's config file::
8
9 maas.url: 'https://maas.domain.com/'
10 maas.token: fdsfdsdsdsfa:fsdfae3fassd:fdsfdsfsafasdfsa
11
12'''
13
14from __future__ import absolute_import
15
16import collections
17import copy
18import hashlib
19import io
20import json
21import logging
22import os.path
23import time
24import urllib2
25#Salt utils
26from salt.exceptions import CommandExecutionError, SaltInvocationError
27
28LOG = logging.getLogger(__name__)
29
30SIZE = {
31 "M": 1000000,
32 "G": 1000000000,
33 "T": 1000000000000,
34}
35
36RAID = {
37 0: "raid-0",
38 1: "raid-1",
39 5: "raid-5",
40 10: "raid-10",
41}
42
43# Import third party libs
44HAS_MASS = False
45try:
46 from maas_client import MAASClient, MAASDispatcher, MAASOAuth
47 HAS_MASS = True
48except ImportError:
49 LOG.debug('Missing MaaS client module is Missing. Skipping')
50
51
52def __virtual__():
53 '''
54 Only load this module if maas-client
55 is installed on this minion.
56 '''
57 if HAS_MASS:
58 return 'maasng'
59 return False
60
61
62APIKEY_FILE = '/var/lib/maas/.maas_credentials'
63
64def _format_data(data):
65 class Lazy:
66 def __str__(self):
67 return ' '.join(['{0}={1}'.format(k, v)
68 for k, v in data.iteritems()])
69 return Lazy()
70
71def _create_maas_client():
72 global APIKEY_FILE
73 try:
74 api_token = file(APIKEY_FILE).read().splitlines()[-1].strip()\
75 .split(':')
76 except:
77 LOG.exception('token')
78 auth = MAASOAuth(*api_token)
79 api_url = 'http://localhost:5240/MAAS'
80 dispatcher = MAASDispatcher()
81 return MAASClient(auth, dispatcher, api_url)
82
83def _get_blockdevice_id_by_name(hostname,device):
84
85 ##TODO validation
86 return list_blockdevices(hostname)[device]["id"]
87
88def _get_volume_group_id_by_name(hostname,device):
89
90 ##TODO validation
91 return list_volume_groups(hostname)[device]["id"]
92
93def _get_partition_id_by_name(hostname, device, partition):
94
95 ##TODO validation
96 return list_partitions(hostname, device)[partition]["id"]
97
98####MACHINE SECTION
99
100def get_machine(hostname):
101 '''
102 Get information aboout specified machine
103
104 CLI Example:
105
106 .. code-block:: bash
107
108 salt-call maasng.get_machine server_hostname
109 '''
110 try:
111 return list_machines()[hostname]
112 except KeyError:
113 return {"error": "Machine not found on MaaS server"}
114
115def list_machines():
116 '''
117 Get list of all machines from maas server
118
119 CLI Example:
120
121 .. code-block:: bash
122
123 salt 'maas-node' maasng.list_machines
124 '''
125 machines={}
126 maas=_create_maas_client()
127 json_res = json.loads(maas.get(u'api/2.0/machines/').read())
128 for item in json_res:
129 machines[item["hostname"]] = item
130 return machines
131
132def create_machine():
133 ##TODO
134
135 return False
136
137def update_machine():
138 ##TODO
139
140 return False
141
142####MACHINE OPERATIONS
143##TODO
144
145####RAID SECTION
146
147def create_raid(hostname, name, level, disks=[], partitions=[], **kwargs):
148 '''
149 Create new raid on machine.
150
151 CLI Example:
152
153 .. code-block:: bash
154
155 salt-call maasng.create_raid hostname=kvm03 name=md0 level=1 disks=[vdb,vdc] partitions=[vdd-part1,vde-part1]
156 '''
157
158 result = {}
159
160 if len(disks) == 0 and len(partitions) == 0:
161 result["error"] = "Disks or partitions need to be provided"
162
163 disk_ids = []
164 partition_ids = []
165
166 for disk in disks:
167 try:
168 disk_ids.append(str(_get_blockdevice_id_by_name(hostname,disk)))
169 except KeyError:
170 result["error"] = "Device {0} does not exists on machine {1}".format(disk, hostname)
171 return result
172
173 for partition in partitions:
174 try:
175 device = partition.split("-")[0]
176 device_part = list_partitions(hostname,device)
177 partition_ids.append(str(device_part[partition]["id"]))
178 except KeyError:
179 result["error"] = "Partition {0} does not exists on machine {1}".format(partition,hostname)
180 return result
181
182 data = {
183 "name": name,
184 "level": RAID[int(level)],
185 "block_devices": disk_ids,
186 "partitions": partition_ids,
187 }
188
189 maas=_create_maas_client()
190 system_id = get_machine(hostname)["system_id"]
191 LOG.info(system_id)
192
193 #TODO validation
194 LOG.info(data)
195 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/raids/".format(system_id), None, **data).read())
196 LOG.info(json_res)
197 result["new"] = "Raid {0} created".format(name)
198
199 return result
200
201def list_raids(hostname):
202 '''
203 Get list all raids on machine
204
205 CLI Example:
206
207 .. code-block:: bash
208
209 salt-call maasng.list_raids server_hostname
210 '''
211
212 maas=_create_maas_client()
213 system_id = get_machine(hostname)["system_id"]
214 LOG.info(system_id)
215 #TODO validation
216 json_res = json.loads(maas.get(u"api/2.0/nodes/{0}/raids/".format(system_id)).read())
217 LOG.info(json_res)
218
219 ##TODO return list of raid devices
220 return True
221
222def get_raid(hostname, name):
223 '''
224 Get information about specific raid on machine
225
226 CLI Example:
227
228 .. code-block:: bash
229
230 salt-call maasng.get_raids server_hostname md0
231 '''
232
233 return list_raids(hostname)[name]
234
235
236####BLOCKDEVICES SECTION
237
238def list_blockdevices(hostname):
239 '''
240 Get list of all blockdevices (disks) on machine
241
242 CLI Example:
243
244 .. code-block:: bash
245
246 salt 'maas-node' maasng.list_blockdevices server_hostname
247 salt-call maasng.list_blockdevices server_hostname
248 '''
249 ret = {}
250
251 maas=_create_maas_client()
252 system_id = get_machine(hostname)["system_id"]
253 LOG.info(system_id)
254
255 #TODO validation if exists
256
257 json_res = json.loads(maas.get(u"api/2.0/nodes/{0}/blockdevices/".format(system_id)).read())
258 LOG.info(json_res)
259 for item in json_res:
260 ret[item["name"]] = item
261
262 return ret
263
264def get_blockdevice(hostname, name):
265 '''
266 Get information about blockdevice (disk) on machine
267
268 CLI Example:
269
270 .. code-block:: bash
271
272 salt 'maas-node' maasng.get_blockdevice server_hostname sda
273 salt-call maasng.get_blockdevice server_hostname sda
274 '''
275
276 return list_blockdevices(hostname)[name]
277
278
279####PARTITIONS
280
281def list_partitions(hostname, device):
282 '''
283 Get list of all partitions on specific device located on specific machine
284
285 CLI Example:
286
287 .. code-block:: bash
288
289 salt 'maas-node' maasng.list_partitions server_hostname sda
290 salt-call maasng.list_partitions server_hostname sda
291 '''
292 ret = {}
293 maas=_create_maas_client()
294 system_id = get_machine(hostname)["system_id"]
295 LOG.info(system_id)
296
297 partitions = get_blockdevice(hostname,device)["partitions"]
298 LOG.info(partitions)
299
300 #json_res = json.loads(maas.get(u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id)).read())
301 #LOG.info(json_res)
302
303 if len(device) > 0:
304 for item in partitions:
305 name = item["path"].split('/')[-1]
306 ret[name] = item
307
308 return ret
309
310def get_partition(hostname, device, partition):
311 '''
312 Get information about specific parition on device located on machine
313
314 CLI Example:
315
316 .. code-block:: bash
317
318 salt 'maas-node' maasng.get_partition server_hostname disk_name partition
319 salt-call maasng.get_partition server_hostname disk_name partition
320
321 root_size = size in GB
322 '''
323
324 return list_partitions(partition)[name]
325
326def create_partition(hostname, disk, size, fs_type=None, mount=None):
327 '''
328 Create new partition on device.
329
330 CLI Example:
331
332 .. code-block:: bash
333
334 salt 'maas-node' maasng.create_partition server_hostname disk_name 10 ext4 "/"
335 salt-call maasng.create_partition server_hostname disk_name 10 ext4 "/"
336 '''
337 ##TODO validation
338 result = {}
339 maas=_create_maas_client()
340 system_id = get_machine(hostname)["system_id"]
341 LOG.info(system_id)
342
343 device_id = _get_blockdevice_id_by_name(hostname,disk)
344 LOG.info(device_id)
345
346 value, unit = size[:-1], size[-1]
347 calc_size = str(int(value) * SIZE[unit])
348 LOG.info(calc_size)
349
350 data = {
351 "size": calc_size
352 }
353
354 #TODO validation
355 partition = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id), None, **data).read())
356 LOG.info(partition)
357 result["partition"] = "Partition created on {0}".format(disk)
358
359 if fs_type != None:
360 data_fs_type = {
361 "fstype": fs_type
362 }
363 partition_id = str(partition["id"])
364 LOG.info("Partition id: " + partition_id)
365 #TODO validation
366 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(system_id, device_id, partition_id), "format", **data_fs_type).read())
367 LOG.info(json_res)
368 result["filesystem"] = "Filesystem {0} created".format(fs_type)
369
370 if mount != None:
371 data = {
372 "mount_point": mount
373 }
374
375 #TODO validation
376 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(system_id, device_id, str(partition['id'])), "mount", **data).read())
377 LOG.info(json_res)
378 result["mount"] = "Mount point {0} created".format(mount)
379
380
381 return result
382
383def delete_partition(hostname, disk, partition_name):
384 '''
385 Delete partition on device.
386
387 CLI Example:
388
389 .. code-block:: bash
390
391 salt 'maas-node' maasng.delete_partition server_hostname disk_name partition_name
392 salt-call maasng.delete_partition server_hostname disk_name partition_name
393
394 root_size = size in GB
395 '''
396 result = {}
397 data = {}
398 maas=_create_maas_client()
399 system_id = get_machine(hostname)["system_id"]
400 LOG.info(system_id)
401
402 device_id = _get_blockdevice_id_by_name(hostname,disk)
403 LOG.info(device_id)
404
405 partition_id = _get_partition_id_by_name(hostname,disk,partition_name)
406
407 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(system_id, device_id, partition_id)).read()
408 result["new"] = "Partition {0} deleted".format(partition_name)
409 return result
410
411def delete_partition_by_id(hostname, disk, partition_id):
412 '''
413 Delete partition on device. Partition spefified by id of parition
414
415 CLI Example:
416
417 .. code-block:: bash
418
419 salt 'maas-node' maasng.delete_partition_by_id server_hostname disk_name partition_id
420 salt-call maasng.delete_partition_by_id server_hostname disk_name partition_id
421
422 root_size = size in GB
423 '''
424 result = {}
425 data = {}
426 maas=_create_maas_client()
427 system_id = get_machine(hostname)["system_id"]
428 LOG.info(system_id)
429
430 device_id = _get_blockdevice_id_by_name(hostname,disk)
431 LOG.info(device_id)
432
433 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(system_id, device_id, partition_id)).read()
434 result["new"] = "Partition {0} deleted".format(partition_id)
435 return result
436
437####CREATE DISK LAYOUT
438##TODO
439
440def update_disk_layout(hostname, layout, root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None):
441 '''
442 Update disk layout. Flat or LVM layout supported.
443
444 CLI Example:
445
446 .. code-block:: bash
447
448 salt 'maas-node' maasng.update_disk_layout server_hostname lvm root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None
449 salt-call maasng.update_disk_layout server_hostname lvm root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None
450
451 root_size = size in GB
452 '''
453 result = {}
454 data = {
455 "storage_layout": layout,
456 }
457
458 maas=_create_maas_client()
459 system_id = get_machine(hostname)["system_id"]
460 LOG.info(system_id)
461
462 if root_size != None:
463 bit_size = str(root_size * 1073741824)
464 LOG.info(bit_size)
465 data["root_size"] = bit_size
466
467
468 if root_device != None:
469 LOG.info(root_device)
470 data["root_device"] = str(_get_blockdevice_id_by_name(hostname,root_device))
471
472 if layout == 'lvm':
473 if volume_group != None:
474 LOG.info(volume_group)
475 data["vg_name"] = volume_group
476 if volume_name != None:
477 LOG.info(volume_name)
478 data["lv_name"] = volume_name
479 if volume_size != None:
480 vol_size = str(volume_size * 1073741824)
481 LOG.info(vol_size)
482 data["lv_size"] = vol_size
483
484
485 #TODO validation
486 json_res = json.loads(maas.post(u"api/2.0/machines/{0}/".format(system_id), "set_storage_layout", **data).read())
487 LOG.info(json_res)
488 result["new"] = {
489 "storage_layout": layout,
490 }
491
492 return result
493
494
495####LVM
496##TODO
497
498def list_volume_groups(hostname):
499 '''
500 Get list of all volume group on machine.
501
502 CLI Example:
503
504 .. code-block:: bash
505
506 salt 'maas-node' maasng.list_volume_groups server_hostname
507 salt-call maasng.list_volume_groups server_hostname
508 '''
509 volume_groups = {}
510
511 maas=_create_maas_client()
512 system_id = get_machine(hostname)["system_id"]
513 LOG.info(system_id)
514
515 #TODO validation if exists
516
517 json_res = json.loads(maas.get(u"api/2.0/nodes/{0}/volume-groups/".format(system_id)).read())
518 LOG.info(json_res)
519 for item in json_res:
520 volume_groups[item["name"]] = item
521 #return
522 return volume_groups
523
524
525def get_volume_group(hostname, name):
526 '''
527 Get information about specific volume group on machine.
528
529 CLI Example:
530
531 .. code-block:: bash
532
533 salt 'maas-node' maasng.list_blockdevices server_hostname
534 salt-call maasng.list_blockdevices server_hostname
535 '''
536 ##TODO validation that exists
537 return list_volume_groups(hostname)[name]
538
539
540def create_volume_group(hostname, volume_group_name, disks=[], partitions=[]):
541 '''
542 Create new volume group on machine. Disks or partitions needs to be provided.
543
544 CLI Example:
545
546 .. code-block:: bash
547
548 salt 'maas-node' maasng.create_volume_group volume_group_name, disks=[sda,sdb], partitions=[]
549 salt-call maasng.create_volume_group server_hostname
550 '''
551 result = {}
552
553 data = {
554 "name": volume_group_name,
555 }
556
557 maas=_create_maas_client()
558 system_id = get_machine(hostname)["system_id"]
559 LOG.info(system_id)
560
561 disk_ids = []
562 partition_ids = []
563
564 for disk in disks:
565 p_disk = get_blockdevice(hostname, disk)
566 if p_disk["partition_table_type"] == None:
567 disk_ids.append(str(p_disk["id"]))
568 else:
569 result["error"] = "Device {0} on machine {1} cointains partition table".format(disk,hostname)
570 return result
571
572 for partition in partitions:
573 try:
574 device = partition.split("-")[0]
575 device_part = list_partitions(hostname,device)
576 partition_ids.append(str(device_part[partition]["id"]))
577 except KeyError:
578 result["error"] = "Partition {0} does not exists on machine {1}".format(partition,hostname)
579 return result
580
581 data["block_devices"] = disk_ids
582 data["partitions"] = partition_ids
583 LOG.info(partition_ids)
584 LOG.info(partitions)
585
586 #TODO validation
587 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-groups/".format(system_id), None, **data).read())
588 LOG.info(json_res)
589 result["new"] = "Volume group {0} created".format(json_res["name"])
590
591 return result
592
593def delete_volume_group(hostname, name):
594 '''
595 Delete volume group on machine.
596
597 CLI Example:
598
599 .. code-block:: bash
600
601 salt 'maas-node' maasng.delete_volume_group server_hostname vg0
602 salt-call maasng.delete_volume_group server_hostname vg0
603 '''
604
605 maas=_create_maas_client()
606 system_id = get_machine(hostname)["system_id"]
607 LOG.info(system_id)
608
609 #TODO partitions
610 #for partition in partitions:
611 # temp = disk.split("-")
612 # disk_ids.append(str(_get_blockdevice_id_by_name(hostname, temp[] partition)))
613
614 #TODO partitions
615 vg_id = name
616
617 #TODO validation
618 json_res = json.loads(maas.delete(u"api/2.0/nodes/{0}/volume-group/{1}/".format(system_id, vg_id)).read())
619 LOG.info(json_res)
620
621 return True
622
623
624def create_volume(hostname, volume_name, volume_group, size, fs_type=None, mount=None):
625 '''
626 Create volume on volume group.
627
628 CLI Example:
629
630 .. code-block:: bash
631
632 salt 'maas-node' maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
633 salt-call maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
634 '''
635
636 data = {
637 "name": volume_name,
638 }
639
640 value, unit = size[:-1], size[-1]
641 bit_size = str(int(value) * SIZE[unit])
642 LOG.info(bit_size)
643
644 data["size"] = bit_size
645
646 maas=_create_maas_client()
647 system_id = get_machine(hostname)["system_id"]
648 LOG.info(system_id)
649
650 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
651
652 LOG.info(volume_group_id)
653
654 #TODO validation
655 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(system_id, volume_group_id), "create_logical_volume", **data).read())
656 LOG.info(json_res)
657
658 if fs_type != None or mount != None:
659 ret = create_volume_filesystem(hostname, volume_group + "-" + volume_name, fs_type, mount)
660
661 return True
662
663def create_volume_filesystem(hostname, device, fs_type= None, mount = None):
664
665 maas=_create_maas_client()
666 system_id = get_machine(hostname)["system_id"]
667
668 blockdevices_id = _get_blockdevice_id_by_name(hostname, device)
669 data = {}
670 if fs_type != None:
671 data["fstype"] = fs_type
672 #TODO validation
673 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(system_id, blockdevices_id), "format", **data).read())
674 LOG.info(json_res)
675
676 if mount != None:
677 data["mount_point"] = mount
678 #TODO validation
679 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(system_id, blockdevices_id), "mount", **data).read())
680 LOG.info(json_res)
681
682 return True
683
684def create_partition_filesystem(hostname, partition, fs_type= None, mount = None):
685
686 maas=_create_maas_client()
687 system_id = get_machine(hostname)["system_id"]
688
689 blockdevices_id = _get_blockdevice_id_by_name(hostname, device)
690 data = {}
691 if fs_type != None:
692 data["fstype"] = fs_type
693 #TODO validation
694 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(system_id, blockdevices_id), "format", **data).read())
695 LOG.info(json_res)
696
697 if mount != None:
698 data["mount_point"] = mount
699 #TODO validation
700 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(system_id, blockdevices_id), "mount", **data).read())
701 LOG.info(json_res)
702
703 return True
704
705def set_boot_disk(hostname, name):
706 '''
707 Create volume on volume group.
708
709 CLI Example:
710
711 .. code-block:: bash
712
713 salt 'maas-node' maasng.set_boot_disk server_hostname disk_name
714 salt-call maasng.set_boot_disk server_hostname disk_name
715 '''
716 data = {}
717 result = {}
718 maas=_create_maas_client()
719 system_id = get_machine(hostname)["system_id"]
720 blockdevices_id = _get_blockdevice_id_by_name(hostname, name)
721
722 maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(system_id, blockdevices_id), "set_boot_disk", **data).read()
723 #TODO validation for error response (disk does not exists and node does not exists)
724 result["new"] = "Disk {0} was set as bootable".format(name)
725
726 return result