blob: 9ba76e594a7dc2dcb477eaded2b3b329209e43fd [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,
mkraynove95cdb62018-05-08 14:17:18 +0400414 'power_type': power_data.get('power_type', 'manual'),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100415 }
mkraynove95cdb62018-05-08 14:17:18 +0400416 if 'power_address' in power_data:
417 data['power_parameters_power_address'] = power_data['power_address']
Ondrej Smola455003c2017-06-01 22:53:39 +0200418 if 'power_driver' in power_data:
419 data['power_parameters_power_driver'] = power_data['power_driver']
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100420 if 'power_user' in power_data:
421 data['power_parameters_power_user'] = power_data['power_user']
422 if 'power_password' in power_data:
423 data['power_parameters_power_pass'] = \
424 power_data['power_password']
Petr Ruzicka5fe96742017-11-10 14:22:24 +0100425 if 'power_id' in power_data:
426 data['power_parameters_power_id'] = power_data['power_id']
mkraynov47c087a2018-04-26 13:23:31 +0400427 if 'power_nova_id' in power_data:
428 data['power_parameters_nova_id'] = power_data['power_nova_id']
429 if 'power_os_tenantname' in power_data:
430 data['power_parameters_os_tenantname'] = power_data['power_os_tenantname']
431 if 'power_os_username' in power_data:
432 data['power_parameters_os_username'] = power_data['power_os_username']
433 if 'power_os_password' in power_data:
434 data['power_parameters_os_password'] = power_data['power_os_password']
435 if 'power_os_authurl' in power_data:
436 data['power_parameters_os_authurl'] = power_data['power_os_authurl']
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100437 return data
438
439 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100440 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
441 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100442 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100443 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200444 self._maas.delete(u'api/2.0/machines/{0}/'
445 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100446 else:
447 new[self._update_key] = str(old[self._update_key])
448 return new
449
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200450
451class AssignMachinesIP(MaasObject):
azvyagintsev06b71e72017-11-08 17:11:07 +0200452 # FIXME
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200453 READY = 4
Andreyef156992017-07-03 14:54:03 -0500454 DEPLOYED = 6
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200455
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200456 def __init__(self):
457 super(AssignMachinesIP, self).__init__()
458 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200459 self._create_url = \
460 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
461 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200462 self._config_path = 'region.machines'
463 self._element_key = 'hostname'
464 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200465 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
466 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200467
azvyagintsev06b71e72017-11-08 17:11:07 +0200468 def _data_old(self, _interface, _machine):
469 """
470 _interface = {
471 "mac": "11:22:33:44:55:77",
472 "mode": "STATIC",
473 "ip": "2.2.3.15",
474 "subnet": "subnet1",
475 "gateway": "2.2.3.2",
476 }
477 :param data:
478 :return:
479 """
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100480 data = {
481 'mode': 'STATIC',
azvyagintsev06b71e72017-11-08 17:11:07 +0200482 'subnet': str(_interface.get('subnet')),
483 'ip_address': str(_interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100484 }
azvyagintsev06b71e72017-11-08 17:11:07 +0200485 if 'gateway' in _interface:
486 data['default_gateway'] = _interface.get('gateway')
Krzysztof Szukiełojć9449af12017-04-11 14:01:30 +0200487 data['force'] = '1'
azvyagintsev06b71e72017-11-08 17:11:07 +0200488 data['system_id'] = str(_machine['system_id'])
489 data['interface_id'] = str(_machine['interface_set'][0]['id'])
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200490 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100491
azvyagintsev06b71e72017-11-08 17:11:07 +0200492 def _get_nic_id_by_mac(self, machine, req_mac=None):
493 data = {}
494 for nic in machine['interface_set']:
495 data[nic['mac_address']] = nic['id']
496 if req_mac:
497 if req_mac in data.keys():
498 return data[req_mac]
499 else:
500 raise Exception('NIC with mac:{} not found at '
501 'node:{}'.format(req_mac, machine['fqdn']))
502 return data
503
504 def _disconnect_all_nic(self, machine):
505 """
506 Maas will fail, in case same config's will be to apply
507 on different interfaces. In same time - not possible to push
508 whole network schema at once. Before configuring - need to clean-up
509 everything
510 :param machine:
511 :return:
512 """
513 for nic in machine['interface_set']:
514 LOG.debug("Disconnecting interface:{}".format(nic['mac_address']))
515 try:
516 self._maas.post(
517 u'/api/2.0/nodes/{}/interfaces/{}/'.format(
518 machine['system_id'], nic['id']), 'disconnect')
519 except Exception as e:
520 LOG.error("Failed to disconnect interface:{} on node:{}".format(
521 nic['mac_address'], machine['fqdn']))
522 raise Exception(str(e))
523
524 def _process_interface(self, nic_data, machine):
525 """
526 Process exactly one interface:
527 - update interface
528 - link to network
529 These functions are self-complementary, and do not require an
530 external "process" method. Those broke old-MaasObject logic,
531 though make functions more simple in case iterable tasks.
532 """
533 nic_id = self._get_nic_id_by_mac(machine, nic_data['mac'])
534
535 # Process op=link_subnet
536 link_data = {}
537 _mode = nic_data.get('mode', 'AUTO').upper()
538 if _mode == 'STATIC':
539 link_data = {
540 'mode': 'STATIC',
541 'subnet': str(nic_data.get('subnet')),
542 'ip_address': str(nic_data.get('ip')),
543 'default_gateway': str(nic_data.get('gateway', "")),
544 }
545 elif _mode == 'DHCP':
546 link_data = {
547 'mode': 'DHCP',
548 'subnet': str(nic_data.get('subnet')),
549 }
550 elif _mode == 'AUTO':
551 link_data = {'mode': 'AUTO',
552 'default_gateway': str(nic_data.get('gateway', "")), }
553 elif _mode in ('LINK_UP', 'UNCONFIGURED'):
554 link_data = {'mode': 'LINK_UP'}
555 else:
556 raise Exception('Wrong IP mode:{}'
557 ' for node:{}'.format(_mode, machine['fqdn']))
558 link_data['force'] = str(1)
559
560 physical_data = {"name": nic_data.get("name", ""),
561 "tags": nic_data.get('tags', ""),
562 "vlan": nic_data.get('vlan', "")}
563
564 try:
565 # Cleanup-old definition
566 LOG.debug("Processing {}:{},{}".format(nic_data['mac'], link_data,
567 physical_data))
568 # "link_subnet" and "fill all other data" - its 2 different
569 # operations. So, first we update NIC:
570 self._maas.put(
571 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
572 nic_id),
573 **physical_data)
574 # And then, link subnet configuration:
575 self._maas.post(
576 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
577 nic_id),
578 'link_subnet', **link_data)
579 except Exception as e:
580 LOG.error("Failed to process interface:{} on node:{}".format(
581 nic_data['mac'], machine['fqdn']))
582 raise Exception(str(e))
583
584 def fill_data(self, name, data, machines):
585 machine = machines[name]
586 if machine['status'] == self.DEPLOYED:
587 LOG.debug("Skipping node:{} "
588 "since it in status:DEPLOYED".format(name))
589 return
590 if machine['status'] != self.READY:
591 raise Exception('Machine:{} not in status:READY'.format(name))
592 # backward comparability, for old schema
593 if data.get("interface", None):
594 if 'ip' not in data["interface"]:
595 LOG.info("No IP NIC definition for:{}".format(name))
596 return
597 LOG.warning(
598 "Old machine-describe detected! "
599 "Please read documentation "
600 "'salt-formulas/maas' for migration!")
601 return self._data_old(data['interface'], machines[name])
602 # NewSchema processing:
603 # Warning: old-style MaasObject.process still be called, but
604 # with empty data for process.
605 interfaces = data.get('interfaces', {})
606 if len(interfaces.keys()) == 0:
607 LOG.info("No IP NIC definition for:{}".format(name))
608 return
609 LOG.info('%s for %s', self.__class__.__name__.lower(),
610 machine['fqdn'])
611 self._disconnect_all_nic(machine)
612 for key, value in sorted(interfaces.iteritems()):
613 self._process_interface(value, machine)
614
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100615
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200616class DeployMachines(MaasObject):
azvyagintsev7605a662017-11-03 19:05:04 +0200617 # FIXME
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200618 READY = 4
Andreyef156992017-07-03 14:54:03 -0500619 DEPLOYED = 6
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200620
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200621 def __init__(self):
622 super(DeployMachines, self).__init__()
623 self._all_elements_url = None
624 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
625 self._config_path = 'region.machines'
626 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200627 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
628 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200629
630 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200631 machine = machines[name]
Andreyef156992017-07-03 14:54:03 -0500632 if machine['status'] == self.DEPLOYED:
633 return
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200634 if machine['status'] != self.READY:
635 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200636 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200637 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200638 }
Krzysztof Szukiełojć32677bf2017-04-13 11:04:25 +0200639 if 'distro_series' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200640 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200641 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200642 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200643 return data
644
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200645 def send(self, data):
646 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
Krzysztof Szukiełojć3b7516d2017-04-12 11:52:55 +0200647 self._maas.post(u'api/2.0/machines/', 'allocate', system_id=data['system_id']).read()
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200648 return self._maas.post(self._create_url[0].format(**data),
649 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200650
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100651class BootResource(MaasObject):
652 def __init__(self):
653 super(BootResource, self).__init__()
654 self._all_elements_url = u'api/2.0/boot-resources/'
655 self._create_url = u'api/2.0/boot-resources/'
656 self._update_url = u'api/2.0/boot-resources/{0}/'
657 self._config_path = 'region.boot_resources'
658
659 def fill_data(self, name, boot_data):
660 sha256 = hashlib.sha256()
661 sha256.update(file(boot_data['content']).read())
662 data = {
663 'name': name,
664 'title': boot_data['title'],
665 'architecture': boot_data['architecture'],
666 'filetype': boot_data['filetype'],
667 'size': str(os.path.getsize(boot_data['content'])),
668 'sha256': sha256.hexdigest(),
669 'content': io.open(boot_data['content']),
670 }
671 return data
672
673 def update(self, new, old):
674 self._update = False
675 return new
676
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200677
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100678class CommissioningScripts(MaasObject):
679 def __init__(self):
680 super(CommissioningScripts, self).__init__()
681 self._all_elements_url = u'api/2.0/commissioning-scripts/'
682 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100683 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100684 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100685 self._update_key = 'name'
686
687 def fill_data(self, name, file_path):
688 data = {
689 'name': name,
690 'content': io.open(file_path),
691 }
692 return data
693
694 def update(self, new, old):
695 return new
696
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200697
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100698class MaasConfig(MaasObject):
699 def __init__(self):
700 super(MaasConfig, self).__init__()
701 self._all_elements_url = None
702 self._create_url = (u'api/2.0/maas/', u'set_config')
703 self._config_path = 'region.maas_config'
704
705 def fill_data(self, name, value):
706 data = {
707 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100708 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100709 }
710 return data
711
712 def update(self, new, old):
713 self._update = False
714 return new
715
716
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200717class SSHPrefs(MaasObject):
718 def __init__(self):
719 super(SSHPrefs, self).__init__()
720 self._all_elements_url = None
721 self._create_url = u'api/2.0/account/prefs/sshkeys/'
722 self._config_path = 'region.sshprefs'
723 self._element_key = 'hostname'
724 self._update_key = 'system_id'
725
726 def fill_data(self, value):
727 data = {
728 'key': value,
729 }
730 return data
731
732 def process(self):
733 config = __salt__['config.get']('maas')
734 for part in self._config_path.split('.'):
735 config = config.get(part, {})
736 extra = {}
737 for name, url_call in self._extra_data_urls.iteritems():
738 key = 'id'
739 if isinstance(url_call, tuple):
740 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200741 json_res = json.loads(self._maas.get(url_call).read())
742 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200743 if self._all_elements_url:
744 all_elements = {}
745 elements = self._maas.get(self._all_elements_url).read()
746 res_json = json.loads(elements)
747 for element in res_json:
748 if isinstance(element, (str, unicode)):
749 all_elements[element] = {}
750 else:
751 all_elements[element[self._element_key]] = element
752 else:
753 all_elements = {}
754 ret = {
755 'success': [],
756 'errors': {},
757 'updated': [],
758 }
759 for config_data in config:
760 name = config_data[:10]
761 try:
762 data = self.fill_data(config_data, **extra)
763 self.send(data)
764 ret['success'].append(name)
765 except urllib2.HTTPError as e:
766 etxt = e.read()
767 LOG.exception('Failed for object %s reason %s', name, etxt)
768 ret['errors'][name] = str(etxt)
769 except Exception as e:
770 LOG.exception('Failed for object %s reason %s', name, e)
771 ret['errors'][name] = str(e)
772 if ret['errors']:
773 raise Exception(ret)
774 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200775
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200776
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200777class Domain(MaasObject):
778 def __init__(self):
779 super(Domain, self).__init__()
780 self._all_elements_url = u'/api/2.0/domains/'
781 self._create_url = u'/api/2.0/domains/'
782 self._config_path = 'region.domain'
783 self._update_url = u'/api/2.0/domains/{0}/'
784
785 def fill_data(self, value):
786 data = {
787 'name': value,
788 }
789 self._update = True
790 return data
791
792 def update(self, new, old):
793 new['id'] = str(old['id'])
794 new['authoritative'] = str(old['authoritative'])
795 return new
796
797 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200798 ret = {
799 'success': [],
800 'errors': {},
801 'updated': [],
802 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200803 config = __salt__['config.get']('maas')
804 for part in self._config_path.split('.'):
805 config = config.get(part, {})
806 extra = {}
807 for name, url_call in self._extra_data_urls.iteritems():
808 key = 'id'
809 if isinstance(url_call, tuple):
810 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200811 json_res = json.loads(self._maas.get(url_call).read())
812 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200813 if self._all_elements_url:
814 all_elements = {}
815 elements = self._maas.get(self._all_elements_url).read()
816 res_json = json.loads(elements)
817 for element in res_json:
818 if isinstance(element, (str, unicode)):
819 all_elements[element] = {}
820 else:
821 all_elements[element[self._element_key]] = element
822 else:
823 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200824 try:
825 data = self.fill_data(config, **extra)
826 data = self.update(data, all_elements.values()[0])
827 self.send(data)
828 ret['success'].append('domain')
829 except urllib2.HTTPError as e:
830 etxt = e.read()
831 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
832 ret['errors']['domain'] = str(etxt)
833 except Exception as e:
834 LOG.exception('Failed for object %s reason %s', 'domain', e)
835 ret['errors']['domain'] = str(e)
836 if ret['errors']:
837 raise Exception(ret)
838 return ret
839
840
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200841class MachinesStatus(MaasObject):
842 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200843 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200844 cls._maas = _create_maas_client()
845 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200846 json_result = json.loads(result.read())
847 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200848 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200849 if objects_name:
850 if ',' in objects_name:
851 objects_name = set(objects_name.split(','))
852 else:
853 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200854 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200855 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200856 continue
Michael Polenchuke438bd32017-11-09 20:42:42 +0400857 status = STATUS_NAME_DICT[machine['status']]
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200858 summary[status] += 1
azvyagintsev7605a662017-11-03 19:05:04 +0200859 res.append(
860 {'hostname': machine['hostname'],
861 'system_id': machine['system_id'],
862 'status': status})
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200863 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200864
azvyagintsev7605a662017-11-03 19:05:04 +0200865 @classmethod
866 def wait_for_machine_status(cls, **kwargs):
867 """
868 A function that wait for any requested status, for any set of maas
869 machines.
870
871 If no kwargs has been passed - will try to wait ALL
872 defined in salt::maas::region::machines
873
874 See readme file for more examples.
875 CLI Example:
876 .. code-block:: bash
877
878 salt-call state.apply maas.machines.wait_for_deployed
879
880 :param kwargs:
881 timeout: in s; Global timeout for wait
882 poll_time: in s;Sleep time, between retry
883 req_status: string; Polling status
884 machines: list; machine names
885 ignore_machines: list; machine names
886 :ret: True
887 Exception - if something fail/timeout reached
888 """
889 timeout = kwargs.get("timeout", 60 * 120)
890 poll_time = kwargs.get("poll_time", 30)
891 req_status = kwargs.get("req_status", "Ready")
892 to_discover = kwargs.get("machines", None)
893 ignore_machines = kwargs.get("ignore_machines", None)
894 if not to_discover:
895 try:
896 to_discover = __salt__['config.get']('maas')['region'][
897 'machines'].keys()
898 except KeyError:
899 LOG.warning("No defined machines!")
900 return True
901 total = copy.deepcopy(to_discover) or []
902 if ignore_machines and total:
903 total = [x for x in to_discover if x not in ignore_machines]
904 started_at = time.time()
905 while len(total) <= len(to_discover):
906 for m in to_discover:
907 for discovered in MachinesStatus.execute()['machines']:
908 if m == discovered['hostname'] and \
Michael Polenchuke438bd32017-11-09 20:42:42 +0400909 discovered['status'].lower() == req_status.lower():
910 if m in total:
911 total.remove(m)
912
azvyagintsev7605a662017-11-03 19:05:04 +0200913 if len(total) <= 0:
914 LOG.debug(
915 "Machines:{} are:{}".format(to_discover, req_status))
916 return True
917 if (timeout - (time.time() - started_at)) <= 0:
918 raise Exception(
919 'Machines:{}not in {} state'.format(total, req_status))
920 LOG.info(
921 "Waiting status:{} "
922 "for machines:{}"
923 "\nsleep for:{}s "
924 "Timeout:{}s".format(req_status, total, poll_time, timeout))
925 time.sleep(poll_time)
926
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200927
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100928def process_fabrics():
929 return Fabric().process()
930
Jiri Broulike30a60f2018-04-09 21:15:10 +0200931def process_boot_sources():
932 return Boot_source().process()
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200933
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100934def process_subnets():
935 return Subnet().process()
936
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200937
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100938def process_dhcp_snippets():
939 return DHCPSnippet().process()
940
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200941
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100942def process_package_repositories():
943 return PacketRepository().process()
944
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200945
Krzysztof Szukiełojć889eee92017-04-14 11:45:35 +0200946def process_devices(*args):
947 return Device().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100948
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200949
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200950def process_machines(*args):
951 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100952
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200953
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200954def process_assign_machines_ip(*args):
azvyagintsev06b71e72017-11-08 17:11:07 +0200955 """
956 Manage interface configurations.
957 See readme.rst for more info
958 """
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200959 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200960
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200961
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200962def machines_status(*args):
963 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200964
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200965
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200966def deploy_machines(*args):
967 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200968
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200969
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100970def process_boot_resources():
971 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100972
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200973
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100974def process_maas_config():
975 return MaasConfig().process()
976
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200977
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100978def process_commissioning_scripts():
979 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200980
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200981
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200982def process_domain():
983 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200984
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200985
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200986def process_sshprefs():
987 return SSHPrefs().process()
azvyagintsev7605a662017-11-03 19:05:04 +0200988
989
990def wait_for_machine_status(**kwargs):
991 return MachinesStatus.wait_for_machine_status(**kwargs)