blob: 1e974cbff6762b6453526ba864fbd3e4556dafb3 [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ćb3216752017-04-05 15:50:41 +020016import collections
azvyagintsev7605a662017-11-03 19:05:04 +020017import copy
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010018import hashlib
azvyagintsev7605a662017-11-03 19:05:04 +020019import io
smolaon27359ae2016-03-11 17:15:34 +010020import json
azvyagintsev7605a662017-11-03 19:05:04 +020021import logging
22import os.path
23import time
24import urllib2
smolaon27359ae2016-03-11 17:15:34 +010025
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
azvyagintsev7605a662017-11-03 19:05:04 +020049STATUS_NAME_DICT = dict([
50 (0, 'New'), (1, 'Commissioning'), (2, 'Failed commissioning'),
51 (3, 'Missing'), (4, 'Ready'), (5, 'Reserved'), (10, 'Allocated'),
52 (9, 'Deploying'), (6, 'Deployed'), (7, 'Retired'), (8, 'Broken'),
53 (11, 'Failed deployment'), (12, 'Releasing'),
54 (13, 'Releasing failed'), (14, 'Disk erasing'),
Alexandru Avadanii08ffc3f2017-12-17 06:30:27 +010055 (15, 'Failed disk erasing'), (16, 'Rescue mode'),
56 (17, 'Entering rescue mode'), (18, 'Failed to enter rescue mode'),
57 (19, 'Exiting rescue mode'), (20, 'Failed to exit rescue mode'),
58 (21, 'Testing'), (22, 'Failed testing')])
azvyagintsev7605a662017-11-03 19:05:04 +020059
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020060
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010061def _format_data(data):
62 class Lazy:
63 def __str__(self):
64 return ' '.join(['{0}={1}'.format(k, v)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020065 for k, v in data.iteritems()])
Krzysztof Szukiełojć52cc6b02017-04-06 09:53:43 +020066 return Lazy()
Ales Komarek663b85c2016-03-11 14:26:42 +010067
68
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010069def _create_maas_client():
70 global APIKEY_FILE
71 try:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020072 api_token = file(APIKEY_FILE).read().splitlines()[-1].strip()\
73 .split(':')
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010074 except:
75 LOG.exception('token')
76 auth = MAASOAuth(*api_token)
77 api_url = 'http://localhost:5240/MAAS'
Ales Komarek663b85c2016-03-11 14:26:42 +010078 dispatcher = MAASDispatcher()
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010079 return MAASClient(auth, dispatcher, api_url)
Ales Komarek663b85c2016-03-11 14:26:42 +010080
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020081
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +010082class MaasObject(object):
83 def __init__(self):
84 self._maas = _create_maas_client()
85 self._extra_data_urls = {}
86 self._extra_data = {}
87 self._update = False
88 self._element_key = 'name'
89 self._update_key = 'id'
90
91 def send(self, data):
92 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
93 if self._update:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +020094 return self._maas.put(
95 self._update_url.format(data[self._update_key]), **data).read()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +010096 if isinstance(self._create_url, tuple):
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +020097 return self._maas.post(self._create_url[0].format(**data),
98 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć76d9a5c2017-04-14 12:00:42 +020099 return self._maas.post(self._create_url.format(**data),
100 None, **data).read()
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100101
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200102 def process(self, objects_name=None):
azvyagintsev06b71e72017-11-08 17:11:07 +0200103 # FIXME: probably, should be extended with "skipped" return.
104 # For example, currently "DEPLOYED" nodes are skipped, and no changes
105 # applied - but they fall into 'updated' list.
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200106 ret = {
107 'success': [],
108 'errors': {},
109 'updated': [],
110 }
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200111 try:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200112 config = __salt__['config.get']('maas')
113 for part in self._config_path.split('.'):
114 config = config.get(part, {})
115 extra = {}
116 for name, url_call in self._extra_data_urls.iteritems():
117 key = 'id'
118 key_name = 'name'
119 if isinstance(url_call, tuple):
120 if len(url_call) == 2:
121 url_call, key = url_call[:]
122 else:
123 url_call, key, key_name = url_call[:]
124 json_res = json.loads(self._maas.get(url_call).read())
125 if key:
126 extra[name] = {v[key_name]: v[key] for v in json_res}
127 else:
Krzysztof Szukiełojć52cc6b02017-04-06 09:53:43 +0200128 extra[name] = {v[key_name]: v for v in json_res}
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200129 if self._all_elements_url:
130 all_elements = {}
131 elements = self._maas.get(self._all_elements_url).read()
132 res_json = json.loads(elements)
133 for element in res_json:
134 if isinstance(element, (str, unicode)):
135 all_elements[element] = {}
136 else:
137 all_elements[element[self._element_key]] = element
138 else:
139 all_elements = {}
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200140
141 def process_single(name, config_data):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200142 self._update = False
143 try:
144 data = self.fill_data(name, config_data, **extra)
145 if data is None:
146 ret['updated'].append(name)
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200147 return
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200148 if name in all_elements:
149 self._update = True
150 data = self.update(data, all_elements[name])
151 self.send(data)
152 ret['updated'].append(name)
153 else:
154 self.send(data)
155 ret['success'].append(name)
156 except urllib2.HTTPError as e:
azvyagintsev06b71e72017-11-08 17:11:07 +0200157 # FIXME add exception's for response:
158 # '{"mode": ["Interface is already set to DHCP."]}
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200159 etxt = e.read()
Krzysztof Szukiełojć199d5af2017-04-12 13:23:10 +0200160 LOG.error('Failed for object %s reason %s', name, etxt)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200161 ret['errors'][name] = str(etxt)
162 except Exception as e:
Krzysztof Szukiełojć199d5af2017-04-12 13:23:10 +0200163 LOG.error('Failed for object %s reason %s', name, e)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200164 ret['errors'][name] = str(e)
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200165 if objects_name is not None:
166 if ',' in objects_name:
167 objects_name = objects_name.split(',')
168 else:
169 objects_name = [objects_name]
170 for object_name in objects_name:
171 process_single(object_name, config[object_name])
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200172 else:
173 for name, config_data in config.iteritems():
174 process_single(name, config_data)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200175 except Exception as e:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200176 LOG.exception('Error Global')
177 raise
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100178 if ret['errors']:
Jiri Broulike30a60f2018-04-09 21:15:10 +0200179 if 'already exists' in str(ret['errors']):
180 ret['success'] = ret['errors']
181 ret['errors'] = {}
182 else:
183 raise Exception(ret)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100184 return ret
Ales Komarek663b85c2016-03-11 14:26:42 +0100185
186
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100187class Fabric(MaasObject):
188 def __init__(self):
189 super(Fabric, self).__init__()
190 self._all_elements_url = u'api/2.0/fabrics/'
191 self._create_url = u'api/2.0/fabrics/'
192 self._update_url = u'api/2.0/fabrics/{0}/'
193 self._config_path = 'region.fabrics'
Ales Komarek663b85c2016-03-11 14:26:42 +0100194
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100195 def fill_data(self, name, fabric):
196 data = {
197 'name': name,
198 'description': fabric.get('description', ''),
199 }
200 if 'class_type' in fabric:
201 data['class_type'] = fabric.get('class_type'),
202 return data
Ales Komarek663b85c2016-03-11 14:26:42 +0100203
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100204 def update(self, new, old):
205 new['id'] = str(old['id'])
206 return new
Ales Komarek663b85c2016-03-11 14:26:42 +0100207
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200208
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100209class Subnet(MaasObject):
210 def __init__(self):
211 super(Subnet, self).__init__()
212 self._all_elements_url = u'api/2.0/subnets/'
213 self._create_url = u'api/2.0/subnets/'
214 self._update_url = u'api/2.0/subnets/{0}/'
215 self._config_path = 'region.subnets'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200216 self._extra_data_urls = {'fabrics': u'api/2.0/fabrics/'}
Ales Komarek0fafa572016-03-11 14:56:44 +0100217
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100218 def fill_data(self, name, subnet, fabrics):
219 data = {
220 'name': name,
221 'fabric': str(fabrics[subnet.get('fabric', '')]),
222 'cidr': subnet.get('cidr'),
223 'gateway_ip': subnet['gateway_ip'],
224 }
225 self._iprange = subnet['iprange']
226 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100227
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100228 def update(self, new, old):
229 new['id'] = str(old['id'])
230 return new
Ales Komarek0fafa572016-03-11 14:56:44 +0100231
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100232 def send(self, data):
233 response = super(Subnet, self).send(data)
234 res_json = json.loads(response)
235 self._process_iprange(res_json['id'])
236 return response
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100237
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100238 def _process_iprange(self, subnet_id):
239 ipranges = json.loads(self._maas.get(u'api/2.0/ipranges/').read())
240 LOG.warn('all %s ipranges %s', subnet_id, ipranges)
241 update = False
242 old_data = None
243 for iprange in ipranges:
244 if iprange['subnet']['id'] == subnet_id:
245 update = True
246 old_data = iprange
247 break
248 data = {
249 'start_ip': self._iprange.get('start'),
250 'end_ip': self._iprange.get('end'),
251 'subnet': str(subnet_id),
252 'type': self._iprange.get('type', 'dynamic')
253 }
254 LOG.warn('INFO: %s\n OLD: %s', data, old_data)
255 LOG.info('iprange %s', _format_data(data))
256 if update:
257 LOG.warn('UPDATING %s %s', data, old_data)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200258 self._maas.put(u'api/2.0/ipranges/{0}/'.format(old_data['id']),
259 **data)
smolaonc3385f82016-03-11 19:01:24 +0100260 else:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100261 self._maas.post(u'api/2.0/ipranges/', None, **data)
smolaonc3385f82016-03-11 19:01:24 +0100262
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200263
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100264class DHCPSnippet(MaasObject):
265 def __init__(self):
266 super(DHCPSnippet, self).__init__()
267 self._all_elements_url = u'api/2.0/dhcp-snippets/'
268 self._create_url = u'api/2.0/dhcp-snippets/'
269 self._update_url = u'api/2.0/dhcp-snippets/{0}/'
270 self._config_path = 'region.dhcp_snippets'
271 self._extra_data_urls = {'subnets': u'api/2.0/subnets/'}
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100272
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100273 def fill_data(self, name, snippet, subnets):
274 data = {
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200275 'name': name,
276 'value': snippet['value'],
277 'description': snippet['description'],
278 'enabled': str(snippet['enabled'] and 1 or 0),
279 'subnet': str(subnets[snippet['subnet']]),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100280 }
281 return data
smolaonc3385f82016-03-11 19:01:24 +0100282
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100283 def update(self, new, old):
284 new['id'] = str(old['id'])
285 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100286
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200287
Jiri Broulike30a60f2018-04-09 21:15:10 +0200288class Boot_source(MaasObject):
289 def __init__(self):
290 super(Boot_source, self).__init__()
291 self._all_elements_url = u'api/2.0/boot-sources/'
292 self._create_url = u'api/2.0/boot-sources/'
293 self._update_url = u'api/2.0/boot-sources/{0}/'
294 self._config_path = 'region.boot_sources'
295 self._element_key = 'id'
296
297 def fill_data(self, name, boot_source):
298 data = {
299 'name': name,
300 'url': boot_source.get('url', ''),
301 'keyring_filename': boot_source.get('keyring_file', ''),
302 }
303 return data
304
305 def update(self, new, old):
306 new['id'] = str(old['id'])
307 return new
308
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100309class PacketRepository(MaasObject):
310 def __init__(self):
311 super(PacketRepository, self).__init__()
312 self._all_elements_url = u'api/2.0/package-repositories/'
313 self._create_url = u'api/2.0/package-repositories/'
314 self._update_url = u'api/2.0/package-repositories/{0}/'
315 self._config_path = 'region.package_repositories'
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100316
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100317 def fill_data(self, name, package_repository):
318 data = {
319 'name': name,
320 'url': package_repository['url'],
321 'distributions': package_repository['distributions'],
322 'components': package_repository['components'],
323 'arches': package_repository['arches'],
324 'key': package_repository['key'],
325 'enabled': str(package_repository['enabled'] and 1 or 0),
326 }
327 if 'disabled_pockets' in package_repository:
328 data['disabled_pockets'] = package_repository['disable_pockets'],
329 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100330
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100331 def update(self, new, old):
332 new['id'] = str(old['id'])
333 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100334
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200335
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100336class Device(MaasObject):
337 def __init__(self):
338 super(Device, self).__init__()
339 self._all_elements_url = u'api/2.0/devices/'
340 self._create_url = u'api/2.0/devices/'
341 self._update_url = u'api/2.0/devices/{0}/'
342 self._config_path = 'region.devices'
343 self._element_key = 'hostname'
344 self._update_key = 'system_id'
smolaonc3385f82016-03-11 19:01:24 +0100345
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100346 def fill_data(self, name, device_data):
347 data = {
348 'mac_addresses': device_data['mac'],
349 'hostname': name,
350 }
351 self._interface = device_data['interface']
352 return data
353
354 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100355 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
356 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100357 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100358 LOG.info('Mac changed deleting old device %s', old['system_id'])
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100359 self._maas.delete(u'api/2.0/devices/{0}/'.format(old['system_id']))
360 else:
361 new[self._update_key] = str(old[self._update_key])
362 return new
363
364 def send(self, data):
365 response = super(Device, self).send(data)
366 resp_json = json.loads(response)
367 system_id = resp_json['system_id']
368 iface_id = resp_json['interface_set'][0]['id']
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100369 self._link_interface(system_id, iface_id)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100370 return response
371
372 def _link_interface(self, system_id, interface_id):
373 data = {
374 'mode': self._interface.get('mode', 'STATIC'),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100375 'subnet': self._interface['subnet'],
376 'ip_address': self._interface['ip_address'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100377 }
378 if 'default_gateway' in self._interface:
379 data['default_gateway'] = self._interface.get('default_gateway')
380 if self._update:
381 data['force'] = '1'
382 LOG.info('interfaces link_subnet %s %s %s', system_id, interface_id,
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200383 _format_data(data))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100384 self._maas.post(u'/api/2.0/nodes/{0}/interfaces/{1}/'
385 .format(system_id, interface_id), 'link_subnet',
386 **data)
387
388
389class Machine(MaasObject):
390 def __init__(self):
391 super(Machine, self).__init__()
392 self._all_elements_url = u'api/2.0/machines/'
393 self._create_url = u'api/2.0/machines/'
394 self._update_url = u'api/2.0/machines/{0}/'
395 self._config_path = 'region.machines'
396 self._element_key = 'hostname'
397 self._update_key = 'system_id'
398
399 def fill_data(self, name, machine_data):
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100400 power_data = machine_data['power_parameters']
azvyagintsev06b71e72017-11-08 17:11:07 +0200401 machine_pxe_mac = machine_data.get('pxe_interface_mac', None)
402 if machine_data.get("interface", None):
403 LOG.warning(
404 "Old machine-describe detected! "
405 "Please read documentation for "
406 "'salt-formulas/maas' for migration!")
407 machine_pxe_mac = machine_data['interface'].get('mac', None)
408 if not machine_pxe_mac:
409 raise Exception("PXE MAC for machine:{} not defined".format(name))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100410 data = {
411 'hostname': name,
412 'architecture': machine_data.get('architecture', 'amd64/generic'),
azvyagintsev06b71e72017-11-08 17:11:07 +0200413 'mac_addresses': machine_pxe_mac,
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100414 'power_type': machine_data.get('power_type', 'ipmi'),
415 'power_parameters_power_address': power_data['power_address'],
416 }
Ondrej Smola455003c2017-06-01 22:53:39 +0200417 if 'power_driver' in power_data:
418 data['power_parameters_power_driver'] = power_data['power_driver']
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100419 if 'power_user' in power_data:
420 data['power_parameters_power_user'] = power_data['power_user']
421 if 'power_password' in power_data:
422 data['power_parameters_power_pass'] = \
423 power_data['power_password']
Petr Ruzicka5fe96742017-11-10 14:22:24 +0100424 if 'power_id' in power_data:
425 data['power_parameters_power_id'] = power_data['power_id']
mkraynov47c087a2018-04-26 13:23:31 +0400426 if 'power_nova_id' in power_data:
427 data['power_parameters_nova_id'] = power_data['power_nova_id']
428 if 'power_os_tenantname' in power_data:
429 data['power_parameters_os_tenantname'] = power_data['power_os_tenantname']
430 if 'power_os_username' in power_data:
431 data['power_parameters_os_username'] = power_data['power_os_username']
432 if 'power_os_password' in power_data:
433 data['power_parameters_os_password'] = power_data['power_os_password']
434 if 'power_os_authurl' in power_data:
435 data['power_parameters_os_authurl'] = power_data['power_os_authurl']
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100436 return data
437
438 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100439 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
440 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100441 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100442 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200443 self._maas.delete(u'api/2.0/machines/{0}/'
444 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100445 else:
446 new[self._update_key] = str(old[self._update_key])
447 return new
448
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200449
450class AssignMachinesIP(MaasObject):
azvyagintsev06b71e72017-11-08 17:11:07 +0200451 # FIXME
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200452 READY = 4
Andreyef156992017-07-03 14:54:03 -0500453 DEPLOYED = 6
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200454
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200455 def __init__(self):
456 super(AssignMachinesIP, self).__init__()
457 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200458 self._create_url = \
459 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
460 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200461 self._config_path = 'region.machines'
462 self._element_key = 'hostname'
463 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200464 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
465 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200466
azvyagintsev06b71e72017-11-08 17:11:07 +0200467 def _data_old(self, _interface, _machine):
468 """
469 _interface = {
470 "mac": "11:22:33:44:55:77",
471 "mode": "STATIC",
472 "ip": "2.2.3.15",
473 "subnet": "subnet1",
474 "gateway": "2.2.3.2",
475 }
476 :param data:
477 :return:
478 """
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100479 data = {
480 'mode': 'STATIC',
azvyagintsev06b71e72017-11-08 17:11:07 +0200481 'subnet': str(_interface.get('subnet')),
482 'ip_address': str(_interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100483 }
azvyagintsev06b71e72017-11-08 17:11:07 +0200484 if 'gateway' in _interface:
485 data['default_gateway'] = _interface.get('gateway')
Krzysztof Szukiełojć9449af12017-04-11 14:01:30 +0200486 data['force'] = '1'
azvyagintsev06b71e72017-11-08 17:11:07 +0200487 data['system_id'] = str(_machine['system_id'])
488 data['interface_id'] = str(_machine['interface_set'][0]['id'])
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200489 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100490
azvyagintsev06b71e72017-11-08 17:11:07 +0200491 def _get_nic_id_by_mac(self, machine, req_mac=None):
492 data = {}
493 for nic in machine['interface_set']:
494 data[nic['mac_address']] = nic['id']
495 if req_mac:
496 if req_mac in data.keys():
497 return data[req_mac]
498 else:
499 raise Exception('NIC with mac:{} not found at '
500 'node:{}'.format(req_mac, machine['fqdn']))
501 return data
502
503 def _disconnect_all_nic(self, machine):
504 """
505 Maas will fail, in case same config's will be to apply
506 on different interfaces. In same time - not possible to push
507 whole network schema at once. Before configuring - need to clean-up
508 everything
509 :param machine:
510 :return:
511 """
512 for nic in machine['interface_set']:
513 LOG.debug("Disconnecting interface:{}".format(nic['mac_address']))
514 try:
515 self._maas.post(
516 u'/api/2.0/nodes/{}/interfaces/{}/'.format(
517 machine['system_id'], nic['id']), 'disconnect')
518 except Exception as e:
519 LOG.error("Failed to disconnect interface:{} on node:{}".format(
520 nic['mac_address'], machine['fqdn']))
521 raise Exception(str(e))
522
523 def _process_interface(self, nic_data, machine):
524 """
525 Process exactly one interface:
526 - update interface
527 - link to network
528 These functions are self-complementary, and do not require an
529 external "process" method. Those broke old-MaasObject logic,
530 though make functions more simple in case iterable tasks.
531 """
532 nic_id = self._get_nic_id_by_mac(machine, nic_data['mac'])
533
534 # Process op=link_subnet
535 link_data = {}
536 _mode = nic_data.get('mode', 'AUTO').upper()
537 if _mode == 'STATIC':
538 link_data = {
539 'mode': 'STATIC',
540 'subnet': str(nic_data.get('subnet')),
541 'ip_address': str(nic_data.get('ip')),
542 'default_gateway': str(nic_data.get('gateway', "")),
543 }
544 elif _mode == 'DHCP':
545 link_data = {
546 'mode': 'DHCP',
547 'subnet': str(nic_data.get('subnet')),
548 }
549 elif _mode == 'AUTO':
550 link_data = {'mode': 'AUTO',
551 'default_gateway': str(nic_data.get('gateway', "")), }
552 elif _mode in ('LINK_UP', 'UNCONFIGURED'):
553 link_data = {'mode': 'LINK_UP'}
554 else:
555 raise Exception('Wrong IP mode:{}'
556 ' for node:{}'.format(_mode, machine['fqdn']))
557 link_data['force'] = str(1)
558
559 physical_data = {"name": nic_data.get("name", ""),
560 "tags": nic_data.get('tags', ""),
561 "vlan": nic_data.get('vlan', "")}
562
563 try:
564 # Cleanup-old definition
565 LOG.debug("Processing {}:{},{}".format(nic_data['mac'], link_data,
566 physical_data))
567 # "link_subnet" and "fill all other data" - its 2 different
568 # operations. So, first we update NIC:
569 self._maas.put(
570 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
571 nic_id),
572 **physical_data)
573 # And then, link subnet configuration:
574 self._maas.post(
575 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
576 nic_id),
577 'link_subnet', **link_data)
578 except Exception as e:
579 LOG.error("Failed to process interface:{} on node:{}".format(
580 nic_data['mac'], machine['fqdn']))
581 raise Exception(str(e))
582
583 def fill_data(self, name, data, machines):
584 machine = machines[name]
585 if machine['status'] == self.DEPLOYED:
586 LOG.debug("Skipping node:{} "
587 "since it in status:DEPLOYED".format(name))
588 return
589 if machine['status'] != self.READY:
590 raise Exception('Machine:{} not in status:READY'.format(name))
591 # backward comparability, for old schema
592 if data.get("interface", None):
593 if 'ip' not in data["interface"]:
594 LOG.info("No IP NIC definition for:{}".format(name))
595 return
596 LOG.warning(
597 "Old machine-describe detected! "
598 "Please read documentation "
599 "'salt-formulas/maas' for migration!")
600 return self._data_old(data['interface'], machines[name])
601 # NewSchema processing:
602 # Warning: old-style MaasObject.process still be called, but
603 # with empty data for process.
604 interfaces = data.get('interfaces', {})
605 if len(interfaces.keys()) == 0:
606 LOG.info("No IP NIC definition for:{}".format(name))
607 return
608 LOG.info('%s for %s', self.__class__.__name__.lower(),
609 machine['fqdn'])
610 self._disconnect_all_nic(machine)
611 for key, value in sorted(interfaces.iteritems()):
612 self._process_interface(value, machine)
613
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100614
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200615class DeployMachines(MaasObject):
azvyagintsev7605a662017-11-03 19:05:04 +0200616 # FIXME
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200617 READY = 4
Andreyef156992017-07-03 14:54:03 -0500618 DEPLOYED = 6
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200619
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200620 def __init__(self):
621 super(DeployMachines, self).__init__()
622 self._all_elements_url = None
623 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
624 self._config_path = 'region.machines'
625 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200626 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
627 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200628
629 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200630 machine = machines[name]
Andreyef156992017-07-03 14:54:03 -0500631 if machine['status'] == self.DEPLOYED:
632 return
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200633 if machine['status'] != self.READY:
634 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200635 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200636 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200637 }
Krzysztof Szukiełojć32677bf2017-04-13 11:04:25 +0200638 if 'distro_series' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200639 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200640 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200641 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200642 return data
643
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200644 def send(self, data):
645 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
Krzysztof Szukiełojć3b7516d2017-04-12 11:52:55 +0200646 self._maas.post(u'api/2.0/machines/', 'allocate', system_id=data['system_id']).read()
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200647 return self._maas.post(self._create_url[0].format(**data),
648 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200649
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100650class BootResource(MaasObject):
651 def __init__(self):
652 super(BootResource, self).__init__()
653 self._all_elements_url = u'api/2.0/boot-resources/'
654 self._create_url = u'api/2.0/boot-resources/'
655 self._update_url = u'api/2.0/boot-resources/{0}/'
656 self._config_path = 'region.boot_resources'
657
658 def fill_data(self, name, boot_data):
659 sha256 = hashlib.sha256()
660 sha256.update(file(boot_data['content']).read())
661 data = {
662 'name': name,
663 'title': boot_data['title'],
664 'architecture': boot_data['architecture'],
665 'filetype': boot_data['filetype'],
666 'size': str(os.path.getsize(boot_data['content'])),
667 'sha256': sha256.hexdigest(),
668 'content': io.open(boot_data['content']),
669 }
670 return data
671
672 def update(self, new, old):
673 self._update = False
674 return new
675
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200676
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100677class CommissioningScripts(MaasObject):
678 def __init__(self):
679 super(CommissioningScripts, self).__init__()
680 self._all_elements_url = u'api/2.0/commissioning-scripts/'
681 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100682 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100683 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100684 self._update_key = 'name'
685
686 def fill_data(self, name, file_path):
687 data = {
688 'name': name,
689 'content': io.open(file_path),
690 }
691 return data
692
693 def update(self, new, old):
694 return new
695
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200696
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100697class MaasConfig(MaasObject):
698 def __init__(self):
699 super(MaasConfig, self).__init__()
700 self._all_elements_url = None
701 self._create_url = (u'api/2.0/maas/', u'set_config')
702 self._config_path = 'region.maas_config'
703
704 def fill_data(self, name, value):
705 data = {
706 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100707 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100708 }
709 return data
710
711 def update(self, new, old):
712 self._update = False
713 return new
714
715
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200716class SSHPrefs(MaasObject):
717 def __init__(self):
718 super(SSHPrefs, self).__init__()
719 self._all_elements_url = None
720 self._create_url = u'api/2.0/account/prefs/sshkeys/'
721 self._config_path = 'region.sshprefs'
722 self._element_key = 'hostname'
723 self._update_key = 'system_id'
724
725 def fill_data(self, value):
726 data = {
727 'key': value,
728 }
729 return data
730
731 def process(self):
732 config = __salt__['config.get']('maas')
733 for part in self._config_path.split('.'):
734 config = config.get(part, {})
735 extra = {}
736 for name, url_call in self._extra_data_urls.iteritems():
737 key = 'id'
738 if isinstance(url_call, tuple):
739 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200740 json_res = json.loads(self._maas.get(url_call).read())
741 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200742 if self._all_elements_url:
743 all_elements = {}
744 elements = self._maas.get(self._all_elements_url).read()
745 res_json = json.loads(elements)
746 for element in res_json:
747 if isinstance(element, (str, unicode)):
748 all_elements[element] = {}
749 else:
750 all_elements[element[self._element_key]] = element
751 else:
752 all_elements = {}
753 ret = {
754 'success': [],
755 'errors': {},
756 'updated': [],
757 }
758 for config_data in config:
759 name = config_data[:10]
760 try:
761 data = self.fill_data(config_data, **extra)
762 self.send(data)
763 ret['success'].append(name)
764 except urllib2.HTTPError as e:
765 etxt = e.read()
766 LOG.exception('Failed for object %s reason %s', name, etxt)
767 ret['errors'][name] = str(etxt)
768 except Exception as e:
769 LOG.exception('Failed for object %s reason %s', name, e)
770 ret['errors'][name] = str(e)
771 if ret['errors']:
772 raise Exception(ret)
773 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200774
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200775
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200776class Domain(MaasObject):
777 def __init__(self):
778 super(Domain, self).__init__()
779 self._all_elements_url = u'/api/2.0/domains/'
780 self._create_url = u'/api/2.0/domains/'
781 self._config_path = 'region.domain'
782 self._update_url = u'/api/2.0/domains/{0}/'
783
784 def fill_data(self, value):
785 data = {
786 'name': value,
787 }
788 self._update = True
789 return data
790
791 def update(self, new, old):
792 new['id'] = str(old['id'])
793 new['authoritative'] = str(old['authoritative'])
794 return new
795
796 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200797 ret = {
798 'success': [],
799 'errors': {},
800 'updated': [],
801 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200802 config = __salt__['config.get']('maas')
803 for part in self._config_path.split('.'):
804 config = config.get(part, {})
805 extra = {}
806 for name, url_call in self._extra_data_urls.iteritems():
807 key = 'id'
808 if isinstance(url_call, tuple):
809 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200810 json_res = json.loads(self._maas.get(url_call).read())
811 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200812 if self._all_elements_url:
813 all_elements = {}
814 elements = self._maas.get(self._all_elements_url).read()
815 res_json = json.loads(elements)
816 for element in res_json:
817 if isinstance(element, (str, unicode)):
818 all_elements[element] = {}
819 else:
820 all_elements[element[self._element_key]] = element
821 else:
822 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200823 try:
824 data = self.fill_data(config, **extra)
825 data = self.update(data, all_elements.values()[0])
826 self.send(data)
827 ret['success'].append('domain')
828 except urllib2.HTTPError as e:
829 etxt = e.read()
830 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
831 ret['errors']['domain'] = str(etxt)
832 except Exception as e:
833 LOG.exception('Failed for object %s reason %s', 'domain', e)
834 ret['errors']['domain'] = str(e)
835 if ret['errors']:
836 raise Exception(ret)
837 return ret
838
839
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200840class MachinesStatus(MaasObject):
841 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200842 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200843 cls._maas = _create_maas_client()
844 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200845 json_result = json.loads(result.read())
846 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200847 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200848 if objects_name:
849 if ',' in objects_name:
850 objects_name = set(objects_name.split(','))
851 else:
852 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200853 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200854 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200855 continue
Michael Polenchuke438bd32017-11-09 20:42:42 +0400856 status = STATUS_NAME_DICT[machine['status']]
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200857 summary[status] += 1
azvyagintsev7605a662017-11-03 19:05:04 +0200858 res.append(
859 {'hostname': machine['hostname'],
860 'system_id': machine['system_id'],
861 'status': status})
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200862 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200863
azvyagintsev7605a662017-11-03 19:05:04 +0200864 @classmethod
865 def wait_for_machine_status(cls, **kwargs):
866 """
867 A function that wait for any requested status, for any set of maas
868 machines.
869
870 If no kwargs has been passed - will try to wait ALL
871 defined in salt::maas::region::machines
872
873 See readme file for more examples.
874 CLI Example:
875 .. code-block:: bash
876
877 salt-call state.apply maas.machines.wait_for_deployed
878
879 :param kwargs:
880 timeout: in s; Global timeout for wait
881 poll_time: in s;Sleep time, between retry
882 req_status: string; Polling status
883 machines: list; machine names
884 ignore_machines: list; machine names
885 :ret: True
886 Exception - if something fail/timeout reached
887 """
888 timeout = kwargs.get("timeout", 60 * 120)
889 poll_time = kwargs.get("poll_time", 30)
890 req_status = kwargs.get("req_status", "Ready")
891 to_discover = kwargs.get("machines", None)
892 ignore_machines = kwargs.get("ignore_machines", None)
893 if not to_discover:
894 try:
895 to_discover = __salt__['config.get']('maas')['region'][
896 'machines'].keys()
897 except KeyError:
898 LOG.warning("No defined machines!")
899 return True
900 total = copy.deepcopy(to_discover) or []
901 if ignore_machines and total:
902 total = [x for x in to_discover if x not in ignore_machines]
903 started_at = time.time()
904 while len(total) <= len(to_discover):
905 for m in to_discover:
906 for discovered in MachinesStatus.execute()['machines']:
907 if m == discovered['hostname'] and \
Michael Polenchuke438bd32017-11-09 20:42:42 +0400908 discovered['status'].lower() == req_status.lower():
909 if m in total:
910 total.remove(m)
911
azvyagintsev7605a662017-11-03 19:05:04 +0200912 if len(total) <= 0:
913 LOG.debug(
914 "Machines:{} are:{}".format(to_discover, req_status))
915 return True
916 if (timeout - (time.time() - started_at)) <= 0:
917 raise Exception(
918 'Machines:{}not in {} state'.format(total, req_status))
919 LOG.info(
920 "Waiting status:{} "
921 "for machines:{}"
922 "\nsleep for:{}s "
923 "Timeout:{}s".format(req_status, total, poll_time, timeout))
924 time.sleep(poll_time)
925
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200926
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100927def process_fabrics():
928 return Fabric().process()
929
Jiri Broulike30a60f2018-04-09 21:15:10 +0200930def process_boot_sources():
931 return Boot_source().process()
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200932
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100933def process_subnets():
934 return Subnet().process()
935
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200936
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100937def process_dhcp_snippets():
938 return DHCPSnippet().process()
939
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200940
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100941def process_package_repositories():
942 return PacketRepository().process()
943
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200944
Krzysztof Szukiełojć889eee92017-04-14 11:45:35 +0200945def process_devices(*args):
946 return Device().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100947
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200948
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200949def process_machines(*args):
950 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100951
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200952
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200953def process_assign_machines_ip(*args):
azvyagintsev06b71e72017-11-08 17:11:07 +0200954 """
955 Manage interface configurations.
956 See readme.rst for more info
957 """
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200958 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200959
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200960
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200961def machines_status(*args):
962 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200963
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200964
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200965def deploy_machines(*args):
966 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200967
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200968
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100969def process_boot_resources():
970 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100971
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200972
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100973def process_maas_config():
974 return MaasConfig().process()
975
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200976
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100977def process_commissioning_scripts():
978 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200979
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200980
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200981def process_domain():
982 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200983
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200984
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200985def process_sshprefs():
986 return SSHPrefs().process()
azvyagintsev7605a662017-11-03 19:05:04 +0200987
988
989def wait_for_machine_status(**kwargs):
990 return MachinesStatus.wait_for_machine_status(**kwargs)