blob: 04836d39610b56cddd0fcf0438c7f583a75a7c68 [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 }
azvyagintsev6543ec82018-11-01 23:45:09 +0200424 for k, v in power_data.items():
Denis Egorenko60963332018-10-23 19:24:28 +0400425 if k == 'power_type':
426 continue
azvyagintsev6543ec82018-11-01 23:45:09 +0200427 elif k == 'power_password':
428 data['power_parameters_power_pass'] = v
429 LOG.warning(
430 "Deprecated power parameter:power_password passed! "
431 "Please change key name to 'power_pass'! ")
432 continue
433 elif k == 'power_nova_id':
434 data['power_parameters_nova_id'] = v
435 LOG.warning(
436 "Deprecated power parameter:power_nova_id passed! "
437 "Please change key name to 'nova_id'! ")
438 continue
439 elif k == 'power_os_tenantname':
440 data['power_parameters_os_tenantname'] = v
441 LOG.warning(
442 "Deprecated power parameter:power_os_tenantname passed! "
443 "Please change key name to 'os_tenantname'! ")
444 continue
445 elif k == 'power_os_username':
446 data['power_parameters_os_username'] = v
447 LOG.warning(
448 "Deprecated power parameter:power_os_username passed! "
449 "Please change key name to 'os_username'! ")
450 continue
451 elif k == 'power_os_password':
452 data['power_parameters_os_password'] = v
453 LOG.warning(
454 "Deprecated power parameter:power_os_password passed! "
455 "Please change key name to 'os_password'! ")
456 continue
457 elif k == 'power_os_authurl':
458 data['power_parameters_os_authurl'] = v
459 LOG.warning(
460 "Deprecated power parameter:power_os_authurl passed! "
461 "Please change key name to 'os_authurl'! ")
462 continue
463
Denis Egorenko60963332018-10-23 19:24:28 +0400464 data_key = 'power_parameters_{}'.format(k)
465 data[data_key] = v
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100466 return data
467
468 def update(self, new, old):
Gabor Toth36bc0282018-08-18 10:39:51 +0200469 LOG.debug('Updating machine')
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100470 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
Gabor Toth36bc0282018-08-18 10:39:51 +0200471 LOG.debug('old_macs: %s' % old_macs)
472 if isinstance(new['mac_addresses'], list):
473 new_macs = set(v.lower() for v in new['mac_addresses'])
474 else:
475 new_macs = set([new['mac_addresses'].lower()])
476 LOG.debug('new_macs: %s' % new_macs)
477 intersect = list(new_macs.intersection(old_macs))
478 if not intersect:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100479 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100480 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200481 self._maas.delete(u'api/2.0/machines/{0}/'
482 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100483 else:
Gabor Toth36bc0282018-08-18 10:39:51 +0200484 new['mac_addresses'] = intersect
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100485 new[self._update_key] = str(old[self._update_key])
486 return new
487
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200488
489class AssignMachinesIP(MaasObject):
azvyagintsev06b71e72017-11-08 17:11:07 +0200490 # FIXME
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200491 READY = 4
Andreyef156992017-07-03 14:54:03 -0500492 DEPLOYED = 6
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200493
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200494 def __init__(self):
495 super(AssignMachinesIP, self).__init__()
496 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200497 self._create_url = \
498 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
499 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200500 self._config_path = 'region.machines'
501 self._element_key = 'hostname'
502 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200503 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
504 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200505
azvyagintsev06b71e72017-11-08 17:11:07 +0200506 def _data_old(self, _interface, _machine):
507 """
508 _interface = {
509 "mac": "11:22:33:44:55:77",
510 "mode": "STATIC",
511 "ip": "2.2.3.15",
512 "subnet": "subnet1",
513 "gateway": "2.2.3.2",
514 }
515 :param data:
516 :return:
517 """
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100518 data = {
519 'mode': 'STATIC',
azvyagintsev06b71e72017-11-08 17:11:07 +0200520 'subnet': str(_interface.get('subnet')),
521 'ip_address': str(_interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100522 }
azvyagintsev06b71e72017-11-08 17:11:07 +0200523 if 'gateway' in _interface:
524 data['default_gateway'] = _interface.get('gateway')
Krzysztof Szukiełojć9449af12017-04-11 14:01:30 +0200525 data['force'] = '1'
azvyagintsev06b71e72017-11-08 17:11:07 +0200526 data['system_id'] = str(_machine['system_id'])
527 data['interface_id'] = str(_machine['interface_set'][0]['id'])
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200528 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100529
azvyagintsev06b71e72017-11-08 17:11:07 +0200530 def _get_nic_id_by_mac(self, machine, req_mac=None):
531 data = {}
532 for nic in machine['interface_set']:
Pavel Cizinskyb844b072019-01-15 14:33:37 +0100533 data[nic['mac_address'].lower()] = nic['id']
azvyagintsev06b71e72017-11-08 17:11:07 +0200534 if req_mac:
Pavel Cizinskyb844b072019-01-15 14:33:37 +0100535 if req_mac.lower() in data.keys():
536 return data[req_mac.lower()]
azvyagintsev06b71e72017-11-08 17:11:07 +0200537 else:
538 raise Exception('NIC with mac:{} not found at '
539 'node:{}'.format(req_mac, machine['fqdn']))
540 return data
541
542 def _disconnect_all_nic(self, machine):
543 """
544 Maas will fail, in case same config's will be to apply
545 on different interfaces. In same time - not possible to push
546 whole network schema at once. Before configuring - need to clean-up
547 everything
548 :param machine:
549 :return:
550 """
551 for nic in machine['interface_set']:
552 LOG.debug("Disconnecting interface:{}".format(nic['mac_address']))
553 try:
554 self._maas.post(
555 u'/api/2.0/nodes/{}/interfaces/{}/'.format(
556 machine['system_id'], nic['id']), 'disconnect')
557 except Exception as e:
558 LOG.error("Failed to disconnect interface:{} on node:{}".format(
559 nic['mac_address'], machine['fqdn']))
560 raise Exception(str(e))
561
562 def _process_interface(self, nic_data, machine):
563 """
564 Process exactly one interface:
565 - update interface
566 - link to network
567 These functions are self-complementary, and do not require an
568 external "process" method. Those broke old-MaasObject logic,
569 though make functions more simple in case iterable tasks.
570 """
571 nic_id = self._get_nic_id_by_mac(machine, nic_data['mac'])
572
573 # Process op=link_subnet
574 link_data = {}
575 _mode = nic_data.get('mode', 'AUTO').upper()
576 if _mode == 'STATIC':
577 link_data = {
578 'mode': 'STATIC',
579 'subnet': str(nic_data.get('subnet')),
580 'ip_address': str(nic_data.get('ip')),
581 'default_gateway': str(nic_data.get('gateway', "")),
582 }
583 elif _mode == 'DHCP':
584 link_data = {
585 'mode': 'DHCP',
586 'subnet': str(nic_data.get('subnet')),
587 }
588 elif _mode == 'AUTO':
589 link_data = {'mode': 'AUTO',
590 'default_gateway': str(nic_data.get('gateway', "")), }
591 elif _mode in ('LINK_UP', 'UNCONFIGURED'):
592 link_data = {'mode': 'LINK_UP'}
593 else:
594 raise Exception('Wrong IP mode:{}'
595 ' for node:{}'.format(_mode, machine['fqdn']))
596 link_data['force'] = str(1)
597
598 physical_data = {"name": nic_data.get("name", ""),
599 "tags": nic_data.get('tags', ""),
600 "vlan": nic_data.get('vlan', "")}
601
602 try:
603 # Cleanup-old definition
604 LOG.debug("Processing {}:{},{}".format(nic_data['mac'], link_data,
605 physical_data))
606 # "link_subnet" and "fill all other data" - its 2 different
607 # operations. So, first we update NIC:
608 self._maas.put(
609 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
610 nic_id),
611 **physical_data)
612 # And then, link subnet configuration:
613 self._maas.post(
614 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
615 nic_id),
616 'link_subnet', **link_data)
617 except Exception as e:
618 LOG.error("Failed to process interface:{} on node:{}".format(
619 nic_data['mac'], machine['fqdn']))
620 raise Exception(str(e))
621
622 def fill_data(self, name, data, machines):
623 machine = machines[name]
624 if machine['status'] == self.DEPLOYED:
625 LOG.debug("Skipping node:{} "
626 "since it in status:DEPLOYED".format(name))
627 return
628 if machine['status'] != self.READY:
629 raise Exception('Machine:{} not in status:READY'.format(name))
630 # backward comparability, for old schema
631 if data.get("interface", None):
632 if 'ip' not in data["interface"]:
633 LOG.info("No IP NIC definition for:{}".format(name))
634 return
635 LOG.warning(
636 "Old machine-describe detected! "
637 "Please read documentation "
638 "'salt-formulas/maas' for migration!")
639 return self._data_old(data['interface'], machines[name])
640 # NewSchema processing:
641 # Warning: old-style MaasObject.process still be called, but
642 # with empty data for process.
643 interfaces = data.get('interfaces', {})
644 if len(interfaces.keys()) == 0:
645 LOG.info("No IP NIC definition for:{}".format(name))
646 return
647 LOG.info('%s for %s', self.__class__.__name__.lower(),
648 machine['fqdn'])
649 self._disconnect_all_nic(machine)
650 for key, value in sorted(interfaces.iteritems()):
651 self._process_interface(value, machine)
652
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100653
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200654class DeployMachines(MaasObject):
azvyagintsev7605a662017-11-03 19:05:04 +0200655 # FIXME
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200656 READY = 4
Andreyef156992017-07-03 14:54:03 -0500657 DEPLOYED = 6
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200658
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200659 def __init__(self):
660 super(DeployMachines, self).__init__()
661 self._all_elements_url = None
662 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
663 self._config_path = 'region.machines'
664 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200665 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
666 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200667
668 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200669 machine = machines[name]
Andreyef156992017-07-03 14:54:03 -0500670 if machine['status'] == self.DEPLOYED:
671 return
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200672 if machine['status'] != self.READY:
673 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200674 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200675 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200676 }
Krzysztof Szukiełojć32677bf2017-04-13 11:04:25 +0200677 if 'distro_series' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200678 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200679 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200680 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200681 return data
682
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200683 def send(self, data):
684 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
Krzysztof Szukiełojć3b7516d2017-04-12 11:52:55 +0200685 self._maas.post(u'api/2.0/machines/', 'allocate', system_id=data['system_id']).read()
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200686 return self._maas.post(self._create_url[0].format(**data),
687 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200688
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100689class BootResource(MaasObject):
690 def __init__(self):
691 super(BootResource, self).__init__()
692 self._all_elements_url = u'api/2.0/boot-resources/'
693 self._create_url = u'api/2.0/boot-resources/'
694 self._update_url = u'api/2.0/boot-resources/{0}/'
695 self._config_path = 'region.boot_resources'
696
697 def fill_data(self, name, boot_data):
698 sha256 = hashlib.sha256()
699 sha256.update(file(boot_data['content']).read())
700 data = {
701 'name': name,
702 'title': boot_data['title'],
703 'architecture': boot_data['architecture'],
704 'filetype': boot_data['filetype'],
705 'size': str(os.path.getsize(boot_data['content'])),
706 'sha256': sha256.hexdigest(),
707 'content': io.open(boot_data['content']),
708 }
709 return data
710
711 def update(self, new, old):
712 self._update = False
713 return new
714
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200715
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100716class CommissioningScripts(MaasObject):
717 def __init__(self):
718 super(CommissioningScripts, self).__init__()
719 self._all_elements_url = u'api/2.0/commissioning-scripts/'
720 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100721 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100722 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100723 self._update_key = 'name'
724
725 def fill_data(self, name, file_path):
726 data = {
727 'name': name,
728 'content': io.open(file_path),
729 }
730 return data
731
732 def update(self, new, old):
733 return new
734
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200735
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100736class MaasConfig(MaasObject):
737 def __init__(self):
738 super(MaasConfig, self).__init__()
739 self._all_elements_url = None
740 self._create_url = (u'api/2.0/maas/', u'set_config')
741 self._config_path = 'region.maas_config'
742
743 def fill_data(self, name, value):
744 data = {
745 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100746 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100747 }
748 return data
749
750 def update(self, new, old):
751 self._update = False
752 return new
753
754
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200755class SSHPrefs(MaasObject):
756 def __init__(self):
757 super(SSHPrefs, self).__init__()
758 self._all_elements_url = None
759 self._create_url = u'api/2.0/account/prefs/sshkeys/'
760 self._config_path = 'region.sshprefs'
761 self._element_key = 'hostname'
762 self._update_key = 'system_id'
763
764 def fill_data(self, value):
765 data = {
766 'key': value,
767 }
768 return data
769
770 def process(self):
771 config = __salt__['config.get']('maas')
772 for part in self._config_path.split('.'):
773 config = config.get(part, {})
774 extra = {}
775 for name, url_call in self._extra_data_urls.iteritems():
776 key = 'id'
777 if isinstance(url_call, tuple):
778 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200779 json_res = json.loads(self._maas.get(url_call).read())
780 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200781 if self._all_elements_url:
782 all_elements = {}
783 elements = self._maas.get(self._all_elements_url).read()
784 res_json = json.loads(elements)
785 for element in res_json:
786 if isinstance(element, (str, unicode)):
787 all_elements[element] = {}
788 else:
789 all_elements[element[self._element_key]] = element
790 else:
791 all_elements = {}
792 ret = {
793 'success': [],
794 'errors': {},
795 'updated': [],
796 }
797 for config_data in config:
798 name = config_data[:10]
799 try:
800 data = self.fill_data(config_data, **extra)
801 self.send(data)
802 ret['success'].append(name)
803 except urllib2.HTTPError as e:
804 etxt = e.read()
805 LOG.exception('Failed for object %s reason %s', name, etxt)
806 ret['errors'][name] = str(etxt)
807 except Exception as e:
808 LOG.exception('Failed for object %s reason %s', name, e)
809 ret['errors'][name] = str(e)
810 if ret['errors']:
811 raise Exception(ret)
812 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200813
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200814
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200815class Domain(MaasObject):
816 def __init__(self):
817 super(Domain, self).__init__()
818 self._all_elements_url = u'/api/2.0/domains/'
819 self._create_url = u'/api/2.0/domains/'
820 self._config_path = 'region.domain'
821 self._update_url = u'/api/2.0/domains/{0}/'
822
823 def fill_data(self, value):
824 data = {
825 'name': value,
826 }
827 self._update = True
828 return data
829
830 def update(self, new, old):
831 new['id'] = str(old['id'])
832 new['authoritative'] = str(old['authoritative'])
833 return new
834
835 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200836 ret = {
837 'success': [],
838 'errors': {},
839 'updated': [],
840 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200841 config = __salt__['config.get']('maas')
842 for part in self._config_path.split('.'):
843 config = config.get(part, {})
844 extra = {}
845 for name, url_call in self._extra_data_urls.iteritems():
846 key = 'id'
847 if isinstance(url_call, tuple):
848 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200849 json_res = json.loads(self._maas.get(url_call).read())
850 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200851 if self._all_elements_url:
852 all_elements = {}
853 elements = self._maas.get(self._all_elements_url).read()
854 res_json = json.loads(elements)
855 for element in res_json:
856 if isinstance(element, (str, unicode)):
857 all_elements[element] = {}
858 else:
859 all_elements[element[self._element_key]] = element
860 else:
861 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200862 try:
863 data = self.fill_data(config, **extra)
864 data = self.update(data, all_elements.values()[0])
865 self.send(data)
866 ret['success'].append('domain')
867 except urllib2.HTTPError as e:
868 etxt = e.read()
869 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
870 ret['errors']['domain'] = str(etxt)
871 except Exception as e:
872 LOG.exception('Failed for object %s reason %s', 'domain', e)
873 ret['errors']['domain'] = str(e)
874 if ret['errors']:
875 raise Exception(ret)
876 return ret
877
878
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200879class MachinesStatus(MaasObject):
880 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200881 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200882 cls._maas = _create_maas_client()
883 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200884 json_result = json.loads(result.read())
885 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200886 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200887 if objects_name:
888 if ',' in objects_name:
889 objects_name = set(objects_name.split(','))
890 else:
891 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200892 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200893 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200894 continue
Michael Polenchuke438bd32017-11-09 20:42:42 +0400895 status = STATUS_NAME_DICT[machine['status']]
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200896 summary[status] += 1
azvyagintsev7605a662017-11-03 19:05:04 +0200897 res.append(
898 {'hostname': machine['hostname'],
899 'system_id': machine['system_id'],
900 'status': status})
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200901 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200902
azvyagintsev7605a662017-11-03 19:05:04 +0200903 @classmethod
904 def wait_for_machine_status(cls, **kwargs):
905 """
906 A function that wait for any requested status, for any set of maas
907 machines.
908
909 If no kwargs has been passed - will try to wait ALL
910 defined in salt::maas::region::machines
911
912 See readme file for more examples.
913 CLI Example:
914 .. code-block:: bash
915
916 salt-call state.apply maas.machines.wait_for_deployed
917
918 :param kwargs:
919 timeout: in s; Global timeout for wait
920 poll_time: in s;Sleep time, between retry
921 req_status: string; Polling status
922 machines: list; machine names
923 ignore_machines: list; machine names
924 :ret: True
925 Exception - if something fail/timeout reached
926 """
927 timeout = kwargs.get("timeout", 60 * 120)
928 poll_time = kwargs.get("poll_time", 30)
929 req_status = kwargs.get("req_status", "Ready")
930 to_discover = kwargs.get("machines", None)
931 ignore_machines = kwargs.get("ignore_machines", None)
932 if not to_discover:
933 try:
934 to_discover = __salt__['config.get']('maas')['region'][
935 'machines'].keys()
936 except KeyError:
937 LOG.warning("No defined machines!")
938 return True
939 total = copy.deepcopy(to_discover) or []
940 if ignore_machines and total:
941 total = [x for x in to_discover if x not in ignore_machines]
942 started_at = time.time()
943 while len(total) <= len(to_discover):
944 for m in to_discover:
945 for discovered in MachinesStatus.execute()['machines']:
946 if m == discovered['hostname'] and \
Michael Polenchuke438bd32017-11-09 20:42:42 +0400947 discovered['status'].lower() == req_status.lower():
948 if m in total:
949 total.remove(m)
950
azvyagintsev7605a662017-11-03 19:05:04 +0200951 if len(total) <= 0:
952 LOG.debug(
953 "Machines:{} are:{}".format(to_discover, req_status))
954 return True
955 if (timeout - (time.time() - started_at)) <= 0:
956 raise Exception(
957 'Machines:{}not in {} state'.format(total, req_status))
958 LOG.info(
959 "Waiting status:{} "
960 "for machines:{}"
961 "\nsleep for:{}s "
962 "Timeout:{}s".format(req_status, total, poll_time, timeout))
963 time.sleep(poll_time)
964
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200965
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100966def process_fabrics():
967 return Fabric().process()
968
Jiri Broulike30a60f2018-04-09 21:15:10 +0200969def process_boot_sources():
970 return Boot_source().process()
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200971
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100972def process_subnets():
973 return Subnet().process()
974
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200975
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100976def process_dhcp_snippets():
977 return DHCPSnippet().process()
978
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200979
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100980def process_package_repositories():
981 return PacketRepository().process()
982
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200983
Krzysztof Szukiełojć889eee92017-04-14 11:45:35 +0200984def process_devices(*args):
985 return Device().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100986
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200987
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200988def process_machines(*args):
989 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100990
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200991
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200992def process_assign_machines_ip(*args):
azvyagintsev06b71e72017-11-08 17:11:07 +0200993 """
994 Manage interface configurations.
995 See readme.rst for more info
996 """
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200997 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200998
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200999
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +02001000def machines_status(*args):
1001 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +02001002
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001003
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +02001004def deploy_machines(*args):
1005 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +02001006
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001007
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +01001008def process_boot_resources():
1009 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +01001010
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001011
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +01001012def process_maas_config():
1013 return MaasConfig().process()
1014
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001015
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +01001016def process_commissioning_scripts():
1017 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +02001018
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001019
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +02001020def process_domain():
1021 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +02001022
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001023
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +02001024def process_sshprefs():
1025 return SSHPrefs().process()
azvyagintsev7605a662017-11-03 19:05:04 +02001026
1027
1028def wait_for_machine_status(**kwargs):
1029 return MachinesStatus.wait_for_machine_status(**kwargs)