Refactoring maas functions to use config
Use config instead of args.
Adding
* process_fabrics
* process_subnets
* process_devices
* process_machines
* process_dhcp_snippets
* process_boot_resources
* process_package_repositories
diff --git a/_modules/maas.py b/_modules/maas.py
index 83e6b2a..b84b99f 100644
--- a/_modules/maas.py
+++ b/_modules/maas.py
@@ -13,8 +13,12 @@
from __future__ import absolute_import
+import io
import logging
-import os
+import os.path
+import subprocess
+import urllib2
+import hashlib
import json
@@ -26,8 +30,7 @@
from apiclient.maas_client import MAASClient, MAASDispatcher, MAASOAuth
HAS_MASS = True
except ImportError:
- pass
-
+ LOG.exception('why??')
def __virtual__():
'''
@@ -38,166 +41,343 @@
return 'maas'
return False
-__opts__ = {}
+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 _auth(**connection_args):
- '''
- Set up maas credentials
-
- Only intended to be used within maas-enabled modules
- '''
-
- prefix = "maas."
-
- # look in connection_args first, then default to config file
- def get(key, default=None):
- return connection_args.get('connection_' + key,
- __salt__['config.get'](prefix + key, default))
-
- api_token = get('token')
- api_url = get('url', 'https://localhost/')
-
- LOG.debug("MAAS url: " + api_url)
- LOG.debug("MAAS token: " + api_token)
- auth = MAASOAuth(*api_token.split(":"))
+def _create_maas_client():
+ global APIKEY_FILE
+ try:
+ api_token = file(APIKEY_FILE).read().strip().split(':')
+ except:
+ LOG.exception('token')
+ auth = MAASOAuth(*api_token)
+ api_url = 'http://localhost:5240/MAAS'
dispatcher = MAASDispatcher()
- client = MAASClient(auth, dispatcher, api_url)
+ return MAASClient(auth, dispatcher, api_url)
- return client
+class MaasObject(object):
+ def __init__(self):
+ self._maas = _create_maas_client()
+ self._extra_data_urls = {}
+ self._extra_data = {}
+ self._update = False
+ self._element_key = 'name'
+ self._update_key = 'id'
+
+ def send(self, data):
+ LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
+ if self._update:
+ return self._maas.put(self._update_url.format(data[self._update_key]), **data).read()
+ return self._maas.post(self._create_url, None, **data).read()
+
+ def process(self):
+ config = __salt__['config.get']('maas')
+ for part in self._config_path.split('.'):
+ config = config.get(part, {})
+ extra = {}
+ for name, url_call in self._extra_data_urls.iteritems():
+ key = 'id'
+ if isinstance(url_call, tuple):
+ url_call, key = url_call[:]
+ extra[name] = {v['name']: v[key] for v in
+ json.loads(self._maas.get(url_call).read())}
+ elements = self._maas.get(self._all_elements_url).read()
+ all_elements = {v[self._element_key]: v for v in json.loads(elements)}
+ ret = {
+ 'success': [],
+ 'errors': {},
+ 'updated': [],
+ }
+ for name, config_data in config.iteritems():
+ try:
+ data = self.fill_data(name, config_data, **extra)
+ if name in all_elements:
+ self._update = True
+ LOG.error('%s DATA %s', all_elements[name], data)
+ data = self.update(data, all_elements[name])
+ self.send(data)
+ ret['updated'].append(name)
+ else:
+ self.send(data)
+ ret['success'].append(name)
+ except urllib2.HTTPError as e:
+ etxt = e.read()
+ LOG.exception('Failed for object %s reason %s', name, etxt)
+ ret['errors'][name] = str(etxt)
+ except Exception as e:
+ LOG.exception('Failed for object %s reason %s', name, e)
+ ret['errors'][name] = str(e)
+ if ret['errors']:
+ raise Exception(ret)
+ return ret
-def cluster_get(cluster_name=None, **connection_args):
- '''
- Return a specific cluster
+class Fabric(MaasObject):
+ def __init__(self):
+ super(Fabric, self).__init__()
+ self._all_elements_url = u'api/2.0/fabrics/'
+ self._create_url = u'api/2.0/fabrics/'
+ self._update_url = u'api/2.0/fabrics/{0}/'
+ self._config_path = 'region.fabrics'
+# self._update_keys = ['name', 'description', 'class_type', 'id']
- CLI Example:
+ def fill_data(self, name, fabric):
+ data = {
+ 'name': name,
+ 'description': fabric.get('description', ''),
+ }
+ if 'class_type' in fabric:
+ data['class_type'] = fabric.get('class_type'),
+ return data
- .. code-block:: bash
+ def update(self, new, old):
+ new['id'] = str(old['id'])
+ return new
- salt '*' maas.cluster_get cluster
- '''
- maas = _auth(**connection_args)
+class Subnet(MaasObject):
+ def __init__(self):
+ super(Subnet, self).__init__()
+ self._all_elements_url = u'api/2.0/subnets/'
+ self._create_url = u'api/2.0/subnets/'
+ self._update_url = u'api/2.0/subnets/{0}/'
+ self._config_path = 'region.subnets'
+ self._extra_data_urls = {'fabrics':u'api/2.0/fabrics/'}
- response = maas.get(u"nodegroups/", "list").read()
- LOG.debug("Response: " + response)
+ def fill_data(self, name, subnet, fabrics):
+ data = {
+ 'name': name,
+ 'fabric': str(fabrics[subnet.get('fabric', '')]),
+ 'cidr': subnet.get('cidr'),
+ 'gateway_ip': subnet['gateway_ip'],
+ }
+ self._iprange = subnet['iprange']
+ return data
- object_list = json.loads(response)
+ def update(self, new, old):
+ new['id'] = str(old['id'])
+ return new
+ def send(self, data):
+ response = super(Subnet, self).send(data)
+ res_json = json.loads(response)
+ self._process_iprange(res_json['id'])
+ return response
- for cluster in object_list:
- if cluster.get('cluster_name') == cluster_name:
- return {cluster.get('cluster_name'): cluster}
- return {'Error': 'Could not find specified cluster'}
-
-
-def cluster_list(**connection_args):
- '''
- Return a list of MAAS clusters
-
- CLI Example:
-
- .. code-block:: bash
-
- salt '*' maas.cluster_list
- '''
- maas = _auth(**connection_args)
- ret = {}
-
- response = maas.get(u"nodegroups/", "list").read()
-
- LOG.debug("Clusters in maas: " + response )
-
- object_list = json.loads(response)
-
- for cluster in object_list:
- ret[cluster.get('cluster_name')] = cluster
- return ret
-
-
-def cluster_create(cluster_name=None, **connection_args):
- '''
- Create MAAS cluster
-
- CLI Examples:
-
- .. code-block:: bash
-
- salt '*' maas.cluster_create cluster
- '''
- maas = auth(**connection_args)
- if project_name:
- project = _get_project(maas, project_name)
- else:
- project = _get_project_by_id(maas, project_id)
- if not project:
- return {'Error': 'Unable to resolve project'}
- create = True
- for cluster in maas.getprojectclusters(project.get('id')):
- if cluster.get('url') == cluster_url:
- create = False
- if create:
- maas.addprojectcluster(project['id'], cluster_url)
- return cluster_get(cluster_url, project_id=project['id'])
-
-
-def cluster_delete(cluster_name=None, **connection_args):
- '''
- Delete MAAS cluster
-
- CLI Examples:
-
- .. code-block:: bash
-
- salt '*' maas.cluster_delete 'https://cluster.url/' project_id=300
- '''
- maas = _auth(**connection_args)
- project = _get_project(maas, project_name)
-
- for cluster in maas.getprojectclusters(project.get('id')):
- if cluster.get('url') == cluster_url:
- return maas.deleteprojectcluster(project['id'], cluster['id'])
- return {'Error': 'Could not find cluster'}
-
-
-def cluster_update(cluster_id=None, old_cluster_name=None, new_cluster_name=None, domain=None, status=None, **connection_args):
- '''
- Update information in specific MAAS cluster
-
- CLI Examples:
-
- .. code-block:: bash
-
- salt '*' maas.cluster_update cluster_id cluster_name dns_name status
- '''
- maas = _auth(**connection_args)
-
- cluster = {}
-
- if not cluster_id and old_cluster_name:
- cluster = cluster_get(old_cluster_name)
- if cluster.get("Error"):
- return cluster
+ def _process_iprange(self, subnet_id):
+ ipranges = json.loads(self._maas.get(u'api/2.0/ipranges/').read())
+ LOG.warn('all %s ipranges %s', subnet_id, ipranges)
+ update = False
+ old_data = None
+ for iprange in ipranges:
+ if iprange['subnet']['id'] == subnet_id:
+ update = True
+ old_data = iprange
+ break
+ data = {
+ 'start_ip': self._iprange.get('start'),
+ 'end_ip': self._iprange.get('end'),
+ 'subnet': str(subnet_id),
+ 'type': self._iprange.get('type', 'dynamic')
+ }
+ LOG.warn('INFO: %s\n OLD: %s', data, old_data)
+ LOG.info('iprange %s', _format_data(data))
+ if update:
+ LOG.warn('UPDATING %s %s', data, old_data)
+ self._maas.put(u'api/2.0/ipranges/{0}/'.format(old_data['id']), **data)
else:
- cluster_id = cluster_get(old_cluster_name).get(old_cluster_name).get("uuid")
+ self._maas.post(u'api/2.0/ipranges/', None, **data)
- else:
- return {'Error': 'No cluster id or name specified'}
+class DHCPSnippet(MaasObject):
+ def __init__(self):
+ super(DHCPSnippet, self).__init__()
+ self._all_elements_url = u'api/2.0/dhcp-snippets/'
+ self._create_url = u'api/2.0/dhcp-snippets/'
+ self._update_url = u'api/2.0/dhcp-snippets/{0}/'
+ self._config_path = 'region.dhcp_snippets'
+ self._extra_data_urls = {'subnets': u'api/2.0/subnets/'}
- if new_cluster_name:
- cluster["cluster_name"] = new_cluster_name
+ def fill_data(self, name, snippet, subnets):
+ data = {
+ 'name': name,
+ 'value': snippet['value'],
+ 'description': snippet['description'],
+ 'enabled': str(snippet['enabled'] and 1 or 0),
+ 'subnet': str(subnets[snippet['subnet']]),
+ }
+ return data
- if domain:
- cluster["name"] = domain
+ def update(self, new, old):
+ new['id'] = str(old['id'])
+ return new
- if status:
- cluster["status"] = status
+class PacketRepository(MaasObject):
+ def __init__(self):
+ super(PacketRepository, self).__init__()
+ self._all_elements_url = u'api/2.0/package-repositories/'
+ self._create_url = u'api/2.0/package-repositories/'
+ self._update_url = u'api/2.0/package-repositories/{0}/'
+ self._config_path = 'region.package_repositories'
- LOG.debug("Cluster id: " + cluster_id)
- LOG.debug("New cluster info: " + str(cluster))
+ def fill_data(self, name, package_repository):
+ data = {
+ 'name': name,
+ 'url': package_repository['url'],
+ 'distributions': package_repository['distributions'],
+ 'components': package_repository['components'],
+ 'arches': package_repository['arches'],
+ 'key': package_repository['key'],
+ 'enabled': str(package_repository['enabled'] and 1 or 0),
+ }
+ if 'disabled_pockets' in package_repository:
+ data['disabled_pockets'] = package_repository['disable_pockets'],
+ return data
- response = maas.put(u"nodegroups/" + cluster_id + "/", **cluster)
+ def update(self, new, old):
+ new['id'] = str(old['id'])
+ return new
- #TODO check response status
- return {'Status': True}
+class Device(MaasObject):
+ def __init__(self):
+ super(Device, self).__init__()
+ self._all_elements_url = u'api/2.0/devices/'
+ self._create_url = u'api/2.0/devices/'
+ self._update_url = u'api/2.0/devices/{0}/'
+ self._config_path = 'region.devices'
+ self._element_key = 'hostname'
+ self._update_key = 'system_id'
+ def fill_data(self, name, device_data):
+ data = {
+ 'mac_addresses': device_data['mac'],
+ 'hostname': name,
+ }
+ self._interface = device_data['interface']
+ return data
+
+ def update(self, new, old):
+ new_macs = set(new['mac_addresses'])
+ old_macs = set(v['mac_address'] for v in old[interface_set])
+ if new_macs - old_macs:
+ self._update = False
+ self._maas.delete(u'api/2.0/devices/{0}/'.format(old['system_id']))
+ else:
+ new[self._update_key] = str(old[self._update_key])
+ return new
+
+ def send(self, data):
+ response = super(Device, self).send(data)
+ resp_json = json.loads(response)
+ system_id = resp_json['system_id']
+ iface_id = resp_json['interface_set'][0]['id']
+ self._link_interface(maas, system_id, iface_id)
+ return response
+
+ def _link_interface(self, system_id, interface_id):
+ data = {
+ 'mode': self._interface.get('mode', 'STATIC'),
+ 'subnet': self._interface.get('subnet'),
+ 'ip_address': self._interface.get('ip_address'),
+ }
+ if 'default_gateway' in self._interface:
+ data['default_gateway'] = self._interface.get('default_gateway')
+ if self._update:
+ data['force'] = '1'
+ LOG.info('interfaces link_subnet %s %s %s', system_id, interface_id,
+ _format_data(data))
+ self._maas.post(u'/api/2.0/nodes/{0}/interfaces/{1}/'
+ .format(system_id, interface_id), 'link_subnet',
+ **data)
+
+
+class Machine(MaasObject):
+ def __init__(self):
+ super(Machine, self).__init__()
+ self._all_elements_url = u'api/2.0/machines/'
+ self._create_url = u'api/2.0/machines/'
+ self._update_url = u'api/2.0/machines/{0}/'
+ self._config_path = 'region.machines'
+ self._element_key = 'hostname'
+ self._update_key = 'system_id'
+
+ def fill_data(self, name, machine_data):
+ main_interface = next(machine_data['interfaces'][0].iteritems())
+ interfaces = machine_data['interfaces'][1:]
+ power_data = machine_data['power_parameters']
+ data = {
+ 'hostname': name,
+ 'architecture': machine_data.get('architecture', 'amd64/generic'),
+ 'mac_addresses': main_interface[1],
+ 'power_type': machine_data.get('power_type', 'ipmi'),
+ 'power_parameters_power_address': power_data['power_address'],
+ }
+ if 'power_user' in power_data:
+ data['power_parameters_power_user'] = power_data['power_user']
+ if 'power_password' in power_data:
+ data['power_parameters_power_pass'] = \
+ power_data['power_password']
+ return data
+
+ def update(self, new, old):
+ new_macs = set(new['mac_addresses'])
+ old_macs = set(v['mac_address'] for v in old[interface_set])
+ if new_macs - old_macs:
+ self._update = False
+ self._maas.delete(u'api/2.0/machiens/{0}/'.format(old['system_id']))
+ else:
+ new[self._update_key] = str(old[self._update_key])
+ return new
+
+class BootResource(MaasObject):
+ def __init__(self):
+ super(BootResource, self).__init__()
+ self._all_elements_url = u'api/2.0/boot-resources/'
+ self._create_url = u'api/2.0/boot-resources/'
+ self._update_url = u'api/2.0/boot-resources/{0}/'
+ self._config_path = 'region.boot_resources'
+
+ def fill_data(self, name, boot_data):
+ sha256 = hashlib.sha256()
+ sha256.update(file(boot_data['content']).read())
+ data = {
+ 'name': name,
+ 'title': boot_data['title'],
+ 'architecture': boot_data['architecture'],
+ 'filetype': boot_data['filetype'],
+ 'size': str(os.path.getsize(boot_data['content'])),
+ 'sha256': sha256.hexdigest(),
+ 'content': io.open(boot_data['content']),
+ }
+ return data
+
+ def update(self, new, old):
+ self._update = False
+ return new
+
+def process_fabrics():
+ return Fabric().process()
+
+def process_subnets():
+ return Subnet().process()
+
+def process_dhcp_snippets():
+ return DHCPSnippet().process()
+
+def process_package_repositories():
+ return PacketRepository().process()
+
+def process_devices():
+ return Device().process()
+
+def process_machines():
+ return Machine().process()
+
+def process_boot_resources():
+ return BootResource().process()