blob: 3aa4e729cc853186dab47622fae54b0be5bbde51 [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ćf5062202017-04-11 12:33:36 +020091 def process(self, objects_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ćf5062202017-04-11 12:33:36 +0200149 if objects_name is not None:
150 if ',' in objects_name:
151 objects_name = objects_name.split(',')
152 else:
153 objects_name = [objects_name]
154 for object_name in objects_name:
155 process_single(object_name, config[object_name])
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200156 else:
157 for name, config_data in config.iteritems():
158 process_single(name, config_data)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200159 except Exception as e:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200160 LOG.exception('Error Global')
161 raise
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100162 if ret['errors']:
163 raise Exception(ret)
164 return ret
Ales Komarek663b85c2016-03-11 14:26:42 +0100165
166
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100167class Fabric(MaasObject):
168 def __init__(self):
169 super(Fabric, self).__init__()
170 self._all_elements_url = u'api/2.0/fabrics/'
171 self._create_url = u'api/2.0/fabrics/'
172 self._update_url = u'api/2.0/fabrics/{0}/'
173 self._config_path = 'region.fabrics'
Ales Komarek663b85c2016-03-11 14:26:42 +0100174
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100175 def fill_data(self, name, fabric):
176 data = {
177 'name': name,
178 'description': fabric.get('description', ''),
179 }
180 if 'class_type' in fabric:
181 data['class_type'] = fabric.get('class_type'),
182 return data
Ales Komarek663b85c2016-03-11 14:26:42 +0100183
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100184 def update(self, new, old):
185 new['id'] = str(old['id'])
186 return new
Ales Komarek663b85c2016-03-11 14:26:42 +0100187
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200188
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100189class Subnet(MaasObject):
190 def __init__(self):
191 super(Subnet, self).__init__()
192 self._all_elements_url = u'api/2.0/subnets/'
193 self._create_url = u'api/2.0/subnets/'
194 self._update_url = u'api/2.0/subnets/{0}/'
195 self._config_path = 'region.subnets'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200196 self._extra_data_urls = {'fabrics': u'api/2.0/fabrics/'}
Ales Komarek0fafa572016-03-11 14:56:44 +0100197
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100198 def fill_data(self, name, subnet, fabrics):
199 data = {
200 'name': name,
201 'fabric': str(fabrics[subnet.get('fabric', '')]),
202 'cidr': subnet.get('cidr'),
203 'gateway_ip': subnet['gateway_ip'],
204 }
205 self._iprange = subnet['iprange']
206 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100207
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100208 def update(self, new, old):
209 new['id'] = str(old['id'])
210 return new
Ales Komarek0fafa572016-03-11 14:56:44 +0100211
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100212 def send(self, data):
213 response = super(Subnet, self).send(data)
214 res_json = json.loads(response)
215 self._process_iprange(res_json['id'])
216 return response
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100217
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100218 def _process_iprange(self, subnet_id):
219 ipranges = json.loads(self._maas.get(u'api/2.0/ipranges/').read())
220 LOG.warn('all %s ipranges %s', subnet_id, ipranges)
221 update = False
222 old_data = None
223 for iprange in ipranges:
224 if iprange['subnet']['id'] == subnet_id:
225 update = True
226 old_data = iprange
227 break
228 data = {
229 'start_ip': self._iprange.get('start'),
230 'end_ip': self._iprange.get('end'),
231 'subnet': str(subnet_id),
232 'type': self._iprange.get('type', 'dynamic')
233 }
234 LOG.warn('INFO: %s\n OLD: %s', data, old_data)
235 LOG.info('iprange %s', _format_data(data))
236 if update:
237 LOG.warn('UPDATING %s %s', data, old_data)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200238 self._maas.put(u'api/2.0/ipranges/{0}/'.format(old_data['id']),
239 **data)
smolaonc3385f82016-03-11 19:01:24 +0100240 else:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100241 self._maas.post(u'api/2.0/ipranges/', None, **data)
smolaonc3385f82016-03-11 19:01:24 +0100242
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200243
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100244class DHCPSnippet(MaasObject):
245 def __init__(self):
246 super(DHCPSnippet, self).__init__()
247 self._all_elements_url = u'api/2.0/dhcp-snippets/'
248 self._create_url = u'api/2.0/dhcp-snippets/'
249 self._update_url = u'api/2.0/dhcp-snippets/{0}/'
250 self._config_path = 'region.dhcp_snippets'
251 self._extra_data_urls = {'subnets': u'api/2.0/subnets/'}
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100252
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100253 def fill_data(self, name, snippet, subnets):
254 data = {
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200255 'name': name,
256 'value': snippet['value'],
257 'description': snippet['description'],
258 'enabled': str(snippet['enabled'] and 1 or 0),
259 'subnet': str(subnets[snippet['subnet']]),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100260 }
261 return data
smolaonc3385f82016-03-11 19:01:24 +0100262
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100263 def update(self, new, old):
264 new['id'] = str(old['id'])
265 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100266
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200267
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100268class PacketRepository(MaasObject):
269 def __init__(self):
270 super(PacketRepository, self).__init__()
271 self._all_elements_url = u'api/2.0/package-repositories/'
272 self._create_url = u'api/2.0/package-repositories/'
273 self._update_url = u'api/2.0/package-repositories/{0}/'
274 self._config_path = 'region.package_repositories'
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100275
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100276 def fill_data(self, name, package_repository):
277 data = {
278 'name': name,
279 'url': package_repository['url'],
280 'distributions': package_repository['distributions'],
281 'components': package_repository['components'],
282 'arches': package_repository['arches'],
283 'key': package_repository['key'],
284 'enabled': str(package_repository['enabled'] and 1 or 0),
285 }
286 if 'disabled_pockets' in package_repository:
287 data['disabled_pockets'] = package_repository['disable_pockets'],
288 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100289
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100290 def update(self, new, old):
291 new['id'] = str(old['id'])
292 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100293
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200294
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100295class Device(MaasObject):
296 def __init__(self):
297 super(Device, self).__init__()
298 self._all_elements_url = u'api/2.0/devices/'
299 self._create_url = u'api/2.0/devices/'
300 self._update_url = u'api/2.0/devices/{0}/'
301 self._config_path = 'region.devices'
302 self._element_key = 'hostname'
303 self._update_key = 'system_id'
smolaonc3385f82016-03-11 19:01:24 +0100304
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100305 def fill_data(self, name, device_data):
306 data = {
307 'mac_addresses': device_data['mac'],
308 'hostname': name,
309 }
310 self._interface = device_data['interface']
311 return data
312
313 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100314 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
315 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100316 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100317 LOG.info('Mac changed deleting old device %s', old['system_id'])
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100318 self._maas.delete(u'api/2.0/devices/{0}/'.format(old['system_id']))
319 else:
320 new[self._update_key] = str(old[self._update_key])
321 return new
322
323 def send(self, data):
324 response = super(Device, self).send(data)
325 resp_json = json.loads(response)
326 system_id = resp_json['system_id']
327 iface_id = resp_json['interface_set'][0]['id']
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100328 self._link_interface(system_id, iface_id)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100329 return response
330
331 def _link_interface(self, system_id, interface_id):
332 data = {
333 'mode': self._interface.get('mode', 'STATIC'),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100334 'subnet': self._interface['subnet'],
335 'ip_address': self._interface['ip_address'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100336 }
337 if 'default_gateway' in self._interface:
338 data['default_gateway'] = self._interface.get('default_gateway')
339 if self._update:
340 data['force'] = '1'
341 LOG.info('interfaces link_subnet %s %s %s', system_id, interface_id,
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200342 _format_data(data))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100343 self._maas.post(u'/api/2.0/nodes/{0}/interfaces/{1}/'
344 .format(system_id, interface_id), 'link_subnet',
345 **data)
346
347
348class Machine(MaasObject):
349 def __init__(self):
350 super(Machine, self).__init__()
351 self._all_elements_url = u'api/2.0/machines/'
352 self._create_url = u'api/2.0/machines/'
353 self._update_url = u'api/2.0/machines/{0}/'
354 self._config_path = 'region.machines'
355 self._element_key = 'hostname'
356 self._update_key = 'system_id'
357
358 def fill_data(self, name, machine_data):
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100359 power_data = machine_data['power_parameters']
360 data = {
361 'hostname': name,
362 'architecture': machine_data.get('architecture', 'amd64/generic'),
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200363 'mac_addresses': machine_data['interface']['mac'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100364 'power_type': machine_data.get('power_type', 'ipmi'),
365 'power_parameters_power_address': power_data['power_address'],
366 }
367 if 'power_user' in power_data:
368 data['power_parameters_power_user'] = power_data['power_user']
369 if 'power_password' in power_data:
370 data['power_parameters_power_pass'] = \
371 power_data['power_password']
372 return data
373
374 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100375 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
376 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100377 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100378 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200379 self._maas.delete(u'api/2.0/machines/{0}/'
380 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100381 else:
382 new[self._update_key] = str(old[self._update_key])
383 return new
384
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200385
386class AssignMachinesIP(MaasObject):
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200387 READY = 4
388
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200389 def __init__(self):
390 super(AssignMachinesIP, self).__init__()
391 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200392 self._create_url = \
393 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
394 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200395 self._config_path = 'region.machines'
396 self._element_key = 'hostname'
397 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200398 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
399 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200400
401 def fill_data(self, name, data, machines):
402 interface = data['interface']
403 machine = machines[name]
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200404 if machine['status'] != self.READY:
405 raise Exception('Not in ready state')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200406 if 'ip' not in interface:
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100407 return
408 data = {
409 'mode': 'STATIC',
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200410 'subnet': str(interface.get('subnet')),
411 'ip_address': str(interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100412 }
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200413 if 'default_gateway' in interface:
414 data['default_gateway'] = interface.get('gateway')
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100415 if self._update:
416 data['force'] = '1'
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200417 data['system_id'] = str(machine['system_id'])
418 data['interface_id'] = str(machine['interface_set'][0]['id'])
419 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100420
421
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200422class DeployMachines(MaasObject):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200423 READY = 4
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200424
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200425 def __init__(self):
426 super(DeployMachines, self).__init__()
427 self._all_elements_url = None
428 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
429 self._config_path = 'region.machines'
430 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200431 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
432 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200433
434 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200435 machine = machines[name]
436 if machine['status'] != self.READY:
437 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200438 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200439 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200440 }
441 if 'os' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200442 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200443 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200444 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200445 return data
446
447
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100448class BootResource(MaasObject):
449 def __init__(self):
450 super(BootResource, self).__init__()
451 self._all_elements_url = u'api/2.0/boot-resources/'
452 self._create_url = u'api/2.0/boot-resources/'
453 self._update_url = u'api/2.0/boot-resources/{0}/'
454 self._config_path = 'region.boot_resources'
455
456 def fill_data(self, name, boot_data):
457 sha256 = hashlib.sha256()
458 sha256.update(file(boot_data['content']).read())
459 data = {
460 'name': name,
461 'title': boot_data['title'],
462 'architecture': boot_data['architecture'],
463 'filetype': boot_data['filetype'],
464 'size': str(os.path.getsize(boot_data['content'])),
465 'sha256': sha256.hexdigest(),
466 'content': io.open(boot_data['content']),
467 }
468 return data
469
470 def update(self, new, old):
471 self._update = False
472 return new
473
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200474
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100475class CommissioningScripts(MaasObject):
476 def __init__(self):
477 super(CommissioningScripts, self).__init__()
478 self._all_elements_url = u'api/2.0/commissioning-scripts/'
479 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100480 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100481 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100482 self._update_key = 'name'
483
484 def fill_data(self, name, file_path):
485 data = {
486 'name': name,
487 'content': io.open(file_path),
488 }
489 return data
490
491 def update(self, new, old):
492 return new
493
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200494
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100495class MaasConfig(MaasObject):
496 def __init__(self):
497 super(MaasConfig, self).__init__()
498 self._all_elements_url = None
499 self._create_url = (u'api/2.0/maas/', u'set_config')
500 self._config_path = 'region.maas_config'
501
502 def fill_data(self, name, value):
503 data = {
504 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100505 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100506 }
507 return data
508
509 def update(self, new, old):
510 self._update = False
511 return new
512
513
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200514class SSHPrefs(MaasObject):
515 def __init__(self):
516 super(SSHPrefs, self).__init__()
517 self._all_elements_url = None
518 self._create_url = u'api/2.0/account/prefs/sshkeys/'
519 self._config_path = 'region.sshprefs'
520 self._element_key = 'hostname'
521 self._update_key = 'system_id'
522
523 def fill_data(self, value):
524 data = {
525 'key': value,
526 }
527 return data
528
529 def process(self):
530 config = __salt__['config.get']('maas')
531 for part in self._config_path.split('.'):
532 config = config.get(part, {})
533 extra = {}
534 for name, url_call in self._extra_data_urls.iteritems():
535 key = 'id'
536 if isinstance(url_call, tuple):
537 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200538 json_res = json.loads(self._maas.get(url_call).read())
539 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200540 if self._all_elements_url:
541 all_elements = {}
542 elements = self._maas.get(self._all_elements_url).read()
543 res_json = json.loads(elements)
544 for element in res_json:
545 if isinstance(element, (str, unicode)):
546 all_elements[element] = {}
547 else:
548 all_elements[element[self._element_key]] = element
549 else:
550 all_elements = {}
551 ret = {
552 'success': [],
553 'errors': {},
554 'updated': [],
555 }
556 for config_data in config:
557 name = config_data[:10]
558 try:
559 data = self.fill_data(config_data, **extra)
560 self.send(data)
561 ret['success'].append(name)
562 except urllib2.HTTPError as e:
563 etxt = e.read()
564 LOG.exception('Failed for object %s reason %s', name, etxt)
565 ret['errors'][name] = str(etxt)
566 except Exception as e:
567 LOG.exception('Failed for object %s reason %s', name, e)
568 ret['errors'][name] = str(e)
569 if ret['errors']:
570 raise Exception(ret)
571 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200572
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200573
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200574class Domain(MaasObject):
575 def __init__(self):
576 super(Domain, self).__init__()
577 self._all_elements_url = u'/api/2.0/domains/'
578 self._create_url = u'/api/2.0/domains/'
579 self._config_path = 'region.domain'
580 self._update_url = u'/api/2.0/domains/{0}/'
581
582 def fill_data(self, value):
583 data = {
584 'name': value,
585 }
586 self._update = True
587 return data
588
589 def update(self, new, old):
590 new['id'] = str(old['id'])
591 new['authoritative'] = str(old['authoritative'])
592 return new
593
594 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200595 ret = {
596 'success': [],
597 'errors': {},
598 'updated': [],
599 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200600 config = __salt__['config.get']('maas')
601 for part in self._config_path.split('.'):
602 config = config.get(part, {})
603 extra = {}
604 for name, url_call in self._extra_data_urls.iteritems():
605 key = 'id'
606 if isinstance(url_call, tuple):
607 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200608 json_res = json.loads(self._maas.get(url_call).read())
609 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200610 if self._all_elements_url:
611 all_elements = {}
612 elements = self._maas.get(self._all_elements_url).read()
613 res_json = json.loads(elements)
614 for element in res_json:
615 if isinstance(element, (str, unicode)):
616 all_elements[element] = {}
617 else:
618 all_elements[element[self._element_key]] = element
619 else:
620 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200621 try:
622 data = self.fill_data(config, **extra)
623 data = self.update(data, all_elements.values()[0])
624 self.send(data)
625 ret['success'].append('domain')
626 except urllib2.HTTPError as e:
627 etxt = e.read()
628 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
629 ret['errors']['domain'] = str(etxt)
630 except Exception as e:
631 LOG.exception('Failed for object %s reason %s', 'domain', e)
632 ret['errors']['domain'] = str(e)
633 if ret['errors']:
634 raise Exception(ret)
635 return ret
636
637
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200638class MachinesStatus(MaasObject):
639 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200640 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200641 cls._maas = _create_maas_client()
642 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200643 json_result = json.loads(result.read())
644 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200645 status_name_dict = dict([
646 (0, 'New'), (1, 'Commissioning'), (2, 'Failed commissioning'),
647 (3, 'Missing'), (4, 'Ready'), (5, 'Reserved'), (10, 'Allocated'),
648 (9, 'Deploying'), (6, 'Deployed'), (7, 'Retired'), (8, 'Broken'),
649 (11, 'Failed deployment'), (12, 'Releasing'),
650 (13, 'Releasing failed'), (14, 'Disk erasing'),
651 (15, 'Failed disk erasing')])
652 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200653 if objects_name:
654 if ',' in objects_name:
655 objects_name = set(objects_name.split(','))
656 else:
657 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200658 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200659 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200660 continue
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200661 status = status_name_dict[machine['status']]
662 summary[status] += 1
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200663 res.append({
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200664 'hostname': machine['hostname'],
665 'system_id': machine['system_id'],
666 'status': status,
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200667 })
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200668 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200669
670
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100671def process_fabrics():
672 return Fabric().process()
673
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200674
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100675def process_subnets():
676 return Subnet().process()
677
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200678
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100679def process_dhcp_snippets():
680 return DHCPSnippet().process()
681
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200682
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100683def process_package_repositories():
684 return PacketRepository().process()
685
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200686
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100687def process_devices():
688 return Device().process()
689
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200690
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200691def process_machines(*args):
692 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100693
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200694
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200695def process_assign_machines_ip(*args):
696 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200697
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200698
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200699def machines_status(*args):
700 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200701
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200702
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200703def deploy_machines(*args):
704 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200705
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200706
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100707def process_boot_resources():
708 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100709
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200710
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100711def process_maas_config():
712 return MaasConfig().process()
713
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200714
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100715def process_commissioning_scripts():
716 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200717
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200718
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200719def process_domain():
720 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200721
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200722
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200723def process_sshprefs():
724 return SSHPrefs().process()