blob: 3a65267fee83e62572799110d11f366d05386b0a [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
azvyagintsevbca1f462018-05-25 19:06:46 +0300124 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100125 try:
126 return list_machines()[hostname]
127 except KeyError:
128 return {"error": "Machine not found on MaaS server"}
129
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200130
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200131def list_machines(status_filter=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300132 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100133 Get list of all machines from maas server
134
135 CLI Example:
136
137 .. code-block:: bash
138
139 salt 'maas-node' maasng.list_machines
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200140 salt 'maas-node' maasng.list_machines status_filter=[Deployed,Ready]
azvyagintsevbca1f462018-05-25 19:06:46 +0300141 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200142 machines = {}
143 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100144 json_res = json.loads(maas.get(u'api/2.0/machines/').read())
145 for item in json_res:
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200146 if not status_filter or item['status_name'] in status_filter:
147 machines[item["hostname"]] = item
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100148 return machines
149
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200150
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100151def create_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200152 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100153
154 return False
155
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200156
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100157def update_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200158 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100159
160 return False
161
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200162def delete_machine(hostname):
163 """
164 Delete specified machine
165
166 CLI Example:
167
168 .. code-block:: bash
169
170 salt 'maas-node' maasng.delete_machine server_hostname
171 salt-call maasng.delete_machine server_hostname
172 """
173 result = {}
174 maas = _create_maas_client()
175 system_id = get_machine(hostname)["system_id"]
176 LOG.debug('delete_machine: {}'.format(system_id))
177 maas.delete(
178 u"api/2.0/machines/{0}/".format(system_id)).read()
179
180 result["new"] = "Machine {0} deleted".format(hostname)
181 return result
182
183def action_machine(hostname, action, comment=None):
184 """
185 Send simple action (e.g. mark_broken, mark_fixed) to machine.
186
187 :param action: Action to send for machine (one of MaaS' op codes)
188 :param comment: Optional comment for the event log.
189
190 CLI Example:
191
192 .. code-block:: bash
193
194 salt 'maas-node' maasng.action_machine server_hostname mark_broken comment='dead'
195 """
196 result = {}
197 data = {}
198 maas = _create_maas_client()
199 system_id = get_machine(hostname)["system_id"]
200 LOG.debug('action_machine: {}'.format(system_id))
201
202 # TODO validation
203 if comment:
204 data["comment"] = comment
205 json_res = json.loads(maas.post(
206 u"api/2.0/machines/{0}/".format(system_id), action, **data).read())
207 LOG.info(json_res)
208 result["new"] = "Machine {0} action {1} executed".format(hostname, action)
209
210 return result
211
azvyagintsevbca1f462018-05-25 19:06:46 +0300212# END MACHINE SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200213# RAID SECTION
214
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100215
216def create_raid(hostname, name, level, disks=[], partitions=[], **kwargs):
azvyagintsevbca1f462018-05-25 19:06:46 +0300217 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100218 Create new raid on machine.
219
220 CLI Example:
221
222 .. code-block:: bash
223
224 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 +0300225 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100226
227 result = {}
228
229 if len(disks) == 0 and len(partitions) == 0:
230 result["error"] = "Disks or partitions need to be provided"
231
232 disk_ids = []
233 partition_ids = []
234
235 for disk in disks:
236 try:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200237 disk_ids.append(str(_get_blockdevice_id_by_name(hostname, disk)))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100238 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200239 result["error"] = "Device {0} does not exists on machine {1}".format(
240 disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100241 return result
242
243 for partition in partitions:
244 try:
245 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200246 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100247 partition_ids.append(str(device_part[partition]["id"]))
248 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200249 result["error"] = "Partition {0} does not exists on machine {1}".format(
250 partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100251 return result
252
253 data = {
254 "name": name,
255 "level": RAID[int(level)],
256 "block_devices": disk_ids,
257 "partitions": partition_ids,
258 }
259
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200260 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100261 system_id = get_machine(hostname)["system_id"]
262 LOG.info(system_id)
263
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200264 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100265 LOG.info(data)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200266 json_res = json.loads(
267 maas.post(u"api/2.0/nodes/{0}/raids/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100268 LOG.info(json_res)
269 result["new"] = "Raid {0} created".format(name)
270
271 return result
272
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200273
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100274def list_raids(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300275 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100276 Get list all raids on machine
277
278 CLI Example:
279
280 .. code-block:: bash
281
282 salt-call maasng.list_raids server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300283 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100284
azvyagintsevbca1f462018-05-25 19:06:46 +0300285 raids = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200286 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100287 system_id = get_machine(hostname)["system_id"]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200288 # TODO validation
289 json_res = json.loads(
290 maas.get(u"api/2.0/nodes/{0}/raids/".format(system_id)).read())
azvyagintsevbca1f462018-05-25 19:06:46 +0300291 LOG.debug('list_raids:{} {}'.format(system_id, json_res))
292 for item in json_res:
293 raids[item["name"]] = item
294 return raids
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100295
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200296
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100297def get_raid(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300298 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100299 Get information about specific raid on machine
300
301 CLI Example:
302
303 .. code-block:: bash
304
305 salt-call maasng.get_raids server_hostname md0
azvyagintsevbca1f462018-05-25 19:06:46 +0300306 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100307
308 return list_raids(hostname)[name]
309
310
azvyagintsevbca1f462018-05-25 19:06:46 +0300311def _get_raid_id_by_name(hostname, raid_name):
312 return get_raid(hostname, raid_name)['id']
313
314
315def delete_raid(hostname, raid_name):
316 """
317 Delete RAID on a machine.
318
319 CLI Example:
320
321 .. code-block:: bash
322
323 salt 'maas-node' maasng.delete_raid server_hostname raid_name
324 salt-call maasng.delete_raid server_hostname raid_name
325 """
326 result = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200327 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +0300328 system_id = get_machine(hostname)["system_id"]
329 raid_id = _get_raid_id_by_name(hostname, raid_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200330 LOG.debug('delete_raid: {} {}'.format(system_id, raid_id))
331 maas.delete(
332 u"api/2.0/nodes/{0}/raid/{1}/".format(system_id, raid_id)).read()
azvyagintsevbca1f462018-05-25 19:06:46 +0300333
334 result["new"] = "Raid {0} deleted".format(raid_name)
335 return result
336
337# END RAID SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200338# BLOCKDEVICES SECTION
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100339
azvyagintsevbca1f462018-05-25 19:06:46 +0300340
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100341def list_blockdevices(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300342 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100343 Get list of all blockdevices (disks) on machine
344
345 CLI Example:
346
347 .. code-block:: bash
348
349 salt 'maas-node' maasng.list_blockdevices server_hostname
350 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300351 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100352 ret = {}
353
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200354 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100355 system_id = get_machine(hostname)["system_id"]
356 LOG.info(system_id)
357
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200358 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100359
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200360 json_res = json.loads(
361 maas.get(u"api/2.0/nodes/{0}/blockdevices/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100362 LOG.info(json_res)
363 for item in json_res:
364 ret[item["name"]] = item
365
366 return ret
367
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200368
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100369def get_blockdevice(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300370 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100371 Get information about blockdevice (disk) on machine
372
373 CLI Example:
374
375 .. code-block:: bash
376
377 salt 'maas-node' maasng.get_blockdevice server_hostname sda
378 salt-call maasng.get_blockdevice server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300379 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100380
381 return list_blockdevices(hostname)[name]
382
azvyagintsevbca1f462018-05-25 19:06:46 +0300383# END BLOCKDEVICES SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200384# PARTITIONS
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100385
azvyagintsevbca1f462018-05-25 19:06:46 +0300386
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100387def list_partitions(hostname, device):
azvyagintsevbca1f462018-05-25 19:06:46 +0300388 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100389 Get list of all partitions on specific device located on specific machine
390
391 CLI Example:
392
393 .. code-block:: bash
394
395 salt 'maas-node' maasng.list_partitions server_hostname sda
396 salt-call maasng.list_partitions server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300397 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100398 ret = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200399 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100400 system_id = get_machine(hostname)["system_id"]
401 LOG.info(system_id)
402
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200403 partitions = get_blockdevice(hostname, device)["partitions"]
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100404 LOG.info(partitions)
405
406 #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 +0200407 # LOG.info(json_res)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100408
409 if len(device) > 0:
410 for item in partitions:
411 name = item["path"].split('/')[-1]
412 ret[name] = item
413
414 return ret
415
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200416
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100417def get_partition(hostname, device, partition):
azvyagintsevbca1f462018-05-25 19:06:46 +0300418 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100419 Get information about specific parition on device located on machine
420
421 CLI Example:
422
423 .. code-block:: bash
424
425 salt 'maas-node' maasng.get_partition server_hostname disk_name partition
426 salt-call maasng.get_partition server_hostname disk_name partition
427
428 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300429 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100430
431 return list_partitions(partition)[name]
432
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200433
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100434def create_partition(hostname, disk, size, fs_type=None, mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300435 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100436 Create new partition on device.
437
438 CLI Example:
439
440 .. code-block:: bash
441
442 salt 'maas-node' maasng.create_partition server_hostname disk_name 10 ext4 "/"
443 salt-call maasng.create_partition server_hostname disk_name 10 ext4 "/"
azvyagintsevbca1f462018-05-25 19:06:46 +0300444 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200445 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100446 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200447 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100448 system_id = get_machine(hostname)["system_id"]
449 LOG.info(system_id)
450
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200451 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100452 LOG.info(device_id)
453
454 value, unit = size[:-1], size[-1]
455 calc_size = str(int(value) * SIZE[unit])
456 LOG.info(calc_size)
457
458 data = {
459 "size": calc_size
460 }
461
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200462 # TODO validation
463 partition = json.loads(maas.post(
464 u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100465 LOG.info(partition)
466 result["partition"] = "Partition created on {0}".format(disk)
467
468 if fs_type != None:
469 data_fs_type = {
470 "fstype": fs_type
471 }
472 partition_id = str(partition["id"])
473 LOG.info("Partition id: " + partition_id)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200474 # TODO validation
475 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
476 system_id, device_id, partition_id), "format", **data_fs_type).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100477 LOG.info(json_res)
478 result["filesystem"] = "Filesystem {0} created".format(fs_type)
479
480 if mount != None:
481 data = {
482 "mount_point": mount
483 }
484
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200485 # TODO validation
486 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
487 system_id, device_id, str(partition['id'])), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100488 LOG.info(json_res)
489 result["mount"] = "Mount point {0} created".format(mount)
490
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100491 return result
492
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200493
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100494def delete_partition(hostname, disk, partition_name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300495 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100496 Delete partition on device.
497
498 CLI Example:
499
500 .. code-block:: bash
501
502 salt 'maas-node' maasng.delete_partition server_hostname disk_name partition_name
503 salt-call maasng.delete_partition server_hostname disk_name partition_name
504
505 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300506 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100507 result = {}
508 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200509 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100510 system_id = get_machine(hostname)["system_id"]
511 LOG.info(system_id)
512
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200513 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100514 LOG.info(device_id)
515
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200516 partition_id = _get_partition_id_by_name(hostname, disk, partition_name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100517
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200518 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
519 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100520 result["new"] = "Partition {0} deleted".format(partition_name)
521 return result
522
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200523
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100524def delete_partition_by_id(hostname, disk, partition_id):
azvyagintsevbca1f462018-05-25 19:06:46 +0300525 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100526 Delete partition on device. Partition spefified by id of parition
527
528 CLI Example:
529
530 .. code-block:: bash
531
532 salt 'maas-node' maasng.delete_partition_by_id server_hostname disk_name partition_id
533 salt-call maasng.delete_partition_by_id server_hostname disk_name partition_id
534
535 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300536 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100537 result = {}
538 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200539 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100540 system_id = get_machine(hostname)["system_id"]
541 LOG.info(system_id)
542
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200543 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100544 LOG.info(device_id)
545
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200546 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
547 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100548 result["new"] = "Partition {0} deleted".format(partition_id)
549 return result
azvyagintsevbca1f462018-05-25 19:06:46 +0300550# END PARTITIONS
551# DISK LAYOUT
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100552
azvyagintsevbca1f462018-05-25 19:06:46 +0300553
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200554def drop_storage_schema(hostname, disk=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300555 """
556 #1. Drop lv
557 #2. Drop vg
558 #3. Drop md # need to zero-block?
559 #3. Drop part
560 """
561
562 if __opts__['test']:
563 ret['result'] = None
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200564 ret['comment'] = 'Storage schema on {0} will be removed'.format(
565 hostname)
azvyagintsevbca1f462018-05-25 19:06:46 +0300566 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200567 # TODO validation if exists
azvyagintsevbca1f462018-05-25 19:06:46 +0300568 vgs = list_volume_groups(hostname)
569 for vg in vgs:
570 delete_volume_group(hostname, vg)
571
572 raids = list_raids(hostname)
573 for raid in raids:
574 delete_raid(hostname, raid)
575
576 blocks = list_blockdevices(hostname)
577 for block_d in blocks:
578 partitions = __salt__['maasng.list_partitions'](hostname, block_d)
579 for partition_name, partition in partitions.iteritems():
580 LOG.info('delete partition:\n{}'.format(partition))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200581 __salt__['maasng.delete_partition_by_id'](
582 hostname, block_d, partition["id"])
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200583
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100584
585def 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 +0300586 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100587 Update disk layout. Flat or LVM layout supported.
588
589 CLI Example:
590
591 .. code-block:: bash
592
593 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
594 salt-call maasng.update_disk_layout server_hostname lvm root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None
595
596 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300597 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100598 result = {}
599 data = {
600 "storage_layout": layout,
601 }
602
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200603 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100604 system_id = get_machine(hostname)["system_id"]
605 LOG.info(system_id)
606
azvyagintsevbca1f462018-05-25 19:06:46 +0300607 if layout == 'custom':
608 drop_storage_schema(hostname)
609 result["new"] = {
610 "storage_layout": layout,
611 }
612
613 return result
614
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100615 if root_size != None:
616 bit_size = str(root_size * 1073741824)
617 LOG.info(bit_size)
618 data["root_size"] = bit_size
619
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100620 if root_device != None:
621 LOG.info(root_device)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200622 data["root_device"] = str(
623 _get_blockdevice_id_by_name(hostname, root_device))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100624
625 if layout == 'lvm':
626 if volume_group != None:
627 LOG.info(volume_group)
628 data["vg_name"] = volume_group
629 if volume_name != None:
630 LOG.info(volume_name)
631 data["lv_name"] = volume_name
632 if volume_size != None:
633 vol_size = str(volume_size * 1073741824)
634 LOG.info(vol_size)
635 data["lv_size"] = vol_size
636
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200637 # TODO validation
638 json_res = json.loads(maas.post(
639 u"api/2.0/machines/{0}/".format(system_id), "set_storage_layout", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100640 LOG.info(json_res)
641 result["new"] = {
642 "storage_layout": layout,
643 }
644
645 return result
646
azvyagintsevbca1f462018-05-25 19:06:46 +0300647# END DISK LAYOUT
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200648# LVM
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100649
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200650
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100651def list_volume_groups(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300652 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100653 Get list of all volume group on machine.
654
655 CLI Example:
656
657 .. code-block:: bash
658
659 salt 'maas-node' maasng.list_volume_groups server_hostname
660 salt-call maasng.list_volume_groups server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300661 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100662 volume_groups = {}
663
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200664 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100665 system_id = get_machine(hostname)["system_id"]
666 LOG.info(system_id)
667
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200668 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100669
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200670 json_res = json.loads(
671 maas.get(u"api/2.0/nodes/{0}/volume-groups/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100672 LOG.info(json_res)
673 for item in json_res:
674 volume_groups[item["name"]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200675 # return
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100676 return volume_groups
677
678
679def get_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300680 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100681 Get information about specific volume group on machine.
682
683 CLI Example:
684
685 .. code-block:: bash
686
687 salt 'maas-node' maasng.list_blockdevices server_hostname
688 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300689 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200690 # TODO validation that exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100691 return list_volume_groups(hostname)[name]
692
693
694def create_volume_group(hostname, volume_group_name, disks=[], partitions=[]):
azvyagintsevbca1f462018-05-25 19:06:46 +0300695 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100696 Create new volume group on machine. Disks or partitions needs to be provided.
697
698 CLI Example:
699
700 .. code-block:: bash
701
702 salt 'maas-node' maasng.create_volume_group volume_group_name, disks=[sda,sdb], partitions=[]
703 salt-call maasng.create_volume_group server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300704 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100705 result = {}
706
707 data = {
708 "name": volume_group_name,
709 }
710
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200711 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100712 system_id = get_machine(hostname)["system_id"]
713 LOG.info(system_id)
714
715 disk_ids = []
716 partition_ids = []
717
718 for disk in disks:
719 p_disk = get_blockdevice(hostname, disk)
720 if p_disk["partition_table_type"] == None:
721 disk_ids.append(str(p_disk["id"]))
722 else:
azvyagintsevf3515c82018-06-26 18:59:05 +0300723 result["error"] = "Device {0} on" \
724 "machine {1} cointains partition" \
725 "table".format(disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100726 return result
727
728 for partition in partitions:
729 try:
730 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200731 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100732 partition_ids.append(str(device_part[partition]["id"]))
733 except KeyError:
azvyagintsevf3515c82018-06-26 18:59:05 +0300734 result["error"] = "Partition {0} does" \
735 "not exists on " \
736 "machine {1}".format(partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100737 return result
738
739 data["block_devices"] = disk_ids
740 data["partitions"] = partition_ids
741 LOG.info(partition_ids)
742 LOG.info(partitions)
743
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200744 # TODO validation
745 json_res = json.loads(maas.post(
746 u"api/2.0/nodes/{0}/volume-groups/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100747 LOG.info(json_res)
748 result["new"] = "Volume group {0} created".format(json_res["name"])
749
750 return result
751
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200752
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100753def delete_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300754 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100755 Delete volume group on machine.
756
757 CLI Example:
758
759 .. code-block:: bash
760
761 salt 'maas-node' maasng.delete_volume_group server_hostname vg0
762 salt-call maasng.delete_volume_group server_hostname vg0
azvyagintsevbca1f462018-05-25 19:06:46 +0300763 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100764
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200765 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100766 system_id = get_machine(hostname)["system_id"]
azvyagintsevbca1f462018-05-25 19:06:46 +0300767 LOG.debug('delete_volume_group:{}'.format(system_id))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100768
azvyagintsevbca1f462018-05-25 19:06:46 +0300769 vg_id = str(_get_volume_group_id_by_name(hostname, name))
770 for vol in get_volumes(hostname, name):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200771 delete_volume(hostname, vol, name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100772
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200773 # TODO validation
774 json_res = json.loads(maas.delete(
775 u"api/2.0/nodes/{0}/volume-group/{1}/".format(system_id, vg_id)).read() or 'null')
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100776 LOG.info(json_res)
777
778 return True
779
780
azvyagintsevf3515c82018-06-26 18:59:05 +0300781def create_volume(hostname, volume_name, volume_group, size, fs_type=None,
782 mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300783 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100784 Create volume on volume group.
785
786 CLI Example:
787
788 .. code-block:: bash
789
790 salt 'maas-node' maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
791 salt-call maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
azvyagintsevbca1f462018-05-25 19:06:46 +0300792 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100793
794 data = {
795 "name": volume_name,
796 }
797
798 value, unit = size[:-1], size[-1]
799 bit_size = str(int(value) * SIZE[unit])
800 LOG.info(bit_size)
801
802 data["size"] = bit_size
803
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200804 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100805 system_id = get_machine(hostname)["system_id"]
806 LOG.info(system_id)
807
808 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
809
810 LOG.info(volume_group_id)
811
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200812 # TODO validation
813 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
814 system_id, volume_group_id), "create_logical_volume", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100815 LOG.info(json_res)
816
817 if fs_type != None or mount != None:
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200818 ret = create_volume_filesystem(
819 hostname, volume_group + "-" + volume_name, fs_type, mount)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100820
821 return True
822
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100823
azvyagintsevbca1f462018-05-25 19:06:46 +0300824def delete_volume(hostname, volume_name, volume_group):
825 """
826 Delete volume from volume group.
827 Tips: maas always use 'volume_group-volume_name' name schema.Example: 'vg0-glusterfs'
828 This function expexts same format.
829
830 CLI Example:
831
832 .. code-block:: bash
833
834 salt 'maas-node' maasng.delete_volume server_hostname volume_name volume_group
835 salt 'maas-node' maasng.delete_volume server_hostname vg0-vol0 vg0
836 salt-call maasng.delete_volume server_hostname volume_name volume_group
837 """
838
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200839 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +0300840 system_id = get_machine(hostname)["system_id"]
841 LOG.debug('delete_volume:{}'.format(system_id))
842
843 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200844 volume_id = str(_get_volume_id_by_name(
845 hostname, volume_name, volume_group))
azvyagintsevbca1f462018-05-25 19:06:46 +0300846
847 if None in [volume_group_id, volume_id]:
848 return False
849
850 data = {
851 "id": volume_id,
852 }
853
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200854 # TODO validation
855 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
856 system_id, volume_group_id), "delete_logical_volume", **data).read() or 'null')
azvyagintsevbca1f462018-05-25 19:06:46 +0300857 return True
858
859
860def get_volumes(hostname, vg_name):
861 """
862 Get list of volumes in volume group.
863 """
864 volumes = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200865 _volumes = list_volume_groups(
866 hostname)[vg_name].get('logical_volumes', False)
azvyagintsevbca1f462018-05-25 19:06:46 +0300867 if _volumes:
868 for item in _volumes:
869 volumes[item["name"]] = item
870 return volumes
871
872# END LVM
873
874
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200875def create_volume_filesystem(hostname, device, fs_type=None, mount=None):
876
877 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100878 system_id = get_machine(hostname)["system_id"]
879
880 blockdevices_id = _get_blockdevice_id_by_name(hostname, device)
881 data = {}
882 if fs_type != None:
883 data["fstype"] = fs_type
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200884 # TODO validation
885 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
886 system_id, blockdevices_id), "format", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100887 LOG.info(json_res)
888
889 if mount != None:
890 data["mount_point"] = mount
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200891 # TODO validation
892 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
893 system_id, blockdevices_id), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100894 LOG.info(json_res)
895
896 return True
897
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100898
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100899def set_boot_disk(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300900 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100901 Create volume on volume group.
902
903 CLI Example:
904
905 .. code-block:: bash
906
907 salt 'maas-node' maasng.set_boot_disk server_hostname disk_name
908 salt-call maasng.set_boot_disk server_hostname disk_name
azvyagintsevbca1f462018-05-25 19:06:46 +0300909 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100910 data = {}
911 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200912 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100913 system_id = get_machine(hostname)["system_id"]
914 blockdevices_id = _get_blockdevice_id_by_name(hostname, name)
915
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200916 maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
917 system_id, blockdevices_id), "set_boot_disk", **data).read()
azvyagintsevf3515c82018-06-26 18:59:05 +0300918 # TODO validation for error response
919 # (disk does not exists and node does not exists)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100920 result["new"] = "Disk {0} was set as bootable".format(name)
921
922 return result
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200923
azvyagintsevbca1f462018-05-25 19:06:46 +0300924# NETWORKING
925
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200926
927def list_fabric():
azvyagintsevbca1f462018-05-25 19:06:46 +0300928 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200929 Get list of all fabric
930
931 CLI Example:
932
933 .. code-block:: bash
934
935 salt 'maas-node' maasng.list_fabric
azvyagintsevbca1f462018-05-25 19:06:46 +0300936 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200937 fabrics = {}
938 maas = _create_maas_client()
939 json_res = json.loads(maas.get(u'api/2.0/fabrics/').read())
940 LOG.info(json_res)
941 for item in json_res:
942 fabrics[item["name"]] = item
943 return fabrics
944
945
azvyagintsevf3515c82018-06-26 18:59:05 +0300946def check_fabric(name):
947 """
948 Simple check that fabric already defined
949 Return format:
950 update - require update
951 correct - fully coincides # not implemented
952 not_exist - need's to be created
953 """
954
955 ret = 'not_exist'
956 fabrics = list_fabric()
957 if name in fabrics.keys():
958 LOG.debug("Requested fabrics with name:{} already exist".format(name))
959 ret = 'update'
960 return ret
961
962
963def check_fabric_guess_with_cidr(name, cidrs):
964 """
965 Check, that fabric already defined OR it was autodiscovered
966 WA to fix issue with hardcoded 'fabric-0'
967 - Find all auto-discovered subnets by cidr
968 - find all subnets, that SHOULD be configured to THIS subent
969 Warning: most probably, will fail if some subnet defined
970 to another fabric :(
971
972 { 'update' : ID } - require update
973 { 'correct' : ID } - fully coincides # not implemented
974 { 'not_exist' : None } - need's to be created
975
976 CLI Example:
977
978 .. code-block:: bash
979
980 salt 'maas-node' maasng.check_fabric_guess_with_cidr name='' cidrs=[]
981 """
982
983 ret = {'not_exist': None}
984 fabrics = list_fabric()
985 # Simple check
986 if name in fabrics:
987 LOG.debug("Requested fabrics with name:{} already exist".format(name))
988 f_id = fabrics[name]['id']
989 ret = {'update': f_id}
990 # Cidr check
991 # All discovered subnets by cidr
992 d_subnets = list_subnets(sort_by='cidr')
993 # Check, that requested cidr already in discovered.
994 # If it is - it would mean that fabric already
995 # exist(fabric-0,most probably) but should be renamed.
996 # Works only for first shot ;(
997 # due curren-single-maas logic for 'vlan-subnet' mapping.
998 # Probably, it will fail with future MAAS releases.
999 for cidr in cidrs:
1000 if cidr in d_subnets:
1001 f_id = d_subnets[cidr]['vlan']['fabric_id']
1002 f_name = d_subnets[cidr]['vlan']['fabric']
1003 LOG.warning("Detected cidr:{} in fabric:{}".format(cidr, f_name))
1004 LOG.warning("Guessing, that fabric "
1005 "with current name:{}\n should be "
1006 "renamed to:{}".format(f_name, name))
1007 ret = {'update': f_id}
1008 return ret
1009 return ret
1010
1011
azvyagintsevf0904ac2018-07-05 18:53:26 +03001012def create_fabric(name, description=None, fabric_id=None, update=False):
azvyagintsevbca1f462018-05-25 19:06:46 +03001013 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001014 Create new fabric.
1015
1016 CLI Example:
1017
1018 .. code-block:: bash
1019
azvyagintsevf3515c82018-06-26 18:59:05 +03001020 salt 'maas-node' maasng.create_fabric name='123'
azvyagintsevbca1f462018-05-25 19:06:46 +03001021 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001022 result = {}
1023 data = {
1024 "name": name,
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001025 "class_type": '',
1026
1027 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001028 if description:
1029 data['description'] = description
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001030
1031 maas = _create_maas_client()
azvyagintsevf0904ac2018-07-05 18:53:26 +03001032 json_res = None
azvyagintsevf3515c82018-06-26 18:59:05 +03001033 try:
1034 if update:
1035 json_res = json.loads(
1036 maas.put(u"api/2.0/fabrics/{0}/".format(fabric_id),
1037 **data).read())
1038 result["new"] = "Fabric {0} created".format(json_res["name"])
1039 else:
1040 json_res = json.loads(
1041 maas.post(u"api/2.0/fabrics/", None, **data).read())
1042 result["changes"] = "Fabric {0} updated".format(json_res["name"])
1043 except Exception as inst:
1044 LOG.debug("create_fabric data:{}".format(data))
1045 try:
1046 m = inst.readlines()
1047 except:
1048 m = inst.message
1049 LOG.error("Message:{0}".format(m))
1050 result['result'] = False
1051 result['comment'] = 'Error creating fabric: {0}'.format(name)
1052 result['error'] = m
1053 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001054 LOG.debug("crete_fabric:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001055 result['result'] = True
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001056 return result
1057
1058
azvyagintsevf3515c82018-06-26 18:59:05 +03001059def list_subnets(sort_by='name'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001060 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001061 Get list of subnets from maas server
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001062
1063 CLI Example:
1064
1065 .. code-block:: bash
1066
azvyagintsevf3515c82018-06-26 18:59:05 +03001067 salt 'maas-node' maasng.list_subnets
azvyagintsevbca1f462018-05-25 19:06:46 +03001068 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001069 subnets = {}
1070 maas = _create_maas_client()
1071 json_res = json.loads(maas.get(u'api/2.0/subnets/').read())
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001072 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001073 subnets[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001074 return subnets
1075
1076
azvyagintsevf3515c82018-06-26 18:59:05 +03001077def list_vlans(fabric, sort_by='vid'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001078 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001079 Get list of vlans in fabric
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001080
1081 CLI Example:
1082
1083 .. code-block:: bash
1084
azvyagintsevf3515c82018-06-26 18:59:05 +03001085 salt 'maas-node' maasng.list_vlans fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001086 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001087 vlans = {}
1088 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001089 fabric_id = get_fabricid(fabric)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001090
azvyagintsevf3515c82018-06-26 18:59:05 +03001091 try:
1092 json_res = json.loads(
1093 maas.get(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id)).read())
1094 except Exception as inst:
1095 m = inst.readlines()
1096 LOG.error("Message:{0}".format(m))
1097 LOG.debug(json_res)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001098 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001099 vlans[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001100 return vlans
1101
1102
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001103def get_fabricid(fabric):
azvyagintsevbca1f462018-05-25 19:06:46 +03001104 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001105 Get id for specific fabric
1106
1107 CLI Example:
1108
1109 .. code-block:: bash
1110
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001111 salt 'maas-node' maasng.get_fabricid fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001112 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001113 try:
1114 return list_fabric()[fabric]['id']
1115 except KeyError:
azvyagintsevefb6f5d2018-07-10 14:16:19 +03001116 return {"error": "Fabric not found on MaaS server"}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001117
1118
azvyagintsevf3515c82018-06-26 18:59:05 +03001119def check_vlan_in_fabric(fabric, vlan):
1120 """
1121 Check that VLAN exactly defined
1122 Return format:
1123 update - require update
1124 correct - fully coincides # not implemented
1125 not_exist - need's to be created
1126 """
1127
1128 ret = 'not_exist'
1129 vlans = list_vlans(fabric)
1130 if vlan in vlans.keys():
1131 LOG.debug("Requested VLAN:{} already exist"
1132 "in FABRIC:{}".format(vlan, fabric))
1133 ret = 'update'
1134 return ret
1135
1136
Petr Ruzicka80471852018-07-13 14:08:27 +02001137def create_vlan_in_fabric(name, fabric, vlan, description, primary_rack, mtu=1500,
azvyagintsevf3515c82018-06-26 18:59:05 +03001138 dhcp_on=False, update=False, vlan_id=""):
azvyagintsevbca1f462018-05-25 19:06:46 +03001139 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001140 Update vlan
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001141 CLI Example:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001142 .. code-block:: bash
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001143 salt 'maas-node' maasng.update_vlan name, fabric, vid, description, dhcp_on
azvyagintsevbca1f462018-05-25 19:06:46 +03001144 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001145 result = {}
1146
1147 data = {
1148 "name": name,
1149 "dhcp_on": str(dhcp_on),
1150 "description": description,
azvyagintsev6913e5e2018-07-05 11:42:53 +03001151 "primary_rack": list_racks()[primary_rack]['system_id'],
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001152 }
Michael Polenchukd25da792018-07-19 18:27:11 +04001153 if mtu:
1154 data['mtu'] = str(mtu)
azvyagintsevf3515c82018-06-26 18:59:05 +03001155 vlan = str(vlan)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001156 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001157 fabric_id = get_fabricid(fabric)
azvyagintsevf3515c82018-06-26 18:59:05 +03001158 try:
1159 if update:
1160 # MAAS have buggy logic here. Fallowing api reference, here should
1161 # be passed VID - which mean, API ID for vlan.
1162 # Otherwise, at least for maas 2.3.3-6498-ge4db91d exactly VLAN
1163 # should be passed. so, make temp.backward-convertation.
1164 # json_res = json.loads(maas.put(u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id,vlan_id), **data).read())
1165 json_res = json.loads(maas.put(
1166 u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id, vlan),
1167 **data).read())
1168 else:
1169 data['vid'] = str(vlan)
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001170 json_res = json.loads(maas.post(
1171 u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id), None, **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001172 except Exception as inst:
1173 LOG.debug("create_vlan_in_fabric data:{}".format(data))
1174 try:
1175 m = inst.readlines()
1176 except:
1177 m = inst.message
1178 LOG.error("Message:{0}".format(m))
1179 result['result'] = False
1180 result['comment'] = 'Error updating vlan: {0}'.format(name)
1181 result['error'] = m
1182 return result
1183 LOG.debug("create_vlan_in_fabric:{}".format(json_res))
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001184 result["new"] = "Vlan {0} was updated".format(json_res["name"])
1185
1186 return result
azvyagintsevbca1f462018-05-25 19:06:46 +03001187
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001188
azvyagintsevf3515c82018-06-26 18:59:05 +03001189def check_subnet(cidr, name, fabric, gateway_ip):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001190 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001191 Check that subnet exactly defined
1192 Return format:
1193 update - require update
1194 correct - fully coincides # not implemented
1195 not_exist - need's to be created
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001196 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001197
1198 ret = 'not_exist'
1199 subnets = list_subnets(sort_by='cidr')
1200 if cidr in subnets.keys():
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001201 LOG.debug("Requested subnet cidr:{} already exist".format(cidr))
1202 ret = 'update'
azvyagintsevf3515c82018-06-26 18:59:05 +03001203 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001204
1205
azvyagintsevf3515c82018-06-26 18:59:05 +03001206def create_subnet(cidr='', name='', fabric='', gateway_ip='', vlan='',
1207 update=False, subnet_id=''):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001208 """
1209 Create subnet
1210
1211 CLI Example:
1212
1213 .. code-block:: bash
1214
1215 salt 'maas-node' maasng.create_subnet cidr, name, fabric, gateway_ip
1216 """
1217
1218 fabric_id = get_fabricid(fabric)
1219 result = {}
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001220 vlan = str(vlan)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001221 data = {
1222 "cidr": cidr,
1223 "name": name,
1224 "fabric": str(fabric_id),
1225 "gateway_ip": gateway_ip,
azvyagintsevf3515c82018-06-26 18:59:05 +03001226 'vlan': vlan,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001227 }
1228 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001229 # FIXME: vlan definition not work in 2.3.3-6498-ge4db91d.
1230 LOG.warning("Ignoring parameter vlan:{}".format(vlan))
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001231 data.pop('vlan', '')
azvyagintsevf3515c82018-06-26 18:59:05 +03001232 try:
1233 if update:
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001234 json_res = json.loads(
1235 maas.put(u"api/2.0/subnets/{0}/".format(subnet_id), **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001236 else:
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001237 json_res = json.loads(
1238 maas.post(u"api/2.0/subnets/", None, **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001239 except Exception as inst:
1240 LOG.debug("create_subnet data:{}".format(data))
1241 try:
1242 m = inst.readlines()
1243 except:
1244 m = inst.message
1245 LOG.error("Message:{0}".format(m))
1246 result['result'] = False
1247 result['comment'] = 'Error creating subnet: {0}'.format(name)
1248 result['error'] = m
1249 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001250 LOG.debug("create_subnet:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001251 result["new"] = "Subnet {0} with CIDR {1}" \
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001252 "and gateway {2} was created".format(
1253 name, cidr, gateway_ip)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001254
1255 return result
1256
1257
1258def get_subnet(subnet):
1259 """
1260 Get details for specific subnet
1261
1262 CLI Example:
1263
1264 .. code-block:: bash
1265
1266 salt 'maas-node' maasng.get_subnet subnet_name
1267 """
1268 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001269 return list_subnets()[subnet]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001270 except KeyError:
1271 return {"error": "Subnet not found on MaaS server"}
1272
1273
1274def get_subnetid(subnet):
1275 """
1276 Get id for specific subnet
1277
1278 CLI Example:
1279
1280 .. code-block:: bash
1281
1282 salt 'maas-node' maasng.get_subnetid subnet_name
1283 """
1284 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001285 return list_subnets()[subnet]['id']
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001286 except KeyError:
1287 return {"error": "Subnet not found on MaaS server"}
1288
1289
1290def list_ipranges():
1291 """
1292 Get list of all ipranges from maas server
1293
1294 CLI Example:
1295
1296 .. code-block:: bash
1297
1298 salt 'maas-node' maasng.list_ipranges
1299 """
1300 ipranges = {}
1301 maas = _create_maas_client()
1302 json_res = json.loads(maas.get(u'api/2.0/ipranges/').read())
1303 for item in json_res:
1304 ipranges[item["start_ip"]] = item
1305 return ipranges
1306
1307
azvyagintsevf0904ac2018-07-05 18:53:26 +03001308def create_iprange(type_range, start_ip, end_ip, subnet=None, comment=None):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001309 """
1310 Create ip range
1311
1312 CLI Example:
1313
1314 .. code-block:: bash
1315
1316 salt 'maas-node' maasng.create_iprange type, start ip, end ip, comment
1317 """
1318 result = {}
1319
1320 data = {
1321 "type": type_range,
1322 "start_ip": start_ip,
1323 "end_ip": end_ip,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001324 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001325 if comment:
1326 data['comment'] = comment
azvyagintsevf0904ac2018-07-05 18:53:26 +03001327 if subnet:
1328 subnet_id = list_subnets()[subnet]['id']
1329 data['subnet'] = str(subnet_id)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001330 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001331 _name = "Type:{}: {}-{}".format(type_range, start_ip, end_ip)
1332 try:
1333 json_res = json.loads(
1334 maas.post(u"api/2.0/ipranges/", None, **data).read())
1335 except Exception as inst:
1336 try:
1337 m = inst.readlines()
1338 except:
1339 m = inst.message
1340 LOG.error("Message:{0}".format(m))
1341 result['result'] = False
1342 result['comment'] = 'Error creating iprange:{0}'.format(_name)
1343 result['error'] = m
1344 return result
1345 result["new"] = "Iprange: {0} has been created".format(_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001346 LOG.debug("create_iprange:{}".format(json_res))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001347
1348 return result
1349
1350
1351def get_iprangeid(start_ip):
1352 """
1353 Get id for ip range from maas server
1354
1355 CLI Example:
1356
1357 .. code-block:: bash
1358
1359 salt 'maas-node' maasng.get_iprangeid start_ip
1360 """
1361 try:
1362 return list_ipranges()[start_ip]['id']
1363 except KeyError:
1364 return {"error": "Ip range not found on MaaS server"}
1365
1366
1367def get_startip(start_ip):
1368 """
1369 Get start ip for ip range
1370
1371 CLI Example:
1372
1373 .. code-block:: bash
1374
1375 salt 'maas-node' maasng.get_startip start ip
1376 """
1377 try:
1378 return list_ipranges()[start_ip]
1379 except KeyError:
1380 return {"error": "Ip range not found on MaaS server"}
azvyagintsevbca1f462018-05-25 19:06:46 +03001381# END NETWORKING
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001382
1383# MAAS CONFIG SECTION
1384
1385
azvyagintsev58947072018-06-29 12:09:48 +03001386def _getHTTPCode(url):
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001387 code = '003'
1388 m = ''
azvyagintsev58947072018-06-29 12:09:48 +03001389 try:
1390 connection = urllib2.urlopen(url)
1391 code = connection.getcode()
1392 connection.close()
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001393 except (urllib2.HTTPError, urllib2.URLError) as e:
1394 try:
1395 code = e.getcode()
1396 except:
1397 m = e.reason
1398 pass
azvyagintsevf3515c82018-06-26 18:59:05 +03001399 LOG.debug("Unexpected http code:{} from "
1400 "url:{}\nwith message:{}".format(code, url, m))
azvyagintsev58947072018-06-29 12:09:48 +03001401 pass
1402 return code
1403
1404
1405def wait_for_http_code(url=None, expected=[200]):
1406 """
1407 Simple function, which just wait for avaible api, aka wait for 200.
1408
1409 CLI Example:
1410
1411 .. code-block:: bash
1412
1413 salt 'maas-node' maasng.wait_for_http_code url expected=[200]
1414
1415 """
1416 ret = {}
1417 started_at = time.time()
1418 poll_time = 5
1419 timeout = 60 * 2
1420 while _getHTTPCode(url) not in expected:
1421 c_timeout = timeout - (time.time() - started_at)
1422 if c_timeout <= 0:
1423 ret['result'] = False
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001424 ret["comment"] = "api:{} not answered in time".format(url)
azvyagintsev58947072018-06-29 12:09:48 +03001425 return ret
1426 LOG.info(
1427 "Waiting for api:{0}\n"
1428 "sleep for:{1}s "
1429 "Left:{2}/{3}s".format(url, poll_time, round(c_timeout),
1430 timeout))
1431 time.sleep(poll_time)
1432 ret['result'] = True
1433 ret["comment"] = "MAAS API:{} up.".format(url)
1434 return ret
1435
1436
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001437def _get_boot_source_id_by_url(url):
1438 # FIXME: fix ret\validation
1439 try:
1440 bs_id = get_boot_source(url=url)["id"]
1441 except KeyError:
1442 return {"error": "boot-source:{0} not exist!".format(url)}
1443 return bs_id
1444
1445
1446def get_boot_source(url=None):
1447 """
1448 Read a boot source by url. If url not specified - return all.
1449
1450 CLI Example:
1451
1452 .. code-block:: bash
1453
1454 salt 'maas-node' maasng.get_boot_source url
1455
1456 """
1457 boot_sources = {}
1458 maas = _create_maas_client()
1459 json_res = json.loads(maas.get(u'api/2.0/boot-sources/').read() or 'null')
1460 for item in json_res:
1461 boot_sources[str(item["url"])] = item
1462 if url:
1463 return boot_sources.get(url, {})
1464 return boot_sources
1465
1466
1467def delete_boot_source(url, bs_id=None):
1468 """
1469 Delete a boot source by url.
1470
1471 CLI Example:
1472
1473 .. code-block:: bash
1474
1475 sal 'maas-node' maasng.delete url
1476
1477 """
1478 result = {}
1479 if not bs_id:
1480 bs_id = _get_boot_source_id_by_url(url)
1481 maas = _create_maas_client()
1482 json_res = json.loads(maas.delete(
1483 u'/api/2.0/boot-sources/{0}/'.format(bs_id)).read() or 'null')
1484 LOG.debug("delete_boot_source:{}".format(json_res))
1485 result["new"] = "Boot-resource {0} deleted".format(url)
1486 return result
1487
1488
1489def boot_sources_delete_all_others(except_urls=[]):
1490 """
1491 Delete all boot-sources, except defined in 'except_urls' list.
1492 """
1493 result = {}
1494 maas_boot_sources = get_boot_source()
1495 if 0 in [len(except_urls), len(maas_boot_sources)]:
1496 result['result'] = None
1497 result[
1498 "comment"] = "Exclude or maas sources for delete empty. No changes goinng to be."
1499 return result
1500 for url in maas_boot_sources.keys():
1501 if url not in except_urls:
1502 LOG.info("Removing boot-source:{}".format(url))
1503 boot_resources_import(action='stop_import', wait=True)
1504 result["changes"] = delete_boot_source(url)
1505 return result
1506
1507
1508def create_boot_source(url, keyring_filename='', keyring_data='', wait=False):
1509 """
1510 Create and import maas boot-source: link to maas-ephemeral repo
1511 Be aware, those step will import resource to rack ctrl, but you also need to import
1512 them into the region!
1513
1514
1515 :param url: The URL of the BootSource.
1516 :param keyring_filename: The path to the keyring file for this BootSource.
1517 :param keyring_data: The GPG keyring for this BootSource, base64-encoded data.
1518
1519 """
1520
1521 # TODO: not work with 'update' currently => keyring update may fail.
1522 result = {}
1523
1524 data = {
1525 "url": url,
1526 "keyring_filename": keyring_filename,
1527 "keyring_data": str(keyring_data),
1528 }
1529
1530 maas = _create_maas_client()
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001531 if url in get_boot_source():
1532 result['result'] = None
1533 result["comment"] = "boot resource already exist"
1534 return result
1535
1536 # NOTE: maas.post will return 400, if url already defined.
1537 json_res = json.loads(
1538 maas.post(u'api/2.0/boot-sources/', None, **data).read())
1539 if wait:
1540 LOG.debug(
1541 "Sleep for 5s,to get MaaS some time to process previous request")
1542 time.sleep(5)
1543 ret = boot_resources_is_importing(wait=True)
1544 if ret is dict:
1545 return ret
1546 LOG.debug("create_boot_source:{}".format(json_res))
1547 result["new"] = "boot resource {0} was created".format(json_res["url"])
1548
1549 return result
1550
1551
1552def boot_resources_import(action='import', wait=False):
1553 """
1554 import/stop_import the boot resources.
1555
1556 :param action: import\stop_import
1557 :param wait: True\False. Wait till process finished.
1558
1559 CLI Example:
1560
1561 .. code-block:: bash
1562
1563 salt 'maas-node' maasng.boot_resources_import action='import'
1564
1565 """
1566 maas = _create_maas_client()
1567 # Have no idea why, but usual jsonloads not work here..
1568 imp = maas.post(u'api/2.0/boot-resources/', action)
1569 if imp.code == 200:
1570 LOG.debug('boot_resources_import:{}'.format(imp.readline()))
1571 if wait:
1572 boot_resources_is_importing(wait=True)
1573 return True
1574 else:
1575 return False
1576
1577
1578def boot_resources_is_importing(wait=False):
1579 maas = _create_maas_client()
1580 result = {}
1581 if wait:
1582 started_at = time.time()
1583 poll_time = 5
1584 timeout = 60 * 15
1585 while boot_resources_is_importing(wait=False):
1586 c_timeout = timeout - (time.time() - started_at)
1587 if c_timeout <= 0:
1588 result['result'] = False
1589 result["comment"] = "Boot-resources import not finished in time"
1590 return result
1591 LOG.info(
1592 "Waiting boot-resources import done\n"
1593 "sleep for:{}s "
1594 "Left:{}/{}s".format(poll_time, round(c_timeout), timeout))
1595 time.sleep(poll_time)
1596 return json.loads(
1597 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1598 else:
1599 return json.loads(
1600 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1601
1602#####
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001603# def boot_sources_selections_delete_all_others(except_urls=[]):
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001604# """
1605# """
1606# result = {}
1607# return result
1608
1609
1610def is_boot_source_selections_in(dict1, list1):
1611 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001612 Check that requested boot-selection already in maas bs selections,
1613 if True- return bss id.
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001614 # FIXME: those hack check doesn't look good.
1615 """
1616 for bs in list1:
1617 same = set(dict1.keys()) & set(bs.keys())
1618 if all(elem in same for elem in
1619 ['os', 'release', 'arches', 'subarches', 'labels']):
azvyagintsevf3515c82018-06-26 18:59:05 +03001620 LOG.debug("boot-selection in maas:{0}\n"
1621 "looks same to requested:{1}".format(bs, dict1))
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001622 return bs['id']
1623 return False
1624
1625
1626def get_boot_source_selections(bs_url):
1627 """
1628 Get boot-source selections.
1629 """
1630 # check for key_error!
1631 bs_id = _get_boot_source_id_by_url(bs_url)
1632 maas = _create_maas_client()
1633 json_res = json.loads(
1634 maas.get(u'/api/2.0/boot-sources/{0}/selections/'.format(bs_id)).read())
1635 LOG.debug(
1636 "get_boot_source_selections for url:{} \n{}".format(bs_url, json_res))
1637 return json_res
1638
1639
1640def create_boot_source_selections(bs_url, os, release, arches="*",
1641 subarches="*", labels="*", wait=True):
1642 """
1643 Create a new boot source selection for bs_url.
1644 :param os: The OS (e.g. ubuntu, centos) for which to import resources.Required.
1645 :param release: The release for which to import resources. Required.
1646 :param arches: The architecture list for which to import resources.
1647 :param subarches: The subarchitecture list for which to import resources.
1648 :param labels: The label lists for which to import resources.
1649 """
1650
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001651 result = {"result": True, 'name': bs_url, 'changes': None}
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001652
1653 data = {
1654 "os": os,
1655 "release": release,
1656 "arches": arches,
1657 "subarches": subarches,
1658 "labels": labels,
1659 }
1660
1661 maas = _create_maas_client()
1662 bs_id = _get_boot_source_id_by_url(bs_url)
1663 # TODO add pre-create verify
1664 maas_bs_s = get_boot_source_selections(bs_url)
1665 if is_boot_source_selections_in(data, maas_bs_s):
1666 result["result"] = True
azvyagintsevf3515c82018-06-26 18:59:05 +03001667 result["comment"] = 'Requested boot-source selection ' \
1668 'for {0} already exist.'.format(
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001669 bs_url)
1670 return result
1671
1672 # NOTE: maas.post will return 400, if url already defined.
azvyagintsevcb54d142018-06-19 16:18:32 +03001673 # Also, maas need's some time to import info about stream.
1674 # unfortunatly, maas don't have any call to check stream-import-info - so, we need to implement
1675 # at least simple retry ;(
1676 json_res = False
1677 poll_time = 5
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001678 for i in range(0, 5):
azvyagintsevcb54d142018-06-19 16:18:32 +03001679 try:
1680 json_res = json.loads(
1681 maas.post(u'api/2.0/boot-sources/{0}/selections/'.format(bs_id), None,
1682 **data).read())
1683 except Exception as inst:
1684 m = inst.readlines()
azvyagintsevf3515c82018-06-26 18:59:05 +03001685 LOG.warning("boot_source_selections "
1686 "catch error during processing. Most-probably, "
1687 "streams not imported yet.\nSleep:{}s"
1688 "Retry:{}/5".format(poll_time, i))
azvyagintsevcb54d142018-06-19 16:18:32 +03001689 LOG.warning("Message:{0}".format(m))
1690 time.sleep(poll_time)
1691 continue
1692 break
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001693 LOG.debug("create_boot_source_selections:{}".format(json_res))
azvyagintsevcb54d142018-06-19 16:18:32 +03001694 if not json_res:
1695 result["result"] = False
azvyagintsevf3515c82018-06-26 18:59:05 +03001696 result["comment"] = 'Failed to create requested boot-source selection' \
1697 ' for {0}.'.format(bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001698 return result
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001699 if wait:
1700 LOG.debug(
1701 "Sleep for 5s,to get MaaS some time to process previous request")
1702 time.sleep(5)
1703 ret = boot_resources_import(action='import', wait=True)
1704 if ret is dict:
1705 return ret
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001706 result["comment"] = "boot-source selection for {0} was created".format(
1707 bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001708 result["new"] = data
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001709
1710 return result
1711
1712# END MAAS CONFIG SECTION
azvyagintsevcb54d142018-06-19 16:18:32 +03001713
1714# RACK CONTROLLERS SECTION
1715
1716
1717def get_rack(hostname):
1718 """
1719 Get information about specified rackd
1720
1721 CLI Example:
1722
1723 .. code-block:: bash
1724
1725 salt-call maasng.get_rack rack_hostname
1726 """
1727 try:
1728 return list_racks()[hostname]
1729 except KeyError:
1730 return {"error": "rack:{} not found on MaaS server".format(hostname)}
1731
1732
azvyagintsev6913e5e2018-07-05 11:42:53 +03001733def list_racks(sort_by='hostname'):
azvyagintsevcb54d142018-06-19 16:18:32 +03001734 """
1735 Get list of all rack controllers from maas server
1736
1737 CLI Example:
1738
1739 .. code-block:: bash
1740
1741 salt-call maasng.list_racks
1742 """
1743 racks = {}
1744 maas = _create_maas_client()
1745 json_res = json.loads(
1746 maas.get(u"/api/2.0/rackcontrollers/").read() or 'null')
1747 for item in json_res:
azvyagintsev6913e5e2018-07-05 11:42:53 +03001748 racks[item[sort_by]] = item
azvyagintsevcb54d142018-06-19 16:18:32 +03001749 return racks
1750
1751
1752def sync_bs_to_rack(hostname=None):
1753 """
1754 Sync RACK boot-sources with REGION. If no hostname probided - sync to all.
1755
1756 CLI Example:
1757
1758 .. code-block:: bash
1759
1760 salt-call maasng.sync_bs_to_rack rack_hostname
1761 """
1762 ret = {}
1763 maas = _create_maas_client()
1764 if not hostname:
1765 LOG.info("boot-sources sync initiated for ALL Rack's")
1766 # Convert to json-like format
1767 json_res = json.loads('["{0}"]'.format(
1768 maas.post(u"/api/2.0/rackcontrollers/",
1769 'import_boot_images').read()))
1770 LOG.debug("sync_bs_to_rack:{}".format(json_res))
1771 ret['result'] = True
1772 ret['comment'] = "boot-sources sync initiated for ALL Rack's"
1773 return ret
1774 LOG.info("boot-sources sync initiated for RACK:{0}".format(hostname))
1775 # Convert to json-like format
1776 json_res = json.loads('["{0}"]'.format(maas.post(
1777 u"/api/2.0/rackcontrollers/{0}/".format(
1778 get_rack(hostname)['system_id']),
1779 'import_boot_images').read()))
1780 LOG.debug("sync_bs_to_rack:{}".format(json_res))
1781 ret['result'] = True
1782 ret['comment'] = "boot-sources sync initiated for {0} Rack's".format(
1783 hostname)
1784 return
1785
1786
1787def rack_list_boot_imgs(hostname):
1788 ret = {}
1789 maas = _create_maas_client()
1790 LOG.debug("rack_list_boot_imgs:{}".format(hostname))
1791 ret = json.loads(maas.get(u"/api/2.0/rackcontrollers/{0}/".format(
1792 get_rack(hostname)['system_id']), 'list_boot_images').read() or 'null')
1793 return ret
1794
1795
1796def is_rack_synced(hostname):
1797 rez = rack_list_boot_imgs(hostname)['status']
1798 if rez == 'synced':
1799 return True
1800 return False
1801
1802# TODO do we actually need _exact_ check per-pack?
1803# def wait_for_images_on_rack(hostname):
1804#
1805# """
1806# WA function, to be able check that RACK actually done SYNC images
1807# for REQUIRED images at least.
1808# Required image to be fetched from
1809# reclass:maas:region:boot_sources_selections:[keys]:os/release formation
1810#
1811# CLI Example:
1812#
1813# .. code-block:: bash
1814#
1815# salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
1816# """
1817# try:
1818# bss = __salt__['config.get']('maas')['region']['boot_sources_selections']
1819# except KeyError:
1820# ret['result'] = None
1821# ret['comment'] = "boot_sources_selections definition for sync not found."
1822# return ret
1823# s_names = []
1824# # Format u'name': u'ubuntu/xenial'
1825# for v in bss.values():s_names.append("{0}/{1}".format(v['os'],v['release']))
1826# # Each names, should be in rack and whole rack should be in sync-ed state
1827
1828
1829def sync_and_wait_bs_to_all_racks():
1830 """
1831 Sync ALL rack's with regions source images.
1832
1833 CLI Example:
1834
1835 .. code-block:: bash
1836
1837 salt-call maasng.sync_and_wait_bs_to_all_racks
1838 """
1839 sync_bs_to_rack()
1840 for rack in list_racks().keys():
1841 wait_for_sync_bs_to_rack(hostname=rack)
1842 return True
1843
1844
1845def wait_for_sync_bs_to_rack(hostname=None):
1846 """
1847 Wait for boot images sync finished, on exact rack
1848
1849 CLI Example:
1850
1851 .. code-block:: bash
1852
1853 salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
1854 """
1855 ret = {}
1856 started_at = time.time()
1857 poll_time = 5
1858 timeout = 60 * 15
1859 while not is_rack_synced(hostname):
1860 c_timeout = timeout - (time.time() - started_at)
1861 if c_timeout <= 0:
1862 ret['result'] = False
1863 ret[
1864 "comment"] = "Boot-resources sync on rackd:{0}" \
1865 "not finished in time".format(
1866 hostname)
1867 return ret
1868 LOG.info(
1869 "Waiting boot-resources sync done to rack:{0}\n"
1870 "sleep for:{1}s "
1871 "Left:{2}/{3}s".format(hostname, poll_time, round(c_timeout),
1872 timeout))
1873 time.sleep(poll_time)
1874 ret['result'] = is_rack_synced(hostname)
1875 ret["comment"] = "Boot-resources sync on rackd:{0} finished".format(
1876 hostname)
1877 return ret
1878
1879# END RACK CONTROLLERS SECTION
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001880# SSHKEYS
1881
1882
1883def list_sshkeys():
1884 """
1885 Get list of all sshkeys
1886
1887 CLI Example:
1888
1889 .. code-block:: bash
1890
1891 salt 'maas-node' maasng.list_sshkeys
1892 salt-call maasng.list_sshkeys
1893 """
1894 ssh = {}
1895 maas = _create_maas_client()
1896 json_res = json.loads(maas.get(u'api/2.0/account/prefs/sshkeys/').read())
1897 LOG.info(json_res)
1898 for item in json_res:
1899 ssh[item["key"]] = item
1900 return ssh
1901
1902
1903def add_sshkey(sshkey):
1904 """
1905 Add SSH key for user to MAAS.
1906
1907 CLI Example:
1908
1909 .. code-block:: bash
1910
1911 salt 'maas-node' maasng.add_sshkey sshkey
1912 salt-call maasng.add_sshkey sshkey
1913 """
1914 data = {
1915 "key": sshkey,
1916 }
1917 result = {}
1918 maas = _create_maas_client()
1919
1920 maas.post(u"/api/2.0/account/prefs/sshkeys/", None, **data).read()
1921 result["new"] = "SSH Key {0} was added.".format(sshkey)
1922
1923 return result
1924
1925
1926def get_sshkey(sshkey):
1927 """
1928 Get start ip for ip range
1929
1930 CLI Example:
1931
1932 .. code-block:: bash
1933
1934 salt 'maas-node' maasng.get_sshkey sshkey
1935 salt-call maasng.get_sshkey sshkey
1936 """
1937 try:
1938 return list_sshkeys()[sshkey]
1939 except KeyError:
1940 return {"error": "SSH key not found on MaaS server"}
1941# END SSHKEYS