blob: 329a46792ef86515a69894bf751a8a4ea55da259 [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']
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100426 return data
427
428 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100429 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
430 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100431 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100432 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200433 self._maas.delete(u'api/2.0/machines/{0}/'
434 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100435 else:
436 new[self._update_key] = str(old[self._update_key])
437 return new
438
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200439
440class AssignMachinesIP(MaasObject):
azvyagintsev06b71e72017-11-08 17:11:07 +0200441 # FIXME
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200442 READY = 4
Andreyef156992017-07-03 14:54:03 -0500443 DEPLOYED = 6
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200444
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200445 def __init__(self):
446 super(AssignMachinesIP, self).__init__()
447 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200448 self._create_url = \
449 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
450 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200451 self._config_path = 'region.machines'
452 self._element_key = 'hostname'
453 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200454 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
455 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200456
azvyagintsev06b71e72017-11-08 17:11:07 +0200457 def _data_old(self, _interface, _machine):
458 """
459 _interface = {
460 "mac": "11:22:33:44:55:77",
461 "mode": "STATIC",
462 "ip": "2.2.3.15",
463 "subnet": "subnet1",
464 "gateway": "2.2.3.2",
465 }
466 :param data:
467 :return:
468 """
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100469 data = {
470 'mode': 'STATIC',
azvyagintsev06b71e72017-11-08 17:11:07 +0200471 'subnet': str(_interface.get('subnet')),
472 'ip_address': str(_interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100473 }
azvyagintsev06b71e72017-11-08 17:11:07 +0200474 if 'gateway' in _interface:
475 data['default_gateway'] = _interface.get('gateway')
Krzysztof Szukiełojć9449af12017-04-11 14:01:30 +0200476 data['force'] = '1'
azvyagintsev06b71e72017-11-08 17:11:07 +0200477 data['system_id'] = str(_machine['system_id'])
478 data['interface_id'] = str(_machine['interface_set'][0]['id'])
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200479 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100480
azvyagintsev06b71e72017-11-08 17:11:07 +0200481 def _get_nic_id_by_mac(self, machine, req_mac=None):
482 data = {}
483 for nic in machine['interface_set']:
484 data[nic['mac_address']] = nic['id']
485 if req_mac:
486 if req_mac in data.keys():
487 return data[req_mac]
488 else:
489 raise Exception('NIC with mac:{} not found at '
490 'node:{}'.format(req_mac, machine['fqdn']))
491 return data
492
493 def _disconnect_all_nic(self, machine):
494 """
495 Maas will fail, in case same config's will be to apply
496 on different interfaces. In same time - not possible to push
497 whole network schema at once. Before configuring - need to clean-up
498 everything
499 :param machine:
500 :return:
501 """
502 for nic in machine['interface_set']:
503 LOG.debug("Disconnecting interface:{}".format(nic['mac_address']))
504 try:
505 self._maas.post(
506 u'/api/2.0/nodes/{}/interfaces/{}/'.format(
507 machine['system_id'], nic['id']), 'disconnect')
508 except Exception as e:
509 LOG.error("Failed to disconnect interface:{} on node:{}".format(
510 nic['mac_address'], machine['fqdn']))
511 raise Exception(str(e))
512
513 def _process_interface(self, nic_data, machine):
514 """
515 Process exactly one interface:
516 - update interface
517 - link to network
518 These functions are self-complementary, and do not require an
519 external "process" method. Those broke old-MaasObject logic,
520 though make functions more simple in case iterable tasks.
521 """
522 nic_id = self._get_nic_id_by_mac(machine, nic_data['mac'])
523
524 # Process op=link_subnet
525 link_data = {}
526 _mode = nic_data.get('mode', 'AUTO').upper()
527 if _mode == 'STATIC':
528 link_data = {
529 'mode': 'STATIC',
530 'subnet': str(nic_data.get('subnet')),
531 'ip_address': str(nic_data.get('ip')),
532 'default_gateway': str(nic_data.get('gateway', "")),
533 }
534 elif _mode == 'DHCP':
535 link_data = {
536 'mode': 'DHCP',
537 'subnet': str(nic_data.get('subnet')),
538 }
539 elif _mode == 'AUTO':
540 link_data = {'mode': 'AUTO',
541 'default_gateway': str(nic_data.get('gateway', "")), }
542 elif _mode in ('LINK_UP', 'UNCONFIGURED'):
543 link_data = {'mode': 'LINK_UP'}
544 else:
545 raise Exception('Wrong IP mode:{}'
546 ' for node:{}'.format(_mode, machine['fqdn']))
547 link_data['force'] = str(1)
548
549 physical_data = {"name": nic_data.get("name", ""),
550 "tags": nic_data.get('tags', ""),
551 "vlan": nic_data.get('vlan', "")}
552
553 try:
554 # Cleanup-old definition
555 LOG.debug("Processing {}:{},{}".format(nic_data['mac'], link_data,
556 physical_data))
557 # "link_subnet" and "fill all other data" - its 2 different
558 # operations. So, first we update NIC:
559 self._maas.put(
560 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
561 nic_id),
562 **physical_data)
563 # And then, link subnet configuration:
564 self._maas.post(
565 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
566 nic_id),
567 'link_subnet', **link_data)
568 except Exception as e:
569 LOG.error("Failed to process interface:{} on node:{}".format(
570 nic_data['mac'], machine['fqdn']))
571 raise Exception(str(e))
572
573 def fill_data(self, name, data, machines):
574 machine = machines[name]
575 if machine['status'] == self.DEPLOYED:
576 LOG.debug("Skipping node:{} "
577 "since it in status:DEPLOYED".format(name))
578 return
579 if machine['status'] != self.READY:
580 raise Exception('Machine:{} not in status:READY'.format(name))
581 # backward comparability, for old schema
582 if data.get("interface", None):
583 if 'ip' not in data["interface"]:
584 LOG.info("No IP NIC definition for:{}".format(name))
585 return
586 LOG.warning(
587 "Old machine-describe detected! "
588 "Please read documentation "
589 "'salt-formulas/maas' for migration!")
590 return self._data_old(data['interface'], machines[name])
591 # NewSchema processing:
592 # Warning: old-style MaasObject.process still be called, but
593 # with empty data for process.
594 interfaces = data.get('interfaces', {})
595 if len(interfaces.keys()) == 0:
596 LOG.info("No IP NIC definition for:{}".format(name))
597 return
598 LOG.info('%s for %s', self.__class__.__name__.lower(),
599 machine['fqdn'])
600 self._disconnect_all_nic(machine)
601 for key, value in sorted(interfaces.iteritems()):
602 self._process_interface(value, machine)
603
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100604
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200605class DeployMachines(MaasObject):
azvyagintsev7605a662017-11-03 19:05:04 +0200606 # FIXME
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200607 READY = 4
Andreyef156992017-07-03 14:54:03 -0500608 DEPLOYED = 6
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200609
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200610 def __init__(self):
611 super(DeployMachines, self).__init__()
612 self._all_elements_url = None
613 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
614 self._config_path = 'region.machines'
615 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200616 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
617 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200618
619 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200620 machine = machines[name]
Andreyef156992017-07-03 14:54:03 -0500621 if machine['status'] == self.DEPLOYED:
622 return
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200623 if machine['status'] != self.READY:
624 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200625 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200626 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200627 }
Krzysztof Szukiełojć32677bf2017-04-13 11:04:25 +0200628 if 'distro_series' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200629 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200630 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200631 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200632 return data
633
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200634 def send(self, data):
635 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
Krzysztof Szukiełojć3b7516d2017-04-12 11:52:55 +0200636 self._maas.post(u'api/2.0/machines/', 'allocate', system_id=data['system_id']).read()
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200637 return self._maas.post(self._create_url[0].format(**data),
638 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200639
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100640class BootResource(MaasObject):
641 def __init__(self):
642 super(BootResource, self).__init__()
643 self._all_elements_url = u'api/2.0/boot-resources/'
644 self._create_url = u'api/2.0/boot-resources/'
645 self._update_url = u'api/2.0/boot-resources/{0}/'
646 self._config_path = 'region.boot_resources'
647
648 def fill_data(self, name, boot_data):
649 sha256 = hashlib.sha256()
650 sha256.update(file(boot_data['content']).read())
651 data = {
652 'name': name,
653 'title': boot_data['title'],
654 'architecture': boot_data['architecture'],
655 'filetype': boot_data['filetype'],
656 'size': str(os.path.getsize(boot_data['content'])),
657 'sha256': sha256.hexdigest(),
658 'content': io.open(boot_data['content']),
659 }
660 return data
661
662 def update(self, new, old):
663 self._update = False
664 return new
665
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200666
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100667class CommissioningScripts(MaasObject):
668 def __init__(self):
669 super(CommissioningScripts, self).__init__()
670 self._all_elements_url = u'api/2.0/commissioning-scripts/'
671 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100672 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100673 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100674 self._update_key = 'name'
675
676 def fill_data(self, name, file_path):
677 data = {
678 'name': name,
679 'content': io.open(file_path),
680 }
681 return data
682
683 def update(self, new, old):
684 return new
685
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200686
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100687class MaasConfig(MaasObject):
688 def __init__(self):
689 super(MaasConfig, self).__init__()
690 self._all_elements_url = None
691 self._create_url = (u'api/2.0/maas/', u'set_config')
692 self._config_path = 'region.maas_config'
693
694 def fill_data(self, name, value):
695 data = {
696 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100697 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100698 }
699 return data
700
701 def update(self, new, old):
702 self._update = False
703 return new
704
705
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200706class SSHPrefs(MaasObject):
707 def __init__(self):
708 super(SSHPrefs, self).__init__()
709 self._all_elements_url = None
710 self._create_url = u'api/2.0/account/prefs/sshkeys/'
711 self._config_path = 'region.sshprefs'
712 self._element_key = 'hostname'
713 self._update_key = 'system_id'
714
715 def fill_data(self, value):
716 data = {
717 'key': value,
718 }
719 return data
720
721 def process(self):
722 config = __salt__['config.get']('maas')
723 for part in self._config_path.split('.'):
724 config = config.get(part, {})
725 extra = {}
726 for name, url_call in self._extra_data_urls.iteritems():
727 key = 'id'
728 if isinstance(url_call, tuple):
729 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200730 json_res = json.loads(self._maas.get(url_call).read())
731 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200732 if self._all_elements_url:
733 all_elements = {}
734 elements = self._maas.get(self._all_elements_url).read()
735 res_json = json.loads(elements)
736 for element in res_json:
737 if isinstance(element, (str, unicode)):
738 all_elements[element] = {}
739 else:
740 all_elements[element[self._element_key]] = element
741 else:
742 all_elements = {}
743 ret = {
744 'success': [],
745 'errors': {},
746 'updated': [],
747 }
748 for config_data in config:
749 name = config_data[:10]
750 try:
751 data = self.fill_data(config_data, **extra)
752 self.send(data)
753 ret['success'].append(name)
754 except urllib2.HTTPError as e:
755 etxt = e.read()
756 LOG.exception('Failed for object %s reason %s', name, etxt)
757 ret['errors'][name] = str(etxt)
758 except Exception as e:
759 LOG.exception('Failed for object %s reason %s', name, e)
760 ret['errors'][name] = str(e)
761 if ret['errors']:
762 raise Exception(ret)
763 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200764
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200765
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200766class Domain(MaasObject):
767 def __init__(self):
768 super(Domain, self).__init__()
769 self._all_elements_url = u'/api/2.0/domains/'
770 self._create_url = u'/api/2.0/domains/'
771 self._config_path = 'region.domain'
772 self._update_url = u'/api/2.0/domains/{0}/'
773
774 def fill_data(self, value):
775 data = {
776 'name': value,
777 }
778 self._update = True
779 return data
780
781 def update(self, new, old):
782 new['id'] = str(old['id'])
783 new['authoritative'] = str(old['authoritative'])
784 return new
785
786 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200787 ret = {
788 'success': [],
789 'errors': {},
790 'updated': [],
791 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200792 config = __salt__['config.get']('maas')
793 for part in self._config_path.split('.'):
794 config = config.get(part, {})
795 extra = {}
796 for name, url_call in self._extra_data_urls.iteritems():
797 key = 'id'
798 if isinstance(url_call, tuple):
799 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200800 json_res = json.loads(self._maas.get(url_call).read())
801 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200802 if self._all_elements_url:
803 all_elements = {}
804 elements = self._maas.get(self._all_elements_url).read()
805 res_json = json.loads(elements)
806 for element in res_json:
807 if isinstance(element, (str, unicode)):
808 all_elements[element] = {}
809 else:
810 all_elements[element[self._element_key]] = element
811 else:
812 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200813 try:
814 data = self.fill_data(config, **extra)
815 data = self.update(data, all_elements.values()[0])
816 self.send(data)
817 ret['success'].append('domain')
818 except urllib2.HTTPError as e:
819 etxt = e.read()
820 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
821 ret['errors']['domain'] = str(etxt)
822 except Exception as e:
823 LOG.exception('Failed for object %s reason %s', 'domain', e)
824 ret['errors']['domain'] = str(e)
825 if ret['errors']:
826 raise Exception(ret)
827 return ret
828
829
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200830class MachinesStatus(MaasObject):
831 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200832 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200833 cls._maas = _create_maas_client()
834 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200835 json_result = json.loads(result.read())
836 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200837 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200838 if objects_name:
839 if ',' in objects_name:
840 objects_name = set(objects_name.split(','))
841 else:
842 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200843 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200844 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200845 continue
Michael Polenchuke438bd32017-11-09 20:42:42 +0400846 status = STATUS_NAME_DICT[machine['status']]
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200847 summary[status] += 1
azvyagintsev7605a662017-11-03 19:05:04 +0200848 res.append(
849 {'hostname': machine['hostname'],
850 'system_id': machine['system_id'],
851 'status': status})
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200852 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200853
azvyagintsev7605a662017-11-03 19:05:04 +0200854 @classmethod
855 def wait_for_machine_status(cls, **kwargs):
856 """
857 A function that wait for any requested status, for any set of maas
858 machines.
859
860 If no kwargs has been passed - will try to wait ALL
861 defined in salt::maas::region::machines
862
863 See readme file for more examples.
864 CLI Example:
865 .. code-block:: bash
866
867 salt-call state.apply maas.machines.wait_for_deployed
868
869 :param kwargs:
870 timeout: in s; Global timeout for wait
871 poll_time: in s;Sleep time, between retry
872 req_status: string; Polling status
873 machines: list; machine names
874 ignore_machines: list; machine names
875 :ret: True
876 Exception - if something fail/timeout reached
877 """
878 timeout = kwargs.get("timeout", 60 * 120)
879 poll_time = kwargs.get("poll_time", 30)
880 req_status = kwargs.get("req_status", "Ready")
881 to_discover = kwargs.get("machines", None)
882 ignore_machines = kwargs.get("ignore_machines", None)
883 if not to_discover:
884 try:
885 to_discover = __salt__['config.get']('maas')['region'][
886 'machines'].keys()
887 except KeyError:
888 LOG.warning("No defined machines!")
889 return True
890 total = copy.deepcopy(to_discover) or []
891 if ignore_machines and total:
892 total = [x for x in to_discover if x not in ignore_machines]
893 started_at = time.time()
894 while len(total) <= len(to_discover):
895 for m in to_discover:
896 for discovered in MachinesStatus.execute()['machines']:
897 if m == discovered['hostname'] and \
Michael Polenchuke438bd32017-11-09 20:42:42 +0400898 discovered['status'].lower() == req_status.lower():
899 if m in total:
900 total.remove(m)
901
azvyagintsev7605a662017-11-03 19:05:04 +0200902 if len(total) <= 0:
903 LOG.debug(
904 "Machines:{} are:{}".format(to_discover, req_status))
905 return True
906 if (timeout - (time.time() - started_at)) <= 0:
907 raise Exception(
908 'Machines:{}not in {} state'.format(total, req_status))
909 LOG.info(
910 "Waiting status:{} "
911 "for machines:{}"
912 "\nsleep for:{}s "
913 "Timeout:{}s".format(req_status, total, poll_time, timeout))
914 time.sleep(poll_time)
915
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200916
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100917def process_fabrics():
918 return Fabric().process()
919
Jiri Broulike30a60f2018-04-09 21:15:10 +0200920def process_boot_sources():
921 return Boot_source().process()
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200922
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100923def process_subnets():
924 return Subnet().process()
925
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200926
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100927def process_dhcp_snippets():
928 return DHCPSnippet().process()
929
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200930
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100931def process_package_repositories():
932 return PacketRepository().process()
933
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200934
Krzysztof Szukiełojć889eee92017-04-14 11:45:35 +0200935def process_devices(*args):
936 return Device().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100937
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200938
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200939def process_machines(*args):
940 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100941
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200942
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200943def process_assign_machines_ip(*args):
azvyagintsev06b71e72017-11-08 17:11:07 +0200944 """
945 Manage interface configurations.
946 See readme.rst for more info
947 """
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200948 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200949
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200950
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200951def machines_status(*args):
952 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200953
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200954
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200955def deploy_machines(*args):
956 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200957
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200958
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100959def process_boot_resources():
960 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100961
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200962
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100963def process_maas_config():
964 return MaasConfig().process()
965
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200966
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100967def process_commissioning_scripts():
968 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200969
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200970
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200971def process_domain():
972 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200973
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200974
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200975def process_sshprefs():
976 return SSHPrefs().process()
azvyagintsev7605a662017-11-03 19:05:04 +0200977
978
979def wait_for_machine_status(**kwargs):
980 return MachinesStatus.wait_for_machine_status(**kwargs)