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.