Merge "Fix Python version for Travis CI tests"
diff --git a/README.rst b/README.rst
index 2f85ea0..4cc62bd 100644
--- a/README.rst
+++ b/README.rst
@@ -46,18 +46,39 @@
       user: mirantis
       token: "89EgtWkX45ddjMYpuL:SqVjxFG87Dr6kVf4Wp:5WLfbUgmm9XQtJxm3V2LUUy7bpCmqmnk"
       fabrics:
-        test-fabric1:
+        fabric1:
+          name: 'tf2'
           description: "Test fabric"
-        test-fabric2:
+        fabric2:
+          name: 'tf2'
           description: "Test fabric2"
+        deploy_network:
+          name: 'deploy_network'
+          description: Fabric for deploy_network
+          vlans:
+            0:
+              name: 'vlan 0'
+              description: Deploy VLAN
+              dhcp: true
+              # FIXME: after refactoring domain module, it should be
+              # fixed exactly for FQDN, not only 'hostname'
+              primary_rack: "${linux:network:hostname}"
+
       subnets:
         subnet1:
-          fabric: test-fabric1
+          fabric: ${maas:region:fabrics:deploy_network:name}
           cidr: 2.2.3.0/24
           gateway_ip: 2.2.3.2
-          iprange: # reserved range for DHCP\auto mapping
-            start: 2.2.3.20
-            end: 2.2.3.250
+          vlan: 150
+          ipranges:
+            1:
+              end: "2.2.3.40"
+              start: "2.2.3.20"
+              type: dynamic
+            2:
+              end: "2.2.3.250"
+              start: "2.2.3.45"
+              type: static
       dhcp_snippets:
         test-snippet:
           value: option bootfile-name "tftp://192.168.0.10/snippet";
@@ -149,7 +170,7 @@
       commissioning_scripts:
         00-maas-05-simplify-network-interfaces: /etc/maas/files/commisioning_scripts/00-maas-05-simplify-network-interfaces
       maas_config:
-        domain: mydomain.local
+        # domain: mydomain.local # This function broken
         http_proxy: http://192.168.0.10:3142
         commissioning_distro_series: xenial
         default_distro_series: xenial
@@ -169,6 +190,7 @@
        sshprefs:
         - 'ssh-rsa ASD.........dfsadf blah@blah'
 
+
 Update Vlan
 
 NOTE: Vid 0 has default name untagged in MaaS UI
diff --git a/_modules/maasng.py b/_modules/maasng.py
index d159c9c..f14b18e 100644
--- a/_modules/maasng.py
+++ b/_modules/maasng.py
@@ -19,7 +19,6 @@
 import io
 import json
 import logging
-import os.path
 import time
 import urllib2
 # Salt utils
@@ -70,7 +69,9 @@
     return Lazy()
 
 
-def _create_maas_client():
+def _create_maas_client(api_url=None):
+    if not api_url:
+        api_url = 'http://localhost:5240/MAAS'
     global APIKEY_FILE
     try:
         api_token = file(APIKEY_FILE).read().splitlines()[-1].strip()\
@@ -78,7 +79,6 @@
     except:
         LOG.exception('token')
     auth = MAASOAuth(*api_token)
-    api_url = 'http://localhost:5240/MAAS'
     dispatcher = MAASDispatcher()
     return MAASClient(auth, dispatcher, api_url)
 
@@ -99,8 +99,8 @@
 
     if not maas_volname:
         # MAAS-like name
-        volume_name = str("%s-%s" % (volume_group,volume_name))
-    ##TODO validation
+        volume_name = str("%s-%s" % (volume_group, volume_name))
+    # TODO validation
     return get_volumes(hostname, volume_group)[volume_name]["id"]
 
 
@@ -233,8 +233,9 @@
     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())
+    # 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
@@ -271,11 +272,12 @@
         salt-call maasng.delete_raid server_hostname raid_name
     """
     result = {}
-    maas=_create_maas_client()
+    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()
+    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
@@ -497,7 +499,7 @@
 # DISK LAYOUT
 
 
-def drop_storage_schema(hostname,disk=None):
+def drop_storage_schema(hostname, disk=None):
     """
     #1. Drop lv
     #2. Drop vg
@@ -507,9 +509,10 @@
 
     if __opts__['test']:
         ret['result'] = None
-        ret['comment'] = 'Storage schema on {0} will be removed'.format(hostname)
+        ret['comment'] = 'Storage schema on {0} will be removed'.format(
+            hostname)
         return ret
-    #TODO validation if exists
+    # TODO validation if exists
     vgs = list_volume_groups(hostname)
     for vg in vgs:
         delete_volume_group(hostname, vg)
@@ -523,7 +526,8 @@
         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"])
+            __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):
@@ -591,6 +595,7 @@
 # END DISK LAYOUT
 # LVM
 
