Merge "New maasng module implementation"
diff --git a/README.rst b/README.rst
index fdfbb38..4382320 100644
--- a/README.rst
+++ b/README.rst
@@ -163,6 +163,104 @@
         - 'ssh-rsa ASD.........dfsadf blah@blah'
 
 
+Create disk schema per machine via maas/client.sls with default lvm schema + default values
+
+NOTE: This should be used mostly for custom root partitioning and RAID configuration. For not-root partitions please use salt-formulas/salt-formula-linux.
+
+.. code-block:: yaml
+
+  maas:
+    region:
+      machines:
+        server1:
+          disk_layout:
+            type: lvm
+            root_size: 20G
+            root_device: vda
+            volume_group: vg1
+            volume_name: root
+            volume_size: 8
+            bootable_device: vda
+
+FLAT layout with custom root size
+
+.. code-block:: yaml
+
+  maas:
+    region:
+      machines:
+        server2:
+          disk_layout:
+            type: flat
+            root_size: 20
+            physical_device: vda
+            bootable_device: vda
+
+Define more complex layout
+
+.. code-block:: yaml
+
+  maas:
+    region:
+      machines:
+        server3:
+          disk_layout:
+            type: flat #This is simplies setup
+            bootable_device: vda
+            disk:
+              vda:
+                type: physical
+                partition_schema:
+                  part1:
+                    size: 10G
+                    type: ext4
+                    mount: '/'
+                  part2:
+                    size: 2G
+                  part3:
+                    size: 3G
+              vdc:
+                type: physical
+                partition_schema:
+                  part1:
+                    size: 100%
+              vdd:
+                type: physical
+                partition_schema:
+                  part1:
+                    size: 100%
+              raid0:
+                type: raid
+                level: 10
+                devices:
+                  - vde
+                  - vdf
+                partition_schema:
+                  part1:
+                    size: 10G
+                  part2:
+                    size: 2G
+                  part3:
+                    size: 3G
+              raid1:
+                type: raid
+                level: 1
+                partitions:
+                  - vdc-part1
+                  - vdd-part1
+              volume_group2:
+                type: lvm
+                devices:
+                  - raid1
+                volume:
+                  tmp:
+                    size: 5G
+                    fs_type: ext4
+                    mount: '/tmp'
+                  log:
+                    size: 7G
+                    fs_type: ext4
+                    mount: '/var/log'
 
 Usage of local repos
 
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
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
diff --git a/maas/client/init.sls b/maas/client/init.sls
new file mode 100644
index 0000000..9826979
--- /dev/null
+++ b/maas/client/init.sls
@@ -0,0 +1 @@
+init.sls
diff --git a/maas/machines/storage.sls b/maas/machines/storage.sls
new file mode 100644
index 0000000..d71bede
--- /dev/null
+++ b/maas/machines/storage.sls
@@ -0,0 +1,151 @@
+{%- from "maas/map.jinja" import region with context %}
+
+
+maas_login_admin:
+  cmd.run:
+  - name: "maas-region apikey --username {{ region.admin.username }} > /var/lib/maas/.maas_credentials"
+
+
+{%- for machine_name, machine in region.machines.iteritems() %}
+
+{%- if machine.disk_layout is defined %}
+
+{%- if machine.disk_layout.type is defined %}
+
+maas_machines_storage_{{ machine_name }}_{{ machine.disk_layout.type }}:
+  maasng.disk_layout_present:
+    - hostname: {{ machine_name }}
+    - layout_type: {{ machine.disk_layout.type }}
+    {%- if machine.disk_layout.root_size is defined %}
+    - root_size: {{ machine.disk_layout.root_size }}
+    {%- endif %}
+    {%- if machine.disk_layout.root_device is defined %}
+    - root_device: {{ machine.disk_layout.root_device }}
+    {%- endif %}
+    {%- if machine.disk_layout.volume_group is defined %}
+    - volume_group: {{ machine.disk_layout.volume_group }}
+    {%- endif %}
+    {%- if machine.disk_layout.volume_name is defined %}
+    - volume_name: {{ machine.disk_layout.volume_name }}
+    {%- endif %}
+    {%- if machine.disk_layout.volume_size is defined %}
+    - volume_size: {{ machine.disk_layout.volume_size }}
+    {%- endif %}
+    - require:
+      - cmd: maas_login_admin
+
+{%- endif %}
+
+{%- if machine.disk_layout.bootable_device is defined %}
+
+maas_machines_storage_set_bootable_disk_{{ machine_name }}_{{ machine.disk_layout.bootable_device }}:
+  maasng.select_boot_disk:
+  - name: {{ machine.disk_layout.bootable_device }}
+  - hostname: {{ machine_name }}
+  - require:
+    - cmd: maas_login_admin
+
+{%- endif %}
+
+{%- if machine.disk_layout.disk is defined %}
+
+{%- for disk_name, disk in machine.disk_layout.disk.iteritems() %}
+
+{%- if disk.type == "physical" %}
+
+maas_machine_{{ machine_name }}_{{ disk_name }}:
+  maasng.disk_partition_present:
+    - hostname: {{ machine_name }}
+    - disk: {{ disk_name }}
+    - partition_schema: {{ disk.get("partition_schema", {}) }}
+
+{%- endif %}
+
+{%- if disk.type == "raid" %}
+
+maas_machine_{{ machine_name }}_{{ disk_name }}:
+  maasng.raid_present:
+    - hostname: {{ machine_name }}
+    - name: {{ disk_name }}
+    - level: {{ disk.level }}
+    - devices: {{ disk.get("devices", []) }}
+    - partitions: {{ disk.get("partitions", []) }}
+    - partition_schema: {{ disk.get("partition_schema", {}) }}
+    - require:
+      - cmd: maas_login_admin
+    {%- if disk.devices is defined %}
+    {%- for device_name in disk.devices %}
+      {%- if salt['pillar.get']('maas:region:machines:'+machine_name+':disk_layout:disk:'+device_name) is mapping %}
+      - maasng: maas_machine_{{ machine_name }}_{{ device_name }}
+      {%- endif %}
+    {%- endfor %}
+    {%- endif %}
+    {%- if disk.partitions is defined %}
+    {%- for partition in disk.partitions %}
+      {% set device_name = partition.split('-')[0] %}
+      {%- if salt['pillar.get']('maas:region:machines:'+machine_name+':disk_layout:disk:'+device_name) is mapping %}
+      - maasng: maas_machine_{{ machine_name }}_{{ device_name }}
+      {%- endif %}
+    {%- endfor %}
+    {%- endif %}
+{%- endif %}
+
+{%- if disk.type == "lvm" %}
+
+maas_machine_vg_{{ machine_name }}_{{ disk_name }}:
+  maasng.volume_group_present:
+    - hostname: {{ machine_name }}
+    - name: {{ disk_name }}
+    {%- if disk.devices is defined %}
+    - devices: {{ disk.devices }}
+    {%- endif %}
+    {%- if disk.partitions is defined %}
+    - partitions: {{ disk.partitions }}
+    {%- endif %}
+    - require:
+      - cmd: maas_login_admin
+    {%- if disk.partitions is defined %}
+    {%- for partition in disk.partitions %}
+      {% set device_name = partition.split('-')[0] %}
+      {%- if salt['pillar.get']('maas:region:machines:'+machine_name+':disk_layout:disk:'+device_name) is mapping %}
+      - maasng: maas_machine_{{ machine_name }}_{{ device_name }}
+      {%- endif %}
+    {%- endfor %}
+    {%- endif %}
+    {%- if disk.devices is defined %}
+    {%- for device_name in disk.devices %}
+      {%- if salt['pillar.get']('maas:region:machines:'+machine_name+':disk_layout:disk:'+device_name) is mapping %}
+      - maasng: maas_machine_{{ machine_name }}_{{ device_name }}
+      {%- endif %}
+    {%- endfor %}
+    {%- endif %}
+
+{%- for volume_name, volume in disk.volume.iteritems() %}
+
+maas_machine_volume_{{ machine_name }}_{{ disk_name }}_{{ volume_name }}:
+  maasng.volume_present:
+    - hostname: {{ machine_name }}
+    - name: {{ volume_name }}
+    - volume_group_name: {{ disk_name }}
+    - size: {{ volume.size }}
+    {%- if volume.fs_type is defined %}
+    - fs_type: {{ volume.fs_type }}
+    {%- endif %}
+    {%- if volume.mount is defined %}
+    - mount: {{ volume.mount }}
+    {%- endif %}
+    - require:
+      - maasng: maas_machine_vg_{{ machine_name }}_{{ disk_name }}
+
+{%- endfor %}
+
+{%- endif %}
+
+{%- endfor %}
+
+{%- endif %}
+
+{%- endif %}
+
+{%- endfor %}
+
diff --git a/tests/pillar/disk_layout.sls b/tests/pillar/disk_layout.sls
new file mode 100644
index 0000000..e9a7405
--- /dev/null
+++ b/tests/pillar/disk_layout.sls
@@ -0,0 +1,77 @@
+maas:
+  region:
+    theme: theme
+    bind:
+      host: localhost
+      port: 80
+    admin:
+      username: admin
+      password: password
+      email:  email@example.com
+    database:
+      engine: postgresql
+      host: localhost
+      name: maasdb
+      password: password
+      username: maas
+    enabled: true
+    salt_master_ip: 127.0.0.1
+    machines:
+      server3:
+        disk_layout:
+          type: flat
+          bootable_device: vda
+          disk:
+            vda:
+              type: physical
+              partition_schema:
+                part1:
+                  size: 10G
+                  type: ext4
+                  mount: '/'
+                part2:
+                  size: 2G
+                part3:
+                  size: 3G
+            vdc:
+              type: physical
+              partition_schema:
+                part1:
+                  size: 100%
+            vdd:
+              type: physical
+              partition_schema:
+                part1:
+                  size: 100%
+            raid0:
+              type: raid
+              level: 10
+              devices:
+                - vde
+                - vdf
+              partition_schema:
+                part1:
+                  size: 10G
+                part2:
+                  size: 2G
+                part3:
+                  size: 3G
+            raid1:
+              type: raid
+              level: 1
+              partitions:
+                - vdc-part1
+                - vdd-part1
+            volume_group2:
+              type: lvm
+              devices:
+                - raid1
+              volume:
+                tmp:
+                  size: 5G
+                  fs_type: ext4
+                  mount: '/tmp'
+                log:
+                  size: 7G
+                  fs_type: ext4
+                  mount: '/var/log'