blob: f10b3bc10de1c8a3504675c43c99e53f6f4d8151 [file] [log] [blame]
# -*- 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