+
 def list_volume_groups(hostname):
     """
     Get list of all volume group on machine.
@@ -663,8 +668,9 @@
         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)
+            result["error"] = "Device {0} on" \
+                              "machine {1} cointains partition" \
+                              "table".format(disk, hostname)
             return result
 
     for partition in partitions:
@@ -673,8 +679,9 @@
             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)
+            result["error"] = "Partition {0} does" \
+                              "not exists on " \
+                              "machine {1}".format(partition, hostname)
             return result
 
     data["block_devices"] = disk_ids
@@ -709,16 +716,18 @@
 
     vg_id = str(_get_volume_group_id_by_name(hostname, name))
     for vol in get_volumes(hostname, name):
-        delete_volume(hostname,vol,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')
+    # 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):
+def create_volume(hostname, volume_name, volume_group, size, fs_type=None,
+                  mount=None):
     """
     Create volume on volume group.
 
@@ -754,7 +763,8 @@
     LOG.info(json_res)
 
     if fs_type != None or mount != None:
-        ret = create_volume_filesystem(hostname, volume_group + "-" + volume_name, fs_type, mount)
+        ret = create_volume_filesystem(
+            hostname, volume_group + "-" + volume_name, fs_type, mount)
 
     return True
 
@@ -774,12 +784,13 @@
         salt-call maasng.delete_volume server_hostname volume_name volume_group
     """
 
-    maas=_create_maas_client()
+    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))
+    volume_id = str(_get_volume_id_by_name(
+        hostname, volume_name, volume_group))
 
     if None in [volume_group_id, volume_id]:
         return False
@@ -788,8 +799,9 @@
         "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')
+    # 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
 
 
@@ -798,7 +810,8 @@
     Get list of volumes in volume group.
     """
     volumes = {}
-    _volumes = list_volume_groups(hostname)[vg_name].get('logical_volumes', False)
+    _volumes = list_volume_groups(
+        hostname)[vg_name].get('logical_volumes', False)
     if _volumes:
         for item in _volumes:
             volumes[item["name"]] = item
@@ -850,7 +863,8 @@
 
     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)
+    # 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
@@ -877,7 +891,73 @@
     return fabrics
 
 
-def create_fabric(name):
+def check_fabric(name):
+    """
+    Simple check that fabric already defined
+    Return format:
+    update  - require update
+    correct - fully coincides # not implemented
+    not_exist  - need's to be created
+    """
+
+    ret = 'not_exist'
+    fabrics = list_fabric()
+    if name in fabrics.keys():
+        LOG.debug("Requested fabrics with  name:{} already exist".format(name))
+        ret = 'update'
+    return ret
+
+
+def check_fabric_guess_with_cidr(name, cidrs):
+    """
+     Check, that fabric  already defined OR it was autodiscovered
+     WA to fix issue with hardcoded 'fabric-0'
+     - Find all auto-discovered subnets by cidr
+     - find all subnets, that SHOULD be configured to THIS subent
+     Warning: most probably, will fail if some subnet defined
+     to another fabric :(
+
+    { 'update'    : ID }  - require update
+    { 'correct'   : ID } - fully coincides # not implemented
+    { 'not_exist' : None }  - need's to be created
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt 'maas-node' maasng.check_fabric_guess_with_cidr name='' cidrs=[]
+    """
+
+    ret = {'not_exist': None}
+    fabrics = list_fabric()
+    # Simple check
+    if name in fabrics:
+        LOG.debug("Requested fabrics with name:{} already exist".format(name))
+        f_id = fabrics[name]['id']
+        ret = {'update': f_id}
+    # Cidr check
+    # All discovered subnets by cidr
+    d_subnets = list_subnets(sort_by='cidr')
+    # Check, that requested cidr already in discovered.
+    # If it is - it would mean that fabric already
+    # exist(fabric-0,most probably) but should be renamed.
+    # Works only for first shot ;(
+    # due curren-single-maas logic for 'vlan-subnet' mapping.
+    # Probably, it will fail with future MAAS releases.
+    for cidr in cidrs:
+        if cidr in d_subnets:
+            f_id = d_subnets[cidr]['vlan']['fabric_id']
+            f_name = d_subnets[cidr]['vlan']['fabric']
+            LOG.warning("Detected cidr:{} in fabric:{}".format(cidr, f_name))
+            LOG.warning("Guessing, that fabric "
+                        "with current name:{}\n should be "
+                        "renamed to:{}".format(f_name, name))
+            ret = {'update': f_id}
+            return ret
+    return ret
+
+
+def create_fabric(name, description=None, fabric_id=None, update=False):
     """
     Create new fabric.
 
@@ -885,65 +965,90 @@
 
     .. code-block:: bash
 
-        salt 'maas-node' maasng.create_fabric
+        salt 'maas-node' maasng.create_fabric name='123'
     """
     result = {}
     data = {
         "name": name,
-        "description": '',
         "class_type": '',
 
     }
+    if description:
+        data['description'] = description
 
     maas = _create_maas_client()
