New maasng module implementation
Change-Id: Icf835fea0dcb0fb11038aa5e2d149ea1953510f8
diff --git a/_modules/maasng.py b/_modules/maasng.py
new file mode 100644
index 0000000..f10b3bc
--- /dev/null
+++ b/_modules/maasng.py
@@ -0,0 +1,726 @@
+# -*- coding: utf-8 -*-
+'''
+Module for handling maas calls.
+
+:optdepends: pyapi-maas Python adapter
+:configuration: This module is not usable until the following are specified
+ either in a pillar or in the minion's config file::
+
+ maas.url: 'https://maas.domain.com/'
+ maas.token: fdsfdsdsdsfa:fsdfae3fassd:fdsfdsfsafasdfsa
+
+'''
+
+from __future__ import absolute_import
+
+import collections
+import copy
+import hashlib
+import io
+import json
+import logging
+import os.path
+import time
+import urllib2
+#Salt utils
+from salt.exceptions import CommandExecutionError, SaltInvocationError
+
+LOG = logging.getLogger(__name__)
+
+SIZE = {
+ "M": 1000000,
+ "G": 1000000000,
+ "T": 1000000000000,
+}
+
+RAID = {
+ 0: "raid-0",
+ 1: "raid-1",
+ 5: "raid-5",
+ 10: "raid-10",
+}
+
+# Import third party libs
+HAS_MASS = False
+try:
+ from maas_client import MAASClient, MAASDispatcher, MAASOAuth
+ HAS_MASS = True
+except ImportError:
+ LOG.debug('Missing MaaS client module is Missing. Skipping')
+
+
+def __virtual__():
+ '''
+ Only load this module if maas-client
+ is installed on this minion.
+ '''
+ if HAS_MASS:
+ return 'maasng'
+ return False
+
+
+APIKEY_FILE = '/var/lib/maas/.maas_credentials'
+
+def _format_data(data):
+ class Lazy:
+ def __str__(self):
+ return ' '.join(['{0}={1}'.format(k, v)
+ for k, v in data.iteritems()])
+ return Lazy()
+
+def _create_maas_client():
+ global APIKEY_FILE
+ try:
+ api_token = file(APIKEY_FILE).read().splitlines()[-1].strip()\
+ .split(':')
+ except:
+ LOG.exception('token')
+ auth = MAASOAuth(*api_token)
+ api_url = 'http://localhost:5240/MAAS'
+ dispatcher = MAASDispatcher()
+ return MAASClient(auth, dispatcher, api_url)
+
+def _get_blockdevice_id_by_name(hostname,device):
+
+ ##TODO validation
+ return list_blockdevices(hostname)[device]["id"]
+
+def _get_volume_group_id_by_name(hostname,device):
+
+ ##TODO validation
+ return list_volume_groups(hostname)[device]["id"]
+
+def _get_partition_id_by_name(hostname, device, partition):
+
+ ##TODO validation
+ return list_partitions(hostname, device)[partition]["id"]
+
+####MACHINE SECTION
+
+def get_machine(hostname):
+ '''
+ Get information aboout specified machine
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt-call maasng.get_machine server_hostname
+ '''
+ try:
+ return list_machines()[hostname]
+ except KeyError:
+ return {"error": "Machine not found on MaaS server"}
+
+def list_machines():
+ '''
+ Get list of all machines from maas server
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.list_machines
+ '''
+ machines={}
+ maas=_create_maas_client()
+ json_res = json.loads(maas.get(u'api/2.0/machines/').read())
+ for item in json_res:
+ machines[item["hostname"]] = item
+ return machines
+
+def create_machine():
+ ##TODO
+
+ return False
+
+def update_machine():
+ ##TODO
+
+ return False
+
+####MACHINE OPERATIONS
+##TODO
+
+####RAID SECTION
+
+def create_raid(hostname, name, level, disks=[], partitions=[], **kwargs):
+ '''
+ Create new raid on machine.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt-call maasng.create_raid hostname=kvm03 name=md0 level=1 disks=[vdb,vdc] partitions=[vdd-part1,vde-part1]
+ '''
+
+ result = {}
+
+ if len(disks) == 0 and len(partitions) == 0:
+ result["error"] = "Disks or partitions need to be provided"
+
+ disk_ids = []
+ partition_ids = []
+
+ for disk in disks:
+ try:
+ disk_ids.append(str(_get_blockdevice_id_by_name(hostname,disk)))
+ except KeyError:
+ result["error"] = "Device {0} does not exists on machine {1}".format(disk, hostname)
+ return result
+
+ for partition in partitions:
+ try:
+ device = partition.split("-")[0]
+ device_part = list_partitions(hostname,device)
+ partition_ids.append(str(device_part[partition]["id"]))
+ except KeyError:
+ result["error"] = "Partition {0} does not exists on machine {1}".format(partition,hostname)
+ return result
+
+ data = {
+ "name": name,
+ "level": RAID[int(level)],
+ "block_devices": disk_ids,
+ "partitions": partition_ids,
+ }
+
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ #TODO validation
+ LOG.info(data)
+ json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/raids/".format(system_id), None, **data).read())
+ LOG.info(json_res)
+ result["new"] = "Raid {0} created".format(name)
+
+ return result
+
+def list_raids(hostname):
+ '''
+ Get list all raids on machine
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt-call maasng.list_raids server_hostname
+ '''
+
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+ #TODO validation
+ json_res = json.loads(maas.get(u"api/2.0/nodes/{0}/raids/".format(system_id)).read())
+ LOG.info(json_res)
+
+ ##TODO return list of raid devices
+ return True
+
+def get_raid(hostname, name):
+ '''
+ Get information about specific raid on machine
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt-call maasng.get_raids server_hostname md0
+ '''
+
+ return list_raids(hostname)[name]
+
+
+####BLOCKDEVICES SECTION
+
+def list_blockdevices(hostname):
+ '''
+ Get list of all blockdevices (disks) on machine
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.list_blockdevices server_hostname
+ salt-call maasng.list_blockdevices server_hostname
+ '''
+ ret = {}
+
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ #TODO validation if exists
+
+ json_res = json.loads(maas.get(u"api/2.0/nodes/{0}/blockdevices/".format(system_id)).read())
+ LOG.info(json_res)
+ for item in json_res:
+ ret[item["name"]] = item
+
+ return ret
+
+def get_blockdevice(hostname, name):
+ '''
+ Get information about blockdevice (disk) on machine
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.get_blockdevice server_hostname sda
+ salt-call maasng.get_blockdevice server_hostname sda
+ '''
+
+ return list_blockdevices(hostname)[name]
+
+
+####PARTITIONS
+
+def list_partitions(hostname, device):
+ '''
+ Get list of all partitions on specific device located on specific machine
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.list_partitions server_hostname sda
+ salt-call maasng.list_partitions server_hostname sda
+ '''
+ ret = {}
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ partitions = get_blockdevice(hostname,device)["partitions"]
+ LOG.info(partitions)
+
+ #json_res = json.loads(maas.get(u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id)).read())
+ #LOG.info(json_res)
+
+ if len(device) > 0:
+ for item in partitions:
+ name = item["path"].split('/')[-1]
+ ret[name] = item
+
+ return ret
+
+def get_partition(hostname, device, partition):
+ '''
+ Get information about specific parition on device located on machine
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.get_partition server_hostname disk_name partition
+ salt-call maasng.get_partition server_hostname disk_name partition
+
+ root_size = size in GB
+ '''
+
+ return list_partitions(partition)[name]
+
+def create_partition(hostname, disk, size, fs_type=None, mount=None):
+ '''
+ Create new partition on device.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.create_partition server_hostname disk_name 10 ext4 "/"
+ salt-call maasng.create_partition server_hostname disk_name 10 ext4 "/"
+ '''
+ ##TODO validation
+ result = {}
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ device_id = _get_blockdevice_id_by_name(hostname,disk)
+ LOG.info(device_id)
+
+ value, unit = size[:-1], size[-1]
+ calc_size = str(int(value) * SIZE[unit])
+ LOG.info(calc_size)
+
+ data = {
+ "size": calc_size
+ }
+
+ #TODO validation
+ partition = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id), None, **data).read())
+ LOG.info(partition)
+ result["partition"] = "Partition created on {0}".format(disk)
+
+ if fs_type != None:
+ data_fs_type = {
+ "fstype": fs_type
+ }
+ partition_id = str(partition["id"])
+ LOG.info("Partition id: " + partition_id)
+ #TODO validation
+ 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())
+ LOG.info(json_res)
+ result["filesystem"] = "Filesystem {0} created".format(fs_type)
+
+ if mount != None:
+ data = {
+ "mount_point": mount
+ }
+
+ #TODO validation
+ 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())
+ LOG.info(json_res)
+ result["mount"] = "Mount point {0} created".format(mount)
+
+
+ return result
+
+def delete_partition(hostname, disk, partition_name):
+ '''
+ Delete partition on device.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.delete_partition server_hostname disk_name partition_name
+ salt-call maasng.delete_partition server_hostname disk_name partition_name
+
+ root_size = size in GB
+ '''
+ result = {}
+ data = {}
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ device_id = _get_blockdevice_id_by_name(hostname,disk)
+ LOG.info(device_id)
+
+ partition_id = _get_partition_id_by_name(hostname,disk,partition_name)
+
+ maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(system_id, device_id, partition_id)).read()
+ result["new"] = "Partition {0} deleted".format(partition_name)
+ return result
+
+def delete_partition_by_id(hostname, disk, partition_id):
+ '''
+ Delete partition on device. Partition spefified by id of parition
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.delete_partition_by_id server_hostname disk_name partition_id
+ salt-call maasng.delete_partition_by_id server_hostname disk_name partition_id
+
+ root_size = size in GB
+ '''
+ result = {}
+ data = {}
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ device_id = _get_blockdevice_id_by_name(hostname,disk)
+ LOG.info(device_id)
+
+ maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(system_id, device_id, partition_id)).read()
+ result["new"] = "Partition {0} deleted".format(partition_id)
+ return result
+
+####CREATE DISK LAYOUT
+##TODO
+
+def update_disk_layout(hostname, layout, root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None):
+ '''
+ Update disk layout. Flat or LVM layout supported.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ 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
+ salt-call maasng.update_disk_layout server_hostname lvm root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None
+
+ root_size = size in GB
+ '''
+ result = {}
+ data = {
+ "storage_layout": layout,
+ }
+
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ if root_size != None:
+ bit_size = str(root_size * 1073741824)
+ LOG.info(bit_size)
+ data["root_size"] = bit_size
+
+
+ if root_device != None:
+ LOG.info(root_device)
+ data["root_device"] = str(_get_blockdevice_id_by_name(hostname,root_device))
+
+ if layout == 'lvm':
+ if volume_group != None:
+ LOG.info(volume_group)
+ data["vg_name"] = volume_group
+ if volume_name != None:
+ LOG.info(volume_name)
+ data["lv_name"] = volume_name
+ if volume_size != None:
+ vol_size = str(volume_size * 1073741824)
+ LOG.info(vol_size)
+ data["lv_size"] = vol_size
+
+
+ #TODO validation
+ json_res = json.loads(maas.post(u"api/2.0/machines/{0}/".format(system_id), "set_storage_layout", **data).read())
+ LOG.info(json_res)
+ result["new"] = {
+ "storage_layout": layout,
+ }
+
+ return result
+
+
+####LVM
+##TODO
+
+def list_volume_groups(hostname):
+ '''
+ Get list of all volume group on machine.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.list_volume_groups server_hostname
+ salt-call maasng.list_volume_groups server_hostname
+ '''
+ volume_groups = {}
+
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ #TODO validation if exists
+
+ json_res = json.loads(maas.get(u"api/2.0/nodes/{0}/volume-groups/".format(system_id)).read())
+ LOG.info(json_res)
+ for item in json_res:
+ volume_groups[item["name"]] = item
+ #return
+ return volume_groups
+
+
+def get_volume_group(hostname, name):
+ '''
+ Get information about specific volume group on machine.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.list_blockdevices server_hostname
+ salt-call maasng.list_blockdevices server_hostname
+ '''
+ ##TODO validation that exists
+ return list_volume_groups(hostname)[name]
+
+
+def create_volume_group(hostname, volume_group_name, disks=[], partitions=[]):
+ '''
+ Create new volume group on machine. Disks or partitions needs to be provided.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.create_volume_group volume_group_name, disks=[sda,sdb], partitions=[]
+ salt-call maasng.create_volume_group server_hostname
+ '''
+ result = {}
+
+ data = {
+ "name": volume_group_name,
+ }
+
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ disk_ids = []
+ partition_ids = []
+
+ for disk in disks:
+ p_disk = get_blockdevice(hostname, disk)
+ if p_disk["partition_table_type"] == None:
+ disk_ids.append(str(p_disk["id"]))
+ else:
+ result["error"] = "Device {0} on machine {1} cointains partition table".format(disk,hostname)
+ return result
+
+ for partition in partitions:
+ try:
+ device = partition.split("-")[0]
+ device_part = list_partitions(hostname,device)
+ partition_ids.append(str(device_part[partition]["id"]))
+ except KeyError:
+ result["error"] = "Partition {0} does not exists on machine {1}".format(partition,hostname)
+ return result
+
+ data["block_devices"] = disk_ids
+ data["partitions"] = partition_ids
+ LOG.info(partition_ids)
+ LOG.info(partitions)
+
+ #TODO validation
+ json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-groups/".format(system_id), None, **data).read())
+ LOG.info(json_res)
+ result["new"] = "Volume group {0} created".format(json_res["name"])
+
+ return result
+
+def delete_volume_group(hostname, name):
+ '''
+ Delete volume group on machine.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.delete_volume_group server_hostname vg0
+ salt-call maasng.delete_volume_group server_hostname vg0
+ '''
+
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ #TODO partitions
+ #for partition in partitions:
+ # temp = disk.split("-")
+ # disk_ids.append(str(_get_blockdevice_id_by_name(hostname, temp[] partition)))
+
+ #TODO partitions
+ vg_id = name
+
+ #TODO validation
+ json_res = json.loads(maas.delete(u"api/2.0/nodes/{0}/volume-group/{1}/".format(system_id, vg_id)).read())
+ LOG.info(json_res)
+
+ return True
+
+
+def create_volume(hostname, volume_name, volume_group, size, fs_type=None, mount=None):
+ '''
+ Create volume on volume group.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
+ salt-call maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
+ '''
+
+ data = {
+ "name": volume_name,
+ }
+
+ value, unit = size[:-1], size[-1]
+ bit_size = str(int(value) * SIZE[unit])
+ LOG.info(bit_size)
+
+ data["size"] = bit_size
+
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ LOG.info(system_id)
+
+ volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
+
+ LOG.info(volume_group_id)
+
+ #TODO validation
+ 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())
+ LOG.info(json_res)
+
+ if fs_type != None or mount != None:
+ ret = create_volume_filesystem(hostname, volume_group + "-" + volume_name, fs_type, mount)
+
+ return True
+
+def create_volume_filesystem(hostname, device, fs_type= None, mount = None):
+
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+
+ blockdevices_id = _get_blockdevice_id_by_name(hostname, device)
+ data = {}
+ if fs_type != None:
+ data["fstype"] = fs_type
+ #TODO validation
+ json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(system_id, blockdevices_id), "format", **data).read())
+ LOG.info(json_res)
+
+ if mount != None:
+ data["mount_point"] = mount
+ #TODO validation
+ json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(system_id, blockdevices_id), "mount", **data).read())
+ LOG.info(json_res)
+
+ return True
+
+def create_partition_filesystem(hostname, partition, fs_type= None, mount = None):
+
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+
+ blockdevices_id = _get_blockdevice_id_by_name(hostname, device)
+ data = {}
+ if fs_type != None:
+ data["fstype"] = fs_type
+ #TODO validation
+ json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(system_id, blockdevices_id), "format", **data).read())
+ LOG.info(json_res)
+
+ if mount != None:
+ data["mount_point"] = mount
+ #TODO validation
+ json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(system_id, blockdevices_id), "mount", **data).read())
+ LOG.info(json_res)
+
+ return True
+
+def set_boot_disk(hostname, name):
+ '''
+ Create volume on volume group.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt 'maas-node' maasng.set_boot_disk server_hostname disk_name
+ salt-call maasng.set_boot_disk server_hostname disk_name
+ '''
+ data = {}
+ result = {}
+ maas=_create_maas_client()
+ system_id = get_machine(hostname)["system_id"]
+ blockdevices_id = _get_blockdevice_id_by_name(hostname, name)
+
+ maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(system_id, blockdevices_id), "set_boot_disk", **data).read()
+ #TODO validation for error response (disk does not exists and node does not exists)
+ result["new"] = "Disk {0} was set as bootable".format(name)
+
+ return result