blob: aa574988a72a7bdc668b9a3c092431613cc60644 [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):
Gabor Toth36bc0282018-08-18 10:39:51 +0200448 LOG.debug('Updating machine')
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100449 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
Gabor Toth36bc0282018-08-18 10:39:51 +0200450 LOG.debug('old_macs: %s' % old_macs)
451 if isinstance(new['mac_addresses'], list):
452 new_macs = set(v.lower() for v in new['mac_addresses'])
453 else:
454 new_macs = set([new['mac_addresses'].lower()])
455 LOG.debug('new_macs: %s' % new_macs)
456 intersect = list(new_macs.intersection(old_macs))
457 if not intersect:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100458 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100459 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200460 self._maas.delete(u'api/2.0/machines/{0}/'
461 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100462 else:
Gabor Toth36bc0282018-08-18 10:39:51 +0200463 new['mac_addresses'] = intersect
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100464 new[self._update_key] = str(old[self._update_key])
465 return new
466
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200467
468class AssignMachinesIP(MaasObject):
azvyagintsev06b71e72017-11-08 17:11:07 +0200469 # FIXME
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200470 READY = 4
Andreyef156992017-07-03 14:54:03 -0500471 DEPLOYED = 6
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200472
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200473 def __init__(self):
474 super(AssignMachinesIP, self).__init__()
475 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200476 self._create_url = \
477 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
478 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200479 self._config_path = 'region.machines'
480 self._element_key = 'hostname'
481 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200482 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
483 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200484
azvyagintsev06b71e72017-11-08 17:11:07 +0200485 def _data_old(self, _interface, _machine):
486 """
487 _interface = {
488 "mac": "11:22:33:44:55:77",
489 "mode": "STATIC",
490 "ip": "2.2.3.15",
491 "subnet": "subnet1",
492 "gateway": "2.2.3.2",
493 }
494 :param data:
495 :return:
496 """
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100497 data = {
498 'mode': 'STATIC',
azvyagintsev06b71e72017-11-08 17:11:07 +0200499 'subnet': str(_interface.get('subnet')),
500 'ip_address': str(_interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100501 }
azvyagintsev06b71e72017-11-08 17:11:07 +0200502 if 'gateway' in _interface:
503 data['default_gateway'] = _interface.get('gateway')
Krzysztof Szukiełojć9449af12017-04-11 14:01:30 +0200504 data['force'] = '1'
azvyagintsev06b71e72017-11-08 17:11:07 +0200505 data['system_id'] = str(_machine['system_id'])
506 data['interface_id'] = str(_machine['interface_set'][0]['id'])
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200507 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100508
azvyagintsev06b71e72017-11-08 17:11:07 +0200509 def _get_nic_id_by_mac(self, machine, req_mac=None):
510 data = {}
511 for nic in machine['interface_set']:
512 data[nic['mac_address']] = nic['id']
513 if req_mac:
514 if req_mac in data.keys():
515 return data[req_mac]
516 else:
517 raise Exception('NIC with mac:{} not found at '
518 'node:{}'.format(req_mac, machine['fqdn']))
519 return data
520
521 def _disconnect_all_nic(self, machine):
522 """
523 Maas will fail, in case same config's will be to apply
524 on different interfaces. In same time - not possible to push
525 whole network schema at once. Before configuring - need to clean-up
526 everything
527 :param machine:
528 :return:
529 """
530 for nic in machine['interface_set']:
531 LOG.debug("Disconnecting interface:{}".format(nic['mac_address']))
532 try:
533 self._maas.post(
534 u'/api/2.0/nodes/{}/interfaces/{}/'.format(
535 machine['system_id'], nic['id']), 'disconnect')
536 except Exception as e:
537 LOG.error("Failed to disconnect interface:{} on node:{}".format(
538 nic['mac_address'], machine['fqdn']))
539 raise Exception(str(e))
540
541 def _process_interface(self, nic_data, machine):
542 """
543 Process exactly one interface:
544 - update interface
545 - link to network
546 These functions are self-complementary, and do not require an
547 external "process" method. Those broke old-MaasObject logic,
548 though make functions more simple in case iterable tasks.
549 """
550 nic_id = self._get_nic_id_by_mac(machine, nic_data['mac'])
551
552 # Process op=link_subnet
553 link_data = {}
554 _mode = nic_data.get('mode', 'AUTO').upper()
555 if _mode == 'STATIC':
556 link_data = {
557 'mode': 'STATIC',
558 'subnet': str(nic_data.get('subnet')),
559 'ip_address': str(nic_data.get('ip')),
560 'default_gateway': str(nic_data.get('gateway', "")),
561 }
562 elif _mode == 'DHCP':
563 link_data = {
564 'mode': 'DHCP',
565 'subnet': str(nic_data.get('subnet')),
566 }
567 elif _mode == 'AUTO':
568 link_data = {'mode': 'AUTO',
569 'default_gateway': str(nic_data.get('gateway', "")), }
570 elif _mode in ('LINK_UP', 'UNCONFIGURED'):
571 link_data = {'mode': 'LINK_UP'}
572 else:
573 raise Exception('Wrong IP mode:{}'
574 ' for node:{}'.format(_mode, machine['fqdn']))
575 link_data['force'] = str(1)
576
577 physical_data = {"name": nic_data.get("name", ""),
578 "tags": nic_data.get('tags', ""),
579 "vlan": nic_data.get('vlan', "")}
580
581 try:
582 # Cleanup-old definition
583 LOG.debug("Processing {}:{},{}".format(nic_data['mac'], link_data,
584 physical_data))
585 # "link_subnet" and "fill all other data" - its 2 different
586 # operations. So, first we update NIC:
587 self._maas.put(
588 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
589 nic_id),
590 **physical_data)
591 # And then, link subnet configuration:
592 self._maas.post(
593 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
594 nic_id),
595 'link_subnet', **link_data)
596 except Exception as e:
597 LOG.error("Failed to process interface:{} on node:{}".format(
598 nic_data['mac'], machine['fqdn']))
599 raise Exception(str(e))
600
601 def fill_data(self, name, data, machines):
602 machine = machines[name]
603 if machine['status'] == self.DEPLOYED:
604 LOG.debug("Skipping node:{} "
605 "since it in status:DEPLOYED".format(name))
606 return
607 if machine['status'] != self.READY:
608 raise Exception('Machine:{} not in status:READY'.format(name))
609 # backward comparability, for old schema
610 if data.get("interface", None):
611 if 'ip' not in data["interface"]:
612 LOG.info("No IP NIC definition for:{}".format(name))
613 return
614 LOG.warning(
615 "Old machine-describe detected! "
616 "Please read documentation "
617 "'salt-formulas/maas' for migration!")
618 return self._data_old(data['interface'], machines[name])
619 # NewSchema processing:
620 # Warning: old-style MaasObject.process still be called, but
621 # with empty data for process.
622 interfaces = data.get('interfaces', {})
623 if len(interfaces.keys()) == 0:
624 LOG.info("No IP NIC definition for:{}".format(name))
625 return
626 LOG.info('%s for %s', self.__class__.__name__.lower(),
627 machine['fqdn'])
628 self._disconnect_all_nic(machine)
629 for key, value in sorted(interfaces.iteritems()):
630 self._process_interface(value, machine)
631
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100632
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200633class DeployMachines(MaasObject):
azvyagintsev7605a662017-11-03 19:05:04 +0200634 # FIXME
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200635 READY = 4
Andreyef156992017-07-03 14:54:03 -0500636 DEPLOYED = 6
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200637
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200638 def __init__(self):
639 super(DeployMachines, self).__init__()
640 self._all_elements_url = None
641 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
642 self._config_path = 'region.machines'
643 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200644 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
645 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200646
647 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200648 machine = machines[name]
Andreyef156992017-07-03 14:54:03 -0500649 if machine['status'] == self.DEPLOYED:
650 return
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200651 if machine['status'] != self.READY:
652 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200653 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200654 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200655 }
Krzysztof Szukiełojć32677bf2017-04-13 11:04:25 +0200656 if 'distro_series' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200657 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200658 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200659 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200660 return data
661
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200662 def send(self, data):
663 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
Krzysztof Szukiełojć3b7516d2017-04-12 11:52:55 +0200664 self._maas.post(u'api/2.0/machines/', 'allocate', system_id=data['system_id']).read()
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200665 return self._maas.post(self._create_url[0].format(**data),
666 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200667
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100668class BootResource(MaasObject):
669 def __init__(self):
670 super(BootResource, self).__init__()
671 self._all_elements_url = u'api/2.0/boot-resources/'
672 self._create_url = u'api/2.0/boot-resources/'
673 self._update_url = u'api/2.0/boot-resources/{0}/'
674 self._config_path = 'region.boot_resources'
675
676 def fill_data(self, name, boot_data):
677 sha256 = hashlib.sha256()
678 sha256.update(file(boot_data['content']).read())
679 data = {
680 'name': name,
681 'title': boot_data['title'],
682 'architecture': boot_data['architecture'],
683 'filetype': boot_data['filetype'],
684 'size': str(os.path.getsize(boot_data['content'])),
685 'sha256': sha256.hexdigest(),
686 'content': io.open(boot_data['content']),
687 }
688 return data
689
690 def update(self, new, old):
691 self._update = False
692 return new
693
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200694
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100695class CommissioningScripts(MaasObject):
696 def __init__(self):
697 super(CommissioningScripts, self).__init__()
698 self._all_elements_url = u'api/2.0/commissioning-scripts/'
699 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100700 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100701 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100702 self._update_key = 'name'
703
704 def fill_data(self, name, file_path):
705 data = {
706 'name': name,
707 'content': io.open(file_path),
708 }
709 return data
710
711 def update(self, new, old):
712 return new
713
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200714
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100715class MaasConfig(MaasObject):
716 def __init__(self):
717 super(MaasConfig, self).__init__()
718 self._all_elements_url = None
719 self._create_url = (u'api/2.0/maas/', u'set_config')
720 self._config_path = 'region.maas_config'
721
722 def fill_data(self, name, value):
723 data = {
724 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100725 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100726 }
727 return data
728
729 def update(self, new, old):
730 self._update = False
731 return new
732
733
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200734class SSHPrefs(MaasObject):
735 def __init__(self):
736 super(SSHPrefs, self).__init__()
737 self._all_elements_url = None
738 self._create_url = u'api/2.0/account/prefs/sshkeys/'
739 self._config_path = 'region.sshprefs'
740 self._element_key = 'hostname'
741 self._update_key = 'system_id'
742
743 def fill_data(self, value):
744 data = {
745 'key': value,
746 }
747 return data
748
749 def process(self):
750 config = __salt__['config.get']('maas')
751 for part in self._config_path.split('.'):
752 config = config.get(part, {})
753 extra = {}
754 for name, url_call in self._extra_data_urls.iteritems():
755 key = 'id'
756 if isinstance(url_call, tuple):
757 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200758 json_res = json.loads(self._maas.get(url_call).read())
759 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200760 if self._all_elements_url:
761 all_elements = {}
762 elements = self._maas.get(self._all_elements_url).read()
763 res_json = json.loads(elements)
764 for element in res_json:
765 if isinstance(element, (str, unicode)):
766 all_elements[element] = {}
767 else:
768 all_elements[element[self._element_key]] = element
769 else:
770 all_elements = {}
771 ret = {
772 'success': [],
773 'errors': {},
774 'updated': [],
775 }
776 for config_data in config:
777 name = config_data[:10]
778 try:
779 data = self.fill_data(config_data, **extra)
780 self.send(data)
781 ret['success'].append(name)
782 except urllib2.HTTPError as e:
783 etxt = e.read()
784 LOG.exception('Failed for object %s reason %s', name, etxt)
785 ret['errors'][name] = str(etxt)
786 except Exception as e:
787 LOG.exception('Failed for object %s reason %s', name, e)
788 ret['errors'][name] = str(e)
789 if ret['errors']:
790 raise Exception(ret)
791 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200792
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200793
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200794class Domain(MaasObject):
795 def __init__(self):
796 super(Domain, self).__init__()
797 self._all_elements_url = u'/api/2.0/domains/'
798 self._create_url = u'/api/2.0/domains/'
799 self._config_path = 'region.domain'
800 self._update_url = u'/api/2.0/domains/{0}/'
801
802 def fill_data(self, value):
803 data = {
804 'name': value,
805 }
806 self._update = True
807 return data
808
809 def update(self, new, old):
810 new['id'] = str(old['id'])
811 new['authoritative'] = str(old['authoritative'])
812 return new
813
814 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200815 ret = {
816 'success': [],
817 'errors': {},
818 'updated': [],
819 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200820 config = __salt__['config.get']('maas')
821 for part in self._config_path.split('.'):
822 config = config.get(part, {})
823 extra = {}
824 for name, url_call in self._extra_data_urls.iteritems():
825 key = 'id'
826 if isinstance(url_call, tuple):
827 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200828 json_res = json.loads(self._maas.get(url_call).read())
829 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200830 if self._all_elements_url:
831 all_elements = {}
832 elements = self._maas.get(self._all_elements_url).read()
833 res_json = json.loads(elements)
834 for element in res_json:
835 if isinstance(element, (str, unicode)):
836 all_elements[element] = {}
837 else:
838 all_elements[element[self._element_key]] = element
839 else:
840 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200841 try:
842 data = self.fill_data(config, **extra)
843 data = self.update(data, all_elements.values()[0])
844 self.send(data)
845 ret['success'].append('domain')
846 except urllib2.HTTPError as e:
847 etxt = e.read()
848 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
849 ret['errors']['domain'] = str(etxt)
850 except Exception as e:
851 LOG.exception('Failed for object %s reason %s', 'domain', e)
852 ret['errors']['domain'] = str(e)
853 if ret['errors']:
854 raise Exception(ret)
855 return ret
856
857
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200858class MachinesStatus(MaasObject):
859 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200860 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200861 cls._maas = _create_maas_client()
862 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200863 json_result = json.loads(result.read())
864 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200865 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200866 if objects_name:
867 if ',' in objects_name:
868 objects_name = set(objects_name.split(','))
869 else:
870 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200871 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200872 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200873 continue
Michael Polenchuke438bd32017-11-09 20:42:42 +0400874 status = STATUS_NAME_DICT[machine['status']]
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200875 summary[status] += 1
azvyagintsev7605a662017-11-03 19:05:04 +0200876 res.append(
877 {'hostname': machine['hostname'],
878 'system_id': machine['system_id'],
879 'status': status})
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200880 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200881
azvyagintsev7605a662017-11-03 19:05:04 +0200882 @classmethod
883 def wait_for_machine_status(cls, **kwargs):
884 """
885 A function that wait for any requested status, for any set of maas
886 machines.
887
888 If no kwargs has been passed - will try to wait ALL
889 defined in salt::maas::region::machines
890
891 See readme file for more examples.
892 CLI Example:
893 .. code-block:: bash
894
895 salt-call state.apply maas.machines.wait_for_deployed
896
897 :param kwargs:
898 timeout: in s; Global timeout for wait
899 poll_time: in s;Sleep time, between retry
900 req_status: string; Polling status
901 machines: list; machine names
902 ignore_machines: list; machine names
903 :ret: True
904 Exception - if something fail/timeout reached
905 """
906 timeout = kwargs.get("timeout", 60 * 120)
907 poll_time = kwargs.get("poll_time", 30)
908 req_status = kwargs.get("req_status", "Ready")
909 to_discover = kwargs.get("machines", None)
910 ignore_machines = kwargs.get("ignore_machines", None)
911 if not to_discover:
912 try:
913 to_discover = __salt__['config.get']('maas')['region'][
914 'machines'].keys()
915 except KeyError:
916 LOG.warning("No defined machines!")
917 return True
918 total = copy.deepcopy(to_discover) or []
919 if ignore_machines and total:
920 total = [x for x in to_discover if x not in ignore_machines]
921 started_at = time.time()
922 while len(total) <= len(to_discover):
923 for m in to_discover:
924 for discovered in MachinesStatus.execute()['machines']:
925 if m == discovered['hostname'] and \
Michael Polenchuke438bd32017-11-09 20:42:42 +0400926 discovered['status'].lower() == req_status.lower():
927 if m in total:
928 total.remove(m)
929
azvyagintsev7605a662017-11-03 19:05:04 +0200930 if len(total) <= 0:
931 LOG.debug(
932 "Machines:{} are:{}".format(to_discover, req_status))
933 return True
934 if (timeout - (time.time() - started_at)) <= 0:
935 raise Exception(
936 'Machines:{}not in {} state'.format(total, req_status))
937 LOG.info(
938 "Waiting status:{} "
939 "for machines:{}"
940 "\nsleep for:{}s "
941 "Timeout:{}s".format(req_status, total, poll_time, timeout))
942 time.sleep(poll_time)
943
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200944
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100945def process_fabrics():
946 return Fabric().process()
947
Jiri Broulike30a60f2018-04-09 21:15:10 +0200948def process_boot_sources():
949 return Boot_source().process()
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200950
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100951def process_subnets():
952 return Subnet().process()
953
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200954
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100955def process_dhcp_snippets():
956 return DHCPSnippet().process()
957
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200958
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100959def process_package_repositories():
960 return PacketRepository().process()
961
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200962
Krzysztof Szukiełojć889eee92017-04-14 11:45:35 +0200963def process_devices(*args):
964 return Device().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100965
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200966
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200967def process_machines(*args):
968 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100969
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200970
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200971def process_assign_machines_ip(*args):
azvyagintsev06b71e72017-11-08 17:11:07 +0200972 """
973 Manage interface configurations.
974 See readme.rst for more info
975 """
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200976 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200977
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200978
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200979def machines_status(*args):
980 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200981
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200982
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200983def deploy_machines(*args):
984 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200985
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200986
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100987def process_boot_resources():
988 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100989
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200990
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100991def process_maas_config():
992 return MaasConfig().process()
993
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200994
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100995def process_commissioning_scripts():
996 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200997
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200998
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200999def process_domain():
1000 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +02001001
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001002
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +02001003def process_sshprefs():
1004 return SSHPrefs().process()
azvyagintsev7605a662017-11-03 19:05:04 +02001005
1006
1007def wait_for_machine_status(**kwargs):
1008 return MachinesStatus.wait_for_machine_status(**kwargs)