blob: 63fb0985927b27428e26176a65430ab6e3bf0fb9 [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:
Damian Szelugaa4004212017-05-17 15:43:14 +020034 LOG.debug('Missing python-oauth module. Skipping')
35 pass
Ales Komarek663b85c2016-03-11 14:26:42 +010036
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020037
Ales Komarek663b85c2016-03-11 14:26:42 +010038def __virtual__():
39 '''
40 Only load this module if maas-client
41 is installed on this minion.
42 '''
43 if HAS_MASS:
44 return 'maas'
45 return False
46
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020047
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010048APIKEY_FILE = '/var/lib/maas/.maas_credentials'
49
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020050
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010051def _format_data(data):
52 class Lazy:
53 def __str__(self):
54 return ' '.join(['{0}={1}'.format(k, v)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020055 for k, v in data.iteritems()])
Krzysztof Szukiełojć52cc6b02017-04-06 09:53:43 +020056 return Lazy()
Ales Komarek663b85c2016-03-11 14:26:42 +010057
58
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010059def _create_maas_client():
60 global APIKEY_FILE
61 try:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020062 api_token = file(APIKEY_FILE).read().splitlines()[-1].strip()\
63 .split(':')
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010064 except:
65 LOG.exception('token')
66 auth = MAASOAuth(*api_token)
67 api_url = 'http://localhost:5240/MAAS'
Ales Komarek663b85c2016-03-11 14:26:42 +010068 dispatcher = MAASDispatcher()
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010069 return MAASClient(auth, dispatcher, api_url)
Ales Komarek663b85c2016-03-11 14:26:42 +010070
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020071
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010072class MaasObject(object):
73 def __init__(self):
74 self._maas = _create_maas_client()
75 self._extra_data_urls = {}
76 self._extra_data = {}
77 self._update = False
78 self._element_key = 'name'
79 self._update_key = 'id'
80
81 def send(self, data):
82 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
83 if self._update:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020084 return self._maas.put(
85 self._update_url.format(data[self._update_key]), **data).read()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +010086 if isinstance(self._create_url, tuple):
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +020087 return self._maas.post(self._create_url[0].format(**data),
88 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć76d9a5c2017-04-14 12:00:42 +020089 return self._maas.post(self._create_url.format(**data),
90 None, **data).read()
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010091
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +020092 def process(self, objects_name=None):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020093 ret = {
94 'success': [],
95 'errors': {},
96 'updated': [],
97 }
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +020098 try:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020099 config = __salt__['config.get']('maas')
100 for part in self._config_path.split('.'):
101 config = config.get(part, {})
102 extra = {}
103 for name, url_call in self._extra_data_urls.iteritems():
104 key = 'id'
105 key_name = 'name'
106 if isinstance(url_call, tuple):
107 if len(url_call) == 2:
108 url_call, key = url_call[:]
109 else:
110 url_call, key, key_name = url_call[:]
111 json_res = json.loads(self._maas.get(url_call).read())
112 if key:
113 extra[name] = {v[key_name]: v[key] for v in json_res}
114 else:
Krzysztof Szukiełojć52cc6b02017-04-06 09:53:43 +0200115 extra[name] = {v[key_name]: v for v in json_res}
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200116 if self._all_elements_url:
117 all_elements = {}
118 elements = self._maas.get(self._all_elements_url).read()
119 res_json = json.loads(elements)
120 for element in res_json:
121 if isinstance(element, (str, unicode)):
122 all_elements[element] = {}
123 else:
124 all_elements[element[self._element_key]] = element
125 else:
126 all_elements = {}
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200127
128 def process_single(name, config_data):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200129 self._update = False
130 try:
131 data = self.fill_data(name, config_data, **extra)
132 if data is None:
133 ret['updated'].append(name)
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200134 return
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200135 if name in all_elements:
136 self._update = True
137 data = self.update(data, all_elements[name])
138 self.send(data)
139 ret['updated'].append(name)
140 else:
141 self.send(data)
142 ret['success'].append(name)
143 except urllib2.HTTPError as e:
144 etxt = e.read()
Krzysztof Szukiełojć199d5af2017-04-12 13:23:10 +0200145 LOG.error('Failed for object %s reason %s', name, etxt)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200146 ret['errors'][name] = str(etxt)
147 except Exception as e:
Krzysztof Szukiełojć199d5af2017-04-12 13:23:10 +0200148 LOG.error('Failed for object %s reason %s', name, e)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200149 ret['errors'][name] = str(e)
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200150 if objects_name is not None:
151 if ',' in objects_name:
152 objects_name = objects_name.split(',')
153 else:
154 objects_name = [objects_name]
155 for object_name in objects_name:
156 process_single(object_name, config[object_name])
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200157 else:
158 for name, config_data in config.iteritems():
159 process_single(name, config_data)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200160 except Exception as e:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200161 LOG.exception('Error Global')
162 raise
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100163 if ret['errors']:
164 raise Exception(ret)
165 return ret
Ales Komarek663b85c2016-03-11 14:26:42 +0100166
167
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100168class Fabric(MaasObject):
169 def __init__(self):
170 super(Fabric, self).__init__()
171 self._all_elements_url = u'api/2.0/fabrics/'
172 self._create_url = u'api/2.0/fabrics/'
173 self._update_url = u'api/2.0/fabrics/{0}/'
174 self._config_path = 'region.fabrics'
Ales Komarek663b85c2016-03-11 14:26:42 +0100175
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100176 def fill_data(self, name, fabric):
177 data = {
178 'name': name,
179 'description': fabric.get('description', ''),
180 }
181 if 'class_type' in fabric:
182 data['class_type'] = fabric.get('class_type'),
183 return data
Ales Komarek663b85c2016-03-11 14:26:42 +0100184
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100185 def update(self, new, old):
186 new['id'] = str(old['id'])
187 return new
Ales Komarek663b85c2016-03-11 14:26:42 +0100188
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200189
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100190class Subnet(MaasObject):
191 def __init__(self):
192 super(Subnet, self).__init__()
193 self._all_elements_url = u'api/2.0/subnets/'
194 self._create_url = u'api/2.0/subnets/'
195 self._update_url = u'api/2.0/subnets/{0}/'
196 self._config_path = 'region.subnets'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200197 self._extra_data_urls = {'fabrics': u'api/2.0/fabrics/'}
Ales Komarek0fafa572016-03-11 14:56:44 +0100198
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100199 def fill_data(self, name, subnet, fabrics):
200 data = {
201 'name': name,
202 'fabric': str(fabrics[subnet.get('fabric', '')]),
203 'cidr': subnet.get('cidr'),
204 'gateway_ip': subnet['gateway_ip'],
205 }
206 self._iprange = subnet['iprange']
207 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100208
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100209 def update(self, new, old):
210 new['id'] = str(old['id'])
211 return new
Ales Komarek0fafa572016-03-11 14:56:44 +0100212
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100213 def send(self, data):
214 response = super(Subnet, self).send(data)
215 res_json = json.loads(response)
216 self._process_iprange(res_json['id'])
217 return response
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100218
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100219 def _process_iprange(self, subnet_id):
220 ipranges = json.loads(self._maas.get(u'api/2.0/ipranges/').read())
221 LOG.warn('all %s ipranges %s', subnet_id, ipranges)
222 update = False
223 old_data = None
224 for iprange in ipranges:
225 if iprange['subnet']['id'] == subnet_id:
226 update = True
227 old_data = iprange
228 break
229 data = {
230 'start_ip': self._iprange.get('start'),
231 'end_ip': self._iprange.get('end'),
232 'subnet': str(subnet_id),
233 'type': self._iprange.get('type', 'dynamic')
234 }
235 LOG.warn('INFO: %s\n OLD: %s', data, old_data)
236 LOG.info('iprange %s', _format_data(data))
237 if update:
238 LOG.warn('UPDATING %s %s', data, old_data)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200239 self._maas.put(u'api/2.0/ipranges/{0}/'.format(old_data['id']),
240 **data)
smolaonc3385f82016-03-11 19:01:24 +0100241 else:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100242 self._maas.post(u'api/2.0/ipranges/', None, **data)
smolaonc3385f82016-03-11 19:01:24 +0100243
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200244
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100245class DHCPSnippet(MaasObject):
246 def __init__(self):
247 super(DHCPSnippet, self).__init__()
248 self._all_elements_url = u'api/2.0/dhcp-snippets/'
249 self._create_url = u'api/2.0/dhcp-snippets/'
250 self._update_url = u'api/2.0/dhcp-snippets/{0}/'
251 self._config_path = 'region.dhcp_snippets'
252 self._extra_data_urls = {'subnets': u'api/2.0/subnets/'}
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100253
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100254 def fill_data(self, name, snippet, subnets):
255 data = {
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200256 'name': name,
257 'value': snippet['value'],
258 'description': snippet['description'],
259 'enabled': str(snippet['enabled'] and 1 or 0),
260 'subnet': str(subnets[snippet['subnet']]),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100261 }
262 return data
smolaonc3385f82016-03-11 19:01:24 +0100263
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100264 def update(self, new, old):
265 new['id'] = str(old['id'])
266 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100267
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200268
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100269class PacketRepository(MaasObject):
270 def __init__(self):
271 super(PacketRepository, self).__init__()
272 self._all_elements_url = u'api/2.0/package-repositories/'
273 self._create_url = u'api/2.0/package-repositories/'
274 self._update_url = u'api/2.0/package-repositories/{0}/'
275 self._config_path = 'region.package_repositories'
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100276
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100277 def fill_data(self, name, package_repository):
278 data = {
279 'name': name,
280 'url': package_repository['url'],
281 'distributions': package_repository['distributions'],
282 'components': package_repository['components'],
283 'arches': package_repository['arches'],
284 'key': package_repository['key'],
285 'enabled': str(package_repository['enabled'] and 1 or 0),
286 }
287 if 'disabled_pockets' in package_repository:
288 data['disabled_pockets'] = package_repository['disable_pockets'],
289 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100290
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100291 def update(self, new, old):
292 new['id'] = str(old['id'])
293 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100294
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200295
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100296class Device(MaasObject):
297 def __init__(self):
298 super(Device, self).__init__()
299 self._all_elements_url = u'api/2.0/devices/'
300 self._create_url = u'api/2.0/devices/'
301 self._update_url = u'api/2.0/devices/{0}/'
302 self._config_path = 'region.devices'
303 self._element_key = 'hostname'
304 self._update_key = 'system_id'
smolaonc3385f82016-03-11 19:01:24 +0100305
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100306 def fill_data(self, name, device_data):
307 data = {
308 'mac_addresses': device_data['mac'],
309 'hostname': name,
310 }
311 self._interface = device_data['interface']
312 return data
313
314 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100315 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
316 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100317 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100318 LOG.info('Mac changed deleting old device %s', old['system_id'])
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100319 self._maas.delete(u'api/2.0/devices/{0}/'.format(old['system_id']))
320 else:
321 new[self._update_key] = str(old[self._update_key])
322 return new
323
324 def send(self, data):
325 response = super(Device, self).send(data)
326 resp_json = json.loads(response)
327 system_id = resp_json['system_id']
328 iface_id = resp_json['interface_set'][0]['id']
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100329 self._link_interface(system_id, iface_id)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100330 return response
331
332 def _link_interface(self, system_id, interface_id):
333 data = {
334 'mode': self._interface.get('mode', 'STATIC'),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100335 'subnet': self._interface['subnet'],
336 'ip_address': self._interface['ip_address'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100337 }
338 if 'default_gateway' in self._interface:
339 data['default_gateway'] = self._interface.get('default_gateway')
340 if self._update:
341 data['force'] = '1'
342 LOG.info('interfaces link_subnet %s %s %s', system_id, interface_id,
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200343 _format_data(data))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100344 self._maas.post(u'/api/2.0/nodes/{0}/interfaces/{1}/'
345 .format(system_id, interface_id), 'link_subnet',
346 **data)
347
348
349class Machine(MaasObject):
350 def __init__(self):
351 super(Machine, self).__init__()
352 self._all_elements_url = u'api/2.0/machines/'
353 self._create_url = u'api/2.0/machines/'
354 self._update_url = u'api/2.0/machines/{0}/'
355 self._config_path = 'region.machines'
356 self._element_key = 'hostname'
357 self._update_key = 'system_id'
358
359 def fill_data(self, name, machine_data):
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100360 power_data = machine_data['power_parameters']
361 data = {
362 'hostname': name,
363 'architecture': machine_data.get('architecture', 'amd64/generic'),
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200364 'mac_addresses': machine_data['interface']['mac'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100365 'power_type': machine_data.get('power_type', 'ipmi'),
366 'power_parameters_power_address': power_data['power_address'],
367 }
Ondrej Smola455003c2017-06-01 22:53:39 +0200368 if 'power_driver' in power_data:
369 data['power_parameters_power_driver'] = power_data['power_driver']
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100370 if 'power_user' in power_data:
371 data['power_parameters_power_user'] = power_data['power_user']
372 if 'power_password' in power_data:
373 data['power_parameters_power_pass'] = \
374 power_data['power_password']
375 return data
376
377 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100378 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
379 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100380 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100381 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200382 self._maas.delete(u'api/2.0/machines/{0}/'
383 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100384 else:
385 new[self._update_key] = str(old[self._update_key])
386 return new
387
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200388
389class AssignMachinesIP(MaasObject):
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200390 READY = 4
391
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200392 def __init__(self):
393 super(AssignMachinesIP, self).__init__()
394 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200395 self._create_url = \
396 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
397 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200398 self._config_path = 'region.machines'
399 self._element_key = 'hostname'
400 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200401 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
402 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200403
404 def fill_data(self, name, data, machines):
405 interface = data['interface']
406 machine = machines[name]
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200407 if machine['status'] != self.READY:
408 raise Exception('Not in ready state')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200409 if 'ip' not in interface:
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100410 return
411 data = {
412 'mode': 'STATIC',
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200413 'subnet': str(interface.get('subnet')),
414 'ip_address': str(interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100415 }
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200416 if 'default_gateway' in interface:
417 data['default_gateway'] = interface.get('gateway')
Krzysztof Szukiełojć9449af12017-04-11 14:01:30 +0200418 data['force'] = '1'
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200419 data['system_id'] = str(machine['system_id'])
420 data['interface_id'] = str(machine['interface_set'][0]['id'])
421 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100422
423
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200424class DeployMachines(MaasObject):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200425 READY = 4
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200426
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200427 def __init__(self):
428 super(DeployMachines, self).__init__()
429 self._all_elements_url = None
430 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
431 self._config_path = 'region.machines'
432 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200433 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
434 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200435
436 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200437 machine = machines[name]
438 if machine['status'] != self.READY:
439 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200440 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200441 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200442 }
Krzysztof Szukiełojć32677bf2017-04-13 11:04:25 +0200443 if 'distro_series' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200444 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200445 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200446 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200447 return data
448
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200449 def send(self, data):
450 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
Krzysztof Szukiełojć3b7516d2017-04-12 11:52:55 +0200451 self._maas.post(u'api/2.0/machines/', 'allocate', system_id=data['system_id']).read()
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200452 return self._maas.post(self._create_url[0].format(**data),
453 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200454
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100455class BootResource(MaasObject):
456 def __init__(self):
457 super(BootResource, self).__init__()
458 self._all_elements_url = u'api/2.0/boot-resources/'
459 self._create_url = u'api/2.0/boot-resources/'
460 self._update_url = u'api/2.0/boot-resources/{0}/'
461 self._config_path = 'region.boot_resources'
462
463 def fill_data(self, name, boot_data):
464 sha256 = hashlib.sha256()
465 sha256.update(file(boot_data['content']).read())
466 data = {
467 'name': name,
468 'title': boot_data['title'],
469 'architecture': boot_data['architecture'],
470 'filetype': boot_data['filetype'],
471 'size': str(os.path.getsize(boot_data['content'])),
472 'sha256': sha256.hexdigest(),
473 'content': io.open(boot_data['content']),
474 }
475 return data
476
477 def update(self, new, old):
478 self._update = False
479 return new
480
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200481
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100482class CommissioningScripts(MaasObject):
483 def __init__(self):
484 super(CommissioningScripts, self).__init__()
485 self._all_elements_url = u'api/2.0/commissioning-scripts/'
486 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100487 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100488 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100489 self._update_key = 'name'
490
491 def fill_data(self, name, file_path):
492 data = {
493 'name': name,
494 'content': io.open(file_path),
495 }
496 return data
497
498 def update(self, new, old):
499 return new
500
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200501
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100502class MaasConfig(MaasObject):
503 def __init__(self):
504 super(MaasConfig, self).__init__()
505 self._all_elements_url = None
506 self._create_url = (u'api/2.0/maas/', u'set_config')
507 self._config_path = 'region.maas_config'
508
509 def fill_data(self, name, value):
510 data = {
511 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100512 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100513 }
514 return data
515
516 def update(self, new, old):
517 self._update = False
518 return new
519
520
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200521class SSHPrefs(MaasObject):
522 def __init__(self):
523 super(SSHPrefs, self).__init__()
524 self._all_elements_url = None
525 self._create_url = u'api/2.0/account/prefs/sshkeys/'
526 self._config_path = 'region.sshprefs'
527 self._element_key = 'hostname'
528 self._update_key = 'system_id'
529
530 def fill_data(self, value):
531 data = {
532 'key': value,
533 }
534 return data
535
536 def process(self):
537 config = __salt__['config.get']('maas')
538 for part in self._config_path.split('.'):
539 config = config.get(part, {})
540 extra = {}
541 for name, url_call in self._extra_data_urls.iteritems():
542 key = 'id'
543 if isinstance(url_call, tuple):
544 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200545 json_res = json.loads(self._maas.get(url_call).read())
546 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200547 if self._all_elements_url:
548 all_elements = {}
549 elements = self._maas.get(self._all_elements_url).read()
550 res_json = json.loads(elements)
551 for element in res_json:
552 if isinstance(element, (str, unicode)):
553 all_elements[element] = {}
554 else:
555 all_elements[element[self._element_key]] = element
556 else:
557 all_elements = {}
558 ret = {
559 'success': [],
560 'errors': {},
561 'updated': [],
562 }
563 for config_data in config:
564 name = config_data[:10]
565 try:
566 data = self.fill_data(config_data, **extra)
567 self.send(data)
568 ret['success'].append(name)
569 except urllib2.HTTPError as e:
570 etxt = e.read()
571 LOG.exception('Failed for object %s reason %s', name, etxt)
572 ret['errors'][name] = str(etxt)
573 except Exception as e:
574 LOG.exception('Failed for object %s reason %s', name, e)
575 ret['errors'][name] = str(e)
576 if ret['errors']:
577 raise Exception(ret)
578 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200579
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200580
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200581class Domain(MaasObject):
582 def __init__(self):
583 super(Domain, self).__init__()
584 self._all_elements_url = u'/api/2.0/domains/'
585 self._create_url = u'/api/2.0/domains/'
586 self._config_path = 'region.domain'
587 self._update_url = u'/api/2.0/domains/{0}/'
588
589 def fill_data(self, value):
590 data = {
591 'name': value,
592 }
593 self._update = True
594 return data
595
596 def update(self, new, old):
597 new['id'] = str(old['id'])
598 new['authoritative'] = str(old['authoritative'])
599 return new
600
601 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200602 ret = {
603 'success': [],
604 'errors': {},
605 'updated': [],
606 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200607 config = __salt__['config.get']('maas')
608 for part in self._config_path.split('.'):
609 config = config.get(part, {})
610 extra = {}
611 for name, url_call in self._extra_data_urls.iteritems():
612 key = 'id'
613 if isinstance(url_call, tuple):
614 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200615 json_res = json.loads(self._maas.get(url_call).read())
616 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200617 if self._all_elements_url:
618 all_elements = {}
619 elements = self._maas.get(self._all_elements_url).read()
620 res_json = json.loads(elements)
621 for element in res_json:
622 if isinstance(element, (str, unicode)):
623 all_elements[element] = {}
624 else:
625 all_elements[element[self._element_key]] = element
626 else:
627 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200628 try:
629 data = self.fill_data(config, **extra)
630 data = self.update(data, all_elements.values()[0])
631 self.send(data)
632 ret['success'].append('domain')
633 except urllib2.HTTPError as e:
634 etxt = e.read()
635 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
636 ret['errors']['domain'] = str(etxt)
637 except Exception as e:
638 LOG.exception('Failed for object %s reason %s', 'domain', e)
639 ret['errors']['domain'] = str(e)
640 if ret['errors']:
641 raise Exception(ret)
642 return ret
643
644
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200645class MachinesStatus(MaasObject):
646 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200647 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200648 cls._maas = _create_maas_client()
649 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200650 json_result = json.loads(result.read())
651 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200652 status_name_dict = dict([
653 (0, 'New'), (1, 'Commissioning'), (2, 'Failed commissioning'),
654 (3, 'Missing'), (4, 'Ready'), (5, 'Reserved'), (10, 'Allocated'),
655 (9, 'Deploying'), (6, 'Deployed'), (7, 'Retired'), (8, 'Broken'),
656 (11, 'Failed deployment'), (12, 'Releasing'),
657 (13, 'Releasing failed'), (14, 'Disk erasing'),
658 (15, 'Failed disk erasing')])
659 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200660 if objects_name:
661 if ',' in objects_name:
662 objects_name = set(objects_name.split(','))
663 else:
664 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200665 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200666 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200667 continue
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200668 status = status_name_dict[machine['status']]
669 summary[status] += 1
Krzysztof Szukiełojć02e10d32017-04-12 12:23:51 +0200670 res.append('hostname:{},system_id:{},status:{}'
671 .format(machine['hostname'], machine['system_id'],
672 status))
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200673 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200674
675
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100676def process_fabrics():
677 return Fabric().process()
678
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200679
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100680def process_subnets():
681 return Subnet().process()
682
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200683
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100684def process_dhcp_snippets():
685 return DHCPSnippet().process()
686
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200687
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100688def process_package_repositories():
689 return PacketRepository().process()
690
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200691
Krzysztof Szukiełojć889eee92017-04-14 11:45:35 +0200692def process_devices(*args):
693 return Device().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100694
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200695
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200696def process_machines(*args):
697 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100698
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200699
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200700def process_assign_machines_ip(*args):
701 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200702
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200703
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200704def machines_status(*args):
705 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200706
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200707
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200708def deploy_machines(*args):
709 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200710
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200711
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100712def process_boot_resources():
713 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100714
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200715
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100716def process_maas_config():
717 return MaasConfig().process()
718
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200719
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100720def process_commissioning_scripts():
721 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200722
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200723
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200724def process_domain():
725 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200726
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200727
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200728def process_sshprefs():
729 return SSHPrefs().process()