blob: b0f5bb2cb13f97f9c15251bdbaab94a31c792bef [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,
Alexandru Avadanii652e7552017-08-19 02:03:01 +0200221 'fabric': str(fabrics[subnet.get('fabric',
222 self._get_fabric_from_cidr(subnet.get('cidr')))]),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100223 'cidr': subnet.get('cidr'),
224 'gateway_ip': subnet['gateway_ip'],
225 }
226 self._iprange = subnet['iprange']
227 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100228
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100229 def update(self, new, old):
230 new['id'] = str(old['id'])
231 return new
Ales Komarek0fafa572016-03-11 14:56:44 +0100232
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100233 def send(self, data):
234 response = super(Subnet, self).send(data)
235 res_json = json.loads(response)
236 self._process_iprange(res_json['id'])
237 return response
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100238
Alexandru Avadanii652e7552017-08-19 02:03:01 +0200239 def _get_fabric_from_cidr(self, cidr):
240 subnets = json.loads(self._maas.get(u'api/2.0/subnets/').read())
241 for subnet in subnets:
242 if subnet['cidr'] == cidr:
243 return subnet['vlan']['fabric']
244 return ''
245
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100246 def _process_iprange(self, subnet_id):
247 ipranges = json.loads(self._maas.get(u'api/2.0/ipranges/').read())
248 LOG.warn('all %s ipranges %s', subnet_id, ipranges)
249 update = False
250 old_data = None
251 for iprange in ipranges:
252 if iprange['subnet']['id'] == subnet_id:
253 update = True
254 old_data = iprange
255 break
256 data = {
257 'start_ip': self._iprange.get('start'),
258 'end_ip': self._iprange.get('end'),
259 'subnet': str(subnet_id),
260 'type': self._iprange.get('type', 'dynamic')
261 }
262 LOG.warn('INFO: %s\n OLD: %s', data, old_data)
263 LOG.info('iprange %s', _format_data(data))
264 if update:
265 LOG.warn('UPDATING %s %s', data, old_data)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200266 self._maas.put(u'api/2.0/ipranges/{0}/'.format(old_data['id']),
267 **data)
smolaonc3385f82016-03-11 19:01:24 +0100268 else:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100269 self._maas.post(u'api/2.0/ipranges/', None, **data)
smolaonc3385f82016-03-11 19:01:24 +0100270
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200271
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100272class DHCPSnippet(MaasObject):
273 def __init__(self):
274 super(DHCPSnippet, self).__init__()
275 self._all_elements_url = u'api/2.0/dhcp-snippets/'
276 self._create_url = u'api/2.0/dhcp-snippets/'
277 self._update_url = u'api/2.0/dhcp-snippets/{0}/'
278 self._config_path = 'region.dhcp_snippets'
279 self._extra_data_urls = {'subnets': u'api/2.0/subnets/'}
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100280
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100281 def fill_data(self, name, snippet, subnets):
282 data = {
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200283 'name': name,
284 'value': snippet['value'],
285 'description': snippet['description'],
286 'enabled': str(snippet['enabled'] and 1 or 0),
287 'subnet': str(subnets[snippet['subnet']]),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100288 }
289 return data
smolaonc3385f82016-03-11 19:01:24 +0100290
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100291 def update(self, new, old):
292 new['id'] = str(old['id'])
293 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100294
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200295
Jiri Broulike30a60f2018-04-09 21:15:10 +0200296class Boot_source(MaasObject):
297 def __init__(self):
298 super(Boot_source, self).__init__()
299 self._all_elements_url = u'api/2.0/boot-sources/'
300 self._create_url = u'api/2.0/boot-sources/'
301 self._update_url = u'api/2.0/boot-sources/{0}/'
302 self._config_path = 'region.boot_sources'
303 self._element_key = 'id'
304
305 def fill_data(self, name, boot_source):
306 data = {
307 'name': name,
308 'url': boot_source.get('url', ''),
309 'keyring_filename': boot_source.get('keyring_file', ''),
310 }
311 return data
312
313 def update(self, new, old):
314 new['id'] = str(old['id'])
315 return new
316
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100317class PacketRepository(MaasObject):
318 def __init__(self):
319 super(PacketRepository, self).__init__()
320 self._all_elements_url = u'api/2.0/package-repositories/'
321 self._create_url = u'api/2.0/package-repositories/'
322 self._update_url = u'api/2.0/package-repositories/{0}/'
323 self._config_path = 'region.package_repositories'
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100324
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100325 def fill_data(self, name, package_repository):
326 data = {
327 'name': name,
328 'url': package_repository['url'],
329 'distributions': package_repository['distributions'],
330 'components': package_repository['components'],
331 'arches': package_repository['arches'],
332 'key': package_repository['key'],
333 'enabled': str(package_repository['enabled'] and 1 or 0),
334 }
335 if 'disabled_pockets' in package_repository:
336 data['disabled_pockets'] = package_repository['disable_pockets'],
337 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100338
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100339 def update(self, new, old):
340 new['id'] = str(old['id'])
341 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100342
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200343
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100344class Device(MaasObject):
345 def __init__(self):
346 super(Device, self).__init__()
347 self._all_elements_url = u'api/2.0/devices/'
348 self._create_url = u'api/2.0/devices/'
349 self._update_url = u'api/2.0/devices/{0}/'
350 self._config_path = 'region.devices'
351 self._element_key = 'hostname'
352 self._update_key = 'system_id'
smolaonc3385f82016-03-11 19:01:24 +0100353
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100354 def fill_data(self, name, device_data):
355 data = {
356 'mac_addresses': device_data['mac'],
357 'hostname': name,
358 }
359 self._interface = device_data['interface']
360 return data
361
362 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100363 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
364 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100365 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100366 LOG.info('Mac changed deleting old device %s', old['system_id'])
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100367 self._maas.delete(u'api/2.0/devices/{0}/'.format(old['system_id']))
368 else:
369 new[self._update_key] = str(old[self._update_key])
370 return new
371
372 def send(self, data):
373 response = super(Device, self).send(data)
374 resp_json = json.loads(response)
375 system_id = resp_json['system_id']
376 iface_id = resp_json['interface_set'][0]['id']
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100377 self._link_interface(system_id, iface_id)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100378 return response
379
380 def _link_interface(self, system_id, interface_id):
381 data = {
382 'mode': self._interface.get('mode', 'STATIC'),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100383 'subnet': self._interface['subnet'],
384 'ip_address': self._interface['ip_address'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100385 }
386 if 'default_gateway' in self._interface:
387 data['default_gateway'] = self._interface.get('default_gateway')
388 if self._update:
389 data['force'] = '1'
390 LOG.info('interfaces link_subnet %s %s %s', system_id, interface_id,
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200391 _format_data(data))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100392 self._maas.post(u'/api/2.0/nodes/{0}/interfaces/{1}/'
393 .format(system_id, interface_id), 'link_subnet',
394 **data)
395
396
397class Machine(MaasObject):
398 def __init__(self):
399 super(Machine, self).__init__()
400 self._all_elements_url = u'api/2.0/machines/'
401 self._create_url = u'api/2.0/machines/'
402 self._update_url = u'api/2.0/machines/{0}/'
403 self._config_path = 'region.machines'
404 self._element_key = 'hostname'
405 self._update_key = 'system_id'
406
407 def fill_data(self, name, machine_data):
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100408 power_data = machine_data['power_parameters']
azvyagintsev06b71e72017-11-08 17:11:07 +0200409 machine_pxe_mac = machine_data.get('pxe_interface_mac', None)
410 if machine_data.get("interface", None):
411 LOG.warning(
412 "Old machine-describe detected! "
413 "Please read documentation for "
414 "'salt-formulas/maas' for migration!")
415 machine_pxe_mac = machine_data['interface'].get('mac', None)
416 if not machine_pxe_mac:
417 raise Exception("PXE MAC for machine:{} not defined".format(name))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100418 data = {
419 'hostname': name,
420 'architecture': machine_data.get('architecture', 'amd64/generic'),
azvyagintsev06b71e72017-11-08 17:11:07 +0200421 'mac_addresses': machine_pxe_mac,
mkraynove95cdb62018-05-08 14:17:18 +0400422 'power_type': power_data.get('power_type', 'manual'),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100423 }
Denis Egorenko60963332018-10-23 19:24:28 +0400424 for k,v in power_data.items():
425 if k == 'power_type':
426 continue
427 data_key = 'power_parameters_{}'.format(k)
428 data[data_key] = v
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100429 return data
430
431 def update(self, new, old):
Gabor Toth36bc0282018-08-18 10:39:51 +0200432 LOG.debug('Updating machine')
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100433 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
Gabor Toth36bc0282018-08-18 10:39:51 +0200434 LOG.debug('old_macs: %s' % old_macs)
435 if isinstance(new['mac_addresses'], list):
436 new_macs = set(v.lower() for v in new['mac_addresses'])
437 else:
438 new_macs = set([new['mac_addresses'].lower()])
439 LOG.debug('new_macs: %s' % new_macs)
440 intersect = list(new_macs.intersection(old_macs))
441 if not intersect:
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:
Gabor Toth36bc0282018-08-18 10:39:51 +0200447 new['mac_addresses'] = intersect
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100448 new[self._update_key] = str(old[self._update_key])
449 return new
450
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200451
452class AssignMachinesIP(MaasObject):
azvyagintsev06b71e72017-11-08 17:11:07 +0200453 # FIXME
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200454 READY = 4
Andreyef156992017-07-03 14:54:03 -0500455 DEPLOYED = 6
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200456
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200457 def __init__(self):
458 super(AssignMachinesIP, self).__init__()
459 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200460 self._create_url = \
461 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
462 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200463 self._config_path = 'region.machines'
464 self._element_key = 'hostname'
465 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200466 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
467 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200468
azvyagintsev06b71e72017-11-08 17:11:07 +0200469 def _data_old(self, _interface, _machine):
470 """
471 _interface = {
472 "mac": "11:22:33:44:55:77",
473 "mode": "STATIC",
474 "ip": "2.2.3.15",
475 "subnet": "subnet1",
476 "gateway": "2.2.3.2",
477 }
478 :param data:
479 :return:
480 """
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100481 data = {
482 'mode': 'STATIC',
azvyagintsev06b71e72017-11-08 17:11:07 +0200483 'subnet': str(_interface.get('subnet')),
484 'ip_address': str(_interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100485 }
azvyagintsev06b71e72017-11-08 17:11:07 +0200486 if 'gateway' in _interface:
487 data['default_gateway'] = _interface.get('gateway')
Krzysztof Szukiełojć9449af12017-04-11 14:01:30 +0200488 data['force'] = '1'
azvyagintsev06b71e72017-11-08 17:11:07 +0200489 data['system_id'] = str(_machine['system_id'])
490 data['interface_id'] = str(_machine['interface_set'][0]['id'])
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200491 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100492
azvyagintsev06b71e72017-11-08 17:11:07 +0200493 def _get_nic_id_by_mac(self, machine, req_mac=None):
494 data = {}
495 for nic in machine['interface_set']:
496 data[nic['mac_address']] = nic['id']
497 if req_mac:
498 if req_mac in data.keys():
499 return data[req_mac]
500 else:
501 raise Exception('NIC with mac:{} not found at '
502 'node:{}'.format(req_mac, machine['fqdn']))
503 return data
504
505 def _disconnect_all_nic(self, machine):
506 """
507 Maas will fail, in case same config's will be to apply
508 on different interfaces. In same time - not possible to push
509 whole network schema at once. Before configuring - need to clean-up
510 everything
511 :param machine:
512 :return:
513 """
514 for nic in machine['interface_set']:
515 LOG.debug("Disconnecting interface:{}".format(nic['mac_address']))
516 try:
517 self._maas.post(
518 u'/api/2.0/nodes/{}/interfaces/{}/'.format(
519 machine['system_id'], nic['id']), 'disconnect')
520 except Exception as e:
521 LOG.error("Failed to disconnect interface:{} on node:{}".format(
522 nic['mac_address'], machine['fqdn']))
523 raise Exception(str(e))
524
525 def _process_interface(self, nic_data, machine):
526 """
527 Process exactly one interface:
528 - update interface
529 - link to network
530 These functions are self-complementary, and do not require an
531 external "process" method. Those broke old-MaasObject logic,
532 though make functions more simple in case iterable tasks.
533 """
534 nic_id = self._get_nic_id_by_mac(machine, nic_data['mac'])
535
536 # Process op=link_subnet
537 link_data = {}
538 _mode = nic_data.get('mode', 'AUTO').upper()
539 if _mode == 'STATIC':
540 link_data = {
541 'mode': 'STATIC',
542 'subnet': str(nic_data.get('subnet')),
543 'ip_address': str(nic_data.get('ip')),
544 'default_gateway': str(nic_data.get('gateway', "")),
545 }
546 elif _mode == 'DHCP':
547 link_data = {
548 'mode': 'DHCP',
549 'subnet': str(nic_data.get('subnet')),
550 }
551 elif _mode == 'AUTO':
552 link_data = {'mode': 'AUTO',
553 'default_gateway': str(nic_data.get('gateway', "")), }
554 elif _mode in ('LINK_UP', 'UNCONFIGURED'):
555 link_data = {'mode': 'LINK_UP'}
556 else:
557 raise Exception('Wrong IP mode:{}'
558 ' for node:{}'.format(_mode, machine['fqdn']))
559 link_data['force'] = str(1)
560
561 physical_data = {"name": nic_data.get("name", ""),
562 "tags": nic_data.get('tags', ""),
563 "vlan": nic_data.get('vlan', "")}
564
565 try:
566 # Cleanup-old definition
567 LOG.debug("Processing {}:{},{}".format(nic_data['mac'], link_data,
568 physical_data))
569 # "link_subnet" and "fill all other data" - its 2 different
570 # operations. So, first we update NIC:
571 self._maas.put(
572 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
573 nic_id),
574 **physical_data)
575 # And then, link subnet configuration:
576 self._maas.post(
577 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
578 nic_id),
579 'link_subnet', **link_data)
580 except Exception as e:
581 LOG.error("Failed to process interface:{} on node:{}".format(
582 nic_data['mac'], machine['fqdn']))
583 raise Exception(str(e))
584
585 def fill_data(self, name, data, machines):
586 machine = machines[name]
587 if machine['status'] == self.DEPLOYED:
588 LOG.debug("Skipping node:{} "
589 "since it in status:DEPLOYED".format(name))
590 return
591 if machine['status'] != self.READY:
592 raise Exception('Machine:{} not in status:READY'.format(name))
593 # backward comparability, for old schema
594 if data.get("interface", None):
595 if 'ip' not in data["interface"]:
596 LOG.info("No IP NIC definition for:{}".format(name))
597 return
598 LOG.warning(
599 "Old machine-describe detected! "
600 "Please read documentation "
601 "'salt-formulas/maas' for migration!")
602 return self._data_old(data['interface'], machines[name])
603 # NewSchema processing:
604 # Warning: old-style MaasObject.process still be called, but
605 # with empty data for process.
606 interfaces = data.get('interfaces', {})
607 if len(interfaces.keys()) == 0:
608 LOG.info("No IP NIC definition for:{}".format(name))
609 return
610 LOG.info('%s for %s', self.__class__.__name__.lower(),
611 machine['fqdn'])
612 self._disconnect_all_nic(machine)
613 for key, value in sorted(interfaces.iteritems()):
614 self._process_interface(value, machine)
615
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100616
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200617class DeployMachines(MaasObject):
azvyagintsev7605a662017-11-03 19:05:04 +0200618 # FIXME
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200619 READY = 4
Andreyef156992017-07-03 14:54:03 -0500620 DEPLOYED = 6
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200621
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200622 def __init__(self):
623 super(DeployMachines, self).__init__()
624 self._all_elements_url = None
625 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
626 self._config_path = 'region.machines'
627 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200628 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
629 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200630
631 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200632 machine = machines[name]
Andreyef156992017-07-03 14:54:03 -0500633 if machine['status'] == self.DEPLOYED:
634 return
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200635 if machine['status'] != self.READY:
636 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200637 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200638 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200639 }
Krzysztof Szukiełojć32677bf2017-04-13 11:04:25 +0200640 if 'distro_series' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200641 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200642 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200643 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200644 return data
645
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200646 def send(self, data):
647 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
Krzysztof Szukiełojć3b7516d2017-04-12 11:52:55 +0200648 self._maas.post(u'api/2.0/machines/', 'allocate', system_id=data['system_id']).read()
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200649 return self._maas.post(self._create_url[0].format(**data),
650 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200651
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100652class BootResource(MaasObject):
653 def __init__(self):
654 super(BootResource, self).__init__()
655 self._all_elements_url = u'api/2.0/boot-resources/'
656 self._create_url = u'api/2.0/boot-resources/'
657 self._update_url = u'api/2.0/boot-resources/{0}/'
658 self._config_path = 'region.boot_resources'
659
660 def fill_data(self, name, boot_data):
661 sha256 = hashlib.sha256()
662 sha256.update(file(boot_data['content']).read())
663 data = {
664 'name': name,
665 'title': boot_data['title'],
666 'architecture': boot_data['architecture'],
667 'filetype': boot_data['filetype'],
668 'size': str(os.path.getsize(boot_data['content'])),
669 'sha256': sha256.hexdigest(),
670 'content': io.open(boot_data['content']),
671 }
672 return data
673
674 def update(self, new, old):
675 self._update = False
676 return new
677
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200678
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100679class CommissioningScripts(MaasObject):
680 def __init__(self):
681 super(CommissioningScripts, self).__init__()
682 self._all_elements_url = u'api/2.0/commissioning-scripts/'
683 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100684 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100685 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100686 self._update_key = 'name'
687
688 def fill_data(self, name, file_path):
689 data = {
690 'name': name,
691 'content': io.open(file_path),
692 }
693 return data
694
695 def update(self, new, old):
696 return new
697
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200698
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100699class MaasConfig(MaasObject):
700 def __init__(self):
701 super(MaasConfig, self).__init__()
702 self._all_elements_url = None
703 self._create_url = (u'api/2.0/maas/', u'set_config')
704 self._config_path = 'region.maas_config'
705
706 def fill_data(self, name, value):
707 data = {
708 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100709 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100710 }
711 return data
712
713 def update(self, new, old):
714 self._update = False
715 return new
716
717
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200718class SSHPrefs(MaasObject):
719 def __init__(self):
720 super(SSHPrefs, self).__init__()
721 self._all_elements_url = None
722 self._create_url = u'api/2.0/account/prefs/sshkeys/'
723 self._config_path = 'region.sshprefs'
724 self._element_key = 'hostname'
725 self._update_key = 'system_id'
726
727 def fill_data(self, value):
728 data = {
729 'key': value,
730 }
731 return data
732
733 def process(self):
734 config = __salt__['config.get']('maas')
735 for part in self._config_path.split('.'):
736 config = config.get(part, {})
737 extra = {}
738 for name, url_call in self._extra_data_urls.iteritems():
739 key = 'id'
740 if isinstance(url_call, tuple):
741 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200742 json_res = json.loads(self._maas.get(url_call).read())
743 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200744 if self._all_elements_url:
745 all_elements = {}
746 elements = self._maas.get(self._all_elements_url).read()
747 res_json = json.loads(elements)
748 for element in res_json:
749 if isinstance(element, (str, unicode)):
750 all_elements[element] = {}
751 else:
752 all_elements[element[self._element_key]] = element
753 else:
754 all_elements = {}
755 ret = {
756 'success': [],
757 'errors': {},
758 'updated': [],
759 }
760 for config_data in config:
761 name = config_data[:10]
762 try:
763 data = self.fill_data(config_data, **extra)
764 self.send(data)
765 ret['success'].append(name)
766 except urllib2.HTTPError as e:
767 etxt = e.read()
768 LOG.exception('Failed for object %s reason %s', name, etxt)
769 ret['errors'][name] = str(etxt)
770 except Exception as e:
771 LOG.exception('Failed for object %s reason %s', name, e)
772 ret['errors'][name] = str(e)
773 if ret['errors']:
774 raise Exception(ret)
775 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200776
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200777
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200778class Domain(MaasObject):
779 def __init__(self):
780 super(Domain, self).__init__()
781 self._all_elements_url = u'/api/2.0/domains/'
782 self._create_url = u'/api/2.0/domains/'
783 self._config_path = 'region.domain'
784 self._update_url = u'/api/2.0/domains/{0}/'
785
786 def fill_data(self, value):
787 data = {
788 'name': value,
789 }
790 self._update = True
791 return data
792
793 def update(self, new, old):
794 new['id'] = str(old['id'])
795 new['authoritative'] = str(old['authoritative'])
796 return new
797
798 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200799 ret = {
800 'success': [],
801 'errors': {},
802 'updated': [],
803 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200804 config = __salt__['config.get']('maas')
805 for part in self._config_path.split('.'):
806 config = config.get(part, {})
807 extra = {}
808 for name, url_call in self._extra_data_urls.iteritems():
809 key = 'id'
810 if isinstance(url_call, tuple):
811 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200812 json_res = json.loads(self._maas.get(url_call).read())
813 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200814 if self._all_elements_url:
815 all_elements = {}
816 elements = self._maas.get(self._all_elements_url).read()
817 res_json = json.loads(elements)
818 for element in res_json:
819 if isinstance(element, (str, unicode)):
820 all_elements[element] = {}
821 else:
822 all_elements[element[self._element_key]] = element
823 else:
824 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200825 try:
826 data = self.fill_data(config, **extra)
827 data = self.update(data, all_elements.values()[0])
828 self.send(data)
829 ret['success'].append('domain')
830 except urllib2.HTTPError as e:
831 etxt = e.read()
832 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
833 ret['errors']['domain'] = str(etxt)
834 except Exception as e:
835 LOG.exception('Failed for object %s reason %s', 'domain', e)
836 ret['errors']['domain'] = str(e)
837 if ret['errors']:
838 raise Exception(ret)
839 return ret
840
841
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200842class MachinesStatus(MaasObject):
843 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200844 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200845 cls._maas = _create_maas_client()
846 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200847 json_result = json.loads(result.read())
848 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200849 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200850 if objects_name:
851 if ',' in objects_name:
852 objects_name = set(objects_name.split(','))
853 else:
854 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200855 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200856 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200857 continue
Michael Polenchuke438bd32017-11-09 20:42:42 +0400858 status = STATUS_NAME_DICT[machine['status']]
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200859 summary[status] += 1
azvyagintsev7605a662017-11-03 19:05:04 +0200860 res.append(
861 {'hostname': machine['hostname'],
862 'system_id': machine['system_id'],
863 'status': status})
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200864 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200865
azvyagintsev7605a662017-11-03 19:05:04 +0200866 @classmethod
867 def wait_for_machine_status(cls, **kwargs):
868 """
869 A function that wait for any requested status, for any set of maas
870 machines.
871
872 If no kwargs has been passed - will try to wait ALL
873 defined in salt::maas::region::machines
874
875 See readme file for more examples.
876 CLI Example:
877 .. code-block:: bash
878
879 salt-call state.apply maas.machines.wait_for_deployed
880
881 :param kwargs:
882 timeout: in s; Global timeout for wait
883 poll_time: in s;Sleep time, between retry
884 req_status: string; Polling status
885 machines: list; machine names
886 ignore_machines: list; machine names
887 :ret: True
888 Exception - if something fail/timeout reached
889 """
890 timeout = kwargs.get("timeout", 60 * 120)
891 poll_time = kwargs.get("poll_time", 30)
892 req_status = kwargs.get("req_status", "Ready")
893 to_discover = kwargs.get("machines", None)
894 ignore_machines = kwargs.get("ignore_machines", None)
895 if not to_discover:
896 try:
897 to_discover = __salt__['config.get']('maas')['region'][
898 'machines'].keys()
899 except KeyError:
900 LOG.warning("No defined machines!")
901 return True
902 total = copy.deepcopy(to_discover) or []
903 if ignore_machines and total:
904 total = [x for x in to_discover if x not in ignore_machines]
905 started_at = time.time()
906 while len(total) <= len(to_discover):
907 for m in to_discover:
908 for discovered in MachinesStatus.execute()['machines']:
909 if m == discovered['hostname'] and \
Michael Polenchuke438bd32017-11-09 20:42:42 +0400910 discovered['status'].lower() == req_status.lower():
911 if m in total:
912 total.remove(m)
913
azvyagintsev7605a662017-11-03 19:05:04 +0200914 if len(total) <= 0:
915 LOG.debug(
916 "Machines:{} are:{}".format(to_discover, req_status))
917 return True
918 if (timeout - (time.time() - started_at)) <= 0:
919 raise Exception(
920 'Machines:{}not in {} state'.format(total, req_status))
921 LOG.info(
922 "Waiting status:{} "
923 "for machines:{}"
924 "\nsleep for:{}s "
925 "Timeout:{}s".format(req_status, total, poll_time, timeout))
926 time.sleep(poll_time)
927
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200928
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100929def process_fabrics():
930 return Fabric().process()
931
Jiri Broulike30a60f2018-04-09 21:15:10 +0200932def process_boot_sources():
933 return Boot_source().process()
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200934
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100935def process_subnets():
936 return Subnet().process()
937
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200938
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100939def process_dhcp_snippets():
940 return DHCPSnippet().process()
941
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200942
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100943def process_package_repositories():
944 return PacketRepository().process()
945
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200946
Krzysztof Szukiełojć889eee92017-04-14 11:45:35 +0200947def process_devices(*args):
948 return Device().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100949
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200950
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200951def process_machines(*args):
952 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100953
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200954
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200955def process_assign_machines_ip(*args):
azvyagintsev06b71e72017-11-08 17:11:07 +0200956 """
957 Manage interface configurations.
958 See readme.rst for more info
959 """
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200960 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200961
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200962
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200963def machines_status(*args):
964 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200965
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200966
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200967def deploy_machines(*args):
968 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200969
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200970
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100971def process_boot_resources():
972 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100973
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200974
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100975def process_maas_config():
976 return MaasConfig().process()
977
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200978
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100979def process_commissioning_scripts():
980 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200981
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200982
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200983def process_domain():
984 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200985
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200986
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200987def process_sshprefs():
988 return SSHPrefs().process()
azvyagintsev7605a662017-11-03 19:05:04 +0200989
990
991def wait_for_machine_status(**kwargs):
992 return MachinesStatus.wait_for_machine_status(**kwargs)