blob: 5263c2be4a91b23e7bfce90bee9ce6ee0f06078e [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
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +020091 def process(self, object_name=None):
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 = {}
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200126
127 def process_single(name, config_data):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200128 self._update = False
129 try:
130 data = self.fill_data(name, config_data, **extra)
131 if data is None:
132 ret['updated'].append(name)
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200133 return
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200134 if name in all_elements:
135 self._update = True
136 data = self.update(data, all_elements[name])
137 self.send(data)
138 ret['updated'].append(name)
139 else:
140 self.send(data)
141 ret['success'].append(name)
142 except urllib2.HTTPError as e:
143 etxt = e.read()
144 LOG.exception('Failed for object %s reason %s', name, etxt)
145 ret['errors'][name] = str(etxt)
146 except Exception as e:
147 LOG.exception('Failed for object %s reason %s', name, e)
148 ret['errors'][name] = str(e)
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200149 if object_name is not None:
150 process_single(object_name, config[object_name])
151 else:
152 for name, config_data in config.iteritems():
153 process_single(name, config_data)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200154 except Exception as e:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200155 LOG.exception('Error Global')
156 raise
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100157 if ret['errors']:
158 raise Exception(ret)
159 return ret
Ales Komarek663b85c2016-03-11 14:26:42 +0100160
161
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100162class Fabric(MaasObject):
163 def __init__(self):
164 super(Fabric, self).__init__()
165 self._all_elements_url = u'api/2.0/fabrics/'
166 self._create_url = u'api/2.0/fabrics/'
167 self._update_url = u'api/2.0/fabrics/{0}/'
168 self._config_path = 'region.fabrics'
Ales Komarek663b85c2016-03-11 14:26:42 +0100169
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100170 def fill_data(self, name, fabric):
171 data = {
172 'name': name,
173 'description': fabric.get('description', ''),
174 }
175 if 'class_type' in fabric:
176 data['class_type'] = fabric.get('class_type'),
177 return data
Ales Komarek663b85c2016-03-11 14:26:42 +0100178
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100179 def update(self, new, old):
180 new['id'] = str(old['id'])
181 return new
Ales Komarek663b85c2016-03-11 14:26:42 +0100182
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200183
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100184class Subnet(MaasObject):
185 def __init__(self):
186 super(Subnet, self).__init__()
187 self._all_elements_url = u'api/2.0/subnets/'
188 self._create_url = u'api/2.0/subnets/'
189 self._update_url = u'api/2.0/subnets/{0}/'
190 self._config_path = 'region.subnets'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200191 self._extra_data_urls = {'fabrics': u'api/2.0/fabrics/'}
Ales Komarek0fafa572016-03-11 14:56:44 +0100192
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100193 def fill_data(self, name, subnet, fabrics):
194 data = {
195 'name': name,
196 'fabric': str(fabrics[subnet.get('fabric', '')]),
197 'cidr': subnet.get('cidr'),
198 'gateway_ip': subnet['gateway_ip'],
199 }
200 self._iprange = subnet['iprange']
201 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100202
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100203 def update(self, new, old):
204 new['id'] = str(old['id'])
205 return new
Ales Komarek0fafa572016-03-11 14:56:44 +0100206
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100207 def send(self, data):
208 response = super(Subnet, self).send(data)
209 res_json = json.loads(response)
210 self._process_iprange(res_json['id'])
211 return response
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100212
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100213 def _process_iprange(self, subnet_id):
214 ipranges = json.loads(self._maas.get(u'api/2.0/ipranges/').read())
215 LOG.warn('all %s ipranges %s', subnet_id, ipranges)
216 update = False
217 old_data = None
218 for iprange in ipranges:
219 if iprange['subnet']['id'] == subnet_id:
220 update = True
221 old_data = iprange
222 break
223 data = {
224 'start_ip': self._iprange.get('start'),
225 'end_ip': self._iprange.get('end'),
226 'subnet': str(subnet_id),
227 'type': self._iprange.get('type', 'dynamic')
228 }
229 LOG.warn('INFO: %s\n OLD: %s', data, old_data)
230 LOG.info('iprange %s', _format_data(data))
231 if update:
232 LOG.warn('UPDATING %s %s', data, old_data)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200233 self._maas.put(u'api/2.0/ipranges/{0}/'.format(old_data['id']),
234 **data)
smolaonc3385f82016-03-11 19:01:24 +0100235 else:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100236 self._maas.post(u'api/2.0/ipranges/', None, **data)
smolaonc3385f82016-03-11 19:01:24 +0100237
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200238
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100239class DHCPSnippet(MaasObject):
240 def __init__(self):
241 super(DHCPSnippet, self).__init__()
242 self._all_elements_url = u'api/2.0/dhcp-snippets/'
243 self._create_url = u'api/2.0/dhcp-snippets/'
244 self._update_url = u'api/2.0/dhcp-snippets/{0}/'
245 self._config_path = 'region.dhcp_snippets'
246 self._extra_data_urls = {'subnets': u'api/2.0/subnets/'}
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100247
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100248 def fill_data(self, name, snippet, subnets):
249 data = {
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200250 'name': name,
251 'value': snippet['value'],
252 'description': snippet['description'],
253 'enabled': str(snippet['enabled'] and 1 or 0),
254 'subnet': str(subnets[snippet['subnet']]),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100255 }
256 return data
smolaonc3385f82016-03-11 19:01:24 +0100257
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100258 def update(self, new, old):
259 new['id'] = str(old['id'])
260 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100261
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200262
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100263class PacketRepository(MaasObject):
264 def __init__(self):
265 super(PacketRepository, self).__init__()
266 self._all_elements_url = u'api/2.0/package-repositories/'
267 self._create_url = u'api/2.0/package-repositories/'
268 self._update_url = u'api/2.0/package-repositories/{0}/'
269 self._config_path = 'region.package_repositories'
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100270
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100271 def fill_data(self, name, package_repository):
272 data = {
273 'name': name,
274 'url': package_repository['url'],
275 'distributions': package_repository['distributions'],
276 'components': package_repository['components'],
277 'arches': package_repository['arches'],
278 'key': package_repository['key'],
279 'enabled': str(package_repository['enabled'] and 1 or 0),
280 }
281 if 'disabled_pockets' in package_repository:
282 data['disabled_pockets'] = package_repository['disable_pockets'],
283 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100284
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100285 def update(self, new, old):
286 new['id'] = str(old['id'])
287 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100288
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200289
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100290class Device(MaasObject):
291 def __init__(self):
292 super(Device, self).__init__()
293 self._all_elements_url = u'api/2.0/devices/'
294 self._create_url = u'api/2.0/devices/'
295 self._update_url = u'api/2.0/devices/{0}/'
296 self._config_path = 'region.devices'
297 self._element_key = 'hostname'
298 self._update_key = 'system_id'
smolaonc3385f82016-03-11 19:01:24 +0100299
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100300 def fill_data(self, name, device_data):
301 data = {
302 'mac_addresses': device_data['mac'],
303 'hostname': name,
304 }
305 self._interface = device_data['interface']
306 return data
307
308 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100309 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
310 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100311 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100312 LOG.info('Mac changed deleting old device %s', old['system_id'])
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100313 self._maas.delete(u'api/2.0/devices/{0}/'.format(old['system_id']))
314 else:
315 new[self._update_key] = str(old[self._update_key])
316 return new
317
318 def send(self, data):
319 response = super(Device, self).send(data)
320 resp_json = json.loads(response)
321 system_id = resp_json['system_id']
322 iface_id = resp_json['interface_set'][0]['id']
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100323 self._link_interface(system_id, iface_id)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100324 return response
325
326 def _link_interface(self, system_id, interface_id):
327 data = {
328 'mode': self._interface.get('mode', 'STATIC'),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100329 'subnet': self._interface['subnet'],
330 'ip_address': self._interface['ip_address'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100331 }
332 if 'default_gateway' in self._interface:
333 data['default_gateway'] = self._interface.get('default_gateway')
334 if self._update:
335 data['force'] = '1'
336 LOG.info('interfaces link_subnet %s %s %s', system_id, interface_id,
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200337 _format_data(data))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100338 self._maas.post(u'/api/2.0/nodes/{0}/interfaces/{1}/'
339 .format(system_id, interface_id), 'link_subnet',
340 **data)
341
342
343class Machine(MaasObject):
344 def __init__(self):
345 super(Machine, self).__init__()
346 self._all_elements_url = u'api/2.0/machines/'
347 self._create_url = u'api/2.0/machines/'
348 self._update_url = u'api/2.0/machines/{0}/'
349 self._config_path = 'region.machines'
350 self._element_key = 'hostname'
351 self._update_key = 'system_id'
352
353 def fill_data(self, name, machine_data):
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100354 power_data = machine_data['power_parameters']
355 data = {
356 'hostname': name,
357 'architecture': machine_data.get('architecture', 'amd64/generic'),
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200358 'mac_addresses': machine_data['interface']['mac'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100359 'power_type': machine_data.get('power_type', 'ipmi'),
360 'power_parameters_power_address': power_data['power_address'],
361 }
362 if 'power_user' in power_data:
363 data['power_parameters_power_user'] = power_data['power_user']
364 if 'power_password' in power_data:
365 data['power_parameters_power_pass'] = \
366 power_data['power_password']
367 return data
368
369 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100370 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
371 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100372 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100373 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200374 self._maas.delete(u'api/2.0/machines/{0}/'
375 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100376 else:
377 new[self._update_key] = str(old[self._update_key])
378 return new
379
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200380
381class AssignMachinesIP(MaasObject):
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200382 READY = 4
383
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200384 def __init__(self):
385 super(AssignMachinesIP, self).__init__()
386 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200387 self._create_url = \
388 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
389 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200390 self._config_path = 'region.machines'
391 self._element_key = 'hostname'
392 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200393 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
394 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200395
396 def fill_data(self, name, data, machines):
397 interface = data['interface']
398 machine = machines[name]
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200399 if machine['status'] != self.READY:
400 raise Exception('Not in ready state')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200401 if 'ip' not in interface:
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100402 return
403 data = {
404 'mode': 'STATIC',
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200405 'subnet': str(interface.get('subnet')),
406 'ip_address': str(interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100407 }
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200408 if 'default_gateway' in interface:
409 data['default_gateway'] = interface.get('gateway')
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100410 if self._update:
411 data['force'] = '1'
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200412 data['system_id'] = str(machine['system_id'])
413 data['interface_id'] = str(machine['interface_set'][0]['id'])
414 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100415
416
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200417class DeployMachines(MaasObject):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200418 READY = 4
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200419
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200420 def __init__(self):
421 super(DeployMachines, self).__init__()
422 self._all_elements_url = None
423 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
424 self._config_path = 'region.machines'
425 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200426 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
427 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200428
429 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200430 machine = machines[name]
431 if machine['status'] != self.READY:
432 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200433 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200434 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200435 }
436 if 'os' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200437 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200438 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200439 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200440 return data
441
442
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100443class BootResource(MaasObject):
444 def __init__(self):
445 super(BootResource, self).__init__()
446 self._all_elements_url = u'api/2.0/boot-resources/'
447 self._create_url = u'api/2.0/boot-resources/'
448 self._update_url = u'api/2.0/boot-resources/{0}/'
449 self._config_path = 'region.boot_resources'
450
451 def fill_data(self, name, boot_data):
452 sha256 = hashlib.sha256()
453 sha256.update(file(boot_data['content']).read())
454 data = {
455 'name': name,
456 'title': boot_data['title'],
457 'architecture': boot_data['architecture'],
458 'filetype': boot_data['filetype'],
459 'size': str(os.path.getsize(boot_data['content'])),
460 'sha256': sha256.hexdigest(),
461 'content': io.open(boot_data['content']),
462 }
463 return data
464
465 def update(self, new, old):
466 self._update = False
467 return new
468
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200469
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100470class CommissioningScripts(MaasObject):
471 def __init__(self):
472 super(CommissioningScripts, self).__init__()
473 self._all_elements_url = u'api/2.0/commissioning-scripts/'
474 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100475 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100476 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100477 self._update_key = 'name'
478
479 def fill_data(self, name, file_path):
480 data = {
481 'name': name,
482 'content': io.open(file_path),
483 }
484 return data
485
486 def update(self, new, old):
487 return new
488
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200489
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100490class MaasConfig(MaasObject):
491 def __init__(self):
492 super(MaasConfig, self).__init__()
493 self._all_elements_url = None
494 self._create_url = (u'api/2.0/maas/', u'set_config')
495 self._config_path = 'region.maas_config'
496
497 def fill_data(self, name, value):
498 data = {
499 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100500 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100501 }
502 return data
503
504 def update(self, new, old):
505 self._update = False
506 return new
507
508
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200509class SSHPrefs(MaasObject):
510 def __init__(self):
511 super(SSHPrefs, self).__init__()
512 self._all_elements_url = None
513 self._create_url = u'api/2.0/account/prefs/sshkeys/'
514 self._config_path = 'region.sshprefs'
515 self._element_key = 'hostname'
516 self._update_key = 'system_id'
517
518 def fill_data(self, value):
519 data = {
520 'key': value,
521 }
522 return data
523
524 def process(self):
525 config = __salt__['config.get']('maas')
526 for part in self._config_path.split('.'):
527 config = config.get(part, {})
528 extra = {}
529 for name, url_call in self._extra_data_urls.iteritems():
530 key = 'id'
531 if isinstance(url_call, tuple):
532 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200533 json_res = json.loads(self._maas.get(url_call).read())
534 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200535 if self._all_elements_url:
536 all_elements = {}
537 elements = self._maas.get(self._all_elements_url).read()
538 res_json = json.loads(elements)
539 for element in res_json:
540 if isinstance(element, (str, unicode)):
541 all_elements[element] = {}
542 else:
543 all_elements[element[self._element_key]] = element
544 else:
545 all_elements = {}
546 ret = {
547 'success': [],
548 'errors': {},
549 'updated': [],
550 }
551 for config_data in config:
552 name = config_data[:10]
553 try:
554 data = self.fill_data(config_data, **extra)
555 self.send(data)
556 ret['success'].append(name)
557 except urllib2.HTTPError as e:
558 etxt = e.read()
559 LOG.exception('Failed for object %s reason %s', name, etxt)
560 ret['errors'][name] = str(etxt)
561 except Exception as e:
562 LOG.exception('Failed for object %s reason %s', name, e)
563 ret['errors'][name] = str(e)
564 if ret['errors']:
565 raise Exception(ret)
566 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200567
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200568
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200569class Domain(MaasObject):
570 def __init__(self):
571 super(Domain, self).__init__()
572 self._all_elements_url = u'/api/2.0/domains/'
573 self._create_url = u'/api/2.0/domains/'
574 self._config_path = 'region.domain'
575 self._update_url = u'/api/2.0/domains/{0}/'
576
577 def fill_data(self, value):
578 data = {
579 'name': value,
580 }
581 self._update = True
582 return data
583
584 def update(self, new, old):
585 new['id'] = str(old['id'])
586 new['authoritative'] = str(old['authoritative'])
587 return new
588
589 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200590 ret = {
591 'success': [],
592 'errors': {},
593 'updated': [],
594 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200595 config = __salt__['config.get']('maas')
596 for part in self._config_path.split('.'):
597 config = config.get(part, {})
598 extra = {}
599 for name, url_call in self._extra_data_urls.iteritems():
600 key = 'id'
601 if isinstance(url_call, tuple):
602 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200603 json_res = json.loads(self._maas.get(url_call).read())
604 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200605 if self._all_elements_url:
606 all_elements = {}
607 elements = self._maas.get(self._all_elements_url).read()
608 res_json = json.loads(elements)
609 for element in res_json:
610 if isinstance(element, (str, unicode)):
611 all_elements[element] = {}
612 else:
613 all_elements[element[self._element_key]] = element
614 else:
615 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200616 try:
617 data = self.fill_data(config, **extra)
618 data = self.update(data, all_elements.values()[0])
619 self.send(data)
620 ret['success'].append('domain')
621 except urllib2.HTTPError as e:
622 etxt = e.read()
623 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
624 ret['errors']['domain'] = str(etxt)
625 except Exception as e:
626 LOG.exception('Failed for object %s reason %s', 'domain', e)
627 ret['errors']['domain'] = str(e)
628 if ret['errors']:
629 raise Exception(ret)
630 return ret
631
632
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200633class MachinesStatus(MaasObject):
634 @classmethod
635 def execute(cls):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200636 cls._maas = _create_maas_client()
637 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200638 json_result = json.loads(result.read())
639 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200640 status_name_dict = dict([
641 (0, 'New'), (1, 'Commissioning'), (2, 'Failed commissioning'),
642 (3, 'Missing'), (4, 'Ready'), (5, 'Reserved'), (10, 'Allocated'),
643 (9, 'Deploying'), (6, 'Deployed'), (7, 'Retired'), (8, 'Broken'),
644 (11, 'Failed deployment'), (12, 'Releasing'),
645 (13, 'Releasing failed'), (14, 'Disk erasing'),
646 (15, 'Failed disk erasing')])
647 summary = collections.Counter()
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200648 for machine in json_result:
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200649 status = status_name_dict[machine['status']]
650 summary[status] += 1
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200651 res.append({
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200652 'hostname': machine['hostname'],
653 'system_id': machine['system_id'],
654 'status': status,
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200655 })
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200656 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200657
658
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100659def process_fabrics():
660 return Fabric().process()
661
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200662
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100663def process_subnets():
664 return Subnet().process()
665
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200666
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100667def process_dhcp_snippets():
668 return DHCPSnippet().process()
669
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200670
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100671def process_package_repositories():
672 return PacketRepository().process()
673
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200674
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100675def process_devices():
676 return Device().process()
677
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200678
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200679def process_machines(*args):
680 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100681
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200682
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200683def process_assign_machines_ip(*args):
684 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200685
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200686
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200687def machines_status(*args):
688 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200689
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200690
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200691def deploy_machines(*args):
692 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200693
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200694
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100695def process_boot_resources():
696 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100697
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200698
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100699def process_maas_config():
700 return MaasConfig().process()
701
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200702
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100703def process_commissioning_scripts():
704 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200705
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200706
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200707def process_domain():
708 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200709
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200710
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200711def process_sshprefs():
712 return SSHPrefs().process()