| # -*- 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_volume_id_by_name(hostname, volume_name, volume_group, maas_volname=True): |
| |
| if not maas_volname: |
| # MAAS-like name |
| volume_name = str("%s-%s" % (volume_group, volume_name)) |
| # TODO validation |
| return get_volumes(hostname, volume_group)[volume_name]["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 |
| |
| # END MACHINE SECTION |
| # 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 |
| """ |
| |
| raids = {} |
| maas = _create_maas_client() |
| system_id = get_machine(hostname)["system_id"] |
| # TODO validation |
| json_res = json.loads( |
| maas.get(u"api/2.0/nodes/{0}/raids/".format(system_id)).read()) |
| LOG.debug('list_raids:{} {}'.format(system_id, json_res)) |
| for item in json_res: |
| raids[item["name"]] = item |
| return raids |
| |
| |
| 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] |
| |
| |
| def _get_raid_id_by_name(hostname, raid_name): |
| return get_raid(hostname, raid_name)['id'] |
| |
| |
| def delete_raid(hostname, raid_name): |
| """ |
| Delete RAID on a machine. |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.delete_raid server_hostname raid_name |
| salt-call maasng.delete_raid server_hostname raid_name |
| """ |
| result = {} |
| maas = _create_maas_client() |
| system_id = get_machine(hostname)["system_id"] |
| raid_id = _get_raid_id_by_name(hostname, raid_name) |
| LOG.debug('delete_raid: {} {}'.format(system_id, raid_id)) |
| maas.delete( |
| u"api/2.0/nodes/{0}/raid/{1}/".format(system_id, raid_id)).read() |
| |
| result["new"] = "Raid {0} deleted".format(raid_name) |
| return result |
| |
| # END RAID SECTION |
| # 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] |
| |
| # END BLOCKDEVICES SECTION |
| # 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 |
| # END PARTITIONS |
| # DISK LAYOUT |
| |
| |
| def drop_storage_schema(hostname, disk=None): |
| """ |
| #1. Drop lv |
| #2. Drop vg |
| #3. Drop md # need to zero-block? |
| #3. Drop part |
| """ |
| |
| if __opts__['test']: |
| ret['result'] = None |
| ret['comment'] = 'Storage schema on {0} will be removed'.format( |
| hostname) |
| return ret |
| # TODO validation if exists |
| vgs = list_volume_groups(hostname) |
| for vg in vgs: |
| delete_volume_group(hostname, vg) |
| |
| raids = list_raids(hostname) |
| for raid in raids: |
| delete_raid(hostname, raid) |
| |
| blocks = list_blockdevices(hostname) |
| for block_d in blocks: |
| partitions = __salt__['maasng.list_partitions'](hostname, block_d) |
| for partition_name, partition in partitions.iteritems(): |
| LOG.info('delete partition:\n{}'.format(partition)) |
| __salt__['maasng.delete_partition_by_id']( |
| hostname, block_d, partition["id"]) |
| |
| |
| 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 layout == 'custom': |
| drop_storage_schema(hostname) |
| result["new"] = { |
| "storage_layout": layout, |
| } |
| |
| return result |
| |
| 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 |
| |
| # END DISK LAYOUT |
| # LVM |
| |
| |
| 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.debug('delete_volume_group:{}'.format(system_id)) |
| |
| vg_id = str(_get_volume_group_id_by_name(hostname, name)) |
| for vol in get_volumes(hostname, name): |
| delete_volume(hostname, vol, name) |
| |
| # TODO validation |
| json_res = json.loads(maas.delete( |
| u"api/2.0/nodes/{0}/volume-group/{1}/".format(system_id, vg_id)).read() or 'null') |
| 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 delete_volume(hostname, volume_name, volume_group): |
| """ |
| Delete volume from volume group. |
| Tips: maas always use 'volume_group-volume_name' name schema.Example: 'vg0-glusterfs' |
| This function expexts same format. |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.delete_volume server_hostname volume_name volume_group |
| salt 'maas-node' maasng.delete_volume server_hostname vg0-vol0 vg0 |
| salt-call maasng.delete_volume server_hostname volume_name volume_group |
| """ |
| |
| maas = _create_maas_client() |
| system_id = get_machine(hostname)["system_id"] |
| LOG.debug('delete_volume:{}'.format(system_id)) |
| |
| volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group)) |
| volume_id = str(_get_volume_id_by_name( |
| hostname, volume_name, volume_group)) |
| |
| if None in [volume_group_id, volume_id]: |
| return False |
| |
| data = { |
| "id": volume_id, |
| } |
| |
| # TODO validation |
| json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format( |
| system_id, volume_group_id), "delete_logical_volume", **data).read() or 'null') |
| return True |
| |
| |
| def get_volumes(hostname, vg_name): |
| """ |
| Get list of volumes in volume group. |
| """ |
| volumes = {} |
| _volumes = list_volume_groups( |
| hostname)[vg_name].get('logical_volumes', False) |
| if _volumes: |
| for item in _volumes: |
| volumes[item["name"]] = item |
| return volumes |
| |
| # END LVM |
| |
| |
| 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 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 |
| |
| # NETWORKING |
| |
| |
| def list_fabric(): |
| """ |
| Get list of all fabric |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.list_fabric |
| """ |
| fabrics = {} |
| maas = _create_maas_client() |
| json_res = json.loads(maas.get(u'api/2.0/fabrics/').read()) |
| LOG.info(json_res) |
| for item in json_res: |
| fabrics[item["name"]] = item |
| return fabrics |
| |
| |
| def create_fabric(name): |
| """ |
| Create new fabric. |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.create_fabric |
| """ |
| result = {} |
| data = { |
| "name": name, |
| "description": '', |
| "class_type": '', |
| |
| } |
| |
| maas = _create_maas_client() |
| json_res = json.loads(maas.post(u"api/2.0/fabrics/", None, **data).read()) |
| LOG.debug("crete_fabric:{}".format(json_res)) |
| result["new"] = "Fabrics {0} created".format(json_res["name"]) |
| return result |
| |
| |
| def list_subnet(): |
| """ |
| Get list of all subnets |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.list_subnet |
| """ |
| subnets = {} |
| maas = _create_maas_client() |
| json_res = json.loads(maas.get(u'api/2.0/subnets/').read()) |
| LOG.info(json_res) |
| for item in json_res: |
| subnets[item["name"]] = item |
| return subnets |
| |
| |
| def list_vlans(fabric): |
| """ |
| Get list of all vlans for specific fabric |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.list_vlans |
| """ |
| vlans = {} |
| maas = _create_maas_client() |
| fabric_id = get_fabricid(fabric) |
| |
| json_res = json.loads( |
| maas.get(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id)).read()) |
| LOG.info(json_res) |
| for item in json_res: |
| vlans[item["name"]] = item |
| return vlans |
| |
| |
| def get_fabricid(fabric): |
| """ |
| Get id for specific fabric |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.get_fabricid fabric_name |
| """ |
| try: |
| return list_fabric()[fabric]['id'] |
| except KeyError: |
| return {"error": "Frabic not found on MaaS server"} |
| |
| |
| def update_vlan(name, fabric, vid, description, primary_rack, dhcp_on=False): |
| """ |
| Update vlan |
| CLI Example: |
| .. code-block:: bash |
| salt 'maas-node' maasng.update_vlan name, fabric, vid, description, dhcp_on |
| """ |
| result = {} |
| |
| data = { |
| "name": name, |
| "dhcp_on": str(dhcp_on), |
| "description": description, |
| "primary_rack": primary_rack, |
| } |
| maas = _create_maas_client() |
| fabric_id = get_fabricid(fabric) |
| |
| json_res = json.loads(maas.put( |
| u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id, vid), **data).read()) |
| LOG.debug("update_vlan:{}".format(json_res)) |
| result["new"] = "Vlan {0} was updated".format(json_res["name"]) |
| |
| return result |
| |
| |
| def list_subnets(): |
| """ |
| Get list of subnet from maas server |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.list_subnet |
| """ |
| subnets = {} |
| maas = _create_maas_client() |
| json_res = json.loads(maas.get(u'api/2.0/subnets/').read()) |
| for item in json_res: |
| subnets[item["name"]] = item |
| return subnets |
| |
| |
| def create_subnet(cidr, name, fabric, gateway_ip): |
| """ |
| Create subnet |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.create_subnet cidr, name, fabric, gateway_ip |
| """ |
| |
| fabric_id = get_fabricid(fabric) |
| result = {} |
| |
| data = { |
| "cidr": cidr, |
| "name": name, |
| "fabric": str(fabric_id), |
| "gateway_ip": gateway_ip, |
| } |
| maas = _create_maas_client() |
| |
| json_res = json.loads(maas.post(u"api/2.0/subnets/", None, **data).read()) |
| LOG.debug("create_subnet:{}".format(json_res)) |
| result["new"] = "Subnet {0} with CIDR {1} and gateway {2} was created".format( |
| name, cidr, gateway_ip) |
| |
| return result |
| |
| |
| def get_subnet(subnet): |
| """ |
| Get details for specific subnet |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.get_subnet subnet_name |
| """ |
| try: |
| return list_subnet()[subnet] |
| except KeyError: |
| return {"error": "Subnet not found on MaaS server"} |
| |
| |
| def get_subnetid(subnet): |
| """ |
| Get id for specific subnet |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.get_subnetid subnet_name |
| """ |
| try: |
| return list_subnet()[subnet]['id'] |
| except KeyError: |
| return {"error": "Subnet not found on MaaS server"} |
| |
| |
| def list_ipranges(): |
| """ |
| Get list of all ipranges from maas server |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.list_ipranges |
| """ |
| ipranges = {} |
| maas = _create_maas_client() |
| json_res = json.loads(maas.get(u'api/2.0/ipranges/').read()) |
| for item in json_res: |
| ipranges[item["start_ip"]] = item |
| return ipranges |
| |
| |
| def create_iprange(type_range, start_ip, end_ip, comment): |
| """ |
| Create ip range |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.create_iprange type, start ip, end ip, comment |
| """ |
| result = {} |
| |
| data = { |
| "type": type_range, |
| "start_ip": start_ip, |
| "end_ip": end_ip, |
| "comment": comment, |
| } |
| maas = _create_maas_client() |
| |
| json_res = json.loads(maas.post(u"api/2.0/ipranges/", None, **data).read()) |
| |
| LOG.debug("create_iprange:{}".format(json_res)) |
| result["new"] = "Iprange with type {0}, start ip {1}, end ip {2}, was created".format( |
| type_range, start_ip, end_ip) |
| |
| return result |
| |
| |
| def get_iprangeid(start_ip): |
| """ |
| Get id for ip range from maas server |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.get_iprangeid start_ip |
| """ |
| try: |
| return list_ipranges()[start_ip]['id'] |
| except KeyError: |
| return {"error": "Ip range not found on MaaS server"} |
| |
| |
| def get_startip(start_ip): |
| """ |
| Get start ip for ip range |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.get_startip start ip |
| """ |
| try: |
| return list_ipranges()[start_ip] |
| except KeyError: |
| return {"error": "Ip range not found on MaaS server"} |
| # END NETWORKING |
| |
| # MAAS CONFIG SECTION |
| |
| |
| def _get_boot_source_id_by_url(url): |
| # FIXME: fix ret\validation |
| try: |
| bs_id = get_boot_source(url=url)["id"] |
| except KeyError: |
| return {"error": "boot-source:{0} not exist!".format(url)} |
| return bs_id |
| |
| |
| def get_boot_source(url=None): |
| """ |
| Read a boot source by url. If url not specified - return all. |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.get_boot_source url |
| |
| """ |
| boot_sources = {} |
| maas = _create_maas_client() |
| json_res = json.loads(maas.get(u'api/2.0/boot-sources/').read() or 'null') |
| for item in json_res: |
| boot_sources[str(item["url"])] = item |
| if url: |
| return boot_sources.get(url, {}) |
| return boot_sources |
| |
| |
| def delete_boot_source(url, bs_id=None): |
| """ |
| Delete a boot source by url. |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| sal 'maas-node' maasng.delete url |
| |
| """ |
| result = {} |
| if not bs_id: |
| bs_id = _get_boot_source_id_by_url(url) |
| maas = _create_maas_client() |
| json_res = json.loads(maas.delete( |
| u'/api/2.0/boot-sources/{0}/'.format(bs_id)).read() or 'null') |
| LOG.debug("delete_boot_source:{}".format(json_res)) |
| result["new"] = "Boot-resource {0} deleted".format(url) |
| return result |
| |
| |
| def boot_sources_delete_all_others(except_urls=[]): |
| """ |
| Delete all boot-sources, except defined in 'except_urls' list. |
| """ |
| result = {} |
| maas_boot_sources = get_boot_source() |
| if 0 in [len(except_urls), len(maas_boot_sources)]: |
| result['result'] = None |
| result[ |
| "comment"] = "Exclude or maas sources for delete empty. No changes goinng to be." |
| return result |
| for url in maas_boot_sources.keys(): |
| if url not in except_urls: |
| LOG.info("Removing boot-source:{}".format(url)) |
| boot_resources_import(action='stop_import', wait=True) |
| result["changes"] = delete_boot_source(url) |
| return result |
| |
| |
| def create_boot_source(url, keyring_filename='', keyring_data='', wait=False): |
| """ |
| Create and import maas boot-source: link to maas-ephemeral repo |
| Be aware, those step will import resource to rack ctrl, but you also need to import |
| them into the region! |
| |
| |
| :param url: The URL of the BootSource. |
| :param keyring_filename: The path to the keyring file for this BootSource. |
| :param keyring_data: The GPG keyring for this BootSource, base64-encoded data. |
| |
| """ |
| |
| # TODO: not work with 'update' currently => keyring update may fail. |
| result = {} |
| |
| data = { |
| "url": url, |
| "keyring_filename": keyring_filename, |
| "keyring_data": str(keyring_data), |
| } |
| |
| maas = _create_maas_client() |
| if url in get_boot_source(): |
| result['result'] = None |
| result["comment"] = "boot resource already exist" |
| return result |
| |
| # NOTE: maas.post will return 400, if url already defined. |
| json_res = json.loads( |
| maas.post(u'api/2.0/boot-sources/', None, **data).read()) |
| if wait: |
| LOG.debug( |
| "Sleep for 5s,to get MaaS some time to process previous request") |
| time.sleep(5) |
| ret = boot_resources_is_importing(wait=True) |
| if ret is dict: |
| return ret |
| LOG.debug("create_boot_source:{}".format(json_res)) |
| result["new"] = "boot resource {0} was created".format(json_res["url"]) |
| |
| return result |
| |
| |
| def boot_resources_import(action='import', wait=False): |
| """ |
| import/stop_import the boot resources. |
| |
| :param action: import\stop_import |
| :param wait: True\False. Wait till process finished. |
| |
| CLI Example: |
| |
| .. code-block:: bash |
| |
| salt 'maas-node' maasng.boot_resources_import action='import' |
| |
| """ |
| maas = _create_maas_client() |
| # Have no idea why, but usual jsonloads not work here.. |
| imp = maas.post(u'api/2.0/boot-resources/', action) |
| if imp.code == 200: |
| LOG.debug('boot_resources_import:{}'.format(imp.readline())) |
| if wait: |
| boot_resources_is_importing(wait=True) |
| return True |
| else: |
| return False |
| |
| |
| def boot_resources_is_importing(wait=False): |
| maas = _create_maas_client() |
| result = {} |
| if wait: |
| started_at = time.time() |
| poll_time = 5 |
| timeout = 60 * 15 |
| while boot_resources_is_importing(wait=False): |
| c_timeout = timeout - (time.time() - started_at) |
| if c_timeout <= 0: |
| result['result'] = False |
| result["comment"] = "Boot-resources import not finished in time" |
| return result |
| LOG.info( |
| "Waiting boot-resources import done\n" |
| "sleep for:{}s " |
| "Left:{}/{}s".format(poll_time, round(c_timeout), timeout)) |
| time.sleep(poll_time) |
| return json.loads( |
| maas.get(u'api/2.0/boot-resources/', 'is_importing').read()) |
| else: |
| return json.loads( |
| maas.get(u'api/2.0/boot-resources/', 'is_importing').read()) |
| |
| ##### |
| # def boot_sources_selections_delete_all_others(except_urls=[]): |
| # """ |
| # """ |
| # result = {} |
| # return result |
| |
| |
| def is_boot_source_selections_in(dict1, list1): |
| """ |
| Check that requested boot-selection already in maas bs selections, if True- return bss id. |
| # FIXME: those hack check doesn't look good. |
| """ |
| for bs in list1: |
| same = set(dict1.keys()) & set(bs.keys()) |
| if all(elem in same for elem in |
| ['os', 'release', 'arches', 'subarches', 'labels']): |
| LOG.debug( |
| "boot-selection in maas:{0}\nlooks same to requested:{1}".format( |
| bs, dict1)) |
| return bs['id'] |
| return False |
| |
| |
| def get_boot_source_selections(bs_url): |
| """ |
| Get boot-source selections. |
| """ |
| # check for key_error! |
| bs_id = _get_boot_source_id_by_url(bs_url) |
| maas = _create_maas_client() |
| json_res = json.loads( |
| maas.get(u'/api/2.0/boot-sources/{0}/selections/'.format(bs_id)).read()) |
| LOG.debug( |
| "get_boot_source_selections for url:{} \n{}".format(bs_url, json_res)) |
| return json_res |
| |
| |
| def create_boot_source_selections(bs_url, os, release, arches="*", |
| subarches="*", labels="*", wait=True): |
| """ |
| Create a new boot source selection for bs_url. |
| :param os: The OS (e.g. ubuntu, centos) for which to import resources.Required. |
| :param release: The release for which to import resources. Required. |
| :param arches: The architecture list for which to import resources. |
| :param subarches: The subarchitecture list for which to import resources. |
| :param labels: The label lists for which to import resources. |
| """ |
| |
| result = {} |
| |
| data = { |
| "os": os, |
| "release": release, |
| "arches": arches, |
| "subarches": subarches, |
| "labels": labels, |
| } |
| |
| maas = _create_maas_client() |
| bs_id = _get_boot_source_id_by_url(bs_url) |
| # TODO add pre-create verify |
| maas_bs_s = get_boot_source_selections(bs_url) |
| if is_boot_source_selections_in(data, maas_bs_s): |
| result["result"] = True |
| result[ |
| "comment"] = 'Requested boot-source selection for {0} already exist.'.format( |
| bs_url) |
| return result |
| |
| # NOTE: maas.post will return 400, if url already defined. |
| json_res = json.loads( |
| maas.post(u'api/2.0/boot-sources/{0}/selections/'.format(bs_id), None, |
| **data).read()) |
| LOG.debug("create_boot_source_selections:{}".format(json_res)) |
| if wait: |
| LOG.debug( |
| "Sleep for 5s,to get MaaS some time to process previous request") |
| time.sleep(5) |
| ret = boot_resources_import(action='import', wait=True) |
| if ret is dict: |
| return ret |
| result["new"] = "boot-source selection for {0} was created".format(bs_url) |
| |
| return result |
| |
| # END MAAS CONFIG SECTION |