blob: 426aff5edcb37cdac67810d3777bf259f3a54180 [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 }
mkraynove95cdb62018-05-08 14:17:18 +0400424 if 'power_address' in power_data:
425 data['power_parameters_power_address'] = power_data['power_address']
Ondrej Smola455003c2017-06-01 22:53:39 +0200426 if 'power_driver' in power_data:
427 data['power_parameters_power_driver'] = power_data['power_driver']
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100428 if 'power_user' in power_data:
429 data['power_parameters_power_user'] = power_data['power_user']
430 if 'power_password' in power_data:
431 data['power_parameters_power_pass'] = \
432 power_data['power_password']
Petr Ruzicka5fe96742017-11-10 14:22:24 +0100433 if 'power_id' in power_data:
434 data['power_parameters_power_id'] = power_data['power_id']
mkraynov47c087a2018-04-26 13:23:31 +0400435 if 'power_nova_id' in power_data:
436 data['power_parameters_nova_id'] = power_data['power_nova_id']
437 if 'power_os_tenantname' in power_data:
438 data['power_parameters_os_tenantname'] = power_data['power_os_tenantname']
439 if 'power_os_username' in power_data:
440 data['power_parameters_os_username'] = power_data['power_os_username']
441 if 'power_os_password' in power_data:
442 data['power_parameters_os_password'] = power_data['power_os_password']
443 if 'power_os_authurl' in power_data:
444 data['power_parameters_os_authurl'] = power_data['power_os_authurl']
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100445 return data
446
447 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100448 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
449 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100450 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100451 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200452 self._maas.delete(u'api/2.0/machines/{0}/'
453 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100454 else:
455 new[self._update_key] = str(old[self._update_key])
456 return new
457
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200458
459class AssignMachinesIP(MaasObject):
azvyagintsev06b71e72017-11-08 17:11:07 +0200460 # FIXME
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200461 READY = 4
Andreyef156992017-07-03 14:54:03 -0500462 DEPLOYED = 6
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200463
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200464 def __init__(self):
465 super(AssignMachinesIP, self).__init__()
466 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200467 self._create_url = \
468 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
469 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200470 self._config_path = 'region.machines'
471 self._element_key = 'hostname'
472 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200473 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
474 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200475
azvyagintsev06b71e72017-11-08 17:11:07 +0200476 def _data_old(self, _interface, _machine):
477 """
478 _interface = {
479 "mac": "11:22:33:44:55:77",
480 "mode": "STATIC",
481 "ip": "2.2.3.15",
482 "subnet": "subnet1",
483 "gateway": "2.2.3.2",
484 }
485 :param data:
486 :return:
487 """
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100488 data = {
489 'mode': 'STATIC',
azvyagintsev06b71e72017-11-08 17:11:07 +0200490 'subnet': str(_interface.get('subnet')),
491 'ip_address': str(_interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100492 }
azvyagintsev06b71e72017-11-08 17:11:07 +0200493 if 'gateway' in _interface:
494 data['default_gateway'] = _interface.get('gateway')
Krzysztof Szukiełojć9449af12017-04-11 14:01:30 +0200495 data['force'] = '1'
azvyagintsev06b71e72017-11-08 17:11:07 +0200496 data['system_id'] = str(_machine['system_id'])
497 data['interface_id'] = str(_machine['interface_set'][0]['id'])
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200498 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100499
azvyagintsev06b71e72017-11-08 17:11:07 +0200500 def _get_nic_id_by_mac(self, machine, req_mac=None):
501 data = {}
502 for nic in machine['interface_set']:
503 data[nic['mac_address']] = nic['id']
504 if req_mac:
505 if req_mac in data.keys():
506 return data[req_mac]
507 else:
508 raise Exception('NIC with mac:{} not found at '
509 'node:{}'.format(req_mac, machine['fqdn']))
510 return data
511
512 def _disconnect_all_nic(self, machine):
513 """
514 Maas will fail, in case same config's will be to apply
515 on different interfaces. In same time - not possible to push
516 whole network schema at once. Before configuring - need to clean-up
517 everything
518 :param machine:
519 :return:
520 """
521 for nic in machine['interface_set']:
522 LOG.debug("Disconnecting interface:{}".format(nic['mac_address']))
523 try:
524 self._maas.post(
525 u'/api/2.0/nodes/{}/interfaces/{}/'.format(
526 machine['system_id'], nic['id']), 'disconnect')
527 except Exception as e:
528 LOG.error("Failed to disconnect interface:{} on node:{}".format(
529 nic['mac_address'], machine['fqdn']))
530 raise Exception(str(e))
531
532 def _process_interface(self, nic_data, machine):
533 """
534 Process exactly one interface:
535 - update interface
536 - link to network
537 These functions are self-complementary, and do not require an
538 external "process" method. Those broke old-MaasObject logic,
539 though make functions more simple in case iterable tasks.
540 """
541 nic_id = self._get_nic_id_by_mac(machine, nic_data['mac'])
542
543 # Process op=link_subnet
544 link_data = {}
545 _mode = nic_data.get('mode', 'AUTO').upper()
546 if _mode == 'STATIC':
547 link_data = {
548 'mode': 'STATIC',
549 'subnet': str(nic_data.get('subnet')),
550 'ip_address': str(nic_data.get('ip')),
551 'default_gateway': str(nic_data.get('gateway', "")),
552 }
553 elif _mode == 'DHCP':
554 link_data = {
555 'mode': 'DHCP',
556 'subnet': str(nic_data.get('subnet')),
557 }
558 elif _mode == 'AUTO':
559 link_data = {'mode': 'AUTO',
560 'default_gateway': str(nic_data.get('gateway', "")), }
561 elif _mode in ('LINK_UP', 'UNCONFIGURED'):
562 link_data = {'mode': 'LINK_UP'}
563 else:
564 raise Exception('Wrong IP mode:{}'
565 ' for node:{}'.format(_mode, machine['fqdn']))
566 link_data['force'] = str(1)
567
568 physical_data = {"name": nic_data.get("name", ""),
569 "tags": nic_data.get('tags', ""),
570 "vlan": nic_data.get('vlan', "")}
571
572 try:
573 # Cleanup-old definition
574 LOG.debug("Processing {}:{},{}".format(nic_data['mac'], link_data,
575 physical_data))
576 # "link_subnet" and "fill all other data" - its 2 different
577 # operations. So, first we update NIC:
578 self._maas.put(
579 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
580 nic_id),
581 **physical_data)
582 # And then, link subnet configuration:
583 self._maas.post(
584 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
585 nic_id),
586 'link_subnet', **link_data)
587 except Exception as e:
588 LOG.error("Failed to process interface:{} on node:{}".format(
589 nic_data['mac'], machine['fqdn']))
590 raise Exception(str(e))
591
592 def fill_data(self, name, data, machines):
593 machine = machines[name]
594 if machine['status'] == self.DEPLOYED:
595 LOG.debug("Skipping node:{} "
596 "since it in status:DEPLOYED".format(name))
597 return
598 if machine['status'] != self.READY:
599 raise Exception('Machine:{} not in status:READY'.format(name))
600 # backward comparability, for old schema
601 if data.get("interface", None):
602 if 'ip' not in data["interface"]:
603 LOG.info("No IP NIC definition for:{}".format(name))
604 return
605 LOG.warning(
606 "Old machine-describe detected! "
607 "Please read documentation "
608 "'salt-formulas/maas' for migration!")
609 return self._data_old(data['interface'], machines[name])
610 # NewSchema processing:
611 # Warning: old-style MaasObject.process still be called, but
612 # with empty data for process.
613 interfaces = data.get('interfaces', {})
614 if len(interfaces.keys()) == 0:
615 LOG.info("No IP NIC definition for:{}".format(name))
616 return
617 LOG.info('%s for %s', self.__class__.__name__.lower(),
618 machine['fqdn'])
619 self._disconnect_all_nic(machine)
620 for key, value in sorted(interfaces.iteritems()):
621 self._process_interface(value, machine)
622
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100623
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200624class DeployMachines(MaasObject):
azvyagintsev7605a662017-11-03 19:05:04 +0200625 # FIXME
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200626 READY = 4
Andreyef156992017-07-03 14:54:03 -0500627 DEPLOYED = 6
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200628
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200629 def __init__(self):
630 super(DeployMachines, self).__init__()
631 self._all_elements_url = None
632 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
633 self._config_path = 'region.machines'
634 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200635 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
636 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200637
638 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200639 machine = machines[name]
Andreyef156992017-07-03 14:54:03 -0500640 if machine['status'] == self.DEPLOYED:
641 return
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200642 if machine['status'] != self.READY:
643 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200644 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200645 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200646 }
Krzysztof Szukiełojć32677bf2017-04-13 11:04:25 +0200647 if 'distro_series' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200648 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200649 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200650 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200651 return data
652
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200653 def send(self, data):
654 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
Krzysztof Szukiełojć3b7516d2017-04-12 11:52:55 +0200655 self._maas.post(u'api/2.0/machines/', 'allocate', system_id=data['system_id']).read()
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200656 return self._maas.post(self._create_url[0].format(**data),
657 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200658
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100659class BootResource(MaasObject):
660 def __init__(self):
661 super(BootResource, self).__init__()
662 self._all_elements_url = u'api/2.0/boot-resources/'
663 self._create_url = u'api/2.0/boot-resources/'
664 self._update_url = u'api/2.0/boot-resources/{0}/'
665 self._config_path = 'region.boot_resources'
666
667 def fill_data(self, name, boot_data):
668 sha256 = hashlib.sha256()
669 sha256.update(file(boot_data['content']).read())
670 data = {
671 'name': name,
672 'title': boot_data['title'],
673 'architecture': boot_data['architecture'],
674 'filetype': boot_data['filetype'],
675 'size': str(os.path.getsize(boot_data['content'])),
676 'sha256': sha256.hexdigest(),
677 'content': io.open(boot_data['content']),
678 }
679 return data
680
681 def update(self, new, old):
682 self._update = False
683 return new
684
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200685
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100686class CommissioningScripts(MaasObject):
687 def __init__(self):
688 super(CommissioningScripts, self).__init__()
689 self._all_elements_url = u'api/2.0/commissioning-scripts/'
690 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100691 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100692 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100693 self._update_key = 'name'
694
695 def fill_data(self, name, file_path):
696 data = {
697 'name': name,
698 'content': io.open(file_path),
699 }
700 return data
701
702 def update(self, new, old):
703 return new
704
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200705
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100706class MaasConfig(MaasObject):
707 def __init__(self):
708 super(MaasConfig, self).__init__()
709 self._all_elements_url = None
710 self._create_url = (u'api/2.0/maas/', u'set_config')
711 self._config_path = 'region.maas_config'
712
713 def fill_data(self, name, value):
714 data = {
715 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100716 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100717 }
718 return data
719
720 def update(self, new, old):
721 self._update = False
722 return new
723
724
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200725class SSHPrefs(MaasObject):
726 def __init__(self):
727 super(SSHPrefs, self).__init__()
728 self._all_elements_url = None
729 self._create_url = u'api/2.0/account/prefs/sshkeys/'
730 self._config_path = 'region.sshprefs'
731 self._element_key = 'hostname'
732 self._update_key = 'system_id'
733
734 def fill_data(self, value):
735 data = {
736 'key': value,
737 }
738 return data
739
740 def process(self):
741 config = __salt__['config.get']('maas')
742 for part in self._config_path.split('.'):
743 config = config.get(part, {})
744 extra = {}
745 for name, url_call in self._extra_data_urls.iteritems():
746 key = 'id'
747 if isinstance(url_call, tuple):
748 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200749 json_res = json.loads(self._maas.get(url_call).read())
750 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200751 if self._all_elements_url:
752 all_elements = {}
753 elements = self._maas.get(self._all_elements_url).read()
754 res_json = json.loads(elements)
755 for element in res_json:
756 if isinstance(element, (str, unicode)):
757 all_elements[element] = {}
758 else:
759 all_elements[element[self._element_key]] = element
760 else:
761 all_elements = {}
762 ret = {
763 'success': [],
764 'errors': {},
765 'updated': [],
766 }
767 for config_data in config:
768 name = config_data[:10]
769 try:
770 data = self.fill_data(config_data, **extra)
771 self.send(data)
772 ret['success'].append(name)
773 except urllib2.HTTPError as e:
774 etxt = e.read()
775 LOG.exception('Failed for object %s reason %s', name, etxt)
776 ret['errors'][name] = str(etxt)
777 except Exception as e:
778 LOG.exception('Failed for object %s reason %s', name, e)
779 ret['errors'][name] = str(e)
780 if ret['errors']:
781 raise Exception(ret)
782 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200783
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200784
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200785class Domain(MaasObject):
786 def __init__(self):
787 super(Domain, self).__init__()
788 self._all_elements_url = u'/api/2.0/domains/'
789 self._create_url = u'/api/2.0/domains/'
790 self._config_path = 'region.domain'
791 self._update_url = u'/api/2.0/domains/{0}/'
792
793 def fill_data(self, value):
794 data = {
795 'name': value,
796 }
797 self._update = True
798 return data
799
800 def update(self, new, old):
801 new['id'] = str(old['id'])
802 new['authoritative'] = str(old['authoritative'])
803 return new
804
805 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200806 ret = {
807 'success': [],
808 'errors': {},
809 'updated': [],
810 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200811 config = __salt__['config.get']('maas')
812 for part in self._config_path.split('.'):
813 config = config.get(part, {})
814 extra = {}
815 for name, url_call in self._extra_data_urls.iteritems():
816 key = 'id'
817 if isinstance(url_call, tuple):
818 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200819 json_res = json.loads(self._maas.get(url_call).read())
820 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200821 if self._all_elements_url:
822 all_elements = {}
823 elements = self._maas.get(self._all_elements_url).read()
824 res_json = json.loads(elements)
825 for element in res_json:
826 if isinstance(element, (str, unicode)):
827 all_elements[element] = {}
828 else:
829 all_elements[element[self._element_key]] = element
830 else:
831 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200832 try:
833 data = self.fill_data(config, **extra)
834 data = self.update(data, all_elements.values()[0])
835 self.send(data)
836 ret['success'].append('domain')
837 except urllib2.HTTPError as e:
838 etxt = e.read()
839 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
840 ret['errors']['domain'] = str(etxt)
841 except Exception as e:
842 LOG.exception('Failed for object %s reason %s', 'domain', e)
843 ret['errors']['domain'] = str(e)
844 if ret['errors']:
845 raise Exception(ret)
846 return ret
847
848
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200849class MachinesStatus(MaasObject):
850 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200851 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200852 cls._maas = _create_maas_client()
853 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200854 json_result = json.loads(result.read())
855 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200856 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200857 if objects_name:
858 if ',' in objects_name:
859 objects_name = set(objects_name.split(','))
860 else:
861 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200862 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200863 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200864 continue
Michael Polenchuke438bd32017-11-09 20:42:42 +0400865 status = STATUS_NAME_DICT[machine['status']]
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200866 summary[status] += 1
azvyagintsev7605a662017-11-03 19:05:04 +0200867 res.append(
868 {'hostname': machine['hostname'],
869 'system_id': machine['system_id'],
870 'status': status})
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200871 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200872
azvyagintsev7605a662017-11-03 19:05:04 +0200873 @classmethod
874 def wait_for_machine_status(cls, **kwargs):
875 """
876 A function that wait for any requested status, for any set of maas
877 machines.
878
879 If no kwargs has been passed - will try to wait ALL
880 defined in salt::maas::region::machines
881
882 See readme file for more examples.
883 CLI Example:
884 .. code-block:: bash
885
886 salt-call state.apply maas.machines.wait_for_deployed
887
888 :param kwargs:
889 timeout: in s; Global timeout for wait
890 poll_time: in s;Sleep time, between retry
891 req_status: string; Polling status
892 machines: list; machine names
893 ignore_machines: list; machine names
894 :ret: True
895 Exception - if something fail/timeout reached
896 """
897 timeout = kwargs.get("timeout", 60 * 120)
898 poll_time = kwargs.get("poll_time", 30)
899 req_status = kwargs.get("req_status", "Ready")
900 to_discover = kwargs.get("machines", None)
901 ignore_machines = kwargs.get("ignore_machines", None)
902 if not to_discover:
903 try:
904 to_discover = __salt__['config.get']('maas')['region'][
905 'machines'].keys()
906 except KeyError:
907 LOG.warning("No defined machines!")
908 return True
909 total = copy.deepcopy(to_discover) or []
910 if ignore_machines and total:
911 total = [x for x in to_discover if x not in ignore_machines]
912 started_at = time.time()
913 while len(total) <= len(to_discover):
914 for m in to_discover:
915 for discovered in MachinesStatus.execute()['machines']:
916 if m == discovered['hostname'] and \
Michael Polenchuke438bd32017-11-09 20:42:42 +0400917 discovered['status'].lower() == req_status.lower():
918 if m in total:
919 total.remove(m)
920
azvyagintsev7605a662017-11-03 19:05:04 +0200921 if len(total) <= 0:
922 LOG.debug(
923 "Machines:{} are:{}".format(to_discover, req_status))
924 return True
925 if (timeout - (time.time() - started_at)) <= 0:
926 raise Exception(
927 'Machines:{}not in {} state'.format(total, req_status))
928 LOG.info(
929 "Waiting status:{} "
930 "for machines:{}"
931 "\nsleep for:{}s "
932 "Timeout:{}s".format(req_status, total, poll_time, timeout))
933 time.sleep(poll_time)
934
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200935
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100936def process_fabrics():
937 return Fabric().process()
938
Jiri Broulike30a60f2018-04-09 21:15:10 +0200939def process_boot_sources():
940 return Boot_source().process()
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200941
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100942def process_subnets():
943 return Subnet().process()
944
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200945
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100946def process_dhcp_snippets():
947 return DHCPSnippet().process()
948
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200949
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100950def process_package_repositories():
951 return PacketRepository().process()
952
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200953
Krzysztof Szukiełojć889eee92017-04-14 11:45:35 +0200954def process_devices(*args):
955 return Device().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100956
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200957
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200958def process_machines(*args):
959 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100960
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200961
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200962def process_assign_machines_ip(*args):
azvyagintsev06b71e72017-11-08 17:11:07 +0200963 """
964 Manage interface configurations.
965 See readme.rst for more info
966 """
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200967 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200968
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200969
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200970def machines_status(*args):
971 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200972
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200973
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200974def deploy_machines(*args):
975 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200976
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200977
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100978def process_boot_resources():
979 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100980
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200981
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100982def process_maas_config():
983 return MaasConfig().process()
984
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200985
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100986def process_commissioning_scripts():
987 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200988
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200989
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200990def process_domain():
991 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200992
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200993
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200994def process_sshprefs():
995 return SSHPrefs().process()
azvyagintsev7605a662017-11-03 19:05:04 +0200996
997
998def wait_for_machine_status(**kwargs):
999 return MachinesStatus.wait_for_machine_status(**kwargs)