Extending functionality of maasng:

* machine_power_state: Check power state of a node
* list_ipaddresses: get list of reserved IPs
* reserve_ipaddress: reserve ip address in specific subnet
* release_ipaddress: release specified ip address
* list_dnsresources: get list of dns records from maas
* sync_address_pool: sync address pool from pillar to maas

  Example:

    openstack_share_node02_deploy_address: deploy_network

  would be recognized as an ip address request from deploy_network
  maasng.reserve_ipaddress openstack_share_node02_deploy_address \
      deploy_network["cidr"]

  will happen.
  Maas reservation from CIDR would be used in ext_pillar to
  back populate and overwrite 'deploy_network' with an ip address.

    salt '*' pillar.get openstack_share_node02_deploy_address

  will return IP address instead of 'deploy_network'

Change-Id: Idac2849a82e30df683df2a83824544ca5f0265f2
diff --git a/_modules/maasng.py b/_modules/maasng.py
index 0ec08f6..29a2c07 100644
--- a/_modules/maasng.py
+++ b/_modules/maasng.py
@@ -21,6 +21,7 @@
 import logging
 import time
 import urllib2
+import netaddr
 # Salt utils
 from salt.exceptions import CommandExecutionError, SaltInvocationError
 
@@ -109,6 +110,39 @@
     # TODO validation
     return list_partitions(hostname, device)[partition]["id"]
 
+def is_valid_ipv4(address):
+    """Verify that address represents a valid IPv4 address.
+    :param address: Value to verify
+    :type address: string
+    :returns: bool
+    .. versionadded:: 1.1
+    """
+    try:
+        return netaddr.valid_ipv4(address)
+    except netaddr.AddrFormatError:
+        return False
+
+def is_valid_ipv6(address):
+    """Verify that address represents a valid IPv6 address.
+    :param address: Value to verify
+    :type address: string
+    :returns: bool
+    .. versionadded:: 1.1
+    """
+    if not address:
+        return False
+
+    parts = address.rsplit("%", 1)
+    address = parts[0]
+    scope = parts[1] if len(parts) > 1 else None
+    if scope is not None and (len(scope) < 1 or len(scope) > 15):
+        return False
+
+    try:
+        return netaddr.valid_ipv6(address, netaddr.core.INET_PTON)
+    except netaddr.AddrFormatError:
+        return False
+
 # MACHINE SECTION
 
 
@@ -185,6 +219,31 @@
     result["new"] = "Machine {0} deleted".format(hostname)
     return result
 