-    json_res = json.loads(maas.post(u"api/2.0/fabrics/", None, **data).read())
-    LOG.info(json_res)
-    result["new"] = "Fabrics {0} created".format(json_res["name"])
+    json_res = None
+    try:
+        if update:
+            json_res = json.loads(
+                maas.put(u"api/2.0/fabrics/{0}/".format(fabric_id),
+                         **data).read())
+            result["new"] = "Fabric  {0} created".format(json_res["name"])
+        else:
+            json_res = json.loads(
+                maas.post(u"api/2.0/fabrics/", None, **data).read())
+            result["changes"] = "Fabric  {0} updated".format(json_res["name"])
+    except Exception as inst:
+        LOG.debug("create_fabric data:{}".format(data))
+        try:
+            m = inst.readlines()
+        except:
+            m = inst.message
+        LOG.error("Message:{0}".format(m))
+        result['result'] = False
+        result['comment'] = 'Error creating fabric: {0}'.format(name)
+        result['error'] = m
+        return result
+    LOG.debug("crete_fabric:{}".format(json_res))
+    result['result'] = True
     return result
 
 
-def list_subnet():
+def list_subnets(sort_by='name'):
     """
-    Get list of all subnets
+    Get list of subnets from maas server
 
     CLI Example:
 
     .. code-block:: bash
 
-        salt 'maas-node' maasng.list_subnet
+        salt 'maas-node' maasng.list_subnets
     """
     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
+        subnets[item[sort_by]] = item
     return subnets
 
 
-def list_vlans(fabric):
+def list_vlans(fabric, sort_by='vid'):
     """
-    Get list of all vlans for specific fabric
+    Get list of vlans in fabric
 
     CLI Example:
 
     .. code-block:: bash
 
-        salt 'maas-node' maasng.list_vlans
+        salt 'maas-node' maasng.list_vlans fabric_name
     """
     vlans = {}
     maas = _create_maas_client()
-    fabric_id = get_fabric(fabric)
+    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)
+    try:
+        json_res = json.loads(
+            maas.get(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id)).read())
+    except Exception as inst:
+        m = inst.readlines()
+        LOG.error("Message:{0}".format(m))
+    LOG.debug(json_res)
     for item in json_res:
-        vlans[item["name"]] = item
+        vlans[item[sort_by]] = item
     return vlans
 
 
-def get_fabric(fabric):
+def get_fabricid(fabric):
     """
     Get id for specific fabric
 
@@ -951,22 +1056,38 @@
 
     .. code-block:: bash
 
-        salt-call maasng.get_fabric fabric_name
+        salt 'maas-node' maasng.get_fabricid fabric_name
     """
     try:
         return list_fabric()[fabric]['id']
     except KeyError:
-        return {"error": "Frabic not found on MaaS server"}
+        return {"error": "Fabric not found on MaaS server"}
 
 
-def update_vlan(name, fabric, vid, description, primary_rack, dhcp_on=False):
+def check_vlan_in_fabric(fabric, vlan):
+    """
+    Check that VLAN exactly defined
+    Return format:
+    update  - require update
+    correct - fully coincides # not implemented
+    not_exist  - need's to be created
+    """
+
+    ret = 'not_exist'
+    vlans = list_vlans(fabric)
+    if vlan in vlans.keys():
+        LOG.debug("Requested VLAN:{} already exist"
+                  "in FABRIC:{}".format(vlan, fabric))
+        ret = 'update'
+    return ret
+
+
+def create_vlan_in_fabric(name, fabric, vlan, description, primary_rack,
+                          dhcp_on=False, update=False, vlan_id=""):
     """
     Update vlan
-
     CLI Example:
-
     .. code-block:: bash
-
         salt 'maas-node' maasng.update_vlan name, fabric, vid, description, dhcp_on
     """
     result = {}
@@ -975,23 +1096,286 @@
         "name": name,
         "dhcp_on": str(dhcp_on),
         "description": description,
-        "primary_rack": primary_rack,
+        "primary_rack": list_racks()[primary_rack]['system_id'],
     }
+    vlan = str(vlan)
     maas = _create_maas_client()
-    fabric_id = get_fabric(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))
+    fabric_id = get_fabricid(fabric)
+    try:
+        if update:
+            # MAAS have buggy logic here. Fallowing api reference, here should
+            # be passed VID - which mean, API ID for vlan.
+            # Otherwise, at least for maas 2.3.3-6498-ge4db91d exactly VLAN
+            # should be passed. so, make temp.backward-convertation.
+            # json_res = json.loads(maas.put(u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id,vlan_id), **data).read())
+            json_res = json.loads(maas.put(
+                u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id, vlan),
+                **data).read())
+        else:
+            data['vid'] = str(vlan)
+            json_res = json.loads(maas.post(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id), None, **data).read())
+    except Exception as inst:
+        LOG.debug("create_vlan_in_fabric data:{}".format(data))
+        try:
+            m = inst.readlines()
+        except:
+            m = inst.message
+        LOG.error("Message:{0}".format(m))
+        result['result'] = False
+        result['comment'] = 'Error updating vlan: {0}'.format(name)
+        result['error'] = m
+        return result
+    LOG.debug("create_vlan_in_fabric:{}".format(json_res))
     result["new"] = "Vlan {0} was updated".format(json_res["name"])
 
     return result
 
