blob: addcd1c212ec718fa76e543f23399614ddb12060 [file] [log] [blame]
Ales Komarek663b85c2016-03-11 14:26:42 +01001# -*- coding: utf-8 -*-
2'''
3Module for handling maas calls.
4
5:optdepends: pyapi-maas Python adapter
6:configuration: This module is not usable until the following are specified
7 either in a pillar or in the minion's config file::
8
9 maas.url: 'https://maas.domain.com/'
10 maas.token: fdsfdsdsdsfa:fsdfae3fassd:fdsfdsfsafasdfsa
11
12'''
13
14from __future__ import absolute_import
15
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010016import io
Ales Komarek663b85c2016-03-11 14:26:42 +010017import logging
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020018import collections
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010019import os.path
20import subprocess
21import urllib2
22import hashlib
Ales Komarek663b85c2016-03-11 14:26:42 +010023
smolaon27359ae2016-03-11 17:15:34 +010024import json
25
Ales Komarek663b85c2016-03-11 14:26:42 +010026LOG = logging.getLogger(__name__)
27
28# Import third party libs
29HAS_MASS = False
30try:
Damian Szelugad0ac0ac2017-03-29 15:15:33 +020031 from maas_client import MAASClient, MAASDispatcher, MAASOAuth
Ales Komarek663b85c2016-03-11 14:26:42 +010032 HAS_MASS = True
33except ImportError:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010034 LOG.exception('why??')
Ales Komarek663b85c2016-03-11 14:26:42 +010035
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020036
Ales Komarek663b85c2016-03-11 14:26:42 +010037def __virtual__():
38 '''
39 Only load this module if maas-client
40 is installed on this minion.
41 '''
42 if HAS_MASS:
43 return 'maas'
44 return False
45
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020046
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010047APIKEY_FILE = '/var/lib/maas/.maas_credentials'
48
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020049
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010050def _format_data(data):
51 class Lazy:
52 def __str__(self):
53 return ' '.join(['{0}={1}'.format(k, v)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020054 for k, v in data.iteritems()])
Krzysztof Szukiełojć52cc6b02017-04-06 09:53:43 +020055 return Lazy()
Ales Komarek663b85c2016-03-11 14:26:42 +010056
57
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010058def _create_maas_client():
59 global APIKEY_FILE
60 try:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020061 api_token = file(APIKEY_FILE).read().splitlines()[-1].strip()\
62 .split(':')
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010063 except:
64 LOG.exception('token')
65 auth = MAASOAuth(*api_token)
66 api_url = 'http://localhost:5240/MAAS'
Ales Komarek663b85c2016-03-11 14:26:42 +010067 dispatcher = MAASDispatcher()
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010068 return MAASClient(auth, dispatcher, api_url)
Ales Komarek663b85c2016-03-11 14:26:42 +010069
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020070
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010071class MaasObject(object):
72 def __init__(self):
73 self._maas = _create_maas_client()
74 self._extra_data_urls = {}
75 self._extra_data = {}
76 self._update = False
77 self._element_key = 'name'
78 self._update_key = 'id'
79
80 def send(self, data):
81 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
82 if self._update:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020083 return self._maas.put(
84 self._update_url.format(data[self._update_key]), **data).read()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +010085 if isinstance(self._create_url, tuple):
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +020086 return self._maas.post(self._create_url[0].format(**data),
87 *self._create_url[1:], **data).read()
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020088 return self._maas.post(self._create_url.format(**data),
89 None, **data).read()
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010090
91 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020092 ret = {
93 'success': [],
94 'errors': {},
95 'updated': [],
96 }
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +020097 try:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020098 config = __salt__['config.get']('maas')
99 for part in self._config_path.split('.'):
100 config = config.get(part, {})
101 extra = {}
102 for name, url_call in self._extra_data_urls.iteritems():
103 key = 'id'
104 key_name = 'name'
105 if isinstance(url_call, tuple):
106 if len(url_call) == 2:
107 url_call, key = url_call[:]
108 else:
109 url_call, key, key_name = url_call[:]
110 json_res = json.loads(self._maas.get(url_call).read())
111 if key:
112 extra[name] = {v[key_name]: v[key] for v in json_res}
113 else:
Krzysztof Szukiełojć52cc6b02017-04-06 09:53:43 +0200114 extra[name] = {v[key_name]: v for v in json_res}
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200115 if self._all_elements_url:
116 all_elements = {}
117 elements = self._maas.get(self._all_elements_url).read()
118 res_json = json.loads(elements)
119 for element in res_json:
120 if isinstance(element, (str, unicode)):
121 all_elements[element] = {}
122 else:
123 all_elements[element[self._element_key]] = element
124 else:
125 all_elements = {}
126 for name, config_data in config.iteritems():
127 self._update = False
128 try:
129 data = self.fill_data(name, config_data, **extra)
130 if data is None:
131 ret['updated'].append(name)
132 continue
133 if name in all_elements:
134 self._update = True
135 data = self.update(data, all_elements[name])
136 self.send(data)
137 ret['updated'].append(name)
138 else:
139 self.send(data)
140 ret['success'].append(name)
141 except urllib2.HTTPError as e:
142 etxt = e.read()
143 LOG.exception('Failed for object %s reason %s', name, etxt)
144 ret['errors'][name] = str(etxt)
145 except Exception as e:
146 LOG.exception('Failed for object %s reason %s', name, e)
147 ret['errors'][name] = str(e)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200148 except Exception as e:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200149 LOG.exception('Error Global')
150 raise
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100151 if ret['errors']:
152 raise Exception(ret)
153 return ret
Ales Komarek663b85c2016-03-11 14:26:42 +0100154
155
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100156class Fabric(MaasObject):
157 def __init__(self):
158 super(Fabric, self).__init__()
159 self._all_elements_url = u'api/2.0/fabrics/'
160 self._create_url = u'api/2.0/fabrics/'
161 self._update_url = u'api/2.0/fabrics/{0}/'
162 self._config_path = 'region.fabrics'
Ales Komarek663b85c2016-03-11 14:26:42 +0100163
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100164 def fill_data(self, name, fabric):
165 data = {
166 'name': name,
167 'description': fabric.get('description', ''),
168 }
169 if 'class_type' in fabric:
170 data['class_type'] = fabric.get('class_type'),
171 return data
Ales Komarek663b85c2016-03-11 14:26:42 +0100172
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100173 def update(self, new, old):
174 new['id'] = str(old['id'])
175 return new
Ales Komarek663b85c2016-03-11 14:26:42 +0100176
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200177
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100178class Subnet(MaasObject):
179 def __init__(self):
180 super(Subnet, self).__init__()
181 self._all_elements_url = u'api/2.0/subnets/'
182 self._create_url = u'api/2.0/subnets/'
183 self._update_url = u'api/2.0/subnets/{0}/'
184 self._config_path = 'region.subnets'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200185 self._extra_data_urls = {'fabrics': u'api/2.0/fabrics/'}
Ales Komarek0fafa572016-03-11 14:56:44 +0100186
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100187 def fill_data(self, name, subnet, fabrics):
188 data = {
189 'name': name,
190 'fabric': str(fabrics[subnet.get('fabric', '')]),
191 'cidr': subnet.get('cidr'),
192 'gateway_ip': subnet['gateway_ip'],
193 }
194 self._iprange = subnet['iprange']
195 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100196
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100197 def update(self, new, old):
198 new['id'] = str(old['id'])
199 return new
Ales Komarek0fafa572016-03-11 14:56:44 +0100200
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100201 def send(self, data):
202 response = super(Subnet, self).send(data)
203 res_json = json.loads(response)
204 self._process_iprange(res_json['id'])
205 return response
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100206
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100207 def _process_iprange(self, subnet_id):
208 ipranges = json.loads(self._maas.get(u'api/2.0/ipranges/').read())
209 LOG.warn('all %s ipranges %s', subnet_id, ipranges)
210 update = False
211 old_data = None
212 for iprange in ipranges:
213 if iprange['subnet']['id'] == subnet_id:
214 update = True
215 old_data = iprange
216 break
217 data = {
218 'start_ip': self._iprange.get('start'),
219 'end_ip': self._iprange.get('end'),
220 'subnet': str(subnet_id),
221 'type': self._iprange.get('type', 'dynamic')
222 }
223 LOG.warn('INFO: %s\n OLD: %s', data, old_data)
224 LOG.info('iprange %s', _format_data(data))
225 if update:
226 LOG.warn('UPDATING %s %s', data, old_data)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200227 self._maas.put(u'api/2.0/ipranges/{0}/'.format(old_data['id']),
228 **data)
smolaonc3385f82016-03-11 19:01:24 +0100229 else:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100230 self._maas.post(u'api/2.0/ipranges/', None, **data)
smolaonc3385f82016-03-11 19:01:24 +0100231
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200232
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100233class DHCPSnippet(MaasObject):
234 def __init__(self):
235 super(DHCPSnippet, self).__init__()
236 self._all_elements_url = u'api/2.0/dhcp-snippets/'
237 self._create_url = u'api/2.0/dhcp-snippets/'
238 self._update_url = u'api/2.0/dhcp-snippets/{0}/'
239 self._config_path = 'region.dhcp_snippets'
240 self._extra_data_urls = {'subnets': u'api/2.0/subnets/'}
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100241
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100242 def fill_data(self, name, snippet, subnets):
243 data = {
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200244 'name': name,
245 'value': snippet['value'],
246 'description': snippet['description'],
247 'enabled': str(snippet['enabled'] and 1 or 0),
248 'subnet': str(subnets[snippet['subnet']]),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100249 }
250 return data
smolaonc3385f82016-03-11 19:01:24 +0100251
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100252 def update(self, new, old):
253 new['id'] = str(old['id'])
254 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100255
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200256
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100257class PacketRepository(MaasObject):
258 def __init__(self):
259 super(PacketRepository, self).__init__()
260 self._all_elements_url = u'api/2.0/package-repositories/'
261 self._create_url = u'api/2.0/package-repositories/'
262 self._update_url = u'api/2.0/package-repositories/{0}/'
263 self._config_path = 'region.package_repositories'
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100264
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100265 def fill_data(self, name, package_repository):
266 data = {
267 'name': name,
268 'url': package_repository['url'],
269 'distributions': package_repository['distributions'],
270 'components': package_repository['components'],
271 'arches': package_repository['arches'],
272 'key': package_repository['key'],
273 'enabled': str(package_repository['enabled'] and 1 or 0),
274 }
275 if 'disabled_pockets' in package_repository:
276 data['disabled_pockets'] = package_repository['disable_pockets'],
277 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100278
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100279 def update(self, new, old):
280 new['id'] = str(old['id'])
281 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100282
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200283
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100284class Device(MaasObject):
285 def __init__(self):
286 super(Device, self).__init__()
287 self._all_elements_url = u'api/2.0/devices/'
288 self._create_url = u'api/2.0/devices/'
289 self._update_url = u'api/2.0/devices/{0}/'
290 self._config_path = 'region.devices'
291 self._element_key = 'hostname'
292 self._update_key = 'system_id'
smolaonc3385f82016-03-11 19:01:24 +0100293
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100294 def fill_data(self, name, device_data):
295 data = {
296 'mac_addresses': device_data['mac'],
297 'hostname': name,
298 }
299 self._interface = device_data['interface']
300 return data
301
302 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100303 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
304 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100305 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100306 LOG.info('Mac changed deleting old device %s', old['system_id'])
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100307 self._maas.delete(u'api/2.0/devices/{0}/'.format(old['system_id']))
308 else:
309 new[self._update_key] = str(old[self._update_key])
310 return new
311
312 def send(self, data):
313 response = super(Device, self).send(data)
314 resp_json = json.loads(response)
315 system_id = resp_json['system_id']
316 iface_id = resp_json['interface_set'][0]['id']
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100317 self._link_interface(system_id, iface_id)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100318 return response
319
320 def _link_interface(self, system_id, interface_id):
321 data = {
322 'mode': self._interface.get('mode', 'STATIC'),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100323 'subnet': self._interface['subnet'],
324 'ip_address': self._interface['ip_address'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100325 }
326 if 'default_gateway' in self._interface:
327 data['default_gateway'] = self._interface.get('default_gateway')
328 if self._update:
329 data['force'] = '1'
330 LOG.info('interfaces link_subnet %s %s %s', system_id, interface_id,
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200331 _format_data(data))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100332 self._maas.post(u'/api/2.0/nodes/{0}/interfaces/{1}/'
333 .format(system_id, interface_id), 'link_subnet',
334 **data)
335
336
337class Machine(MaasObject):
338 def __init__(self):
339 super(Machine, self).__init__()
340 self._all_elements_url = u'api/2.0/machines/'
341 self._create_url = u'api/2.0/machines/'
342 self._update_url = u'api/2.0/machines/{0}/'
343 self._config_path = 'region.machines'
344 self._element_key = 'hostname'
345 self._update_key = 'system_id'
346
347 def fill_data(self, name, machine_data):
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100348 power_data = machine_data['power_parameters']
349 data = {
350 'hostname': name,
351 'architecture': machine_data.get('architecture', 'amd64/generic'),
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200352 'mac_addresses': machine_data['interface']['mac'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100353 'power_type': machine_data.get('power_type', 'ipmi'),
354 'power_parameters_power_address': power_data['power_address'],
355 }
356 if 'power_user' in power_data:
357 data['power_parameters_power_user'] = power_data['power_user']
358 if 'power_password' in power_data:
359 data['power_parameters_power_pass'] = \
360 power_data['power_password']
361 return data
362
363 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100364 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
365 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100366 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100367 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200368 self._maas.delete(u'api/2.0/machines/{0}/'
369 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100370 else:
371 new[self._update_key] = str(old[self._update_key])
372 return new
373
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200374
375class AssignMachinesIP(MaasObject):
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200376 READY = 4
377
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200378 def __init__(self):
379 super(AssignMachinesIP, self).__init__()
380 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200381 self._create_url = \
382 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
383 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200384 self._config_path = 'region.machines'
385 self._element_key = 'hostname'
386 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200387 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
388 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200389
390 def fill_data(self, name, data, machines):
391 interface = data['interface']
392 machine = machines[name]
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200393 if machine['status'] != self.READY:
394 raise Exception('Not in ready state')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200395 if 'ip' not in interface:
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100396 return
397 data = {
398 'mode': 'STATIC',
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200399 'subnet': str(interface.get('subnet')),
400 'ip_address': str(interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100401 }
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200402 if 'default_gateway' in interface:
403 data['default_gateway'] = interface.get('gateway')
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100404 if self._update:
405 data['force'] = '1'
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200406 data['system_id'] = str(machine['system_id'])
407 data['interface_id'] = str(machine['interface_set'][0]['id'])
408 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100409
410
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200411class DeployMachines(MaasObject):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200412 READY = 4
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200413
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200414 def __init__(self):
415 super(DeployMachines, self).__init__()
416 self._all_elements_url = None
417 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
418 self._config_path = 'region.machines'
419 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200420 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
421 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200422
423 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200424 machine = machines[name]
425 if machine['status'] != self.READY:
426 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200427 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200428 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200429 }
430 if 'os' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200431 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200432 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200433 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200434 return data
435
436
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100437class BootResource(MaasObject):
438 def __init__(self):
439 super(BootResource, self).__init__()
440 self._all_elements_url = u'api/2.0/boot-resources/'
441 self._create_url = u'api/2.0/boot-resources/'
442 self._update_url = u'api/2.0/boot-resources/{0}/'
443 self._config_path = 'region.boot_resources'
444
445 def fill_data(self, name, boot_data):
446 sha256 = hashlib.sha256()
447 sha256.update(file(boot_data['content']).read())
448 data = {
449 'name': name,
450 'title': boot_data['title'],
451 'architecture': boot_data['architecture'],
452 'filetype': boot_data['filetype'],
453 'size': str(os.path.getsize(boot_data['content'])),
454 'sha256': sha256.hexdigest(),
455 'content': io.open(boot_data['content']),
456 }
457 return data
458
459 def update(self, new, old):
460 self._update = False
461 return new
462
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200463
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100464class CommissioningScripts(MaasObject):
465 def __init__(self):
466 super(CommissioningScripts, self).__init__()
467 self._all_elements_url = u'api/2.0/commissioning-scripts/'
468 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100469 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100470 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100471 self._update_key = 'name'
472
473 def fill_data(self, name, file_path):
474 data = {
475 'name': name,
476 'content': io.open(file_path),
477 }
478 return data
479
480 def update(self, new, old):
481 return new
482
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200483
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100484class MaasConfig(MaasObject):
485 def __init__(self):
486 super(MaasConfig, self).__init__()
487 self._all_elements_url = None
488 self._create_url = (u'api/2.0/maas/', u'set_config')
489 self._config_path = 'region.maas_config'
490
491 def fill_data(self, name, value):
492 data = {
493 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100494 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100495 }
496 return data
497
498 def update(self, new, old):
499 self._update = False
500 return new
501
502
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200503class SSHPrefs(MaasObject):
504 def __init__(self):
505 super(SSHPrefs, self).__init__()
506 self._all_elements_url = None
507 self._create_url = u'api/2.0/account/prefs/sshkeys/'
508 self._config_path = 'region.sshprefs'
509 self._element_key = 'hostname'
510 self._update_key = 'system_id'
511
512 def fill_data(self, value):
513 data = {
514 'key': value,
515 }
516 return data
517
518 def process(self):
519 config = __salt__['config.get']('maas')
520 for part in self._config_path.split('.'):
521 config = config.get(part, {})
522 extra = {}
523 for name, url_call in self._extra_data_urls.iteritems():
524 key = 'id'
525 if isinstance(url_call, tuple):
526 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200527 json_res = json.loads(self._maas.get(url_call).read())
528 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200529 if self._all_elements_url:
530 all_elements = {}
531 elements = self._maas.get(self._all_elements_url).read()
532 res_json = json.loads(elements)
533 for element in res_json:
534 if isinstance(element, (str, unicode)):
535 all_elements[element] = {}
536 else:
537 all_elements[element[self._element_key]] = element
538 else:
539 all_elements = {}
540 ret = {
541 'success': [],
542 'errors': {},
543 'updated': [],
544 }
545 for config_data in config:
546 name = config_data[:10]
547 try:
548 data = self.fill_data(config_data, **extra)
549 self.send(data)
550 ret['success'].append(name)
551 except urllib2.HTTPError as e:
552 etxt = e.read()
553 LOG.exception('Failed for object %s reason %s', name, etxt)
554 ret['errors'][name] = str(etxt)
555 except Exception as e:
556 LOG.exception('Failed for object %s reason %s', name, e)
557 ret['errors'][name] = str(e)
558 if ret['errors']:
559 raise Exception(ret)
560 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200561
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200562
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200563class Domain(MaasObject):
564 def __init__(self):
565 super(Domain, self).__init__()
566 self._all_elements_url = u'/api/2.0/domains/'
567 self._create_url = u'/api/2.0/domains/'
568 self._config_path = 'region.domain'
569 self._update_url = u'/api/2.0/domains/{0}/'
570
571 def fill_data(self, value):
572 data = {
573 'name': value,
574 }
575 self._update = True
576 return data
577
578 def update(self, new, old):
579 new['id'] = str(old['id'])
580 new['authoritative'] = str(old['authoritative'])
581 return new
582
583 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200584 ret = {
585 'success': [],
586 'errors': {},
587 'updated': [],
588 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200589 config = __salt__['config.get']('maas')
590 for part in self._config_path.split('.'):
591 config = config.get(part, {})
592 extra = {}
593 for name, url_call in self._extra_data_urls.iteritems():
594 key = 'id'
595 if isinstance(url_call, tuple):
596 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200597 json_res = json.loads(self._maas.get(url_call).read())
598 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200599 if self._all_elements_url:
600 all_elements = {}
601 elements = self._maas.get(self._all_elements_url).read()
602 res_json = json.loads(elements)
603 for element in res_json:
604 if isinstance(element, (str, unicode)):
605 all_elements[element] = {}
606 else:
607 all_elements[element[self._element_key]] = element
608 else:
609 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200610 try:
611 data = self.fill_data(config, **extra)
612 data = self.update(data, all_elements.values()[0])
613 self.send(data)
614 ret['success'].append('domain')
615 except urllib2.HTTPError as e:
616 etxt = e.read()
617 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
618 ret['errors']['domain'] = str(etxt)
619 except Exception as e:
620 LOG.exception('Failed for object %s reason %s', 'domain', e)
621 ret['errors']['domain'] = str(e)
622 if ret['errors']:
623 raise Exception(ret)
624 return ret
625
626
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200627class MachinesStatus(MaasObject):
628 @classmethod
629 def execute(cls):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200630 cls._maas = _create_maas_client()
631 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200632 json_result = json.loads(result.read())
633 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200634 status_name_dict = dict([
635 (0, 'New'), (1, 'Commissioning'), (2, 'Failed commissioning'),
636 (3, 'Missing'), (4, 'Ready'), (5, 'Reserved'), (10, 'Allocated'),
637 (9, 'Deploying'), (6, 'Deployed'), (7, 'Retired'), (8, 'Broken'),
638 (11, 'Failed deployment'), (12, 'Releasing'),
639 (13, 'Releasing failed'), (14, 'Disk erasing'),
640 (15, 'Failed disk erasing')])
641 summary = collections.Counter()
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200642 for machine in json_result:
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200643 status = status_name_dict[machine['status']]
644 summary[status] += 1
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200645 res.append({
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200646 'hostname': machine['hostname'],
647 'system_id': machine['system_id'],
648 'status': status,
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200649 })
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200650 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200651
652
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100653def process_fabrics():
654 return Fabric().process()
655
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200656
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100657def process_subnets():
658 return Subnet().process()
659
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200660
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100661def process_dhcp_snippets():
662 return DHCPSnippet().process()
663
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200664
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100665def process_package_repositories():
666 return PacketRepository().process()
667
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200668
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100669def process_devices():
670 return Device().process()
671
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200672
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100673def process_machines():
674 return Machine().process()
675
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200676
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200677def process_assign_machines_ip():
678 return AssignMachinesIP().process()
679
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200680
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200681def machines_status():
682 return MachinesStatus.execute()
683
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200684
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200685def deploy_machines():
686 return DeployMachines().process()
687
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200688
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100689def process_boot_resources():
690 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100691
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200692
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100693def process_maas_config():
694 return MaasConfig().process()
695
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200696
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100697def process_commissioning_scripts():
698 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200699
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200700
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200701def process_domain():
702 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200703
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200704
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200705def process_sshprefs():
706 return SSHPrefs().process()