+def machine_power_state(hostname):
+    """
+    Query the power state of a node.
+
+    :param hostname: Node hostname
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt 'maas-node' maasng.machine_power_state kvm06
+
+    """
+    result = {}
+    maas = _create_maas_client()
+    system_id = get_machine(hostname)["system_id"]
+    LOG.debug('action_machine: {}'.format(system_id))
+
+    # TODO validation
+    json_res = json.loads(maas.get(
+        u"api/2.0/machines/{0}/".format(system_id), "query_power_state").read())
+    LOG.info(json_res)
+
+    return json_res
+
 def action_machine(hostname, action, comment=None):
     """
     Send simple action (e.g. mark_broken, mark_fixed) to machine.
@@ -496,6 +555,220 @@
     return result
 
 
+def list_dnsresources():
+    """
+    List DNS resources known to MAAS.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt-call maasng.list_dnsresources
+
+    """
+    result = {}
+    res_json = []
+    maas = _create_maas_client()
+
+    # TODO validation
+    result = json.loads(maas.get(u"api/2.0/dnsresources/").read())
+    for elem in result:
+        ip_addresses = []
+        for ip in elem["ip_addresses"]:
+            ip_addresses.append(ip["ip"])
+        res_json.append(
+            {
+                "ip_addresses": ip_addresses,
+                "id": elem["id"],
+                "fqdn": elem["fqdn"],
+                "hostname": elem["fqdn"].split(".")[0]
+            }
+        )
+
+    LOG.debug(res_json)
+
+    return res_json
+
+
+def list_ipaddresses():
+    """
+    List IP addresses known to MAAS.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt-call maasng.list_ipaddresses
+
+    """
+    result = {}
+    res_json = []
+    maas = _create_maas_client()
+
+    # TODO validation
+    result = json.loads(maas.get(u"api/2.0/ipaddresses/?all").read())
+    for elem in result:
+        res_json.append(
+            {
+                "ip": elem["ip"],
+                "owner": { "username": elem["owner"]["username"] },
+                "created": elem["created"],
+                "alloc_type_name": elem["alloc_type_name"],
+                "alloc_type": elem["alloc_type"],
+                "subnet": {
+                    "id": elem["subnet"]["id"],
+                    "cidr": elem["subnet"]["cidr"],
+                    "name": elem["subnet"]["name"]
+                }
+            }
+        )
+
+    LOG.debug(res_json)
+
+    return res_json
+
+
+def reserve_ipaddress(hostname,subnet,ip=""):
+    """
+    Reserve IP address for specified hostname in specified subnet
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt-call maasng.reserve_ipaddress hostname 192.168.0.0/24 192.168.0.254
+        salt-call maasng.reserve_ipaddress hostname 192.168.0.0/24
+
+    """
+    result = {}
+    data = {}
+    maas = _create_maas_client()
+
+    data = {
+        "subnet": subnet,
+        "hostname": hostname
+    }
+
+    if ip:
+        data["ip"] = ip
+
+    # TODO validation
+    result = json.loads(maas.post(u"api/2.0/ipaddresses/", "reserve", **data).read())
+    res_json = {
+                   "created": result["created"],
+                   "type": "DNS",
+                   "hostname": hostname,
+                   "ip": result["ip"]
+               }
+
+    LOG.info(res_json)
+
+    return res_json
+
+
+def release_ipaddress(ipaddress):
+    """
+    Release an IP address that was previously reserved by the user.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt-call maasng.release_ipaddress 192.168.2.10
+
+    """
+    result = {}
+    data = {}
+    maas = _create_maas_client()
+
+    data = {
+      "ip": ipaddress
+    }
+
+    # TODO validation
+    return maas.post(u"api/2.0/ipaddresses/", "release", **data).read()
+
+
+def sync_address_pool():
+    """
+    Manage address pool for ext_pillar.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt-call maasng.sync_address_pool
+
+    """
+
+    address_pool = __pillar__["address_pool"]
+    LOG.debug("Address pool:")
+    LOG.debug(address_pool)
+
+    cluster_networks = __pillar__["cluster_networks"]
+    LOG.debug("Cluster networks:")
+    LOG.debug(cluster_networks)
+
+    dnsresources = list_dnsresources()
+    LOG.debug("DNS resources:")
+    LOG.debug(dnsresources)
+
+    machines = list_machines()
+    LOG.debug("Machines:")
+    LOG.debug(machines)
+
+    for net in address_pool:
+        if net == "external":
+            continue
+        for addr in address_pool[net]['pool']:
+            ipaddr = address_pool[net]['pool'][addr]
+            if ipaddr == "":
+                LOG.debug('Releasing IP address for: ' + addr)
+                release_required = False
+                for elem in dnsresources:
+                    if elem["hostname"] == addr:
+                        release_required = True
+                        ip_addresses = elem["ip_addresses"]
+                if release_required:
+                    for ip in ip_addresses:
+                        res = release_ipaddress(ip)
+                        LOG.debug(res)
+                else:
+                    LOG.debug('IP for ' + addr + ' already released')
+            elif is_valid_ipv6(ipaddr) or is_valid_ipv4(ipaddr):
+                LOG.debug('Ensure static IP address "' + ipaddr + '" for ' + addr)
+                reserve_required = True
+                for elem in dnsresources:
+                    if elem["hostname"] == addr:
+                        reserve_required = False
+                for elem, elemval in machines.iteritems():
+                    for iface in elemval["interface_set"]:
+                        for link in iface["links"]:
+                            if "ip_address" in link:
+                                if link["ip_address"] == ipaddr:
+                                    reserve_required = False
+                if reserve_required:
+                    res = reserve_ipaddress(addr, cluster_networks[net]['cidr'], ipaddr)
+                    reserve_required = False
+                    LOG.debug(res)
+                else:
+                    LOG.debug('Static IP address "' + ipaddr + '" for ' + addr + ' ensured')
+            else:
+                LOG.debug('Requesting IP address for' + addr)
+                reserve_required = True
+                for elem in dnsresources:
+                    if elem["hostname"] == addr:
+                        reserve_required = False
+                        ip = elem["ip_addresses"][0]
+                if reserve_required:
+                    res = reserve_ipaddress(addr, cluster_networks[net]['cidr'])
+                    LOG.debug(res)
+                else:
+                    LOG.debug(addr + " already has IP " + ip)
+
+    return True
+
+
 def delete_partition(hostname, disk, partition_name):
     """
     Delete partition on device.