+
+def check_subnet(cidr, name, fabric, gateway_ip):
+    """
+    Check that subnet exactly defined
+    Return format:
+    update  - require update
+    correct - fully coincides # not implemented
+    not_exist  - need's to be created
+    """
+
+    ret = 'not_exist'
+    subnets = list_subnets(sort_by='cidr')
+    if cidr in subnets.keys():
+         LOG.debug("Requested subnet cidr:{} already exist".format(cidr))
+         ret = 'update'
+    return ret
+
+
+def create_subnet(cidr='', name='', fabric='', gateway_ip='', vlan='',
+                  update=False, subnet_id=''):
+    """
+    Create subnet
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt 'maas-node' maasng.create_subnet cidr, name, fabric, gateway_ip
+    """
+
+    fabric_id = get_fabricid(fabric)
+    result = {}
+    vlan=str(vlan)
+    data = {
+        "cidr": cidr,
+        "name": name,
+        "fabric": str(fabric_id),
+        "gateway_ip": gateway_ip,
+        'vlan': vlan,
+    }
+    maas = _create_maas_client()
+    # FIXME: vlan definition not work in 2.3.3-6498-ge4db91d.
+    LOG.warning("Ignoring parameter vlan:{}".format(vlan))
+    data.pop('vlan','')
+    try:
+        if update:
+            json_res = json.loads(maas.put(u"api/2.0/subnets/{0}/".format(subnet_id), **data).read())
+        else:
+            json_res = json.loads(maas.post(u"api/2.0/subnets/", None, **data).read())
+    except Exception as inst:
+        LOG.debug("create_subnet data:{}".format(data))
+        try:
+            m = inst.readlines()
+        except:
+            m = inst.message
+        LOG.error("Message:{0}".format(m))
+        result['result'] = False
+        result['comment'] = 'Error creating subnet: {0}'.format(name)
+        result['error'] = m
+        return result
+    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_subnets()[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_subnets()[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, subnet=None, comment=None):
+    """
+    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,
+    }
+    if comment:
+        data['comment'] = comment
+    if subnet:
+        subnet_id = list_subnets()[subnet]['id']
+        data['subnet'] = str(subnet_id)
+    maas = _create_maas_client()
+    _name = "Type:{}: {}-{}".format(type_range, start_ip, end_ip)
+    try:
+        json_res = json.loads(
+            maas.post(u"api/2.0/ipranges/", None, **data).read())
+    except Exception as inst:
+        try:
+            m = inst.readlines()
+        except:
+            m = inst.message
+        LOG.error("Message:{0}".format(m))
+        result['result'] = False
+        result['comment'] = 'Error creating iprange:{0}'.format(_name)
+        result['error'] = m
+        return result
+    result["new"] = "Iprange: {0} has been created".format(_name)
+    LOG.debug("create_iprange:{}".format(json_res))
+
+    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 _getHTTPCode(url):
+    code = '003'
+    m = ''
+    try:
+        connection = urllib2.urlopen(url)
+        code = connection.getcode()
+        connection.close()
+    except (urllib2.HTTPError, urllib2.URLError) as e:
+        try:
+            code = e.getcode()
+        except:
+            m = e.reason
+            pass
+        LOG.debug("Unexpected http code:{} from "
+                  "url:{}\nwith message:{}".format(code, url, m))
+        pass
+    return code
+
+
+def wait_for_http_code(url=None, expected=[200]):
+    """
+    Simple function, which just wait for avaible api, aka wait for 200.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt 'maas-node' maasng.wait_for_http_code url expected=[200]
+
+    """
+    ret = {}
+    started_at = time.time()
+    poll_time = 5
+    timeout = 60 * 2
+    while _getHTTPCode(url) not in expected:
+        c_timeout = timeout - (time.time() - started_at)
+        if c_timeout <= 0:
+            ret['result'] = False
+            ret["comment"] = "api:{} not answered in time".format(url)
+            return ret
+        LOG.info(
+            "Waiting for api:{0}\n"
+            "sleep for:{1}s "
+            "Left:{2}/{3}s".format(url, poll_time, round(c_timeout),
+                                   timeout))
+        time.sleep(poll_time)
+    ret['result'] = True
+    ret["comment"] = "MAAS API:{} up.".format(url)
+    return ret
+
+
 def _get_boot_source_id_by_url(url):
     # FIXME: fix ret\validation
     try:
@@ -1158,7 +1542,7 @@
             maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
 
 #####
-#def boot_sources_selections_delete_all_others(except_urls=[]):
+# def boot_sources_selections_delete_all_others(except_urls=[]):
 #    """
 #    """
 #    result = {}
@@ -1167,16 +1551,16 @@
 
 def is_boot_source_selections_in(dict1, list1):
     """
-    Check that requested boot-selection already in maas bs selections, if True- return bss id.
+    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))
+            LOG.debug("boot-selection in maas:{0}\n"
+                      "looks same to requested:{1}".format(bs, dict1))
             return bs['id']
     return False
 
@@ -1222,8 +1606,8 @@
     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(
+        result["comment"] = 'Requested boot-source selection ' \
+                            'for {0} already exist.'.format(
             bs_url)
         return result
 
@@ -1240,7 +1624,10 @@
                           **data).read())
         except Exception as inst:
             m = inst.readlines()
-            LOG.warning("boot_source_selections catch error during processing. Most-probably, streams not imported yet.Sleep:{}s\nRetry:{}/5".format(poll_time,i))
+            LOG.warning("boot_source_selections "
+                        "catch error during processing. Most-probably, "
+                        "streams not imported yet.\nSleep:{}s"
+                        "Retry:{}/5".format(poll_time, i))
             LOG.warning("Message:{0}".format(m))
             time.sleep(poll_time)
             continue
@@ -1248,8 +1635,8 @@
     LOG.debug("create_boot_source_selections:{}".format(json_res))
     if not json_res:
         result["result"] = False
-        result[
-            "comment"] = 'Failed to create requested boot-source selection for {0}.'.format(bs_url)
+        result["comment"] = 'Failed to create requested boot-source selection' \
+                            ' for {0}.'.format(bs_url)
         return result
     if wait:
         LOG.debug(
@@ -1284,7 +1671,7 @@
         return {"error": "rack:{} not found on MaaS server".format(hostname)}
 
 
-def list_racks():
+def list_racks(sort_by='hostname'):
     """
     Get list of all rack controllers from maas server
 
@@ -1299,7 +1686,7 @@
     json_res = json.loads(
         maas.get(u"/api/2.0/rackcontrollers/").read() or 'null')
     for item in json_res:
-        racks[item["hostname"]] = item
+        racks[item[sort_by]] = item
     return racks
 
 
diff --git a/_states/maasng.py b/_states/maasng.py
index 99d2fde..40ec9a4 100644
--- a/_states/maasng.py
+++ b/_states/maasng.py
@@ -1,4 +1,3 @@
-
 import logging
 from salt.exceptions import CommandExecutionError, SaltInvocationError
 
@@ -19,9 +18,9 @@
 
 
 def __virtual__():
-    '''
+    """
     Load MaaSng module
-    '''
+    """
     return 'maasng'
 
 
@@ -67,7 +66,8 @@
             hostname, layout_type, root_size, root_device, volume_group, volume_name, volume_size)
 
     elif layout_type == "custom":
-        ret["changes"] = __salt__['maasng.update_disk_layout'](hostname, layout_type)
+        ret["changes"] = __salt__[
+            'maasng.update_disk_layout'](hostname, layout_type)
 
     else:
         ret["comment"] = "Not supported layout provided. Choose flat or lvm"
@@ -225,8 +225,8 @@
 
     machine = __salt__['maasng.get_machine'](hostname)
     if "error" in machine:
-        ret['comment'] = "State execution failed for machine {0}".format(
-            hostname)
+        ret['comment'] = "State execution" \
+                         "failed for machine {0}".format(hostname)
         ret['result'] = False
         ret['changes'] = machine
         return ret
@@ -257,12 +257,13 @@
     return ret
 
 
-def volume_present(hostname, name, volume_group_name, size, type=None, mount=None):
-    '''
+def volume_present(hostname, name, volume_group_name, size, 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': {},
@@ -309,8 +310,8 @@
 
     machine = __salt__['maasng.get_machine'](hostname)
     if "error" in machine:
-        ret['comment'] = "State execution failed for machine {0}".format(
-            hostname)
+        ret['comment'] = "State execution" \
+                         "failed for machine {0}".format(hostname)
         ret['result'] = False
         ret['changes'] = machine
         return ret
@@ -321,8 +322,8 @@
 
     if __opts__['test']:
         ret['result'] = None
-        ret['comment'] = 'LVM volume {0} will be updated on {1}'.format(
-            name, hostname)
+        ret['comment'] = 'LVM volume {0}' \
+                         'will be updated on {1}'.format(name, hostname)
 
     # TODO disk validation if exists
 
@@ -331,36 +332,56 @@
     return ret
 
 
-def update_vlan(name, fabric, vid, description, primary_rack, dhcp_on=False):
-    '''
+def vlan_present_in_fabric(name, fabric, vlan, primary_rack, description='', dhcp_on=False):
+    """
 
     :param name: Name of vlan
     :param fabric: Name of fabric
-    :param vid: Vlan id
+    :param vlan: Vlan id
     :param description: Description of vlan
     :param dhcp_on: State of dhcp
     :param primary_rack: primary_rack
 
-    '''
+    """
 
     ret = {'name': fabric,
            'changes': {},
            'result': True,
            'comment': 'Module function maasng.update_vlan executed'}
 
-    ret["changes"] = __salt__['maasng.update_vlan'](
-        name=name, fabric=fabric, vid=vid, description=description,
-        primary_rack=primary_rack, dhcp_on=dhcp_on)
-
-    if "error" in fabric:
-        ret['comment'] = "State execution failed for fabric {0}".format(fabric)
-        ret['result'] = False
-        ret['changes'] = fabric
-        return ret
-
     if __opts__['test']:
         ret['result'] = None
-        ret['comment'] = 'Vlan {0} will be updated for {1}'.format(vid, fabric)
+        ret['comment'] = 'Vlan {0} will be updated for {1}'.format(vlan, fabric)
+        return ret
+    # Check, that vlan  already defined
+    _rez = __salt__['maasng.check_vlan_in_fabric'](fabric=fabric,
+                                                   vlan=vlan)
+    if _rez == 'not_exist':
+        changes = __salt__['maasng.create_vlan_in_fabric'](name=name,
+                                                           fabric=fabric,
+                                                           vlan=vlan,
+                                                           description=description,
+                                                           primary_rack=primary_rack,
+                                                           dhcp_on=dhcp_on)
+        ret['comment'] = 'Vlan {0} has' \
+                         'been created for {1}'.format(name, fabric)
+    elif _rez == 'update':
+        _id = __salt__['maasng.list_vlans'](fabric)[vlan]['id']
+        changes = __salt__['maasng.create_vlan_in_fabric'](name=name,
+                                                           fabric=fabric,
+                                                           vlan=vlan,
+                                                           description=description,
+                                                           primary_rack=primary_rack,
+                                                           dhcp_on=dhcp_on,
+                                                           update=True,
+                                                           vlan_id=_id)
+        ret['comment'] = 'Vlan {0} has been' \
+                         'updated for {1}'.format(name, fabric)
+    ret['changes'] = changes
+
+    if "error" in changes:
+        ret['comment'] = "State execution failed for fabric {0}".format(fabric)
+        ret['result'] = False
         return ret
 
     return ret
@@ -419,16 +440,15 @@
 
     if __opts__['test']:
         ret['result'] = None
-        ret['comment'] = 'boot-source {0}  selection will be updated'.format(
-            bs_url)
+        ret['comment'] = 'boot-source {0}' \
+                         'selection will be updated'.format(bs_url)
 
     maas_boot_sources = __salt__['maasng.get_boot_source']()
     if bs_url not in maas_boot_sources.keys():
         ret["result"] = False
-        ret[
-            "comment"] = 'Requested boot-source {0} not exist! Unable' \
-                         'to proceed selection for it'.format(
-            bs_url)
+        ret["comment"] = 'Requested boot-source' \
+                         '{0} not exist! Unable' \
+                         'to proceed selection for it'.format(bs_url)
         return ret
 
     ret = __salt__['maasng.create_boot_source_selections'](bs_url,
@@ -439,3 +459,140 @@
                                                            labels=labels,
                                                            wait=wait)
     return ret
+
+
+def iprange_present(name, type_range, start_ip, end_ip, subnet=None,
+                    comment=None):
+    """
+
+    :param name: Name of iprange
+    :param type_range: Type of iprange
+    :param start_ip: Start ip of iprange
+    :param end_ip: End ip of iprange
+    :param comment: Comment for specific iprange
+
+    """
+
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Module function maasng.iprange_present executed'}
+
+    # Check, that range  already defined
+    _rez = __salt__['maasng.get_startip'](start_ip)
+    if 'start_ip' in _rez.keys():
+        if _rez["start_ip"] == start_ip:
+            ret['comment'] = 'Iprange {0} already exist.'.format(name)
+            return ret
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'Ip range {0} will be ' \
+                         'created with start ip: {1} ' \
+                         'and end ip: {2} and ' \
+                         'type {3}'.format(name, start_ip, end_ip, type_range)
+        return ret
+
+    changes = __salt__['maasng.create_iprange'](type_range=type_range,
+                                                start_ip=start_ip,
+                                                end_ip=end_ip,subnet=subnet, comment=comment)
+    ret["changes"] = changes
+    if "error" in changes:
+        ret['comment'] = "State execution failed for iprange {0}".format(name)
+        ret['result'] = False
+        return ret
+    return ret
+
+
+def subnet_present(cidr, name, fabric, gateway_ip, vlan):
+    """
+
+    :param cidr: Cidr for subnet
+    :param name: Name of subnet
+    :param fabric: Name of fabric for subnet
+    :param gateway_ip: gateway_ip
+
+    """
+
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Module function maasng.subnet_present executed'}
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'Subnet {0} will be created for {1}'.format(
+            name, fabric)
+        return ret
+    # Check, that subnet already defined
+    _rez = __salt__['maasng.check_subnet'](cidr, name, fabric, gateway_ip)
+    if _rez == 'not_exist':
+        changes = __salt__['maasng.create_subnet'](cidr=cidr, name=name,
+                                                   fabric=fabric,
+                                                   gateway_ip=gateway_ip,
+                                                   vlan=vlan)
+        ret['comment'] = 'Subnet {0} ' \
+                         'has been created for {1}'.format(name, fabric)
+    elif _rez == 'update':
+        _id = __salt__['maasng.list_subnets'](sort_by='cidr')[cidr]['id']
+        changes = __salt__['maasng.create_subnet'](cidr=cidr, name=name,
+                                                   fabric=fabric,
+                                                   gateway_ip=gateway_ip,
+                                                   vlan=vlan, update=True,
+                                                   subnet_id=_id)
+        ret['comment'] = 'Subnet {0} ' \
+                         'has been updated for {1}'.format(name, fabric)
+
+    if "error" in changes:
+        ret['comment'] = "State execution failed for subnet {0}".format(name)
+        ret['result'] = False
+        ret['changes'] = changes
+        return ret
+
+    return ret
+
+
+def fabric_present(name, description=None):
+    """
+
+    :param name: Name of fabric
+    :param description: Name of description
+
+    """
+
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Module function maasng.fabric_present executed'}
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = 'fabric {0} will be updated for {1}'.format(vlan, name)
+        return ret
+    # All requested subnets
+    _r_subnets = __salt__['config.get']('maas').get('region', {}).get('subnets',
+                                                                      {})
+    # Assumed subnet CIDrs, expected to be in requested fabric
+    _a_subnets = [_r_subnets[f]['cidr'] for f in _r_subnets.keys() if
+                  _r_subnets[f]['fabric'] == name]
+    _rez = __salt__['maasng.check_fabric_guess_with_cidr'](name=name,
+                                                           cidrs=_a_subnets)
+
+    if 'not_exist' in _rez:
+        changes = __salt__['maasng.create_fabric'](name=name,
+                                                   description=description)
+        ret['new'] = 'Fabric {0} has been created'.format(name)
+    elif 'update' in _rez:
+        f_id = _rez['update']
+        changes = __salt__['maasng.create_fabric'](name=name,
+                                                   description=description,
+                                                   update=True, fabric_id=f_id)
+        ret['new'] = 'Fabric {0} has been updated'.format(name)
+    ret['changes'] = changes
+
+    if "error" in changes:
+        ret['comment'] = "State execution failed for fabric {0}".format(fabric)
+        ret['result'] = False
+        return ret
+
+    return ret
diff --git a/maas/cluster.sls b/maas/cluster.sls
index 59284db..ad15c63 100644
--- a/maas/cluster.sls
+++ b/maas/cluster.sls
@@ -1,5 +1,5 @@
 {%- from "maas/map.jinja" import cluster with context %}
-{%- if cluster.enabled %}
+{%- if cluster.get('enabled', False) %}
 
 {%- if cluster.role == 'slave' %}
 
diff --git a/maas/files/regiond.conf b/maas/files/regiond.conf
index 8c9e8df..8c2fb3d 100644
--- a/maas/files/regiond.conf
+++ b/maas/files/regiond.conf
@@ -4,4 +4,4 @@
 database_name: {{ region.database.name }}
 database_pass: {{ region.database.password }}
 database_user: {{ region.database.username }}
-maas_url: http://{{ region.bind.host }}/MAAS
+maas_url: http://{{ region.bind.host }}:{{ region.bind.get('port','5240') }}/MAAS
diff --git a/maas/init.sls b/maas/init.sls
index da8376b..1a43457 100644
--- a/maas/init.sls
+++ b/maas/init.sls
@@ -9,7 +9,4 @@
 {%- if pillar.maas.region is defined %}
 - maas.region
 {%- endif %}
-{%- if pillar.maas.machines is defined %}
-- maas.machines
-{%- endif %}
 {%- endif %}
diff --git a/maas/region.sls b/maas/region.sls
index e29da0e..c2a4c6b 100644
--- a/maas/region.sls
+++ b/maas/region.sls
@@ -1,5 +1,5 @@
 {%- from "maas/map.jinja" import region with context %}
-{%- if region.enabled %}
+{%- if region.get('enabled', False) %}
 
 maas_region_packages:
   pkg.installed:
@@ -146,6 +146,20 @@
   - onlyif: /bin/false
   {%- endif %}
 
+maas_warmup:
+  module.run:
+  - name: maasng.wait_for_http_code
+# FIXME
+  - url: "http://localhost:5240/MAAS"
+# 405 - should be removed ,since twisted will be fixed
+# Currently - api always throw 405=>500 even if request has been made with 'expected 'HEAD
+  - expected: [200, 405]
+  - require_in:
+    - module: maas_set_admin_password
+  {%- if grains.get('kitchen-test') %}
+  - onlyif: /bin/false
+  {%- endif %}
+
 maas_set_admin_password:
   cmd.run:
   - name: "maas createadmin --username {{ region.admin.username }} --password {{ region.admin.password }} --email {{ region.admin.email }} && touch /var/lib/maas/.setup_admin"
@@ -283,22 +297,77 @@
 {%- endif %}
 
 {%- if region.get('fabrics', False)  %}
-maas_fabrics:
-  module.run:
-  - name: maas.process_fabrics
+  {%- for _, fabric in region.fabrics.iteritems() %}
+  {% set fabric_name=fabric.get('name', _) %}
+# First, create fabrics
+# Bakward-compat.name:
+
+maas_fabrics_{{ fabric_name }}:
+  maasng.fabric_present:
+  - name: {{ fabric_name }}
+  - description: {{ fabric.get('description', '') }}
   - require:
     - cmd: maas_login_admin
+
+# Second, add VLAN into fabric's
+    {%- for vlan_n, data in fabric.get('vlans',{}).iteritems() %}
+maas_vlan{{ vlan_n }}_present_for_{{ fabric_name }}:
+  maasng.vlan_present_in_fabric:
+  - vlan: {{ vlan_n }}
+  - fabric: {{ fabric_name }}
+  - name: {{ data.get('name','') }}
+  - description: {{ data.description }}
+  - primary_rack: {{ data.get('primary_rack', '')  }}
+    {%- endfor %}
+  {%- endfor %}
 {%- endif %}
 
-{%- if region.get('subnets', False)  %}
-maas_subnets:
-  module.run:
-  - name: maas.process_subnets
+# Create subnets
+{%- if region.subnets is defined %}
+  {%- for _, subnet in region.subnets.iteritems() %}
+maas_create_subnet_{{ subnet.cidr }}:
+  maasng.subnet_present:
+  - cidr: {{ subnet.cidr }}
+  - name: {{ subnet.get('name','') }}
+  - fabric: {{ subnet.fabric }}
+  - vlan: {{ subnet.get('vlan','') }}
+  - gateway_ip: {{ subnet.gateway_ip }}
   - require:
     - cmd: maas_login_admin
     {%- if region.get('fabrics', False)  %}
-    - module: maas_fabrics
+    - maas_fabrics_{{ subnet.fabric }}
     {%- endif %}
+# create ranges
+    {%- for _r, iprange in subnet.get('ipranges',{}).iteritems() %}
+maas_create_iprange_{{ _r }}:
+  maasng.iprange_present:
+  - name: {{ iprange.get('name', _r) }}
+  - type_range: {{ iprange.type }}
+  - start_ip: {{ iprange.start }}
+  - end_ip: {{ iprange.end }}
+  - subnet: {{ iprange.get('subnet', '' ) }}
+  - comment: {{ iprange.get('comment', "") }}
+  - require:
+    - maas_create_subnet_{{ subnet.cidr }}
+    {%- endfor %}
+  {%- endfor %}
+{%- endif %}
+
+# Get back to fabrics again and enable DHCP
+{%- if region.get('fabrics', False)  %}
+  {%- for _, fabric in region.fabrics.iteritems() %}
+    {%- for vlan_n, data in fabric.get('vlans',{}).iteritems() %}
+    {% set fabric_name=fabric.get('name', _) %}
+maas_vlan{{ vlan_n }}_present_for_{{ fabric_name }}_dhcp:
+  maasng.vlan_present_in_fabric:
+  - vlan: {{ vlan_n }}
+  - fabric: {{ fabric_name }}
+  - name: {{ data.get('name','') }}
+  - description: {{ data.description }}
+  - primary_rack: {{ data.get('primary_rack', '')  }}
+  - dhcp_on: {{ data.get('dhcp','False') }}
+    {%- endfor %}
+  {%- endfor %}
 {%- endif %}
 
 {%- if region.get('devices', False)  %}
@@ -328,29 +397,17 @@
     - cmd: maas_login_admin
 {%- endif %}
 
-maas_domain:
-  module.run:
-  - name: maas.process_domain
-  - require:
-    - cmd: maas_login_admin
-  {%- if grains.get('kitchen-test') %}
-  - onlyif: /bin/false
-  {%- endif %}
-
-{%- if region.fabrics is defined %}
-{%- for fabric_name, fabric in region.fabrics.iteritems() %}
-{%- for vid, vlan in fabric.get('vlan',{}).items() %}
-maas_update_vlan_for_{{ fabric_name }}_{{ vid }}:
-  maasng.update_vlan:
-  - vid: {{ vid }}
-  - fabric: {{ fabric_name }}
-  - name: {{ vlan.get('name','') }}
-  - description: {{ vlan.description }}
-  - primary_rack: {{ region.maas_config.maas_name }}
-  - dhcp_on: {{ vlan.get('dhcp','False') }}
-{%- endfor %}
-{%- endfor %}
-{%- endif %}
+# FIXME
+# This function usless since broken API logic in module.
+# Should be refactored to be able work with regaiond-domain structure.
+#maas_domain:
+#  module.run:
+#  - name: maas.process_domain
+#  - require:
+#    - cmd: maas_login_admin
+#  {%- if grains.get('kitchen-test') %}
+#  - onlyif: /bin/false
+#  {%- endif %}
 
 
 {%- if region.get('sshprefs', False)  %}