New maasng module implementation

Change-Id: Icf835fea0dcb0fb11038aa5e2d149ea1953510f8
diff --git a/_states/maasng.py b/_states/maasng.py
new file mode 100644
index 0000000..5a1eb28
--- /dev/null
+++ b/_states/maasng.py
@@ -0,0 +1,300 @@
+
+import logging
+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",
+}
+
+def __virtual__():
+    '''
+    Load MaaSng module
+    '''
+    return 'maasng'
+
+def disk_layout_present(hostname, layout_type, root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None, disk={} , **kwargs):
+    '''
+    Ensure that the disk layout does exist
+
+    :param name: The name of the cloud that should not exist
+    '''
+    ret = {'name': hostname,
+           'changes': {},
+           'result': True,
+           'comment': 'Disk layout "{0}" updated'.format(hostname)}
+
+    machine = __salt__['maasng.get_machine'](hostname)
+    if "error" in machine:
+        ret['comment'] = "State execution failed for machine {0}".format(hostname)
+        ret['result'] = False
+        ret['changes'] = machine
+        return ret
+
+    if machine["status_name"] != "Ready":
+        ret['comment'] = 'Machine {0} is not in Ready state.'.format(hostname)
+        return ret
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'Disk layout will be updated on {0}, this action will delete current layout.'.format(hostname)
+        return ret
+
+    if layout_type == "flat":
+
+        ret["changes"] = __salt__['maasng.update_disk_layout'](hostname, layout_type, root_size, root_device)
+
+    elif layout_type == "lvm":
+
+        ret["changes"] = __salt__['maasng.update_disk_layout'](hostname, layout_type, root_size, root_device, volume_group, volume_name, volume_size)
+
+    else:
+        ret["comment"] = "Not supported layout provided. Choose flat or lvm"
+        ret['result'] = False
+
+    return ret
+
+def raid_present(hostname, name, level, devices=[], partitions=[], partition_schema={}):
+    '''
+    Ensure that the raid does exist
+
+    :param name: The name of the cloud that should not exist
+    '''
+
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Raid {0} presented on {1}'.format(name, hostname)}
+
+    machine = __salt__['maasng.get_machine'](hostname)
+    if "error" in machine:
+        ret['comment'] = "State execution failed for machine {0}".format(hostname)
+        ret['result'] = False
+        ret['changes'] = machine
+        return ret
+
+    if machine["status_name"] != "Ready":
+        ret['comment'] = 'Machine {0} is not in Ready state.'.format(hostname)
+        return ret
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'Raid {0} will be updated on {1}'.format(name,hostname)
+        return ret
+
+    #Validate that raid exists
+    ##With correct devices/partition
+    #OR
+    ##Create raid
+
+    ret["changes"] = __salt__['maasng.create_raid'](hostname=hostname, name=name, level=level, disks=devices, partitions=partitions)
+
+    #TODO partitions
+    ret["changes"].update(disk_partition_present(hostname, name, partition_schema)["changes"])
+
+    if "error" in ret["changes"]:
+        ret["result"] = False
+
+    return ret
+
+
+def disk_partition_present(hostname, disk, partition_schema={}):
+    '''
+    Ensure that the disk has correct partititioning schema
+
+    :param name: The name of the cloud that should not exist
+    '''
+
+    #1. Validate that disk has correct values for size and mount
+        #a. validate count of partitions
+        #b. validate size of partitions
+    #2. If not delete all partitions on disk and recreate schema
+    #3. Validate fs_type exists
+        #if should not exits
+        #delete mount and unformat
+    #4. Validate mount exists
+    #5. if not enforce umount or mount
+
+    ret = {'name': hostname,
+           'changes': {},
+           'result': True,
+           'comment': 'Disk layout {0} presented'.format(disk)}
+
+    machine = __salt__['maasng.get_machine'](hostname)
+    if "error" in machine:
+        ret['comment'] = "State execution failed for machine {0}".format(hostname)
+        ret['result'] = False
+        ret['changes'] = machine
+        return ret
+
+    if machine["status_name"] != "Ready":
+        ret['comment'] = 'Machine {0} is not in Ready state.'.format(hostname)
+        return ret
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'Partition schema will be changed on {0}'.format(disk)
+        return ret
+
+    partitions = __salt__['maasng.list_partitions'](hostname, disk)
+
+    ##Calculate actual size in bytes from provided data
+    for part_name, part in partition_schema.iteritems():
+        size, unit = part["size"][:-1], part["size"][-1]
+        part["calc_size"] = int(size) * SIZE[unit]
+
+    if len(partitions) == len(partition_schema):
+
+        for part_name, part in partition_schema.iteritems():
+            LOG.info('validated {0}'.format(part["calc_size"]))
+            LOG.info('validated {0}'.format(int(partitions[disk+"-"+part_name.split("-")[-1]]["size"])))
+            if part["calc_size"] == int(partitions[disk+"-"+part_name.split("-")[-1]]["size"]):
+                LOG.info('validated')
+                #TODO validate size (size from maas is not same as calculate?)
+                #TODO validate mount
+                #TODO validate fs type
+            else:
+                LOG.info('breaking')
+                break
+            return ret
+
+    #DELETE and RECREATE
+    LOG.info('delete')
+    for partition_name, partition in partitions.iteritems():
+        LOG.info(partition)
+        ##TODO IF LVM create ERROR
+        ret["changes"] = __salt__['maasng.delete_partition_by_id'](hostname, disk, partition["id"])
+
+    LOG.info('recreating')
+    for part_name, part in partition_schema.iteritems():
+        LOG.info("partitition for creation")
+        LOG.info(part)
+        if "mount" not in part:
+            part["mount"] = None
+        if "type" not in part:
+            part["type"] = None
+        ret["changes"] = __salt__['maasng.create_partition'](hostname, disk, part["size"], part["type"], part["mount"])
+
+    if "error" in ret["changes"]:
+        ret["result"] = False
+
+    return ret
+
+def volume_group_present(hostname, name, devices=[], partitions=[]):
+    '''
+    Ensure that the disk layout does exist
+
+    :param name: The name of the cloud that should not exist
+    '''
+    ret = {'name': hostname,
+        'changes': {},
+        'result': True,
+        'comment': 'LVM group {0} presented on {1}'.format(name,hostname)}
+
+    machine = __salt__['maasng.get_machine'](hostname)
+    if "error" in machine:
+        ret['comment'] = "State execution failed for machine {0}".format(hostname)
+        ret['result'] = False
+        ret['changes'] = machine
+        return ret
+
+    if machine["status_name"] != "Ready":
+        ret['comment'] = 'Machine {0} is not in Ready state.'.format(hostname)
+        return ret
+
+    #TODO validation if exists
+    vgs = __salt__['maasng.list_volume_groups'](hostname)
+
+    if name in vgs:
+        #TODO validation for devices and partitions
+        return ret
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'LVM group {0} will be updated on {1}'.format(name,hostname)
+        return ret
+
+    ret["changes"] = __salt__['maasng.create_volume_group'](hostname, name, devices, partitions)
+
+    if "error" in ret["changes"]:
+        ret["result"] = False
+
+    return ret
+
+def volume_present(hostname, name, volume_group_name, size, fs_type=None, mount=None):
+    '''
+    Ensure that the disk layout does exist
+
+    :param name: The name of the cloud that should not exist
+    '''
+
+    ret = {'name': hostname,
+           'changes': {},
+           'result': True,
+           'comment': 'LVM group {0} presented on {1}'.format(name,hostname)}
+
+    machine = __salt__['maasng.get_machine'](hostname)
+    if "error" in machine:
+        ret['comment'] = "State execution failed for machine {0}".format(hostname)
+        ret['result'] = False
+        ret['changes'] = machine
+        return ret
+
+    if machine["status_name"] != "Ready":
+        ret['comment'] = 'Machine {0} is not in Ready state.'.format(hostname)
+        return ret
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'LVM volume {0} will be updated on {1}'.format(name,hostname)
+
+    #TODO validation if exists
+
+    ret["changes"] = __salt__['maasng.create_volume'](hostname, name, volume_group_name, size, fs_type, mount)
+
+    return ret
+
+
+def select_boot_disk(hostname, name):
+    '''
+    Select disk that will be used to boot partition
+
+    :param name: The name of disk on machine
+    :param hostname: The hostname of machine
+    '''
+
+    ret = {'name': hostname,
+           'changes': {},
+           'result': True,
+           'comment': 'LVM group {0} presented on {1}'.format(name,hostname)}
+
+    machine = __salt__['maasng.get_machine'](hostname)
+    if "error" in machine:
+        ret['comment'] = "State execution failed for machine {0}".format(hostname)
+        ret['result'] = False
+        ret['changes'] = machine
+        return ret
+
+    if machine["status_name"] != "Ready":
+        ret['comment'] = 'Machine {0} is not in Ready state.'.format(hostname)
+        return ret
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'LVM volume {0} will be updated on {1}'.format(name,hostname)
+
+    #TODO disk validation if exists
+
+    ret["changes"] = __salt__['maasng.set_boot_disk'](hostname, name)
+
+    return ret
\ No newline at end of file