blob: a51ad362561c4e769697bd4da650f74b003d3353 [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):
376 def __init__(self):
377 super(AssignMachinesIP, self).__init__()
378 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200379 self._create_url = \
380 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
381 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200382 self._config_path = 'region.machines'
383 self._element_key = 'hostname'
384 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200385 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
386 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200387
388 def fill_data(self, name, data, machines):
389 interface = data['interface']
390 machine = machines[name]
391 if 'ip' not in interface:
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100392 return
393 data = {
394 'mode': 'STATIC',
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200395 'subnet': str(interface.get('subnet')),
396 'ip_address': str(interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100397 }
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200398 if 'default_gateway' in interface:
399 data['default_gateway'] = interface.get('gateway')
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100400 if self._update:
401 data['force'] = '1'
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200402 data['system_id'] = str(machine['system_id'])
403 data['interface_id'] = str(machine['interface_set'][0]['id'])
404 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100405
406
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200407class DeployMachines(MaasObject):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200408 READY = 4
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200409
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200410 def __init__(self):
411 super(DeployMachines, self).__init__()
412 self._all_elements_url = None
413 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
414 self._config_path = 'region.machines'
415 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200416 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
417 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200418
419 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200420 machine = machines[name]
421 if machine['status'] != self.READY:
422 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200423 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200424 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200425 }
426 if 'os' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200427 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200428 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200429 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200430 return data
431
432
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100433class BootResource(MaasObject):
434 def __init__(self):
435 super(BootResource, self).__init__()
436 self._all_elements_url = u'api/2.0/boot-resources/'
437 self._create_url = u'api/2.0/boot-resources/'
438 self._update_url = u'api/2.0/boot-resources/{0}/'
439 self._config_path = 'region.boot_resources'
440
441 def fill_data(self, name, boot_data):
442 sha256 = hashlib.sha256()
443 sha256.update(file(boot_data['content']).read())
444 data = {
445 'name': name,
446 'title': boot_data['title'],
447 'architecture': boot_data['architecture'],
448 'filetype': boot_data['filetype'],
449 'size': str(os.path.getsize(boot_data['content'])),
450 'sha256': sha256.hexdigest(),
451 'content': io.open(boot_data['content']),
452 }
453 return data
454
455 def update(self, new, old):
456 self._update = False
457 return new
458
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200459
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100460class CommissioningScripts(MaasObject):
461 def __init__(self):
462 super(CommissioningScripts, self).__init__()
463 self._all_elements_url = u'api/2.0/commissioning-scripts/'
464 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100465 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100466 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100467 self._update_key = 'name'
468
469 def fill_data(self, name, file_path):
470 data = {
471 'name': name,
472 'content': io.open(file_path),
473 }
474 return data
475
476 def update(self, new, old):
477 return new
478
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200479
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100480class MaasConfig(MaasObject):
481 def __init__(self):
482 super(MaasConfig, self).__init__()
483 self._all_elements_url = None
484 self._create_url = (u'api/2.0/maas/', u'set_config')
485 self._config_path = 'region.maas_config'
486
487 def fill_data(self, name, value):
488 data = {
489 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100490 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100491 }
492 return data
493
494 def update(self, new, old):
495 self._update = False
496 return new
497
498
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200499class SSHPrefs(MaasObject):
500 def __init__(self):
501 super(SSHPrefs, self).__init__()
502 self._all_elements_url = None
503 self._create_url = u'api/2.0/account/prefs/sshkeys/'
504 self._config_path = 'region.sshprefs'
505 self._element_key = 'hostname'
506 self._update_key = 'system_id'
507
508 def fill_data(self, value):
509 data = {
510 'key': value,
511 }
512 return data
513
514 def process(self):
515 config = __salt__['config.get']('maas')
516 for part in self._config_path.split('.'):
517 config = config.get(part, {})
518 extra = {}
519 for name, url_call in self._extra_data_urls.iteritems():
520 key = 'id'
521 if isinstance(url_call, tuple):
522 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200523 json_res = json.loads(self._maas.get(url_call).read())
524 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200525 if self._all_elements_url:
526 all_elements = {}
527 elements = self._maas.get(self._all_elements_url).read()
528 res_json = json.loads(elements)
529 for element in res_json:
530 if isinstance(element, (str, unicode)):
531 all_elements[element] = {}
532 else:
533 all_elements[element[self._element_key]] = element
534 else:
535 all_elements = {}
536 ret = {
537 'success': [],
538 'errors': {},
539 'updated': [],
540 }
541 for config_data in config:
542 name = config_data[:10]
543 try:
544 data = self.fill_data(config_data, **extra)
545 self.send(data)
546 ret['success'].append(name)
547 except urllib2.HTTPError as e:
548 etxt = e.read()
549 LOG.exception('Failed for object %s reason %s', name, etxt)
550 ret['errors'][name] = str(etxt)
551 except Exception as e:
552 LOG.exception('Failed for object %s reason %s', name, e)
553 ret['errors'][name] = str(e)
554 if ret['errors']:
555 raise Exception(ret)
556 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200557
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200558
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200559class Domain(MaasObject):
560 def __init__(self):
561 super(Domain, self).__init__()
562 self._all_elements_url = u'/api/2.0/domains/'
563 self._create_url = u'/api/2.0/domains/'
564 self._config_path = 'region.domain'
565 self._update_url = u'/api/2.0/domains/{0}/'
566
567 def fill_data(self, value):
568 data = {
569 'name': value,
570 }
571 self._update = True
572 return data
573
574 def update(self, new, old):
575 new['id'] = str(old['id'])
576 new['authoritative'] = str(old['authoritative'])
577 return new
578
579 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200580 ret = {
581 'success': [],
582 'errors': {},
583 'updated': [],
584 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200585 config = __salt__['config.get']('maas')
586 for part in self._config_path.split('.'):
587 config = config.get(part, {})
588 extra = {}
589 for name, url_call in self._extra_data_urls.iteritems():
590 key = 'id'
591 if isinstance(url_call, tuple):
592 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200593 json_res = json.loads(self._maas.get(url_call).read())
594 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200595 if self._all_elements_url:
596 all_elements = {}
597 elements = self._maas.get(self._all_elements_url).read()
598 res_json = json.loads(elements)
599 for element in res_json:
600 if isinstance(element, (str, unicode)):
601 all_elements[element] = {}
602 else:
603 all_elements[element[self._element_key]] = element
604 else:
605 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200606 try:
607 data = self.fill_data(config, **extra)
608 data = self.update(data, all_elements.values()[0])
609 self.send(data)
610 ret['success'].append('domain')
611 except urllib2.HTTPError as e:
612 etxt = e.read()
613 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
614 ret['errors']['domain'] = str(etxt)
615 except Exception as e:
616 LOG.exception('Failed for object %s reason %s', 'domain', e)
617 ret['errors']['domain'] = str(e)
618 if ret['errors']:
619 raise Exception(ret)
620 return ret
621
622
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200623class MachinesStatus(MaasObject):
624 @classmethod
625 def execute(cls):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200626 cls._maas = _create_maas_client()
627 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200628 json_result = json.loads(result.read())
629 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200630 status_name_dict = dict([
631 (0, 'New'), (1, 'Commissioning'), (2, 'Failed commissioning'),
632 (3, 'Missing'), (4, 'Ready'), (5, 'Reserved'), (10, 'Allocated'),
633 (9, 'Deploying'), (6, 'Deployed'), (7, 'Retired'), (8, 'Broken'),
634 (11, 'Failed deployment'), (12, 'Releasing'),
635 (13, 'Releasing failed'), (14, 'Disk erasing'),
636 (15, 'Failed disk erasing')])
637 summary = collections.Counter()
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200638 for machine in json_result:
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200639 status = status_name_dict[machine['status']]
640 summary[status] += 1
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200641 res.append({
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200642 'hostname': machine['hostname'],
643 'system_id': machine['system_id'],
644 'status': status,
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200645 })
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200646 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200647
648
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100649def process_fabrics():
650 return Fabric().process()
651
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200652
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100653def process_subnets():
654 return Subnet().process()
655
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200656
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100657def process_dhcp_snippets():
658 return DHCPSnippet().process()
659
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200660
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100661def process_package_repositories():
662 return PacketRepository().process()
663
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200664
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100665def process_devices():
666 return Device().process()
667
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200668
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100669def process_machines():
670 return Machine().process()
671
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200672
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200673def process_assign_machines_ip():
674 return AssignMachinesIP().process()
675
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200676
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200677def machines_status():
678 return MachinesStatus.execute()
679
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200680
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200681def deploy_machines():
682 return DeployMachines().process()
683
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200684
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100685def process_boot_resources():
686 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100687
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200688
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100689def process_maas_config():
690 return MaasConfig().process()
691
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200692
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100693def process_commissioning_scripts():
694 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200695
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200696
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200697def process_domain():
698 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200699
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200700
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200701def process_sshprefs():
702 return SSHPrefs().process()