blob: 0ec08f6e4dbf9255ce5e6e08c841c5cf431429c7 [file] [log] [blame]
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001# -*- coding: utf-8 -*-
azvyagintsevbca1f462018-05-25 19:06:46 +03002"""
Ondrej Smolab57a23b2018-01-24 11:18:24 +01003Module 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
azvyagintsevbca1f462018-05-25 19:06:46 +030012"""
Ondrej Smolab57a23b2018-01-24 11:18:24 +010013
14from __future__ import absolute_import
15
16import collections
17import copy
18import hashlib
19import io
20import json
21import logging
Ondrej Smolab57a23b2018-01-24 11:18:24 +010022import time
23import urllib2
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020024# Salt utils
Ondrej Smolab57a23b2018-01-24 11:18:24 +010025from salt.exceptions import CommandExecutionError, SaltInvocationError
26
27LOG = logging.getLogger(__name__)
28
29SIZE = {
30 "M": 1000000,
31 "G": 1000000000,
32 "T": 1000000000000,
33}
34
35RAID = {
36 0: "raid-0",
37 1: "raid-1",
38 5: "raid-5",
39 10: "raid-10",
40}
41
42# Import third party libs
43HAS_MASS = False
44try:
45 from maas_client import MAASClient, MAASDispatcher, MAASOAuth
46 HAS_MASS = True
47except ImportError:
48 LOG.debug('Missing MaaS client module is Missing. Skipping')
49
50
51def __virtual__():
azvyagintsevbca1f462018-05-25 19:06:46 +030052 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +010053 Only load this module if maas-client
54 is installed on this minion.
azvyagintsevbca1f462018-05-25 19:06:46 +030055 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +010056 if HAS_MASS:
57 return 'maasng'
58 return False
59
60
61APIKEY_FILE = '/var/lib/maas/.maas_credentials'
62
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020063
Ondrej Smolab57a23b2018-01-24 11:18:24 +010064def _format_data(data):
65 class Lazy:
66 def __str__(self):
67 return ' '.join(['{0}={1}'.format(k, v)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020068 for k, v in data.iteritems()])
Ondrej Smolab57a23b2018-01-24 11:18:24 +010069 return Lazy()
70
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020071
azvyagintsev58947072018-06-29 12:09:48 +030072def _create_maas_client(api_url=None):
73 if not api_url:
74 api_url = 'http://localhost:5240/MAAS'
Ondrej Smolab57a23b2018-01-24 11:18:24 +010075 global APIKEY_FILE
76 try:
77 api_token = file(APIKEY_FILE).read().splitlines()[-1].strip()\
78 .split(':')
79 except:
80 LOG.exception('token')
81 auth = MAASOAuth(*api_token)
Ondrej Smolab57a23b2018-01-24 11:18:24 +010082 dispatcher = MAASDispatcher()
83 return MAASClient(auth, dispatcher, api_url)
84
Ondrej Smolab57a23b2018-01-24 11:18:24 +010085
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020086def _get_blockdevice_id_by_name(hostname, device):
87
88 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +010089 return list_blockdevices(hostname)[device]["id"]
90
Ondrej Smolab57a23b2018-01-24 11:18:24 +010091
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020092def _get_volume_group_id_by_name(hostname, device):
93
94 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +010095 return list_volume_groups(hostname)[device]["id"]
96
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020097
azvyagintsevbca1f462018-05-25 19:06:46 +030098def _get_volume_id_by_name(hostname, volume_name, volume_group, maas_volname=True):
99
100 if not maas_volname:
101 # MAAS-like name
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200102 volume_name = str("%s-%s" % (volume_group, volume_name))
103 # TODO validation
azvyagintsevbca1f462018-05-25 19:06:46 +0300104 return get_volumes(hostname, volume_group)[volume_name]["id"]
105
106
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100107def _get_partition_id_by_name(hostname, device, partition):
108
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200109 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100110 return list_partitions(hostname, device)[partition]["id"]
111
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200112# MACHINE SECTION
113
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100114
115def get_machine(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300116 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100117 Get information aboout specified machine
118
119 CLI Example:
120
121 .. code-block:: bash
122
123 salt-call maasng.get_machine server_hostname
Alexei Lugovoie5b64122018-11-06 12:30:01 +0100124
125 Error codes:
126 0 : Machine not found
azvyagintsevbca1f462018-05-25 19:06:46 +0300127 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100128 try:
129 return list_machines()[hostname]
130 except KeyError:
Alexei Lugovoie5b64122018-11-06 12:30:01 +0100131 return {"error":
132 { 0: "Machine not found" }
133 }
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100134
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200135
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200136def list_machines(status_filter=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300137 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100138 Get list of all machines from maas server
139
140 CLI Example:
141
142 .. code-block:: bash
143
144 salt 'maas-node' maasng.list_machines
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200145 salt 'maas-node' maasng.list_machines status_filter=[Deployed,Ready]
azvyagintsevbca1f462018-05-25 19:06:46 +0300146 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200147 machines = {}
148 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100149 json_res = json.loads(maas.get(u'api/2.0/machines/').read())
150 for item in json_res:
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200151 if not status_filter or item['status_name'] in status_filter:
152 machines[item["hostname"]] = item
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100153 return machines
154
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200155
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100156def create_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200157 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100158
159 return False
160
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200161
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100162def update_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200163 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100164
165 return False
166
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200167def delete_machine(hostname):
168 """
169 Delete specified machine
170
171 CLI Example:
172
173 .. code-block:: bash
174
175 salt 'maas-node' maasng.delete_machine server_hostname
176 salt-call maasng.delete_machine server_hostname
177 """
178 result = {}
179 maas = _create_maas_client()
180 system_id = get_machine(hostname)["system_id"]
181 LOG.debug('delete_machine: {}'.format(system_id))
182 maas.delete(
183 u"api/2.0/machines/{0}/".format(system_id)).read()
184
185 result["new"] = "Machine {0} deleted".format(hostname)
186 return result
187
188def action_machine(hostname, action, comment=None):
189 """
190 Send simple action (e.g. mark_broken, mark_fixed) to machine.
191
192 :param action: Action to send for machine (one of MaaS' op codes)
193 :param comment: Optional comment for the event log.
194
195 CLI Example:
196
197 .. code-block:: bash
198
199 salt 'maas-node' maasng.action_machine server_hostname mark_broken comment='dead'
200 """
201 result = {}
202 data = {}
203 maas = _create_maas_client()
204 system_id = get_machine(hostname)["system_id"]
205 LOG.debug('action_machine: {}'.format(system_id))
206
207 # TODO validation
208 if comment:
209 data["comment"] = comment
210 json_res = json.loads(maas.post(
211 u"api/2.0/machines/{0}/".format(system_id), action, **data).read())
212 LOG.info(json_res)
213 result["new"] = "Machine {0} action {1} executed".format(hostname, action)
214
215 return result
216
azvyagintsevbca1f462018-05-25 19:06:46 +0300217# END MACHINE SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200218# RAID SECTION
219
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100220
221def create_raid(hostname, name, level, disks=[], partitions=[], **kwargs):
azvyagintsevbca1f462018-05-25 19:06:46 +0300222 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100223 Create new raid on machine.
224
225 CLI Example:
226
227 .. code-block:: bash
228
229 salt-call maasng.create_raid hostname=kvm03 name=md0 level=1 disks=[vdb,vdc] partitions=[vdd-part1,vde-part1]
azvyagintsevbca1f462018-05-25 19:06:46 +0300230 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100231
232 result = {}
233
234 if len(disks) == 0 and len(partitions) == 0:
235 result["error"] = "Disks or partitions need to be provided"
236
237 disk_ids = []
238 partition_ids = []
239
240 for disk in disks:
241 try:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200242 disk_ids.append(str(_get_blockdevice_id_by_name(hostname, disk)))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100243 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200244 result["error"] = "Device {0} does not exists on machine {1}".format(
245 disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100246 return result
247
248 for partition in partitions:
249 try:
250 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200251 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100252 partition_ids.append(str(device_part[partition]["id"]))
253 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200254 result["error"] = "Partition {0} does not exists on machine {1}".format(
255 partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100256 return result
257
258 data = {
259 "name": name,
260 "level": RAID[int(level)],
261 "block_devices": disk_ids,
262 "partitions": partition_ids,
263 }
264
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200265 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100266 system_id = get_machine(hostname)["system_id"]
267 LOG.info(system_id)
268
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200269 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100270 LOG.info(data)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200271 json_res = json.loads(
272 maas.post(u"api/2.0/nodes/{0}/raids/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100273 LOG.info(json_res)
274 result["new"] = "Raid {0} created".format(name)
275
276 return result
277
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200278
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100279def list_raids(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300280 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100281 Get list all raids on machine
282
283 CLI Example:
284
285 .. code-block:: bash
286
287 salt-call maasng.list_raids server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300288 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100289
azvyagintsevbca1f462018-05-25 19:06:46 +0300290 raids = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200291 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100292 system_id = get_machine(hostname)["system_id"]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200293 # TODO validation
294 json_res = json.loads(
295 maas.get(u"api/2.0/nodes/{0}/raids/".format(system_id)).read())
azvyagintsevbca1f462018-05-25 19:06:46 +0300296 LOG.debug('list_raids:{} {}'.format(system_id, json_res))
297 for item in json_res:
298 raids[item["name"]] = item
299 return raids
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100300
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200301
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100302def get_raid(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300303 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100304 Get information about specific raid on machine
305
306 CLI Example:
307
308 .. code-block:: bash
309
310 salt-call maasng.get_raids server_hostname md0
azvyagintsevbca1f462018-05-25 19:06:46 +0300311 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100312
313 return list_raids(hostname)[name]
314
315
azvyagintsevbca1f462018-05-25 19:06:46 +0300316def _get_raid_id_by_name(hostname, raid_name):
317 return get_raid(hostname, raid_name)['id']
318
319
320def delete_raid(hostname, raid_name):
321 """
322 Delete RAID on a machine.
323
324 CLI Example:
325
326 .. code-block:: bash
327
328 salt 'maas-node' maasng.delete_raid server_hostname raid_name
329 salt-call maasng.delete_raid server_hostname raid_name
330 """
331 result = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200332 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +0300333 system_id = get_machine(hostname)["system_id"]
334 raid_id = _get_raid_id_by_name(hostname, raid_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200335 LOG.debug('delete_raid: {} {}'.format(system_id, raid_id))
336 maas.delete(
337 u"api/2.0/nodes/{0}/raid/{1}/".format(system_id, raid_id)).read()
azvyagintsevbca1f462018-05-25 19:06:46 +0300338
339 result["new"] = "Raid {0} deleted".format(raid_name)
340 return result
341
342# END RAID SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200343# BLOCKDEVICES SECTION
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100344
azvyagintsevbca1f462018-05-25 19:06:46 +0300345
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100346def list_blockdevices(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300347 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100348 Get list of all blockdevices (disks) on machine
349
350 CLI Example:
351
352 .. code-block:: bash
353
354 salt 'maas-node' maasng.list_blockdevices server_hostname
355 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300356 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100357 ret = {}
358
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200359 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100360 system_id = get_machine(hostname)["system_id"]
361 LOG.info(system_id)
362
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200363 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100364
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200365 json_res = json.loads(
366 maas.get(u"api/2.0/nodes/{0}/blockdevices/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100367 LOG.info(json_res)
368 for item in json_res:
369 ret[item["name"]] = item
370
371 return ret
372
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200373
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100374def get_blockdevice(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300375 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100376 Get information about blockdevice (disk) on machine
377
378 CLI Example:
379
380 .. code-block:: bash
381
382 salt 'maas-node' maasng.get_blockdevice server_hostname sda
383 salt-call maasng.get_blockdevice server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300384 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100385
386 return list_blockdevices(hostname)[name]
387
azvyagintsevbca1f462018-05-25 19:06:46 +0300388# END BLOCKDEVICES SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200389# PARTITIONS
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100390
azvyagintsevbca1f462018-05-25 19:06:46 +0300391
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100392def list_partitions(hostname, device):
azvyagintsevbca1f462018-05-25 19:06:46 +0300393 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100394 Get list of all partitions on specific device located on specific machine
395
396 CLI Example:
397
398 .. code-block:: bash
399
400 salt 'maas-node' maasng.list_partitions server_hostname sda
401 salt-call maasng.list_partitions server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300402 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100403 ret = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200404 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100405 system_id = get_machine(hostname)["system_id"]
406 LOG.info(system_id)
407
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200408 partitions = get_blockdevice(hostname, device)["partitions"]
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100409 LOG.info(partitions)
410
411 #json_res = json.loads(maas.get(u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id)).read())
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200412 # LOG.info(json_res)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100413
414 if len(device) > 0:
415 for item in partitions:
416 name = item["path"].split('/')[-1]
417 ret[name] = item
418
419 return ret
420
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200421
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100422def get_partition(hostname, device, partition):
azvyagintsevbca1f462018-05-25 19:06:46 +0300423 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100424 Get information about specific parition on device located on machine
425
426 CLI Example:
427
428 .. code-block:: bash
429
430 salt 'maas-node' maasng.get_partition server_hostname disk_name partition
431 salt-call maasng.get_partition server_hostname disk_name partition
432
433 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300434 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100435
436 return list_partitions(partition)[name]
437
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200438
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100439def create_partition(hostname, disk, size, fs_type=None, mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300440 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100441 Create new partition on device.
442
443 CLI Example:
444
445 .. code-block:: bash
446
447 salt 'maas-node' maasng.create_partition server_hostname disk_name 10 ext4 "/"
448 salt-call maasng.create_partition server_hostname disk_name 10 ext4 "/"
azvyagintsevbca1f462018-05-25 19:06:46 +0300449 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200450 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100451 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200452 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100453 system_id = get_machine(hostname)["system_id"]
454 LOG.info(system_id)
455
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200456 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100457 LOG.info(device_id)
458
459 value, unit = size[:-1], size[-1]
460 calc_size = str(int(value) * SIZE[unit])
461 LOG.info(calc_size)
462
463 data = {
464 "size": calc_size
465 }
466
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200467 # TODO validation
468 partition = json.loads(maas.post(
469 u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100470 LOG.info(partition)
471 result["partition"] = "Partition created on {0}".format(disk)
472
473 if fs_type != None:
474 data_fs_type = {
475 "fstype": fs_type
476 }
477 partition_id = str(partition["id"])
478 LOG.info("Partition id: " + partition_id)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200479 # TODO validation
480 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
481 system_id, device_id, partition_id), "format", **data_fs_type).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100482 LOG.info(json_res)
483 result["filesystem"] = "Filesystem {0} created".format(fs_type)
484
485 if mount != None:
486 data = {
487 "mount_point": mount
488 }
489
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200490 # TODO validation
491 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
492 system_id, device_id, str(partition['id'])), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100493 LOG.info(json_res)
494 result["mount"] = "Mount point {0} created".format(mount)
495
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100496 return result
497
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200498
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100499def delete_partition(hostname, disk, partition_name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300500 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100501 Delete partition on device.
502
503 CLI Example:
504
505 .. code-block:: bash
506
507 salt 'maas-node' maasng.delete_partition server_hostname disk_name partition_name
508 salt-call maasng.delete_partition server_hostname disk_name partition_name
509
510 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300511 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100512 result = {}
513 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200514 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100515 system_id = get_machine(hostname)["system_id"]
516 LOG.info(system_id)
517
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200518 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100519 LOG.info(device_id)
520
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200521 partition_id = _get_partition_id_by_name(hostname, disk, partition_name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100522
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200523 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
524 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100525 result["new"] = "Partition {0} deleted".format(partition_name)
526 return result
527
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200528
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100529def delete_partition_by_id(hostname, disk, partition_id):
azvyagintsevbca1f462018-05-25 19:06:46 +0300530 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100531 Delete partition on device. Partition spefified by id of parition
532
533 CLI Example:
534
535 .. code-block:: bash
536
537 salt 'maas-node' maasng.delete_partition_by_id server_hostname disk_name partition_id
538 salt-call maasng.delete_partition_by_id server_hostname disk_name partition_id
539
540 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300541 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100542 result = {}
543 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200544 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100545 system_id = get_machine(hostname)["system_id"]
546 LOG.info(system_id)
547
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200548 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100549 LOG.info(device_id)
550
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200551 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
552 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100553 result["new"] = "Partition {0} deleted".format(partition_id)
554 return result
azvyagintsevbca1f462018-05-25 19:06:46 +0300555# END PARTITIONS
556# DISK LAYOUT
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100557
azvyagintsevbca1f462018-05-25 19:06:46 +0300558
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200559def drop_storage_schema(hostname, disk=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300560 """
561 #1. Drop lv
562 #2. Drop vg
563 #3. Drop md # need to zero-block?
564 #3. Drop part
565 """
566
567 if __opts__['test']:
568 ret['result'] = None
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200569 ret['comment'] = 'Storage schema on {0} will be removed'.format(
570 hostname)
azvyagintsevbca1f462018-05-25 19:06:46 +0300571 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200572 # TODO validation if exists
azvyagintsevbca1f462018-05-25 19:06:46 +0300573 vgs = list_volume_groups(hostname)
574 for vg in vgs:
575 delete_volume_group(hostname, vg)
576
577 raids = list_raids(hostname)
578 for raid in raids:
579 delete_raid(hostname, raid)
580
581 blocks = list_blockdevices(hostname)
582 for block_d in blocks:
583 partitions = __salt__['maasng.list_partitions'](hostname, block_d)
584 for partition_name, partition in partitions.iteritems():
585 LOG.info('delete partition:\n{}'.format(partition))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200586 __salt__['maasng.delete_partition_by_id'](
587 hostname, block_d, partition["id"])
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200588
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100589
590def update_disk_layout(hostname, layout, root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300591 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100592 Update disk layout. Flat or LVM layout supported.
593
594 CLI Example:
595
596 .. code-block:: bash
597
598 salt 'maas-node' maasng.update_disk_layout server_hostname lvm root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None
599 salt-call maasng.update_disk_layout server_hostname lvm root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None
600
601 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300602 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100603 result = {}
604 data = {
605 "storage_layout": layout,
606 }
607
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200608 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100609 system_id = get_machine(hostname)["system_id"]
610 LOG.info(system_id)
611
azvyagintsevbca1f462018-05-25 19:06:46 +0300612 if layout == 'custom':
613 drop_storage_schema(hostname)
614 result["new"] = {
615 "storage_layout": layout,
616 }
617
618 return result
619
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100620 if root_size != None:
621 bit_size = str(root_size * 1073741824)
622 LOG.info(bit_size)
623 data["root_size"] = bit_size
624
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100625 if root_device != None:
626 LOG.info(root_device)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200627 data["root_device"] = str(
628 _get_blockdevice_id_by_name(hostname, root_device))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100629
630 if layout == 'lvm':
631 if volume_group != None:
632 LOG.info(volume_group)
633 data["vg_name"] = volume_group
634 if volume_name != None:
635 LOG.info(volume_name)
636 data["lv_name"] = volume_name
637 if volume_size != None:
638 vol_size = str(volume_size * 1073741824)
639 LOG.info(vol_size)
640 data["lv_size"] = vol_size
641
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200642 # TODO validation
643 json_res = json.loads(maas.post(
644 u"api/2.0/machines/{0}/".format(system_id), "set_storage_layout", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100645 LOG.info(json_res)
646 result["new"] = {
647 "storage_layout": layout,
648 }
649
650 return result
651
azvyagintsevbca1f462018-05-25 19:06:46 +0300652# END DISK LAYOUT
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200653# LVM
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100654
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200655
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100656def list_volume_groups(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300657 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100658 Get list of all volume group on machine.
659
660 CLI Example:
661
662 .. code-block:: bash
663
664 salt 'maas-node' maasng.list_volume_groups server_hostname
665 salt-call maasng.list_volume_groups server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300666 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100667 volume_groups = {}
668
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200669 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100670 system_id = get_machine(hostname)["system_id"]
671 LOG.info(system_id)
672
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200673 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100674
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200675 json_res = json.loads(
676 maas.get(u"api/2.0/nodes/{0}/volume-groups/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100677 LOG.info(json_res)
678 for item in json_res:
679 volume_groups[item["name"]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200680 # return
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100681 return volume_groups
682
683
684def get_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300685 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100686 Get information about specific volume group on machine.
687
688 CLI Example:
689
690 .. code-block:: bash
691
692 salt 'maas-node' maasng.list_blockdevices server_hostname
693 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300694 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200695 # TODO validation that exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100696 return list_volume_groups(hostname)[name]
697
698
699def create_volume_group(hostname, volume_group_name, disks=[], partitions=[]):
azvyagintsevbca1f462018-05-25 19:06:46 +0300700 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100701 Create new volume group on machine. Disks or partitions needs to be provided.
702
703 CLI Example:
704
705 .. code-block:: bash
706
707 salt 'maas-node' maasng.create_volume_group volume_group_name, disks=[sda,sdb], partitions=[]
708 salt-call maasng.create_volume_group server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300709 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100710 result = {}
711
712 data = {
713 "name": volume_group_name,
714 }
715
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200716 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100717 system_id = get_machine(hostname)["system_id"]
718 LOG.info(system_id)
719
720 disk_ids = []
721 partition_ids = []
722
723 for disk in disks:
724 p_disk = get_blockdevice(hostname, disk)
725 if p_disk["partition_table_type"] == None:
726 disk_ids.append(str(p_disk["id"]))
727 else:
azvyagintsevf3515c82018-06-26 18:59:05 +0300728 result["error"] = "Device {0} on" \
729 "machine {1} cointains partition" \
730 "table".format(disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100731 return result
732
733 for partition in partitions:
734 try:
735 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200736 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100737 partition_ids.append(str(device_part[partition]["id"]))
738 except KeyError:
azvyagintsevf3515c82018-06-26 18:59:05 +0300739 result["error"] = "Partition {0} does" \
740 "not exists on " \
741 "machine {1}".format(partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100742 return result
743
744 data["block_devices"] = disk_ids
745 data["partitions"] = partition_ids
746 LOG.info(partition_ids)
747 LOG.info(partitions)
748
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200749 # TODO validation
750 json_res = json.loads(maas.post(
751 u"api/2.0/nodes/{0}/volume-groups/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100752 LOG.info(json_res)
753 result["new"] = "Volume group {0} created".format(json_res["name"])
754
755 return result
756
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200757
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100758def delete_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300759 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100760 Delete volume group on machine.
761
762 CLI Example:
763
764 .. code-block:: bash
765
766 salt 'maas-node' maasng.delete_volume_group server_hostname vg0
767 salt-call maasng.delete_volume_group server_hostname vg0
azvyagintsevbca1f462018-05-25 19:06:46 +0300768 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100769
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200770 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100771 system_id = get_machine(hostname)["system_id"]
azvyagintsevbca1f462018-05-25 19:06:46 +0300772 LOG.debug('delete_volume_group:{}'.format(system_id))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100773
azvyagintsevbca1f462018-05-25 19:06:46 +0300774 vg_id = str(_get_volume_group_id_by_name(hostname, name))
775 for vol in get_volumes(hostname, name):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200776 delete_volume(hostname, vol, name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100777
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200778 # TODO validation
779 json_res = json.loads(maas.delete(
780 u"api/2.0/nodes/{0}/volume-group/{1}/".format(system_id, vg_id)).read() or 'null')
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100781 LOG.info(json_res)
782
783 return True
784
785
azvyagintsevf3515c82018-06-26 18:59:05 +0300786def create_volume(hostname, volume_name, volume_group, size, fs_type=None,
787 mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300788 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100789 Create volume on volume group.
790
791 CLI Example:
792
793 .. code-block:: bash
794
795 salt 'maas-node' maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
796 salt-call maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
azvyagintsevbca1f462018-05-25 19:06:46 +0300797 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100798
799 data = {
800 "name": volume_name,
801 }
802
803 value, unit = size[:-1], size[-1]
804 bit_size = str(int(value) * SIZE[unit])
805 LOG.info(bit_size)
806
807 data["size"] = bit_size
808
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200809 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100810 system_id = get_machine(hostname)["system_id"]
811 LOG.info(system_id)
812
813 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
814
815 LOG.info(volume_group_id)
816
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200817 # TODO validation
818 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
819 system_id, volume_group_id), "create_logical_volume", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100820 LOG.info(json_res)
821
822 if fs_type != None or mount != None:
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200823 ret = create_volume_filesystem(
824 hostname, volume_group + "-" + volume_name, fs_type, mount)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100825
826 return True
827
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100828
azvyagintsevbca1f462018-05-25 19:06:46 +0300829def delete_volume(hostname, volume_name, volume_group):
830 """
831 Delete volume from volume group.
832 Tips: maas always use 'volume_group-volume_name' name schema.Example: 'vg0-glusterfs'
833 This function expexts same format.
834
835 CLI Example:
836
837 .. code-block:: bash
838
839 salt 'maas-node' maasng.delete_volume server_hostname volume_name volume_group
840 salt 'maas-node' maasng.delete_volume server_hostname vg0-vol0 vg0
841 salt-call maasng.delete_volume server_hostname volume_name volume_group
842 """
843
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200844 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +0300845 system_id = get_machine(hostname)["system_id"]
846 LOG.debug('delete_volume:{}'.format(system_id))
847
848 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200849 volume_id = str(_get_volume_id_by_name(
850 hostname, volume_name, volume_group))
azvyagintsevbca1f462018-05-25 19:06:46 +0300851
852 if None in [volume_group_id, volume_id]:
853 return False
854
855 data = {
856 "id": volume_id,
857 }
858
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200859 # TODO validation
860 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
861 system_id, volume_group_id), "delete_logical_volume", **data).read() or 'null')
azvyagintsevbca1f462018-05-25 19:06:46 +0300862 return True
863
864
865def get_volumes(hostname, vg_name):
866 """
867 Get list of volumes in volume group.
868 """
869 volumes = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200870 _volumes = list_volume_groups(
871 hostname)[vg_name].get('logical_volumes', False)
azvyagintsevbca1f462018-05-25 19:06:46 +0300872 if _volumes:
873 for item in _volumes:
874 volumes[item["name"]] = item
875 return volumes
876
877# END LVM
878
879
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200880def create_volume_filesystem(hostname, device, fs_type=None, mount=None):
881
882 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100883 system_id = get_machine(hostname)["system_id"]
884
885 blockdevices_id = _get_blockdevice_id_by_name(hostname, device)
886 data = {}
887 if fs_type != None:
888 data["fstype"] = fs_type
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200889 # TODO validation
890 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
891 system_id, blockdevices_id), "format", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100892 LOG.info(json_res)
893
894 if mount != None:
895 data["mount_point"] = mount
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200896 # TODO validation
897 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
898 system_id, blockdevices_id), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100899 LOG.info(json_res)
900
901 return True
902
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100903
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100904def set_boot_disk(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300905 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100906 Create volume on volume group.
907
908 CLI Example:
909
910 .. code-block:: bash
911
912 salt 'maas-node' maasng.set_boot_disk server_hostname disk_name
913 salt-call maasng.set_boot_disk server_hostname disk_name
azvyagintsevbca1f462018-05-25 19:06:46 +0300914 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100915 data = {}
916 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200917 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100918 system_id = get_machine(hostname)["system_id"]
919 blockdevices_id = _get_blockdevice_id_by_name(hostname, name)
920
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200921 maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
922 system_id, blockdevices_id), "set_boot_disk", **data).read()
azvyagintsevf3515c82018-06-26 18:59:05 +0300923 # TODO validation for error response
924 # (disk does not exists and node does not exists)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100925 result["new"] = "Disk {0} was set as bootable".format(name)
926
927 return result
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200928
azvyagintsevbca1f462018-05-25 19:06:46 +0300929# NETWORKING
930
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200931
932def list_fabric():
azvyagintsevbca1f462018-05-25 19:06:46 +0300933 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200934 Get list of all fabric
935
936 CLI Example:
937
938 .. code-block:: bash
939
940 salt 'maas-node' maasng.list_fabric
azvyagintsevbca1f462018-05-25 19:06:46 +0300941 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200942 fabrics = {}
943 maas = _create_maas_client()
944 json_res = json.loads(maas.get(u'api/2.0/fabrics/').read())
945 LOG.info(json_res)
946 for item in json_res:
947 fabrics[item["name"]] = item
948 return fabrics
949
950
azvyagintsevf3515c82018-06-26 18:59:05 +0300951def check_fabric(name):
952 """
953 Simple check that fabric already defined
954 Return format:
955 update - require update
956 correct - fully coincides # not implemented
957 not_exist - need's to be created
958 """
959
960 ret = 'not_exist'
961 fabrics = list_fabric()
962 if name in fabrics.keys():
963 LOG.debug("Requested fabrics with name:{} already exist".format(name))
964 ret = 'update'
965 return ret
966
967
968def check_fabric_guess_with_cidr(name, cidrs):
969 """
970 Check, that fabric already defined OR it was autodiscovered
971 WA to fix issue with hardcoded 'fabric-0'
972 - Find all auto-discovered subnets by cidr
973 - find all subnets, that SHOULD be configured to THIS subent
974 Warning: most probably, will fail if some subnet defined
975 to another fabric :(
976
977 { 'update' : ID } - require update
978 { 'correct' : ID } - fully coincides # not implemented
979 { 'not_exist' : None } - need's to be created
980
981 CLI Example:
982
983 .. code-block:: bash
984
985 salt 'maas-node' maasng.check_fabric_guess_with_cidr name='' cidrs=[]
986 """
987
988 ret = {'not_exist': None}
989 fabrics = list_fabric()
990 # Simple check
991 if name in fabrics:
992 LOG.debug("Requested fabrics with name:{} already exist".format(name))
993 f_id = fabrics[name]['id']
994 ret = {'update': f_id}
995 # Cidr check
996 # All discovered subnets by cidr
997 d_subnets = list_subnets(sort_by='cidr')
998 # Check, that requested cidr already in discovered.
999 # If it is - it would mean that fabric already
1000 # exist(fabric-0,most probably) but should be renamed.
1001 # Works only for first shot ;(
1002 # due curren-single-maas logic for 'vlan-subnet' mapping.
1003 # Probably, it will fail with future MAAS releases.
1004 for cidr in cidrs:
1005 if cidr in d_subnets:
1006 f_id = d_subnets[cidr]['vlan']['fabric_id']
1007 f_name = d_subnets[cidr]['vlan']['fabric']
1008 LOG.warning("Detected cidr:{} in fabric:{}".format(cidr, f_name))
1009 LOG.warning("Guessing, that fabric "
1010 "with current name:{}\n should be "
1011 "renamed to:{}".format(f_name, name))
1012 ret = {'update': f_id}
1013 return ret
1014 return ret
1015
1016
azvyagintsevf0904ac2018-07-05 18:53:26 +03001017def create_fabric(name, description=None, fabric_id=None, update=False):
azvyagintsevbca1f462018-05-25 19:06:46 +03001018 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001019 Create new fabric.
1020
1021 CLI Example:
1022
1023 .. code-block:: bash
1024
azvyagintsevf3515c82018-06-26 18:59:05 +03001025 salt 'maas-node' maasng.create_fabric name='123'
azvyagintsevbca1f462018-05-25 19:06:46 +03001026 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001027 result = {}
1028 data = {
1029 "name": name,
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001030 "class_type": '',
1031
1032 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001033 if description:
1034 data['description'] = description
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001035
1036 maas = _create_maas_client()
azvyagintsevf0904ac2018-07-05 18:53:26 +03001037 json_res = None
azvyagintsevf3515c82018-06-26 18:59:05 +03001038 try:
1039 if update:
1040 json_res = json.loads(
1041 maas.put(u"api/2.0/fabrics/{0}/".format(fabric_id),
1042 **data).read())
1043 result["new"] = "Fabric {0} created".format(json_res["name"])
1044 else:
1045 json_res = json.loads(
1046 maas.post(u"api/2.0/fabrics/", None, **data).read())
1047 result["changes"] = "Fabric {0} updated".format(json_res["name"])
1048 except Exception as inst:
1049 LOG.debug("create_fabric data:{}".format(data))
1050 try:
1051 m = inst.readlines()
1052 except:
1053 m = inst.message
1054 LOG.error("Message:{0}".format(m))
1055 result['result'] = False
1056 result['comment'] = 'Error creating fabric: {0}'.format(name)
1057 result['error'] = m
1058 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001059 LOG.debug("crete_fabric:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001060 result['result'] = True
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001061 return result
1062
1063
azvyagintsevf3515c82018-06-26 18:59:05 +03001064def list_subnets(sort_by='name'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001065 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001066 Get list of subnets from maas server
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001067
1068 CLI Example:
1069
1070 .. code-block:: bash
1071
azvyagintsevf3515c82018-06-26 18:59:05 +03001072 salt 'maas-node' maasng.list_subnets
azvyagintsevbca1f462018-05-25 19:06:46 +03001073 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001074 subnets = {}
1075 maas = _create_maas_client()
1076 json_res = json.loads(maas.get(u'api/2.0/subnets/').read())
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001077 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001078 subnets[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001079 return subnets
1080
1081
azvyagintsevf3515c82018-06-26 18:59:05 +03001082def list_vlans(fabric, sort_by='vid'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001083 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001084 Get list of vlans in fabric
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001085
1086 CLI Example:
1087
1088 .. code-block:: bash
1089
azvyagintsevf3515c82018-06-26 18:59:05 +03001090 salt 'maas-node' maasng.list_vlans fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001091 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001092 vlans = {}
1093 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001094 fabric_id = get_fabricid(fabric)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001095
azvyagintsevf3515c82018-06-26 18:59:05 +03001096 try:
1097 json_res = json.loads(
1098 maas.get(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id)).read())
1099 except Exception as inst:
1100 m = inst.readlines()
1101 LOG.error("Message:{0}".format(m))
1102 LOG.debug(json_res)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001103 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001104 vlans[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001105 return vlans
1106
1107
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001108def get_fabricid(fabric):
azvyagintsevbca1f462018-05-25 19:06:46 +03001109 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001110 Get id for specific fabric
1111
1112 CLI Example:
1113
1114 .. code-block:: bash
1115
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001116 salt 'maas-node' maasng.get_fabricid fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001117 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001118 try:
1119 return list_fabric()[fabric]['id']
1120 except KeyError:
azvyagintsevefb6f5d2018-07-10 14:16:19 +03001121 return {"error": "Fabric not found on MaaS server"}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001122
1123
azvyagintsevf3515c82018-06-26 18:59:05 +03001124def check_vlan_in_fabric(fabric, vlan):
1125 """
1126 Check that VLAN exactly defined
1127 Return format:
1128 update - require update
1129 correct - fully coincides # not implemented
1130 not_exist - need's to be created
1131 """
1132
1133 ret = 'not_exist'
1134 vlans = list_vlans(fabric)
1135 if vlan in vlans.keys():
1136 LOG.debug("Requested VLAN:{} already exist"
1137 "in FABRIC:{}".format(vlan, fabric))
1138 ret = 'update'
1139 return ret
1140
1141
Petr Ruzicka80471852018-07-13 14:08:27 +02001142def create_vlan_in_fabric(name, fabric, vlan, description, primary_rack, mtu=1500,
azvyagintsevf3515c82018-06-26 18:59:05 +03001143 dhcp_on=False, update=False, vlan_id=""):
azvyagintsevbca1f462018-05-25 19:06:46 +03001144 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001145 Update vlan
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001146 CLI Example:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001147 .. code-block:: bash
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001148 salt 'maas-node' maasng.update_vlan name, fabric, vid, description, dhcp_on
azvyagintsevbca1f462018-05-25 19:06:46 +03001149 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001150 result = {}
1151
1152 data = {
1153 "name": name,
1154 "dhcp_on": str(dhcp_on),
1155 "description": description,
azvyagintsev6913e5e2018-07-05 11:42:53 +03001156 "primary_rack": list_racks()[primary_rack]['system_id'],
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001157 }
Michael Polenchukd25da792018-07-19 18:27:11 +04001158 if mtu:
1159 data['mtu'] = str(mtu)
azvyagintsevf3515c82018-06-26 18:59:05 +03001160 vlan = str(vlan)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001161 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001162 fabric_id = get_fabricid(fabric)
azvyagintsevf3515c82018-06-26 18:59:05 +03001163 try:
1164 if update:
1165 # MAAS have buggy logic here. Fallowing api reference, here should
1166 # be passed VID - which mean, API ID for vlan.
1167 # Otherwise, at least for maas 2.3.3-6498-ge4db91d exactly VLAN
1168 # should be passed. so, make temp.backward-convertation.
1169 # json_res = json.loads(maas.put(u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id,vlan_id), **data).read())
1170 json_res = json.loads(maas.put(
1171 u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id, vlan),
1172 **data).read())
1173 else:
1174 data['vid'] = str(vlan)
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001175 json_res = json.loads(maas.post(
1176 u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id), None, **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001177 except Exception as inst:
1178 LOG.debug("create_vlan_in_fabric data:{}".format(data))
1179 try:
1180 m = inst.readlines()
1181 except:
1182 m = inst.message
1183 LOG.error("Message:{0}".format(m))
1184 result['result'] = False
1185 result['comment'] = 'Error updating vlan: {0}'.format(name)
1186 result['error'] = m
1187 return result
1188 LOG.debug("create_vlan_in_fabric:{}".format(json_res))
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001189 result["new"] = "Vlan {0} was updated".format(json_res["name"])
1190
1191 return result
azvyagintsevbca1f462018-05-25 19:06:46 +03001192
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001193
azvyagintsevf3515c82018-06-26 18:59:05 +03001194def check_subnet(cidr, name, fabric, gateway_ip):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001195 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001196 Check that subnet exactly defined
1197 Return format:
1198 update - require update
1199 correct - fully coincides # not implemented
1200 not_exist - need's to be created
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001201 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001202
1203 ret = 'not_exist'
1204 subnets = list_subnets(sort_by='cidr')
1205 if cidr in subnets.keys():
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001206 LOG.debug("Requested subnet cidr:{} already exist".format(cidr))
1207 ret = 'update'
azvyagintsevf3515c82018-06-26 18:59:05 +03001208 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001209
1210
azvyagintsevf3515c82018-06-26 18:59:05 +03001211def create_subnet(cidr='', name='', fabric='', gateway_ip='', vlan='',
1212 update=False, subnet_id=''):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001213 """
1214 Create subnet
1215
1216 CLI Example:
1217
1218 .. code-block:: bash
1219
1220 salt 'maas-node' maasng.create_subnet cidr, name, fabric, gateway_ip
1221 """
1222
1223 fabric_id = get_fabricid(fabric)
1224 result = {}
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001225 vlan = str(vlan)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001226 data = {
1227 "cidr": cidr,
1228 "name": name,
1229 "fabric": str(fabric_id),
1230 "gateway_ip": gateway_ip,
azvyagintsevf3515c82018-06-26 18:59:05 +03001231 'vlan': vlan,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001232 }
1233 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001234 # FIXME: vlan definition not work in 2.3.3-6498-ge4db91d.
1235 LOG.warning("Ignoring parameter vlan:{}".format(vlan))
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001236 data.pop('vlan', '')
azvyagintsevf3515c82018-06-26 18:59:05 +03001237 try:
1238 if update:
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001239 json_res = json.loads(
1240 maas.put(u"api/2.0/subnets/{0}/".format(subnet_id), **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001241 else:
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001242 json_res = json.loads(
1243 maas.post(u"api/2.0/subnets/", None, **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001244 except Exception as inst:
1245 LOG.debug("create_subnet data:{}".format(data))
1246 try:
1247 m = inst.readlines()
1248 except:
1249 m = inst.message
1250 LOG.error("Message:{0}".format(m))
1251 result['result'] = False
1252 result['comment'] = 'Error creating subnet: {0}'.format(name)
1253 result['error'] = m
1254 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001255 LOG.debug("create_subnet:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001256 result["new"] = "Subnet {0} with CIDR {1}" \
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001257 "and gateway {2} was created".format(
1258 name, cidr, gateway_ip)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001259
1260 return result
1261
1262
1263def get_subnet(subnet):
1264 """
1265 Get details for specific subnet
1266
1267 CLI Example:
1268
1269 .. code-block:: bash
1270
1271 salt 'maas-node' maasng.get_subnet subnet_name
1272 """
1273 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001274 return list_subnets()[subnet]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001275 except KeyError:
1276 return {"error": "Subnet not found on MaaS server"}
1277
1278
1279def get_subnetid(subnet):
1280 """
1281 Get id for specific subnet
1282
1283 CLI Example:
1284
1285 .. code-block:: bash
1286
1287 salt 'maas-node' maasng.get_subnetid subnet_name
1288 """
1289 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001290 return list_subnets()[subnet]['id']
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001291 except KeyError:
1292 return {"error": "Subnet not found on MaaS server"}
1293
1294
1295def list_ipranges():
1296 """
1297 Get list of all ipranges from maas server
1298
1299 CLI Example:
1300
1301 .. code-block:: bash
1302
1303 salt 'maas-node' maasng.list_ipranges
1304 """
1305 ipranges = {}
1306 maas = _create_maas_client()
1307 json_res = json.loads(maas.get(u'api/2.0/ipranges/').read())
1308 for item in json_res:
1309 ipranges[item["start_ip"]] = item
1310 return ipranges
1311
1312
azvyagintsevf0904ac2018-07-05 18:53:26 +03001313def create_iprange(type_range, start_ip, end_ip, subnet=None, comment=None):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001314 """
1315 Create ip range
1316
1317 CLI Example:
1318
1319 .. code-block:: bash
1320
1321 salt 'maas-node' maasng.create_iprange type, start ip, end ip, comment
1322 """
1323 result = {}
1324
1325 data = {
1326 "type": type_range,
1327 "start_ip": start_ip,
1328 "end_ip": end_ip,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001329 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001330 if comment:
1331 data['comment'] = comment
azvyagintsevf0904ac2018-07-05 18:53:26 +03001332 if subnet:
1333 subnet_id = list_subnets()[subnet]['id']
1334 data['subnet'] = str(subnet_id)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001335 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001336 _name = "Type:{}: {}-{}".format(type_range, start_ip, end_ip)
1337 try:
1338 json_res = json.loads(
1339 maas.post(u"api/2.0/ipranges/", None, **data).read())
1340 except Exception as inst:
1341 try:
1342 m = inst.readlines()
1343 except:
1344 m = inst.message
1345 LOG.error("Message:{0}".format(m))
1346 result['result'] = False
1347 result['comment'] = 'Error creating iprange:{0}'.format(_name)
1348 result['error'] = m
1349 return result
1350 result["new"] = "Iprange: {0} has been created".format(_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001351 LOG.debug("create_iprange:{}".format(json_res))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001352
1353 return result
1354
1355
1356def get_iprangeid(start_ip):
1357 """
1358 Get id for ip range from maas server
1359
1360 CLI Example:
1361
1362 .. code-block:: bash
1363
1364 salt 'maas-node' maasng.get_iprangeid start_ip
1365 """
1366 try:
1367 return list_ipranges()[start_ip]['id']
1368 except KeyError:
1369 return {"error": "Ip range not found on MaaS server"}
1370
1371
1372def get_startip(start_ip):
1373 """
1374 Get start ip for ip range
1375
1376 CLI Example:
1377
1378 .. code-block:: bash
1379
1380 salt 'maas-node' maasng.get_startip start ip
1381 """
1382 try:
1383 return list_ipranges()[start_ip]
1384 except KeyError:
1385 return {"error": "Ip range not found on MaaS server"}
azvyagintsevbca1f462018-05-25 19:06:46 +03001386# END NETWORKING
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001387
1388# MAAS CONFIG SECTION
1389
1390
azvyagintsev58947072018-06-29 12:09:48 +03001391def _getHTTPCode(url):
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001392 code = '003'
1393 m = ''
azvyagintsev58947072018-06-29 12:09:48 +03001394 try:
1395 connection = urllib2.urlopen(url)
1396 code = connection.getcode()
1397 connection.close()
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001398 except (urllib2.HTTPError, urllib2.URLError) as e:
1399 try:
1400 code = e.getcode()
1401 except:
1402 m = e.reason
1403 pass
azvyagintsevf3515c82018-06-26 18:59:05 +03001404 LOG.debug("Unexpected http code:{} from "
1405 "url:{}\nwith message:{}".format(code, url, m))
azvyagintsev58947072018-06-29 12:09:48 +03001406 pass
1407 return code
1408
1409
1410def wait_for_http_code(url=None, expected=[200]):
1411 """
1412 Simple function, which just wait for avaible api, aka wait for 200.
1413
1414 CLI Example:
1415
1416 .. code-block:: bash
1417
1418 salt 'maas-node' maasng.wait_for_http_code url expected=[200]
1419
1420 """
1421 ret = {}
1422 started_at = time.time()
1423 poll_time = 5
1424 timeout = 60 * 2
1425 while _getHTTPCode(url) not in expected:
1426 c_timeout = timeout - (time.time() - started_at)
1427 if c_timeout <= 0:
1428 ret['result'] = False
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001429 ret["comment"] = "api:{} not answered in time".format(url)
azvyagintsev58947072018-06-29 12:09:48 +03001430 return ret
1431 LOG.info(
1432 "Waiting for api:{0}\n"
1433 "sleep for:{1}s "
1434 "Left:{2}/{3}s".format(url, poll_time, round(c_timeout),
1435 timeout))
1436 time.sleep(poll_time)
1437 ret['result'] = True
1438 ret["comment"] = "MAAS API:{} up.".format(url)
1439 return ret
1440
1441
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001442def _get_boot_source_id_by_url(url):
1443 # FIXME: fix ret\validation
1444 try:
1445 bs_id = get_boot_source(url=url)["id"]
1446 except KeyError:
1447 return {"error": "boot-source:{0} not exist!".format(url)}
1448 return bs_id
1449
1450
1451def get_boot_source(url=None):
1452 """
1453 Read a boot source by url. If url not specified - return all.
1454
1455 CLI Example:
1456
1457 .. code-block:: bash
1458
1459 salt 'maas-node' maasng.get_boot_source url
1460
1461 """
1462 boot_sources = {}
1463 maas = _create_maas_client()
1464 json_res = json.loads(maas.get(u'api/2.0/boot-sources/').read() or 'null')
1465 for item in json_res:
1466 boot_sources[str(item["url"])] = item
1467 if url:
1468 return boot_sources.get(url, {})
1469 return boot_sources
1470
1471
1472def delete_boot_source(url, bs_id=None):
1473 """
1474 Delete a boot source by url.
1475
1476 CLI Example:
1477
1478 .. code-block:: bash
1479
1480 sal 'maas-node' maasng.delete url
1481
1482 """
1483 result = {}
1484 if not bs_id:
1485 bs_id = _get_boot_source_id_by_url(url)
1486 maas = _create_maas_client()
1487 json_res = json.loads(maas.delete(
1488 u'/api/2.0/boot-sources/{0}/'.format(bs_id)).read() or 'null')
1489 LOG.debug("delete_boot_source:{}".format(json_res))
1490 result["new"] = "Boot-resource {0} deleted".format(url)
1491 return result
1492
1493
1494def boot_sources_delete_all_others(except_urls=[]):
1495 """
1496 Delete all boot-sources, except defined in 'except_urls' list.
1497 """
azvyagintseve2e37a12018-11-01 14:45:49 +02001498 result = {'changes': {}}
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001499 maas_boot_sources = get_boot_source()
1500 if 0 in [len(except_urls), len(maas_boot_sources)]:
1501 result['result'] = None
azvyagintseve2e37a12018-11-01 14:45:49 +02001502 result["comment"] = "'except_urls' or 'maas boot-sources' for " \
1503 "delete empty. No changes goinng to be."
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001504 return result
azvyagintseve2e37a12018-11-01 14:45:49 +02001505 # FIXME: fix 'changes' accumulator
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001506 for url in maas_boot_sources.keys():
1507 if url not in except_urls:
1508 LOG.info("Removing boot-source:{}".format(url))
1509 boot_resources_import(action='stop_import', wait=True)
1510 result["changes"] = delete_boot_source(url)
1511 return result
1512
1513
1514def create_boot_source(url, keyring_filename='', keyring_data='', wait=False):
1515 """
1516 Create and import maas boot-source: link to maas-ephemeral repo
1517 Be aware, those step will import resource to rack ctrl, but you also need to import
1518 them into the region!
1519
1520
1521 :param url: The URL of the BootSource.
1522 :param keyring_filename: The path to the keyring file for this BootSource.
1523 :param keyring_data: The GPG keyring for this BootSource, base64-encoded data.
1524
1525 """
1526
1527 # TODO: not work with 'update' currently => keyring update may fail.
1528 result = {}
1529
1530 data = {
1531 "url": url,
1532 "keyring_filename": keyring_filename,
1533 "keyring_data": str(keyring_data),
1534 }
1535
1536 maas = _create_maas_client()
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001537 if url in get_boot_source():
1538 result['result'] = None
1539 result["comment"] = "boot resource already exist"
1540 return result
1541
1542 # NOTE: maas.post will return 400, if url already defined.
1543 json_res = json.loads(
1544 maas.post(u'api/2.0/boot-sources/', None, **data).read())
1545 if wait:
1546 LOG.debug(
1547 "Sleep for 5s,to get MaaS some time to process previous request")
1548 time.sleep(5)
1549 ret = boot_resources_is_importing(wait=True)
1550 if ret is dict:
1551 return ret
1552 LOG.debug("create_boot_source:{}".format(json_res))
1553 result["new"] = "boot resource {0} was created".format(json_res["url"])
1554
1555 return result
1556
1557
1558def boot_resources_import(action='import', wait=False):
1559 """
1560 import/stop_import the boot resources.
1561
1562 :param action: import\stop_import
1563 :param wait: True\False. Wait till process finished.
1564
1565 CLI Example:
1566
1567 .. code-block:: bash
1568
1569 salt 'maas-node' maasng.boot_resources_import action='import'
1570
1571 """
1572 maas = _create_maas_client()
1573 # Have no idea why, but usual jsonloads not work here..
1574 imp = maas.post(u'api/2.0/boot-resources/', action)
1575 if imp.code == 200:
1576 LOG.debug('boot_resources_import:{}'.format(imp.readline()))
1577 if wait:
1578 boot_resources_is_importing(wait=True)
1579 return True
1580 else:
1581 return False
1582
1583
1584def boot_resources_is_importing(wait=False):
1585 maas = _create_maas_client()
1586 result = {}
1587 if wait:
1588 started_at = time.time()
1589 poll_time = 5
1590 timeout = 60 * 15
1591 while boot_resources_is_importing(wait=False):
1592 c_timeout = timeout - (time.time() - started_at)
1593 if c_timeout <= 0:
1594 result['result'] = False
1595 result["comment"] = "Boot-resources import not finished in time"
1596 return result
1597 LOG.info(
1598 "Waiting boot-resources import done\n"
1599 "sleep for:{}s "
1600 "Left:{}/{}s".format(poll_time, round(c_timeout), timeout))
1601 time.sleep(poll_time)
1602 return json.loads(
1603 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1604 else:
1605 return json.loads(
1606 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1607
1608#####
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001609# def boot_sources_selections_delete_all_others(except_urls=[]):
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001610# """
1611# """
1612# result = {}
1613# return result
1614
1615
1616def is_boot_source_selections_in(dict1, list1):
1617 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001618 Check that requested boot-selection already in maas bs selections,
1619 if True- return bss id.
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001620 # FIXME: those hack check doesn't look good.
1621 """
1622 for bs in list1:
1623 same = set(dict1.keys()) & set(bs.keys())
1624 if all(elem in same for elem in
1625 ['os', 'release', 'arches', 'subarches', 'labels']):
azvyagintsevf3515c82018-06-26 18:59:05 +03001626 LOG.debug("boot-selection in maas:{0}\n"
1627 "looks same to requested:{1}".format(bs, dict1))
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001628 return bs['id']
1629 return False
1630
1631
1632def get_boot_source_selections(bs_url):
1633 """
1634 Get boot-source selections.
1635 """
1636 # check for key_error!
1637 bs_id = _get_boot_source_id_by_url(bs_url)
1638 maas = _create_maas_client()
1639 json_res = json.loads(
1640 maas.get(u'/api/2.0/boot-sources/{0}/selections/'.format(bs_id)).read())
1641 LOG.debug(
1642 "get_boot_source_selections for url:{} \n{}".format(bs_url, json_res))
1643 return json_res
1644
1645
1646def create_boot_source_selections(bs_url, os, release, arches="*",
1647 subarches="*", labels="*", wait=True):
1648 """
1649 Create a new boot source selection for bs_url.
1650 :param os: The OS (e.g. ubuntu, centos) for which to import resources.Required.
1651 :param release: The release for which to import resources. Required.
1652 :param arches: The architecture list for which to import resources.
1653 :param subarches: The subarchitecture list for which to import resources.
1654 :param labels: The label lists for which to import resources.
1655 """
1656
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001657 result = {"result": True, 'name': bs_url, 'changes': None}
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001658
1659 data = {
1660 "os": os,
1661 "release": release,
1662 "arches": arches,
1663 "subarches": subarches,
1664 "labels": labels,
1665 }
1666
1667 maas = _create_maas_client()
1668 bs_id = _get_boot_source_id_by_url(bs_url)
1669 # TODO add pre-create verify
1670 maas_bs_s = get_boot_source_selections(bs_url)
1671 if is_boot_source_selections_in(data, maas_bs_s):
1672 result["result"] = True
azvyagintsevf3515c82018-06-26 18:59:05 +03001673 result["comment"] = 'Requested boot-source selection ' \
1674 'for {0} already exist.'.format(
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001675 bs_url)
1676 return result
1677
1678 # NOTE: maas.post will return 400, if url already defined.
azvyagintsevcb54d142018-06-19 16:18:32 +03001679 # Also, maas need's some time to import info about stream.
1680 # unfortunatly, maas don't have any call to check stream-import-info - so, we need to implement
1681 # at least simple retry ;(
1682 json_res = False
1683 poll_time = 5
azvyagintseve2e37a12018-11-01 14:45:49 +02001684 for i in range(0, 10):
azvyagintsevcb54d142018-06-19 16:18:32 +03001685 try:
1686 json_res = json.loads(
1687 maas.post(u'api/2.0/boot-sources/{0}/selections/'.format(bs_id), None,
1688 **data).read())
1689 except Exception as inst:
1690 m = inst.readlines()
azvyagintsevf3515c82018-06-26 18:59:05 +03001691 LOG.warning("boot_source_selections "
1692 "catch error during processing. Most-probably, "
azvyagintseve2e37a12018-11-01 14:45:49 +02001693 "streams data not imported yet.\nSleep:{}s "
1694 "Retry:{}/10".format(poll_time, i))
azvyagintsevcb54d142018-06-19 16:18:32 +03001695 LOG.warning("Message:{0}".format(m))
1696 time.sleep(poll_time)
1697 continue
1698 break
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001699 LOG.debug("create_boot_source_selections:{}".format(json_res))
azvyagintsevcb54d142018-06-19 16:18:32 +03001700 if not json_res:
1701 result["result"] = False
azvyagintsevf3515c82018-06-26 18:59:05 +03001702 result["comment"] = 'Failed to create requested boot-source selection' \
1703 ' for {0}.'.format(bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001704 return result
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001705 if wait:
1706 LOG.debug(
1707 "Sleep for 5s,to get MaaS some time to process previous request")
1708 time.sleep(5)
1709 ret = boot_resources_import(action='import', wait=True)
1710 if ret is dict:
1711 return ret
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001712 result["comment"] = "boot-source selection for {0} was created".format(
1713 bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001714 result["new"] = data
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001715
1716 return result
1717
1718# END MAAS CONFIG SECTION
azvyagintsevcb54d142018-06-19 16:18:32 +03001719
1720# RACK CONTROLLERS SECTION
1721
1722
1723def get_rack(hostname):
1724 """
1725 Get information about specified rackd
1726
1727 CLI Example:
1728
1729 .. code-block:: bash
1730
1731 salt-call maasng.get_rack rack_hostname
1732 """
1733 try:
1734 return list_racks()[hostname]
1735 except KeyError:
1736 return {"error": "rack:{} not found on MaaS server".format(hostname)}
1737
1738
azvyagintsev6913e5e2018-07-05 11:42:53 +03001739def list_racks(sort_by='hostname'):
azvyagintsevcb54d142018-06-19 16:18:32 +03001740 """
1741 Get list of all rack controllers from maas server
1742
1743 CLI Example:
1744
1745 .. code-block:: bash
1746
1747 salt-call maasng.list_racks
1748 """
1749 racks = {}
1750 maas = _create_maas_client()
1751 json_res = json.loads(
1752 maas.get(u"/api/2.0/rackcontrollers/").read() or 'null')
1753 for item in json_res:
azvyagintsev6913e5e2018-07-05 11:42:53 +03001754 racks[item[sort_by]] = item
azvyagintsevcb54d142018-06-19 16:18:32 +03001755 return racks
1756
1757
1758def sync_bs_to_rack(hostname=None):
1759 """
1760 Sync RACK boot-sources with REGION. If no hostname probided - sync to all.
1761
1762 CLI Example:
1763
1764 .. code-block:: bash
1765
1766 salt-call maasng.sync_bs_to_rack rack_hostname
1767 """
1768 ret = {}
1769 maas = _create_maas_client()
1770 if not hostname:
1771 LOG.info("boot-sources sync initiated for ALL Rack's")
1772 # Convert to json-like format
1773 json_res = json.loads('["{0}"]'.format(
1774 maas.post(u"/api/2.0/rackcontrollers/",
1775 'import_boot_images').read()))
1776 LOG.debug("sync_bs_to_rack:{}".format(json_res))
1777 ret['result'] = True
1778 ret['comment'] = "boot-sources sync initiated for ALL Rack's"
1779 return ret
1780 LOG.info("boot-sources sync initiated for RACK:{0}".format(hostname))
1781 # Convert to json-like format
1782 json_res = json.loads('["{0}"]'.format(maas.post(
1783 u"/api/2.0/rackcontrollers/{0}/".format(
1784 get_rack(hostname)['system_id']),
1785 'import_boot_images').read()))
1786 LOG.debug("sync_bs_to_rack:{}".format(json_res))
1787 ret['result'] = True
1788 ret['comment'] = "boot-sources sync initiated for {0} Rack's".format(
1789 hostname)
1790 return
1791
1792
1793def rack_list_boot_imgs(hostname):
1794 ret = {}
1795 maas = _create_maas_client()
1796 LOG.debug("rack_list_boot_imgs:{}".format(hostname))
1797 ret = json.loads(maas.get(u"/api/2.0/rackcontrollers/{0}/".format(
1798 get_rack(hostname)['system_id']), 'list_boot_images').read() or 'null')
1799 return ret
1800
1801
1802def is_rack_synced(hostname):
1803 rez = rack_list_boot_imgs(hostname)['status']
1804 if rez == 'synced':
1805 return True
1806 return False
1807
1808# TODO do we actually need _exact_ check per-pack?
1809# def wait_for_images_on_rack(hostname):
1810#
1811# """
1812# WA function, to be able check that RACK actually done SYNC images
1813# for REQUIRED images at least.
1814# Required image to be fetched from
1815# reclass:maas:region:boot_sources_selections:[keys]:os/release formation
1816#
1817# CLI Example:
1818#
1819# .. code-block:: bash
1820#
1821# salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
1822# """
1823# try:
1824# bss = __salt__['config.get']('maas')['region']['boot_sources_selections']
1825# except KeyError:
1826# ret['result'] = None
1827# ret['comment'] = "boot_sources_selections definition for sync not found."
1828# return ret
1829# s_names = []
1830# # Format u'name': u'ubuntu/xenial'
1831# for v in bss.values():s_names.append("{0}/{1}".format(v['os'],v['release']))
1832# # Each names, should be in rack and whole rack should be in sync-ed state
1833
1834
1835def sync_and_wait_bs_to_all_racks():
1836 """
1837 Sync ALL rack's with regions source images.
1838
1839 CLI Example:
1840
1841 .. code-block:: bash
1842
1843 salt-call maasng.sync_and_wait_bs_to_all_racks
1844 """
1845 sync_bs_to_rack()
1846 for rack in list_racks().keys():
1847 wait_for_sync_bs_to_rack(hostname=rack)
1848 return True
1849
1850
1851def wait_for_sync_bs_to_rack(hostname=None):
1852 """
1853 Wait for boot images sync finished, on exact rack
1854
1855 CLI Example:
1856
1857 .. code-block:: bash
1858
1859 salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
1860 """
1861 ret = {}
1862 started_at = time.time()
1863 poll_time = 5
1864 timeout = 60 * 15
1865 while not is_rack_synced(hostname):
1866 c_timeout = timeout - (time.time() - started_at)
1867 if c_timeout <= 0:
1868 ret['result'] = False
1869 ret[
1870 "comment"] = "Boot-resources sync on rackd:{0}" \
1871 "not finished in time".format(
1872 hostname)
1873 return ret
1874 LOG.info(
1875 "Waiting boot-resources sync done to rack:{0}\n"
1876 "sleep for:{1}s "
1877 "Left:{2}/{3}s".format(hostname, poll_time, round(c_timeout),
1878 timeout))
1879 time.sleep(poll_time)
1880 ret['result'] = is_rack_synced(hostname)
1881 ret["comment"] = "Boot-resources sync on rackd:{0} finished".format(
1882 hostname)
1883 return ret
1884
1885# END RACK CONTROLLERS SECTION
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001886# SSHKEYS
1887
1888
1889def list_sshkeys():
1890 """
1891 Get list of all sshkeys
1892
1893 CLI Example:
1894
1895 .. code-block:: bash
1896
1897 salt 'maas-node' maasng.list_sshkeys
1898 salt-call maasng.list_sshkeys
1899 """
1900 ssh = {}
1901 maas = _create_maas_client()
1902 json_res = json.loads(maas.get(u'api/2.0/account/prefs/sshkeys/').read())
1903 LOG.info(json_res)
1904 for item in json_res:
1905 ssh[item["key"]] = item
1906 return ssh
1907
1908
1909def add_sshkey(sshkey):
1910 """
1911 Add SSH key for user to MAAS.
1912
1913 CLI Example:
1914
1915 .. code-block:: bash
1916
1917 salt 'maas-node' maasng.add_sshkey sshkey
1918 salt-call maasng.add_sshkey sshkey
1919 """
1920 data = {
1921 "key": sshkey,
1922 }
1923 result = {}
1924 maas = _create_maas_client()
1925
1926 maas.post(u"/api/2.0/account/prefs/sshkeys/", None, **data).read()
1927 result["new"] = "SSH Key {0} was added.".format(sshkey)
1928
1929 return result
1930
1931
1932def get_sshkey(sshkey):
1933 """
1934 Get start ip for ip range
1935
1936 CLI Example:
1937
1938 .. code-block:: bash
1939
1940 salt 'maas-node' maasng.get_sshkey sshkey
1941 salt-call maasng.get_sshkey sshkey
1942 """
1943 try:
1944 return list_sshkeys()[sshkey]
1945 except KeyError:
1946 return {"error": "SSH key not found on MaaS server"}
1947# END SSHKEYS