blob: 59070683423ce6a53389f9234f4b943e4171851f [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')
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ć76d9a5c2017-04-14 12:00:42 +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()
Krzysztof Szukiełojć199d5af2017-04-12 13:23:10 +0200144 LOG.error('Failed for object %s reason %s', name, etxt)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200145 ret['errors'][name] = str(etxt)
146 except Exception as e:
Krzysztof Szukiełojć199d5af2017-04-12 13:23:10 +0200147 LOG.error('Failed for object %s reason %s', name, e)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200148 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 }
Ondrej Smola455003c2017-06-01 22:53:39 +0200367 if 'power_driver' in power_data:
368 data['power_parameters_power_driver'] = power_data['power_driver']
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100369 if 'power_user' in power_data:
370 data['power_parameters_power_user'] = power_data['power_user']
371 if 'power_password' in power_data:
372 data['power_parameters_power_pass'] = \
373 power_data['power_password']
Petr Ruzicka5fe96742017-11-10 14:22:24 +0100374 if 'power_id' in power_data:
375 data['power_parameters_power_id'] = power_data['power_id']
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100376 return data
377
378 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100379 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
380 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100381 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100382 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200383 self._maas.delete(u'api/2.0/machines/{0}/'
384 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100385 else:
386 new[self._update_key] = str(old[self._update_key])
387 return new
388
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200389
390class AssignMachinesIP(MaasObject):
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200391 READY = 4
Andreyef156992017-07-03 14:54:03 -0500392 DEPLOYED = 6
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200393
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200394 def __init__(self):
395 super(AssignMachinesIP, self).__init__()
396 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200397 self._create_url = \
398 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
399 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200400 self._config_path = 'region.machines'
401 self._element_key = 'hostname'
402 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200403 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
404 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200405
406 def fill_data(self, name, data, machines):
407 interface = data['interface']
408 machine = machines[name]
Andreyef156992017-07-03 14:54:03 -0500409 if machine['status'] == self.DEPLOYED:
410 return
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200411 if machine['status'] != self.READY:
412 raise Exception('Not in ready state')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200413 if 'ip' not in interface:
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100414 return
415 data = {
416 'mode': 'STATIC',
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200417 'subnet': str(interface.get('subnet')),
418 'ip_address': str(interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100419 }
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200420 if 'default_gateway' in interface:
421 data['default_gateway'] = interface.get('gateway')
Krzysztof Szukiełojć9449af12017-04-11 14:01:30 +0200422 data['force'] = '1'
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200423 data['system_id'] = str(machine['system_id'])
424 data['interface_id'] = str(machine['interface_set'][0]['id'])
425 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100426
427
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200428class DeployMachines(MaasObject):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200429 READY = 4
Andreyef156992017-07-03 14:54:03 -0500430 DEPLOYED = 6
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200431
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200432 def __init__(self):
433 super(DeployMachines, self).__init__()
434 self._all_elements_url = None
435 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
436 self._config_path = 'region.machines'
437 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200438 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
439 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200440
441 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200442 machine = machines[name]
Andreyef156992017-07-03 14:54:03 -0500443 if machine['status'] == self.DEPLOYED:
444 return
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200445 if machine['status'] != self.READY:
446 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200447 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200448 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200449 }
Krzysztof Szukiełojć32677bf2017-04-13 11:04:25 +0200450 if 'distro_series' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200451 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200452 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200453 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200454 return data
455
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200456 def send(self, data):
457 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
Krzysztof Szukiełojć3b7516d2017-04-12 11:52:55 +0200458 self._maas.post(u'api/2.0/machines/', 'allocate', system_id=data['system_id']).read()
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200459 return self._maas.post(self._create_url[0].format(**data),
460 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200461
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100462class BootResource(MaasObject):
463 def __init__(self):
464 super(BootResource, self).__init__()
465 self._all_elements_url = u'api/2.0/boot-resources/'
466 self._create_url = u'api/2.0/boot-resources/'
467 self._update_url = u'api/2.0/boot-resources/{0}/'
468 self._config_path = 'region.boot_resources'
469
470 def fill_data(self, name, boot_data):
471 sha256 = hashlib.sha256()
472 sha256.update(file(boot_data['content']).read())
473 data = {
474 'name': name,
475 'title': boot_data['title'],
476 'architecture': boot_data['architecture'],
477 'filetype': boot_data['filetype'],
478 'size': str(os.path.getsize(boot_data['content'])),
479 'sha256': sha256.hexdigest(),
480 'content': io.open(boot_data['content']),
481 }
482 return data
483
484 def update(self, new, old):
485 self._update = False
486 return new
487
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200488
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100489class CommissioningScripts(MaasObject):
490 def __init__(self):
491 super(CommissioningScripts, self).__init__()
492 self._all_elements_url = u'api/2.0/commissioning-scripts/'
493 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100494 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100495 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100496 self._update_key = 'name'
497
498 def fill_data(self, name, file_path):
499 data = {
500 'name': name,
501 'content': io.open(file_path),
502 }
503 return data
504
505 def update(self, new, old):
506 return new
507
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200508
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100509class MaasConfig(MaasObject):
510 def __init__(self):
511 super(MaasConfig, self).__init__()
512 self._all_elements_url = None
513 self._create_url = (u'api/2.0/maas/', u'set_config')
514 self._config_path = 'region.maas_config'
515
516 def fill_data(self, name, value):
517 data = {
518 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100519 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100520 }
521 return data
522
523 def update(self, new, old):
524 self._update = False
525 return new
526
527
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200528class SSHPrefs(MaasObject):
529 def __init__(self):
530 super(SSHPrefs, self).__init__()
531 self._all_elements_url = None
532 self._create_url = u'api/2.0/account/prefs/sshkeys/'
533 self._config_path = 'region.sshprefs'
534 self._element_key = 'hostname'
535 self._update_key = 'system_id'
536
537 def fill_data(self, value):
538 data = {
539 'key': value,
540 }
541 return data
542
543 def process(self):
544 config = __salt__['config.get']('maas')
545 for part in self._config_path.split('.'):
546 config = config.get(part, {})
547 extra = {}
548 for name, url_call in self._extra_data_urls.iteritems():
549 key = 'id'
550 if isinstance(url_call, tuple):
551 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200552 json_res = json.loads(self._maas.get(url_call).read())
553 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200554 if self._all_elements_url:
555 all_elements = {}
556 elements = self._maas.get(self._all_elements_url).read()
557 res_json = json.loads(elements)
558 for element in res_json:
559 if isinstance(element, (str, unicode)):
560 all_elements[element] = {}
561 else:
562 all_elements[element[self._element_key]] = element
563 else:
564 all_elements = {}
565 ret = {
566 'success': [],
567 'errors': {},
568 'updated': [],
569 }
570 for config_data in config:
571 name = config_data[:10]
572 try:
573 data = self.fill_data(config_data, **extra)
574 self.send(data)
575 ret['success'].append(name)
576 except urllib2.HTTPError as e:
577 etxt = e.read()
578 LOG.exception('Failed for object %s reason %s', name, etxt)
579 ret['errors'][name] = str(etxt)
580 except Exception as e:
581 LOG.exception('Failed for object %s reason %s', name, e)
582 ret['errors'][name] = str(e)
583 if ret['errors']:
584 raise Exception(ret)
585 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200586
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200587
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200588class Domain(MaasObject):
589 def __init__(self):
590 super(Domain, self).__init__()
591 self._all_elements_url = u'/api/2.0/domains/'
592 self._create_url = u'/api/2.0/domains/'
593 self._config_path = 'region.domain'
594 self._update_url = u'/api/2.0/domains/{0}/'
595
596 def fill_data(self, value):
597 data = {
598 'name': value,
599 }
600 self._update = True
601 return data
602
603 def update(self, new, old):
604 new['id'] = str(old['id'])
605 new['authoritative'] = str(old['authoritative'])
606 return new
607
608 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200609 ret = {
610 'success': [],
611 'errors': {},
612 'updated': [],
613 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200614 config = __salt__['config.get']('maas')
615 for part in self._config_path.split('.'):
616 config = config.get(part, {})
617 extra = {}
618 for name, url_call in self._extra_data_urls.iteritems():
619 key = 'id'
620 if isinstance(url_call, tuple):
621 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200622 json_res = json.loads(self._maas.get(url_call).read())
623 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200624 if self._all_elements_url:
625 all_elements = {}
626 elements = self._maas.get(self._all_elements_url).read()
627 res_json = json.loads(elements)
628 for element in res_json:
629 if isinstance(element, (str, unicode)):
630 all_elements[element] = {}
631 else:
632 all_elements[element[self._element_key]] = element
633 else:
634 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200635 try:
636 data = self.fill_data(config, **extra)
637 data = self.update(data, all_elements.values()[0])
638 self.send(data)
639 ret['success'].append('domain')
640 except urllib2.HTTPError as e:
641 etxt = e.read()
642 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
643 ret['errors']['domain'] = str(etxt)
644 except Exception as e:
645 LOG.exception('Failed for object %s reason %s', 'domain', e)
646 ret['errors']['domain'] = str(e)
647 if ret['errors']:
648 raise Exception(ret)
649 return ret
650
651
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200652class MachinesStatus(MaasObject):
653 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200654 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200655 cls._maas = _create_maas_client()
656 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200657 json_result = json.loads(result.read())
658 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200659 status_name_dict = dict([
660 (0, 'New'), (1, 'Commissioning'), (2, 'Failed commissioning'),
661 (3, 'Missing'), (4, 'Ready'), (5, 'Reserved'), (10, 'Allocated'),
662 (9, 'Deploying'), (6, 'Deployed'), (7, 'Retired'), (8, 'Broken'),
663 (11, 'Failed deployment'), (12, 'Releasing'),
664 (13, 'Releasing failed'), (14, 'Disk erasing'),
665 (15, 'Failed disk erasing')])
666 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200667 if objects_name:
668 if ',' in objects_name:
669 objects_name = set(objects_name.split(','))
670 else:
671 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200672 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200673 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200674 continue
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200675 status = status_name_dict[machine['status']]
676 summary[status] += 1
azvyagintsev9f1b0342017-11-03 15:29:36 +0200677 res.append(
678 {'hostname': machine['hostname'],
679 'system_id': machine['system_id'],
680 'status': status})
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200681 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200682
683
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100684def process_fabrics():
685 return Fabric().process()
686
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200687
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100688def process_subnets():
689 return Subnet().process()
690
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200691
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100692def process_dhcp_snippets():
693 return DHCPSnippet().process()
694
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200695
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100696def process_package_repositories():
697 return PacketRepository().process()
698
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200699
Krzysztof Szukiełojć889eee92017-04-14 11:45:35 +0200700def process_devices(*args):
701 return Device().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100702
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200703
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200704def process_machines(*args):
705 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100706
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200707
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200708def process_assign_machines_ip(*args):
709 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200710
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200711
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200712def machines_status(*args):
713 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200714
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200715
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200716def deploy_machines(*args):
717 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200718
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200719
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100720def process_boot_resources():
721 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100722
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200723
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100724def process_maas_config():
725 return MaasConfig().process()
726
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200727
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100728def process_commissioning_scripts():
729 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200730
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200731
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200732def process_domain():
733 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200734
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200735
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200736def process_sshprefs():
737 return SSHPrefs().process()