blob: 20c713a3a8ac5b19ce37f0fbc0e7d0d22ad6c278 [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)
Dzmitry Stremkouski4394bd92020-03-14 15:58:21 +0100165
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200166 if objects_name is not None:
167 if ',' in objects_name:
168 objects_name = objects_name.split(',')
169 else:
170 objects_name = [objects_name]
171 for object_name in objects_name:
172 process_single(object_name, config[object_name])
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +0200173 else:
174 for name, config_data in config.iteritems():
Dzmitry Stremkouski4394bd92020-03-14 15:58:21 +0100175 if isinstance(config_data, dict) and 'status_name' in all_elements[name] and all_elements[name]['status_name'] == 'Deployed':
176 LOG.info('Machine %s already deployed, skipping it.' % name)
177 else:
178 process_single(name, config_data)
179
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200180 except Exception as e:
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200181 LOG.exception('Error Global')
182 raise
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100183 if ret['errors']:
Jiri Broulike30a60f2018-04-09 21:15:10 +0200184 if 'already exists' in str(ret['errors']):
185 ret['success'] = ret['errors']
186 ret['errors'] = {}
187 else:
188 raise Exception(ret)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100189 return ret
Ales Komarek663b85c2016-03-11 14:26:42 +0100190
191
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100192class Fabric(MaasObject):
193 def __init__(self):
194 super(Fabric, self).__init__()
195 self._all_elements_url = u'api/2.0/fabrics/'
196 self._create_url = u'api/2.0/fabrics/'
197 self._update_url = u'api/2.0/fabrics/{0}/'
198 self._config_path = 'region.fabrics'
Ales Komarek663b85c2016-03-11 14:26:42 +0100199
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100200 def fill_data(self, name, fabric):
201 data = {
202 'name': name,
203 'description': fabric.get('description', ''),
204 }
205 if 'class_type' in fabric:
206 data['class_type'] = fabric.get('class_type'),
207 return data
Ales Komarek663b85c2016-03-11 14:26:42 +0100208
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100209 def update(self, new, old):
210 new['id'] = str(old['id'])
211 return new
Ales Komarek663b85c2016-03-11 14:26:42 +0100212
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200213
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100214class Subnet(MaasObject):
215 def __init__(self):
216 super(Subnet, self).__init__()
217 self._all_elements_url = u'api/2.0/subnets/'
218 self._create_url = u'api/2.0/subnets/'
219 self._update_url = u'api/2.0/subnets/{0}/'
220 self._config_path = 'region.subnets'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200221 self._extra_data_urls = {'fabrics': u'api/2.0/fabrics/'}
Ales Komarek0fafa572016-03-11 14:56:44 +0100222
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100223 def fill_data(self, name, subnet, fabrics):
224 data = {
225 'name': name,
Alexandru Avadanii652e7552017-08-19 02:03:01 +0200226 'fabric': str(fabrics[subnet.get('fabric',
227 self._get_fabric_from_cidr(subnet.get('cidr')))]),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100228 'cidr': subnet.get('cidr'),
229 'gateway_ip': subnet['gateway_ip'],
230 }
231 self._iprange = subnet['iprange']
232 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100233
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100234 def update(self, new, old):
235 new['id'] = str(old['id'])
236 return new
Ales Komarek0fafa572016-03-11 14:56:44 +0100237
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100238 def send(self, data):
239 response = super(Subnet, self).send(data)
240 res_json = json.loads(response)
241 self._process_iprange(res_json['id'])
242 return response
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100243
Alexandru Avadanii652e7552017-08-19 02:03:01 +0200244 def _get_fabric_from_cidr(self, cidr):
245 subnets = json.loads(self._maas.get(u'api/2.0/subnets/').read())
246 for subnet in subnets:
247 if subnet['cidr'] == cidr:
248 return subnet['vlan']['fabric']
249 return ''
250
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100251 def _process_iprange(self, subnet_id):
252 ipranges = json.loads(self._maas.get(u'api/2.0/ipranges/').read())
253 LOG.warn('all %s ipranges %s', subnet_id, ipranges)
254 update = False
255 old_data = None
256 for iprange in ipranges:
257 if iprange['subnet']['id'] == subnet_id:
258 update = True
259 old_data = iprange
260 break
261 data = {
262 'start_ip': self._iprange.get('start'),
263 'end_ip': self._iprange.get('end'),
264 'subnet': str(subnet_id),
265 'type': self._iprange.get('type', 'dynamic')
266 }
267 LOG.warn('INFO: %s\n OLD: %s', data, old_data)
268 LOG.info('iprange %s', _format_data(data))
269 if update:
270 LOG.warn('UPDATING %s %s', data, old_data)
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200271 self._maas.put(u'api/2.0/ipranges/{0}/'.format(old_data['id']),
272 **data)
smolaonc3385f82016-03-11 19:01:24 +0100273 else:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100274 self._maas.post(u'api/2.0/ipranges/', None, **data)
smolaonc3385f82016-03-11 19:01:24 +0100275
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200276
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100277class DHCPSnippet(MaasObject):
278 def __init__(self):
279 super(DHCPSnippet, self).__init__()
280 self._all_elements_url = u'api/2.0/dhcp-snippets/'
281 self._create_url = u'api/2.0/dhcp-snippets/'
282 self._update_url = u'api/2.0/dhcp-snippets/{0}/'
283 self._config_path = 'region.dhcp_snippets'
284 self._extra_data_urls = {'subnets': u'api/2.0/subnets/'}
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100285
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100286 def fill_data(self, name, snippet, subnets):
287 data = {
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200288 'name': name,
289 'value': snippet['value'],
290 'description': snippet['description'],
291 'enabled': str(snippet['enabled'] and 1 or 0),
292 'subnet': str(subnets[snippet['subnet']]),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100293 }
294 return data
smolaonc3385f82016-03-11 19:01:24 +0100295
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100296 def update(self, new, old):
297 new['id'] = str(old['id'])
298 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100299
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200300
Jiri Broulike30a60f2018-04-09 21:15:10 +0200301class Boot_source(MaasObject):
302 def __init__(self):
303 super(Boot_source, self).__init__()
304 self._all_elements_url = u'api/2.0/boot-sources/'
305 self._create_url = u'api/2.0/boot-sources/'
306 self._update_url = u'api/2.0/boot-sources/{0}/'
307 self._config_path = 'region.boot_sources'
308 self._element_key = 'id'
309
310 def fill_data(self, name, boot_source):
311 data = {
312 'name': name,
313 'url': boot_source.get('url', ''),
314 'keyring_filename': boot_source.get('keyring_file', ''),
315 }
316 return data
317
318 def update(self, new, old):
319 new['id'] = str(old['id'])
320 return new
321
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100322class PacketRepository(MaasObject):
323 def __init__(self):
324 super(PacketRepository, self).__init__()
325 self._all_elements_url = u'api/2.0/package-repositories/'
326 self._create_url = u'api/2.0/package-repositories/'
327 self._update_url = u'api/2.0/package-repositories/{0}/'
328 self._config_path = 'region.package_repositories'
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100329
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100330 def fill_data(self, name, package_repository):
331 data = {
332 'name': name,
333 'url': package_repository['url'],
334 'distributions': package_repository['distributions'],
335 'components': package_repository['components'],
336 'arches': package_repository['arches'],
337 'key': package_repository['key'],
338 'enabled': str(package_repository['enabled'] and 1 or 0),
339 }
340 if 'disabled_pockets' in package_repository:
341 data['disabled_pockets'] = package_repository['disable_pockets'],
342 return data
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100343
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100344 def update(self, new, old):
345 new['id'] = str(old['id'])
346 return new
Krzysztof Szukiełojć15b62b72017-02-15 08:58:18 +0100347
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200348
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100349class Device(MaasObject):
350 def __init__(self):
351 super(Device, self).__init__()
352 self._all_elements_url = u'api/2.0/devices/'
353 self._create_url = u'api/2.0/devices/'
354 self._update_url = u'api/2.0/devices/{0}/'
355 self._config_path = 'region.devices'
356 self._element_key = 'hostname'
357 self._update_key = 'system_id'
smolaonc3385f82016-03-11 19:01:24 +0100358
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100359 def fill_data(self, name, device_data):
360 data = {
361 'mac_addresses': device_data['mac'],
362 'hostname': name,
363 }
364 self._interface = device_data['interface']
365 return data
366
367 def update(self, new, old):
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100368 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
369 if new['mac_addresses'].lower() not in old_macs:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100370 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100371 LOG.info('Mac changed deleting old device %s', old['system_id'])
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100372 self._maas.delete(u'api/2.0/devices/{0}/'.format(old['system_id']))
373 else:
374 new[self._update_key] = str(old[self._update_key])
375 return new
376
377 def send(self, data):
378 response = super(Device, self).send(data)
379 resp_json = json.loads(response)
380 system_id = resp_json['system_id']
381 iface_id = resp_json['interface_set'][0]['id']
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100382 self._link_interface(system_id, iface_id)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100383 return response
384
385 def _link_interface(self, system_id, interface_id):
386 data = {
387 'mode': self._interface.get('mode', 'STATIC'),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100388 'subnet': self._interface['subnet'],
389 'ip_address': self._interface['ip_address'],
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100390 }
391 if 'default_gateway' in self._interface:
392 data['default_gateway'] = self._interface.get('default_gateway')
393 if self._update:
394 data['force'] = '1'
395 LOG.info('interfaces link_subnet %s %s %s', system_id, interface_id,
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200396 _format_data(data))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100397 self._maas.post(u'/api/2.0/nodes/{0}/interfaces/{1}/'
398 .format(system_id, interface_id), 'link_subnet',
399 **data)
400
401
402class Machine(MaasObject):
403 def __init__(self):
404 super(Machine, self).__init__()
405 self._all_elements_url = u'api/2.0/machines/'
406 self._create_url = u'api/2.0/machines/'
407 self._update_url = u'api/2.0/machines/{0}/'
408 self._config_path = 'region.machines'
409 self._element_key = 'hostname'
410 self._update_key = 'system_id'
411
412 def fill_data(self, name, machine_data):
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100413 power_data = machine_data['power_parameters']
azvyagintsev06b71e72017-11-08 17:11:07 +0200414 machine_pxe_mac = machine_data.get('pxe_interface_mac', None)
415 if machine_data.get("interface", None):
416 LOG.warning(
417 "Old machine-describe detected! "
418 "Please read documentation for "
419 "'salt-formulas/maas' for migration!")
420 machine_pxe_mac = machine_data['interface'].get('mac', None)
421 if not machine_pxe_mac:
422 raise Exception("PXE MAC for machine:{} not defined".format(name))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100423 data = {
424 'hostname': name,
425 'architecture': machine_data.get('architecture', 'amd64/generic'),
azvyagintsev06b71e72017-11-08 17:11:07 +0200426 'mac_addresses': machine_pxe_mac,
mkraynove95cdb62018-05-08 14:17:18 +0400427 'power_type': power_data.get('power_type', 'manual'),
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100428 }
azvyagintsev6543ec82018-11-01 23:45:09 +0200429 for k, v in power_data.items():
Denis Egorenko60963332018-10-23 19:24:28 +0400430 if k == 'power_type':
431 continue
azvyagintsev6543ec82018-11-01 23:45:09 +0200432 elif k == 'power_password':
433 data['power_parameters_power_pass'] = v
434 LOG.warning(
435 "Deprecated power parameter:power_password passed! "
436 "Please change key name to 'power_pass'! ")
437 continue
438 elif k == 'power_nova_id':
439 data['power_parameters_nova_id'] = v
440 LOG.warning(
441 "Deprecated power parameter:power_nova_id passed! "
442 "Please change key name to 'nova_id'! ")
443 continue
444 elif k == 'power_os_tenantname':
445 data['power_parameters_os_tenantname'] = v
446 LOG.warning(
447 "Deprecated power parameter:power_os_tenantname passed! "
448 "Please change key name to 'os_tenantname'! ")
449 continue
450 elif k == 'power_os_username':
451 data['power_parameters_os_username'] = v
452 LOG.warning(
453 "Deprecated power parameter:power_os_username passed! "
454 "Please change key name to 'os_username'! ")
455 continue
456 elif k == 'power_os_password':
457 data['power_parameters_os_password'] = v
458 LOG.warning(
459 "Deprecated power parameter:power_os_password passed! "
460 "Please change key name to 'os_password'! ")
461 continue
462 elif k == 'power_os_authurl':
463 data['power_parameters_os_authurl'] = v
464 LOG.warning(
465 "Deprecated power parameter:power_os_authurl passed! "
466 "Please change key name to 'os_authurl'! ")
467 continue
468
Denis Egorenko60963332018-10-23 19:24:28 +0400469 data_key = 'power_parameters_{}'.format(k)
470 data[data_key] = v
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100471 return data
472
473 def update(self, new, old):
Gabor Toth36bc0282018-08-18 10:39:51 +0200474 LOG.debug('Updating machine')
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100475 old_macs = set(v['mac_address'].lower() for v in old['interface_set'])
Gabor Toth36bc0282018-08-18 10:39:51 +0200476 LOG.debug('old_macs: %s' % old_macs)
477 if isinstance(new['mac_addresses'], list):
478 new_macs = set(v.lower() for v in new['mac_addresses'])
479 else:
480 new_macs = set([new['mac_addresses'].lower()])
481 LOG.debug('new_macs: %s' % new_macs)
482 intersect = list(new_macs.intersection(old_macs))
483 if not intersect:
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100484 self._update = False
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100485 LOG.info('Mac changed deleting old machine %s', old['system_id'])
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200486 self._maas.delete(u'api/2.0/machines/{0}/'
487 .format(old['system_id']))
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100488 else:
Gabor Toth36bc0282018-08-18 10:39:51 +0200489 new['mac_addresses'] = intersect
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100490 new[self._update_key] = str(old[self._update_key])
491 return new
492
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200493
494class AssignMachinesIP(MaasObject):
azvyagintsev06b71e72017-11-08 17:11:07 +0200495 # FIXME
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200496 READY = 4
Andreyef156992017-07-03 14:54:03 -0500497 DEPLOYED = 6
Krzysztof Szukiełojćd6ee1a02017-04-07 14:01:30 +0200498
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200499 def __init__(self):
500 super(AssignMachinesIP, self).__init__()
501 self._all_elements_url = None
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200502 self._create_url = \
503 (u'/api/2.0/nodes/{system_id}/interfaces/{interface_id}/',
504 'link_subnet')
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200505 self._config_path = 'region.machines'
506 self._element_key = 'hostname'
507 self._update_key = 'system_id'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200508 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
509 None, 'hostname')}
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200510
azvyagintsev06b71e72017-11-08 17:11:07 +0200511 def _data_old(self, _interface, _machine):
512 """
513 _interface = {
514 "mac": "11:22:33:44:55:77",
515 "mode": "STATIC",
516 "ip": "2.2.3.15",
517 "subnet": "subnet1",
518 "gateway": "2.2.3.2",
519 }
520 :param data:
521 :return:
522 """
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100523 data = {
524 'mode': 'STATIC',
azvyagintsev06b71e72017-11-08 17:11:07 +0200525 'subnet': str(_interface.get('subnet')),
526 'ip_address': str(_interface.get('ip')),
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100527 }
azvyagintsev06b71e72017-11-08 17:11:07 +0200528 if 'gateway' in _interface:
529 data['default_gateway'] = _interface.get('gateway')
Krzysztof Szukiełojć9449af12017-04-11 14:01:30 +0200530 data['force'] = '1'
azvyagintsev06b71e72017-11-08 17:11:07 +0200531 data['system_id'] = str(_machine['system_id'])
532 data['interface_id'] = str(_machine['interface_set'][0]['id'])
Krzysztof Szukiełojćd57a32d2017-04-04 11:25:02 +0200533 return data
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100534
azvyagintsev06b71e72017-11-08 17:11:07 +0200535 def _get_nic_id_by_mac(self, machine, req_mac=None):
536 data = {}
537 for nic in machine['interface_set']:
538 data[nic['mac_address']] = nic['id']
539 if req_mac:
540 if req_mac in data.keys():
541 return data[req_mac]
542 else:
543 raise Exception('NIC with mac:{} not found at '
544 'node:{}'.format(req_mac, machine['fqdn']))
545 return data
546
547 def _disconnect_all_nic(self, machine):
548 """
549 Maas will fail, in case same config's will be to apply
550 on different interfaces. In same time - not possible to push
551 whole network schema at once. Before configuring - need to clean-up
552 everything
553 :param machine:
554 :return:
555 """
556 for nic in machine['interface_set']:
557 LOG.debug("Disconnecting interface:{}".format(nic['mac_address']))
558 try:
559 self._maas.post(
560 u'/api/2.0/nodes/{}/interfaces/{}/'.format(
561 machine['system_id'], nic['id']), 'disconnect')
562 except Exception as e:
563 LOG.error("Failed to disconnect interface:{} on node:{}".format(
564 nic['mac_address'], machine['fqdn']))
565 raise Exception(str(e))
566
567 def _process_interface(self, nic_data, machine):
568 """
569 Process exactly one interface:
570 - update interface
571 - link to network
572 These functions are self-complementary, and do not require an
573 external "process" method. Those broke old-MaasObject logic,
574 though make functions more simple in case iterable tasks.
575 """
576 nic_id = self._get_nic_id_by_mac(machine, nic_data['mac'])
577
578 # Process op=link_subnet
579 link_data = {}
580 _mode = nic_data.get('mode', 'AUTO').upper()
581 if _mode == 'STATIC':
582 link_data = {
583 'mode': 'STATIC',
584 'subnet': str(nic_data.get('subnet')),
585 'ip_address': str(nic_data.get('ip')),
586 'default_gateway': str(nic_data.get('gateway', "")),
587 }
588 elif _mode == 'DHCP':
589 link_data = {
590 'mode': 'DHCP',
591 'subnet': str(nic_data.get('subnet')),
592 }
593 elif _mode == 'AUTO':
594 link_data = {'mode': 'AUTO',
595 'default_gateway': str(nic_data.get('gateway', "")), }
596 elif _mode in ('LINK_UP', 'UNCONFIGURED'):
597 link_data = {'mode': 'LINK_UP'}
598 else:
599 raise Exception('Wrong IP mode:{}'
600 ' for node:{}'.format(_mode, machine['fqdn']))
601 link_data['force'] = str(1)
602
603 physical_data = {"name": nic_data.get("name", ""),
604 "tags": nic_data.get('tags', ""),
605 "vlan": nic_data.get('vlan', "")}
606
607 try:
608 # Cleanup-old definition
609 LOG.debug("Processing {}:{},{}".format(nic_data['mac'], link_data,
610 physical_data))
611 # "link_subnet" and "fill all other data" - its 2 different
612 # operations. So, first we update NIC:
613 self._maas.put(
614 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
615 nic_id),
616 **physical_data)
617 # And then, link subnet configuration:
618 self._maas.post(
619 u'/api/2.0/nodes/{}/interfaces/{}/'.format(machine['system_id'],
620 nic_id),
621 'link_subnet', **link_data)
622 except Exception as e:
623 LOG.error("Failed to process interface:{} on node:{}".format(
624 nic_data['mac'], machine['fqdn']))
625 raise Exception(str(e))
626
627 def fill_data(self, name, data, machines):
628 machine = machines[name]
629 if machine['status'] == self.DEPLOYED:
630 LOG.debug("Skipping node:{} "
631 "since it in status:DEPLOYED".format(name))
632 return
633 if machine['status'] != self.READY:
634 raise Exception('Machine:{} not in status:READY'.format(name))
635 # backward comparability, for old schema
636 if data.get("interface", None):
637 if 'ip' not in data["interface"]:
638 LOG.info("No IP NIC definition for:{}".format(name))
639 return
640 LOG.warning(
641 "Old machine-describe detected! "
642 "Please read documentation "
643 "'salt-formulas/maas' for migration!")
644 return self._data_old(data['interface'], machines[name])
645 # NewSchema processing:
646 # Warning: old-style MaasObject.process still be called, but
647 # with empty data for process.
648 interfaces = data.get('interfaces', {})
649 if len(interfaces.keys()) == 0:
650 LOG.info("No IP NIC definition for:{}".format(name))
651 return
652 LOG.info('%s for %s', self.__class__.__name__.lower(),
653 machine['fqdn'])
654 self._disconnect_all_nic(machine)
655 for key, value in sorted(interfaces.iteritems()):
656 self._process_interface(value, machine)
657
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100658
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200659class DeployMachines(MaasObject):
azvyagintsev7605a662017-11-03 19:05:04 +0200660 # FIXME
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200661 READY = 4
Andreyef156992017-07-03 14:54:03 -0500662 DEPLOYED = 6
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200663
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200664 def __init__(self):
665 super(DeployMachines, self).__init__()
666 self._all_elements_url = None
667 self._create_url = (u'api/2.0/machines/{system_id}/', 'deploy')
668 self._config_path = 'region.machines'
669 self._element_key = 'hostname'
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200670 self._extra_data_urls = {'machines': (u'api/2.0/machines/',
671 None, 'hostname')}
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200672
673 def fill_data(self, name, machine_data, machines):
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200674 machine = machines[name]
Andreyef156992017-07-03 14:54:03 -0500675 if machine['status'] == self.DEPLOYED:
676 return
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200677 if machine['status'] != self.READY:
678 raise Exception('Not in ready state')
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200679 data = {
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200680 'system_id': machine['system_id'],
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200681 }
Krzysztof Szukiełojć32677bf2017-04-13 11:04:25 +0200682 if 'distro_series' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200683 data['distro_series'] = machine_data['distro_series']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200684 if 'hwe_kernel' in machine_data:
Krzysztof Szukiełojć008d7d42017-04-05 15:26:01 +0200685 data['hwe_kernel'] = machine_data['hwe_kernel']
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200686 return data
687
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200688 def send(self, data):
689 LOG.info('%s %s', self.__class__.__name__.lower(), _format_data(data))
Krzysztof Szukiełojć3b7516d2017-04-12 11:52:55 +0200690 self._maas.post(u'api/2.0/machines/', 'allocate', system_id=data['system_id']).read()
Krzysztof Szukiełojć33f9b592017-04-12 11:43:50 +0200691 return self._maas.post(self._create_url[0].format(**data),
692 *self._create_url[1:], **data).read()
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200693
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +0100694class BootResource(MaasObject):
695 def __init__(self):
696 super(BootResource, self).__init__()
697 self._all_elements_url = u'api/2.0/boot-resources/'
698 self._create_url = u'api/2.0/boot-resources/'
699 self._update_url = u'api/2.0/boot-resources/{0}/'
700 self._config_path = 'region.boot_resources'
701
702 def fill_data(self, name, boot_data):
703 sha256 = hashlib.sha256()
704 sha256.update(file(boot_data['content']).read())
705 data = {
706 'name': name,
707 'title': boot_data['title'],
708 'architecture': boot_data['architecture'],
709 'filetype': boot_data['filetype'],
710 'size': str(os.path.getsize(boot_data['content'])),
711 'sha256': sha256.hexdigest(),
712 'content': io.open(boot_data['content']),
713 }
714 return data
715
716 def update(self, new, old):
717 self._update = False
718 return new
719
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +0200720
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100721class CommissioningScripts(MaasObject):
722 def __init__(self):
723 super(CommissioningScripts, self).__init__()
724 self._all_elements_url = u'api/2.0/commissioning-scripts/'
725 self._create_url = u'api/2.0/commissioning-scripts/'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100726 self._config_path = 'region.commissioning_scripts'
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100727 self._update_url = u'api/2.0/commissioning-scripts/{0}'
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100728 self._update_key = 'name'
729
730 def fill_data(self, name, file_path):
731 data = {
732 'name': name,
733 'content': io.open(file_path),
734 }
735 return data
736
737 def update(self, new, old):
738 return new
739
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200740
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100741class MaasConfig(MaasObject):
742 def __init__(self):
743 super(MaasConfig, self).__init__()
744 self._all_elements_url = None
745 self._create_url = (u'api/2.0/maas/', u'set_config')
746 self._config_path = 'region.maas_config'
747
748 def fill_data(self, name, value):
749 data = {
750 'name': name,
Krzysztof Szukiełojća6352a42017-03-17 14:21:57 +0100751 'value': str(value),
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +0100752 }
753 return data
754
755 def update(self, new, old):
756 self._update = False
757 return new
758
759
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200760class SSHPrefs(MaasObject):
761 def __init__(self):
762 super(SSHPrefs, self).__init__()
763 self._all_elements_url = None
764 self._create_url = u'api/2.0/account/prefs/sshkeys/'
765 self._config_path = 'region.sshprefs'
766 self._element_key = 'hostname'
767 self._update_key = 'system_id'
768
769 def fill_data(self, value):
770 data = {
771 'key': value,
772 }
773 return data
774
775 def process(self):
776 config = __salt__['config.get']('maas')
777 for part in self._config_path.split('.'):
778 config = config.get(part, {})
779 extra = {}
780 for name, url_call in self._extra_data_urls.iteritems():
781 key = 'id'
782 if isinstance(url_call, tuple):
783 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200784 json_res = json.loads(self._maas.get(url_call).read())
785 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +0200786 if self._all_elements_url:
787 all_elements = {}
788 elements = self._maas.get(self._all_elements_url).read()
789 res_json = json.loads(elements)
790 for element in res_json:
791 if isinstance(element, (str, unicode)):
792 all_elements[element] = {}
793 else:
794 all_elements[element[self._element_key]] = element
795 else:
796 all_elements = {}
797 ret = {
798 'success': [],
799 'errors': {},
800 'updated': [],
801 }
802 for config_data in config:
803 name = config_data[:10]
804 try:
805 data = self.fill_data(config_data, **extra)
806 self.send(data)
807 ret['success'].append(name)
808 except urllib2.HTTPError as e:
809 etxt = e.read()
810 LOG.exception('Failed for object %s reason %s', name, etxt)
811 ret['errors'][name] = str(etxt)
812 except Exception as e:
813 LOG.exception('Failed for object %s reason %s', name, e)
814 ret['errors'][name] = str(e)
815 if ret['errors']:
816 raise Exception(ret)
817 return ret
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200818
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200819
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200820class Domain(MaasObject):
821 def __init__(self):
822 super(Domain, self).__init__()
823 self._all_elements_url = u'/api/2.0/domains/'
824 self._create_url = u'/api/2.0/domains/'
825 self._config_path = 'region.domain'
826 self._update_url = u'/api/2.0/domains/{0}/'
827
828 def fill_data(self, value):
829 data = {
830 'name': value,
831 }
832 self._update = True
833 return data
834
835 def update(self, new, old):
836 new['id'] = str(old['id'])
837 new['authoritative'] = str(old['authoritative'])
838 return new
839
840 def process(self):
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200841 ret = {
842 'success': [],
843 'errors': {},
844 'updated': [],
845 }
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200846 config = __salt__['config.get']('maas')
847 for part in self._config_path.split('.'):
848 config = config.get(part, {})
849 extra = {}
850 for name, url_call in self._extra_data_urls.iteritems():
851 key = 'id'
852 if isinstance(url_call, tuple):
853 url_call, key = url_call[:]
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200854 json_res = json.loads(self._maas.get(url_call).read())
855 extra[name] = {v['name']: v[key] for v in json_res}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200856 if self._all_elements_url:
857 all_elements = {}
858 elements = self._maas.get(self._all_elements_url).read()
859 res_json = json.loads(elements)
860 for element in res_json:
861 if isinstance(element, (str, unicode)):
862 all_elements[element] = {}
863 else:
864 all_elements[element[self._element_key]] = element
865 else:
866 all_elements = {}
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +0200867 try:
868 data = self.fill_data(config, **extra)
869 data = self.update(data, all_elements.values()[0])
870 self.send(data)
871 ret['success'].append('domain')
872 except urllib2.HTTPError as e:
873 etxt = e.read()
874 LOG.exception('Failed for object %s reason %s', 'domain', etxt)
875 ret['errors']['domain'] = str(etxt)
876 except Exception as e:
877 LOG.exception('Failed for object %s reason %s', 'domain', e)
878 ret['errors']['domain'] = str(e)
879 if ret['errors']:
880 raise Exception(ret)
881 return ret
882
883
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200884class MachinesStatus(MaasObject):
885 @classmethod
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200886 def execute(cls, objects_name=None):
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200887 cls._maas = _create_maas_client()
888 result = cls._maas.get(u'api/2.0/machines/')
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200889 json_result = json.loads(result.read())
890 res = []
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200891 summary = collections.Counter()
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200892 if objects_name:
893 if ',' in objects_name:
894 objects_name = set(objects_name.split(','))
895 else:
896 objects_name = set([objects_name])
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200897 for machine in json_result:
Krzysztof Szukiełojćf5062202017-04-11 12:33:36 +0200898 if objects_name and machine['hostname'] not in objects_name:
Krzysztof Szukiełojć2497cdb2017-04-11 09:50:28 +0200899 continue
Michael Polenchuke438bd32017-11-09 20:42:42 +0400900 status = STATUS_NAME_DICT[machine['status']]
Krzysztof Szukiełojć0be1a162017-04-04 11:59:09 +0200901 summary[status] += 1
azvyagintsev7605a662017-11-03 19:05:04 +0200902 res.append(
903 {'hostname': machine['hostname'],
904 'system_id': machine['system_id'],
905 'status': status})
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +0200906 return {'machines': res, 'summary': summary}
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +0200907
azvyagintsev7605a662017-11-03 19:05:04 +0200908 @classmethod
909 def wait_for_machine_status(cls, **kwargs):
910 """
911 A function that wait for any requested status, for any set of maas
912 machines.
913
914 If no kwargs has been passed - will try to wait ALL
915 defined in salt::maas::region::machines
916
917 See readme file for more examples.
918 CLI Example:
919 .. code-block:: bash
920
921 salt-call state.apply maas.machines.wait_for_deployed
922
923 :param kwargs:
924 timeout: in s; Global timeout for wait
925 poll_time: in s;Sleep time, between retry
926 req_status: string; Polling status
927 machines: list; machine names
928 ignore_machines: list; machine names
Alexandru Avadanii3206fe72018-09-23 03:57:27 +0200929 attempts: max number of automatic hard retries
azvyagintsev7605a662017-11-03 19:05:04 +0200930 :ret: True
931 Exception - if something fail/timeout reached
932 """
933 timeout = kwargs.get("timeout", 60 * 120)
934 poll_time = kwargs.get("poll_time", 30)
935 req_status = kwargs.get("req_status", "Ready")
936 to_discover = kwargs.get("machines", None)
937 ignore_machines = kwargs.get("ignore_machines", None)
Martin Polreiche3183ad2019-10-30 13:01:50 +0100938 ignore_deployed_machines = kwargs.get("ignore_deployed_machines", False)
Alexandru Avadanii3206fe72018-09-23 03:57:27 +0200939 attempts = kwargs.get("attempts", 0)
940 counter = {}
Martin Polreiche3183ad2019-10-30 13:01:50 +0100941 ignored_deployed = []
azvyagintsev7605a662017-11-03 19:05:04 +0200942 if not to_discover:
943 try:
944 to_discover = __salt__['config.get']('maas')['region'][
945 'machines'].keys()
946 except KeyError:
947 LOG.warning("No defined machines!")
948 return True
949 total = copy.deepcopy(to_discover) or []
950 if ignore_machines and total:
951 total = [x for x in to_discover if x not in ignore_machines]
952 started_at = time.time()
953 while len(total) <= len(to_discover):
Alexandru Avadanii3206fe72018-09-23 03:57:27 +0200954 for machine in to_discover:
azvyagintsev7605a662017-11-03 19:05:04 +0200955 for discovered in MachinesStatus.execute()['machines']:
Alexandru Avadanii3206fe72018-09-23 03:57:27 +0200956 if machine == discovered['hostname'] and machine in total:
Martin Polreiche3183ad2019-10-30 13:01:50 +0100957 if ignore_deployed_machines and discovered['status'].lower() == 'deployed':
958 total.remove(machine)
959 ignored_deployed.append(machine)
Alexandru Avadanii3206fe72018-09-23 03:57:27 +0200960 if discovered['status'].lower() == req_status.lower():
961 total.remove(machine)
962 elif attempts > 0 and (machine not in counter or counter[machine] < attempts):
963 status = discovered['status']
964 sid = discovered['system_id']
965 cls._maas = _create_maas_client()
966 if status in ['Failed commissioning', 'New']:
967 cls._maas.delete(u'api/2.0/machines/{0}/'
968 .format(sid))
969 Machine().process()
970 LOG.info('Machine {0} deleted'.format(sid))
971 counter[machine] = 1 if machine not in counter else (counter[machine] + 1)
972 elif status in ['Failed testing']:
973 data = {}
974 action = 'override_failed_testing'
975 cls._maas.post(u'api/2.0/machines/{0}/'
Ivan Berezovskiyec560012019-10-11 18:05:15 +0400976 .format(sid), action, **data)
Alexandru Avadanii3206fe72018-09-23 03:57:27 +0200977 LOG.info('Machine {0} overriden'.format(sid))
978 counter[machine] = 1 if machine not in counter else (counter[machine] + 1)
979 elif status in ['Failed deployment', 'Allocated']:
980 data = {}
981 cls._maas.post(u'api/2.0/machines/{0}/'
Ivan Berezovskiyec560012019-10-11 18:05:15 +0400982 .format(sid), 'mark_broken', **data)
Alexandru Avadanii3206fe72018-09-23 03:57:27 +0200983 LOG.info('Machine {0} marked broken'.format(sid))
984 cls._maas.post(u'api/2.0/machines/{0}/'
Ivan Berezovskiyec560012019-10-11 18:05:15 +0400985 .format(sid), 'mark_fixed', **data)
Alexandru Avadanii3206fe72018-09-23 03:57:27 +0200986 LOG.info('Machine {0} marked fixed'.format(sid))
987 if machine in counter and counter[machine]:
988 data['testing_scripts'] = 'fio'
989 cls._maas.post(u'api/2.0/machines/{0}/'
Ivan Berezovskiyec560012019-10-11 18:05:15 +0400990 .format(sid), 'commission', **data)
Alexandru Avadanii3206fe72018-09-23 03:57:27 +0200991 LOG.info('Machine {0} fio test'.format(sid))
992 DeployMachines().process()
993 counter[machine] = 1 if machine not in counter else (counter[machine] + 1)
azvyagintsev7605a662017-11-03 19:05:04 +0200994 if len(total) <= 0:
Martin Polreiche3183ad2019-10-30 13:01:50 +0100995 if len(ignored_deployed) > 0:
996 for mach in ignored_deployed:
997 to_discover.remove(mach)
998 if len(to_discover) > 0:
999 LOG.debug(
1000 "Machines:{} are:{} and machines:{} were ignored as already deployed.".format(to_discover, req_status, ignored_deployed))
1001 else:
1002 LOG.debug(
1003 "All required machines already exist and were ignored as already deployed:{}".format(ignored_deployed))
1004 else:
1005 LOG.debug(
1006 "Machines:{} are:{}".format(to_discover, req_status))
azvyagintsev7605a662017-11-03 19:05:04 +02001007 return True
1008 if (timeout - (time.time() - started_at)) <= 0:
1009 raise Exception(
1010 'Machines:{}not in {} state'.format(total, req_status))
1011 LOG.info(
1012 "Waiting status:{} "
1013 "for machines:{}"
1014 "\nsleep for:{}s "
Alexandru Avadanii3206fe72018-09-23 03:57:27 +02001015 "Timeout:{}s ({}s left)"
1016 .format(req_status, total, poll_time, timeout,
1017 timeout - (time.time() - started_at)))
azvyagintsev7605a662017-11-03 19:05:04 +02001018 time.sleep(poll_time)
1019
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +02001020
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +01001021def process_fabrics():
1022 return Fabric().process()
1023
Jiri Broulike30a60f2018-04-09 21:15:10 +02001024def process_boot_sources():
1025 return Boot_source().process()
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001026
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +01001027def process_subnets():
1028 return Subnet().process()
1029
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001030
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +01001031def process_dhcp_snippets():
1032 return DHCPSnippet().process()
1033
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001034
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +01001035def process_package_repositories():
1036 return PacketRepository().process()
1037
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001038
Krzysztof Szukiełojć889eee92017-04-14 11:45:35 +02001039def process_devices(*args):
1040 return Device().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +01001041
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001042
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +02001043def process_machines(*args):
1044 return Machine().process(*args)
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +01001045
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001046
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +02001047def process_assign_machines_ip(*args):
azvyagintsev06b71e72017-11-08 17:11:07 +02001048 """
1049 Manage interface configurations.
1050 See readme.rst for more info
1051 """
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +02001052 return AssignMachinesIP().process(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +02001053
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001054
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +02001055def machines_status(*args):
1056 return MachinesStatus.execute(*args)
Krzysztof Szukiełojć04e18332017-04-04 11:51:44 +02001057
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001058
Krzysztof Szukiełojć222a3eb2017-04-11 09:39:07 +02001059def deploy_machines(*args):
1060 return DeployMachines().process(*args)
Krzysztof Szukiełojć7c16e052017-04-05 10:04:45 +02001061
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001062
Krzysztof Szukiełojćc4b33092017-02-15 13:25:38 +01001063def process_boot_resources():
1064 return BootResource().process()
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +01001065
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001066
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +01001067def process_maas_config():
1068 return MaasConfig().process()
1069
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001070
Krzysztof Szukiełojć43bc7e02017-03-17 10:32:07 +01001071def process_commissioning_scripts():
1072 return CommissioningScripts().process()
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +02001073
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001074
Krzysztof Szukiełojć8cc32b42017-03-29 15:22:57 +02001075def process_domain():
1076 return Domain().process()
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +02001077
Krzysztof Szukiełojćb3216752017-04-05 15:50:41 +02001078
Krzysztof Szukiełojća1bd77e2017-03-30 08:34:22 +02001079def process_sshprefs():
1080 return SSHPrefs().process()
azvyagintsev7605a662017-11-03 19:05:04 +02001081
1082
1083def wait_for_machine_status(**kwargs):
1084 return MachinesStatus.wait_for_machine_status(**kwargs)