blob: 0ea58ae823befb3ae696e73ebd0258003e11c20e [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
22import os.path
23import time
24import urllib2
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020025# Salt utils
Ondrej Smolab57a23b2018-01-24 11:18:24 +010026from salt.exceptions import CommandExecutionError, SaltInvocationError
27
28LOG = logging.getLogger(__name__)
29
30SIZE = {
31 "M": 1000000,
32 "G": 1000000000,
33 "T": 1000000000000,
34}
35
36RAID = {
37 0: "raid-0",
38 1: "raid-1",
39 5: "raid-5",
40 10: "raid-10",
41}
42
43# Import third party libs
44HAS_MASS = False
45try:
46 from maas_client import MAASClient, MAASDispatcher, MAASOAuth
47 HAS_MASS = True
48except ImportError:
49 LOG.debug('Missing MaaS client module is Missing. Skipping')
50
51
52def __virtual__():
azvyagintsevbca1f462018-05-25 19:06:46 +030053 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +010054 Only load this module if maas-client
55 is installed on this minion.
azvyagintsevbca1f462018-05-25 19:06:46 +030056 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +010057 if HAS_MASS:
58 return 'maasng'
59 return False
60
61
62APIKEY_FILE = '/var/lib/maas/.maas_credentials'
63
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020064
Ondrej Smolab57a23b2018-01-24 11:18:24 +010065def _format_data(data):
66 class Lazy:
67 def __str__(self):
68 return ' '.join(['{0}={1}'.format(k, v)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020069 for k, v in data.iteritems()])
Ondrej Smolab57a23b2018-01-24 11:18:24 +010070 return Lazy()
71
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020072
azvyagintsev58947072018-06-29 12:09:48 +030073def _create_maas_client(api_url=None):
74 if not api_url:
75 api_url = 'http://localhost:5240/MAAS'
Ondrej Smolab57a23b2018-01-24 11:18:24 +010076 global APIKEY_FILE
77 try:
78 api_token = file(APIKEY_FILE).read().splitlines()[-1].strip()\
79 .split(':')
80 except:
81 LOG.exception('token')
82 auth = MAASOAuth(*api_token)
Ondrej Smolab57a23b2018-01-24 11:18:24 +010083 dispatcher = MAASDispatcher()
84 return MAASClient(auth, dispatcher, api_url)
85
Ondrej Smolab57a23b2018-01-24 11:18:24 +010086
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020087def _get_blockdevice_id_by_name(hostname, device):
88
89 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +010090 return list_blockdevices(hostname)[device]["id"]
91
Ondrej Smolab57a23b2018-01-24 11:18:24 +010092
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020093def _get_volume_group_id_by_name(hostname, device):
94
95 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +010096 return list_volume_groups(hostname)[device]["id"]
97
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020098
azvyagintsevbca1f462018-05-25 19:06:46 +030099def _get_volume_id_by_name(hostname, volume_name, volume_group, maas_volname=True):
100
101 if not maas_volname:
102 # MAAS-like name
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200103 volume_name = str("%s-%s" % (volume_group, volume_name))
104 # TODO validation
azvyagintsevbca1f462018-05-25 19:06:46 +0300105 return get_volumes(hostname, volume_group)[volume_name]["id"]
106
107
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100108def _get_partition_id_by_name(hostname, device, partition):
109
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200110 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100111 return list_partitions(hostname, device)[partition]["id"]
112
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200113# MACHINE SECTION
114
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100115
116def get_machine(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300117 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100118 Get information aboout specified machine
119
120 CLI Example:
121
122 .. code-block:: bash
123
124 salt-call maasng.get_machine server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300125 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100126 try:
127 return list_machines()[hostname]
128 except KeyError:
129 return {"error": "Machine not found on MaaS server"}
130
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200131
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100132def list_machines():
azvyagintsevbca1f462018-05-25 19:06:46 +0300133 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100134 Get list of all machines from maas server
135
136 CLI Example:
137
138 .. code-block:: bash
139
140 salt 'maas-node' maasng.list_machines
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:
146 machines[item["hostname"]] = item
147 return machines
148
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200149
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100150def create_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200151 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100152
153 return False
154
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200155
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100156def update_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200157 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100158
159 return False
160
azvyagintsevbca1f462018-05-25 19:06:46 +0300161# END MACHINE SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200162# RAID SECTION
163
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100164
165def create_raid(hostname, name, level, disks=[], partitions=[], **kwargs):
azvyagintsevbca1f462018-05-25 19:06:46 +0300166 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100167 Create new raid on machine.
168
169 CLI Example:
170
171 .. code-block:: bash
172
173 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 +0300174 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100175
176 result = {}
177
178 if len(disks) == 0 and len(partitions) == 0:
179 result["error"] = "Disks or partitions need to be provided"
180
181 disk_ids = []
182 partition_ids = []
183
184 for disk in disks:
185 try:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200186 disk_ids.append(str(_get_blockdevice_id_by_name(hostname, disk)))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100187 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200188 result["error"] = "Device {0} does not exists on machine {1}".format(
189 disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100190 return result
191
192 for partition in partitions:
193 try:
194 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200195 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100196 partition_ids.append(str(device_part[partition]["id"]))
197 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200198 result["error"] = "Partition {0} does not exists on machine {1}".format(
199 partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100200 return result
201
202 data = {
203 "name": name,
204 "level": RAID[int(level)],
205 "block_devices": disk_ids,
206 "partitions": partition_ids,
207 }
208
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200209 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100210 system_id = get_machine(hostname)["system_id"]
211 LOG.info(system_id)
212
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200213 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100214 LOG.info(data)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200215 json_res = json.loads(
216 maas.post(u"api/2.0/nodes/{0}/raids/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100217 LOG.info(json_res)
218 result["new"] = "Raid {0} created".format(name)
219
220 return result
221
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200222
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100223def list_raids(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300224 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100225 Get list all raids on machine
226
227 CLI Example:
228
229 .. code-block:: bash
230
231 salt-call maasng.list_raids server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300232 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100233
azvyagintsevbca1f462018-05-25 19:06:46 +0300234 raids = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200235 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100236 system_id = get_machine(hostname)["system_id"]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200237 # TODO validation
238 json_res = json.loads(
239 maas.get(u"api/2.0/nodes/{0}/raids/".format(system_id)).read())
azvyagintsevbca1f462018-05-25 19:06:46 +0300240 LOG.debug('list_raids:{} {}'.format(system_id, json_res))
241 for item in json_res:
242 raids[item["name"]] = item
243 return raids
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100244
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200245
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100246def get_raid(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300247 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100248 Get information about specific raid on machine
249
250 CLI Example:
251
252 .. code-block:: bash
253
254 salt-call maasng.get_raids server_hostname md0
azvyagintsevbca1f462018-05-25 19:06:46 +0300255 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100256
257 return list_raids(hostname)[name]
258
259
azvyagintsevbca1f462018-05-25 19:06:46 +0300260def _get_raid_id_by_name(hostname, raid_name):
261 return get_raid(hostname, raid_name)['id']
262
263
264def delete_raid(hostname, raid_name):
265 """
266 Delete RAID on a machine.
267
268 CLI Example:
269
270 .. code-block:: bash
271
272 salt 'maas-node' maasng.delete_raid server_hostname raid_name
273 salt-call maasng.delete_raid server_hostname raid_name
274 """
275 result = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200276 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +0300277 system_id = get_machine(hostname)["system_id"]
278 raid_id = _get_raid_id_by_name(hostname, raid_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200279 LOG.debug('delete_raid: {} {}'.format(system_id, raid_id))
280 maas.delete(
281 u"api/2.0/nodes/{0}/raid/{1}/".format(system_id, raid_id)).read()
azvyagintsevbca1f462018-05-25 19:06:46 +0300282
283 result["new"] = "Raid {0} deleted".format(raid_name)
284 return result
285
286# END RAID SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200287# BLOCKDEVICES SECTION
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100288
azvyagintsevbca1f462018-05-25 19:06:46 +0300289
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100290def list_blockdevices(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300291 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100292 Get list of all blockdevices (disks) on machine
293
294 CLI Example:
295
296 .. code-block:: bash
297
298 salt 'maas-node' maasng.list_blockdevices server_hostname
299 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300300 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100301 ret = {}
302
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200303 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100304 system_id = get_machine(hostname)["system_id"]
305 LOG.info(system_id)
306
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200307 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100308
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200309 json_res = json.loads(
310 maas.get(u"api/2.0/nodes/{0}/blockdevices/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100311 LOG.info(json_res)
312 for item in json_res:
313 ret[item["name"]] = item
314
315 return ret
316
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200317
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100318def get_blockdevice(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300319 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100320 Get information about blockdevice (disk) on machine
321
322 CLI Example:
323
324 .. code-block:: bash
325
326 salt 'maas-node' maasng.get_blockdevice server_hostname sda
327 salt-call maasng.get_blockdevice server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300328 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100329
330 return list_blockdevices(hostname)[name]
331
azvyagintsevbca1f462018-05-25 19:06:46 +0300332# END BLOCKDEVICES SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200333# PARTITIONS
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100334
azvyagintsevbca1f462018-05-25 19:06:46 +0300335
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100336def list_partitions(hostname, device):
azvyagintsevbca1f462018-05-25 19:06:46 +0300337 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100338 Get list of all partitions on specific device located on specific machine
339
340 CLI Example:
341
342 .. code-block:: bash
343
344 salt 'maas-node' maasng.list_partitions server_hostname sda
345 salt-call maasng.list_partitions server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300346 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100347 ret = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200348 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100349 system_id = get_machine(hostname)["system_id"]
350 LOG.info(system_id)
351
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200352 partitions = get_blockdevice(hostname, device)["partitions"]
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100353 LOG.info(partitions)
354
355 #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 +0200356 # LOG.info(json_res)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100357
358 if len(device) > 0:
359 for item in partitions:
360 name = item["path"].split('/')[-1]
361 ret[name] = item
362
363 return ret
364
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200365
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100366def get_partition(hostname, device, partition):
azvyagintsevbca1f462018-05-25 19:06:46 +0300367 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100368 Get information about specific parition on device located on machine
369
370 CLI Example:
371
372 .. code-block:: bash
373
374 salt 'maas-node' maasng.get_partition server_hostname disk_name partition
375 salt-call maasng.get_partition server_hostname disk_name partition
376
377 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300378 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100379
380 return list_partitions(partition)[name]
381
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200382
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100383def create_partition(hostname, disk, size, fs_type=None, mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300384 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100385 Create new partition on device.
386
387 CLI Example:
388
389 .. code-block:: bash
390
391 salt 'maas-node' maasng.create_partition server_hostname disk_name 10 ext4 "/"
392 salt-call maasng.create_partition server_hostname disk_name 10 ext4 "/"
azvyagintsevbca1f462018-05-25 19:06:46 +0300393 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200394 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100395 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200396 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100397 system_id = get_machine(hostname)["system_id"]
398 LOG.info(system_id)
399
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200400 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100401 LOG.info(device_id)
402
403 value, unit = size[:-1], size[-1]
404 calc_size = str(int(value) * SIZE[unit])
405 LOG.info(calc_size)
406
407 data = {
408 "size": calc_size
409 }
410
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200411 # TODO validation
412 partition = json.loads(maas.post(
413 u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100414 LOG.info(partition)
415 result["partition"] = "Partition created on {0}".format(disk)
416
417 if fs_type != None:
418 data_fs_type = {
419 "fstype": fs_type
420 }
421 partition_id = str(partition["id"])
422 LOG.info("Partition id: " + partition_id)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200423 # TODO validation
424 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
425 system_id, device_id, partition_id), "format", **data_fs_type).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100426 LOG.info(json_res)
427 result["filesystem"] = "Filesystem {0} created".format(fs_type)
428
429 if mount != None:
430 data = {
431 "mount_point": mount
432 }
433
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200434 # TODO validation
435 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
436 system_id, device_id, str(partition['id'])), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100437 LOG.info(json_res)
438 result["mount"] = "Mount point {0} created".format(mount)
439
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100440 return result
441
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200442
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100443def delete_partition(hostname, disk, partition_name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300444 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100445 Delete partition on device.
446
447 CLI Example:
448
449 .. code-block:: bash
450
451 salt 'maas-node' maasng.delete_partition server_hostname disk_name partition_name
452 salt-call maasng.delete_partition server_hostname disk_name partition_name
453
454 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300455 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100456 result = {}
457 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200458 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100459 system_id = get_machine(hostname)["system_id"]
460 LOG.info(system_id)
461
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200462 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100463 LOG.info(device_id)
464
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200465 partition_id = _get_partition_id_by_name(hostname, disk, partition_name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100466
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200467 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
468 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100469 result["new"] = "Partition {0} deleted".format(partition_name)
470 return result
471
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200472
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100473def delete_partition_by_id(hostname, disk, partition_id):
azvyagintsevbca1f462018-05-25 19:06:46 +0300474 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100475 Delete partition on device. Partition spefified by id of parition
476
477 CLI Example:
478
479 .. code-block:: bash
480
481 salt 'maas-node' maasng.delete_partition_by_id server_hostname disk_name partition_id
482 salt-call maasng.delete_partition_by_id server_hostname disk_name partition_id
483
484 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300485 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100486 result = {}
487 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200488 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100489 system_id = get_machine(hostname)["system_id"]
490 LOG.info(system_id)
491
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200492 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100493 LOG.info(device_id)
494
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200495 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
496 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100497 result["new"] = "Partition {0} deleted".format(partition_id)
498 return result
azvyagintsevbca1f462018-05-25 19:06:46 +0300499# END PARTITIONS
500# DISK LAYOUT
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100501
azvyagintsevbca1f462018-05-25 19:06:46 +0300502
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200503def drop_storage_schema(hostname, disk=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300504 """
505 #1. Drop lv
506 #2. Drop vg
507 #3. Drop md # need to zero-block?
508 #3. Drop part
509 """
510
511 if __opts__['test']:
512 ret['result'] = None
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200513 ret['comment'] = 'Storage schema on {0} will be removed'.format(
514 hostname)
azvyagintsevbca1f462018-05-25 19:06:46 +0300515 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200516 # TODO validation if exists
azvyagintsevbca1f462018-05-25 19:06:46 +0300517 vgs = list_volume_groups(hostname)
518 for vg in vgs:
519 delete_volume_group(hostname, vg)
520
521 raids = list_raids(hostname)
522 for raid in raids:
523 delete_raid(hostname, raid)
524
525 blocks = list_blockdevices(hostname)
526 for block_d in blocks:
527 partitions = __salt__['maasng.list_partitions'](hostname, block_d)
528 for partition_name, partition in partitions.iteritems():
529 LOG.info('delete partition:\n{}'.format(partition))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200530 __salt__['maasng.delete_partition_by_id'](
531 hostname, block_d, partition["id"])
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200532
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100533
534def 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 +0300535 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100536 Update disk layout. Flat or LVM layout supported.
537
538 CLI Example:
539
540 .. code-block:: bash
541
542 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
543 salt-call maasng.update_disk_layout server_hostname lvm root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None
544
545 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300546 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100547 result = {}
548 data = {
549 "storage_layout": layout,
550 }
551
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200552 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100553 system_id = get_machine(hostname)["system_id"]
554 LOG.info(system_id)
555
azvyagintsevbca1f462018-05-25 19:06:46 +0300556 if layout == 'custom':
557 drop_storage_schema(hostname)
558 result["new"] = {
559 "storage_layout": layout,
560 }
561
562 return result
563
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100564 if root_size != None:
565 bit_size = str(root_size * 1073741824)
566 LOG.info(bit_size)
567 data["root_size"] = bit_size
568
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100569 if root_device != None:
570 LOG.info(root_device)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200571 data["root_device"] = str(
572 _get_blockdevice_id_by_name(hostname, root_device))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100573
574 if layout == 'lvm':
575 if volume_group != None:
576 LOG.info(volume_group)
577 data["vg_name"] = volume_group
578 if volume_name != None:
579 LOG.info(volume_name)
580 data["lv_name"] = volume_name
581 if volume_size != None:
582 vol_size = str(volume_size * 1073741824)
583 LOG.info(vol_size)
584 data["lv_size"] = vol_size
585
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200586 # TODO validation
587 json_res = json.loads(maas.post(
588 u"api/2.0/machines/{0}/".format(system_id), "set_storage_layout", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100589 LOG.info(json_res)
590 result["new"] = {
591 "storage_layout": layout,
592 }
593
594 return result
595
azvyagintsevbca1f462018-05-25 19:06:46 +0300596# END DISK LAYOUT
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200597# LVM
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100598
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200599
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100600def list_volume_groups(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300601 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100602 Get list of all volume group on machine.
603
604 CLI Example:
605
606 .. code-block:: bash
607
608 salt 'maas-node' maasng.list_volume_groups server_hostname
609 salt-call maasng.list_volume_groups server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300610 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100611 volume_groups = {}
612
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200613 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100614 system_id = get_machine(hostname)["system_id"]
615 LOG.info(system_id)
616
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200617 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100618
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200619 json_res = json.loads(
620 maas.get(u"api/2.0/nodes/{0}/volume-groups/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100621 LOG.info(json_res)
622 for item in json_res:
623 volume_groups[item["name"]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200624 # return
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100625 return volume_groups
626
627
628def get_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300629 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100630 Get information about specific volume group on machine.
631
632 CLI Example:
633
634 .. code-block:: bash
635
636 salt 'maas-node' maasng.list_blockdevices server_hostname
637 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300638 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200639 # TODO validation that exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100640 return list_volume_groups(hostname)[name]
641
642
643def create_volume_group(hostname, volume_group_name, disks=[], partitions=[]):
azvyagintsevbca1f462018-05-25 19:06:46 +0300644 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100645 Create new volume group on machine. Disks or partitions needs to be provided.
646
647 CLI Example:
648
649 .. code-block:: bash
650
651 salt 'maas-node' maasng.create_volume_group volume_group_name, disks=[sda,sdb], partitions=[]
652 salt-call maasng.create_volume_group server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300653 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100654 result = {}
655
656 data = {
657 "name": volume_group_name,
658 }
659
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200660 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100661 system_id = get_machine(hostname)["system_id"]
662 LOG.info(system_id)
663
664 disk_ids = []
665 partition_ids = []
666
667 for disk in disks:
668 p_disk = get_blockdevice(hostname, disk)
669 if p_disk["partition_table_type"] == None:
670 disk_ids.append(str(p_disk["id"]))
671 else:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200672 result["error"] = "Device {0} on machine {1} cointains partition table".format(
673 disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100674 return result
675
676 for partition in partitions:
677 try:
678 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200679 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100680 partition_ids.append(str(device_part[partition]["id"]))
681 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200682 result["error"] = "Partition {0} does not exists on machine {1}".format(
683 partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100684 return result
685
686 data["block_devices"] = disk_ids
687 data["partitions"] = partition_ids
688 LOG.info(partition_ids)
689 LOG.info(partitions)
690
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200691 # TODO validation
692 json_res = json.loads(maas.post(
693 u"api/2.0/nodes/{0}/volume-groups/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100694 LOG.info(json_res)
695 result["new"] = "Volume group {0} created".format(json_res["name"])
696
697 return result
698
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200699
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100700def delete_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300701 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100702 Delete volume group on machine.
703
704 CLI Example:
705
706 .. code-block:: bash
707
708 salt 'maas-node' maasng.delete_volume_group server_hostname vg0
709 salt-call maasng.delete_volume_group server_hostname vg0
azvyagintsevbca1f462018-05-25 19:06:46 +0300710 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100711
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200712 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100713 system_id = get_machine(hostname)["system_id"]
azvyagintsevbca1f462018-05-25 19:06:46 +0300714 LOG.debug('delete_volume_group:{}'.format(system_id))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100715
azvyagintsevbca1f462018-05-25 19:06:46 +0300716 vg_id = str(_get_volume_group_id_by_name(hostname, name))
717 for vol in get_volumes(hostname, name):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200718 delete_volume(hostname, vol, name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100719
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200720 # TODO validation
721 json_res = json.loads(maas.delete(
722 u"api/2.0/nodes/{0}/volume-group/{1}/".format(system_id, vg_id)).read() or 'null')
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100723 LOG.info(json_res)
724
725 return True
726
727
728def create_volume(hostname, volume_name, volume_group, size, fs_type=None, mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300729 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100730 Create volume on volume group.
731
732 CLI Example:
733
734 .. code-block:: bash
735
736 salt 'maas-node' maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
737 salt-call maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
azvyagintsevbca1f462018-05-25 19:06:46 +0300738 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100739
740 data = {
741 "name": volume_name,
742 }
743
744 value, unit = size[:-1], size[-1]
745 bit_size = str(int(value) * SIZE[unit])
746 LOG.info(bit_size)
747
748 data["size"] = bit_size
749
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200750 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100751 system_id = get_machine(hostname)["system_id"]
752 LOG.info(system_id)
753
754 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
755
756 LOG.info(volume_group_id)
757
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200758 # TODO validation
759 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
760 system_id, volume_group_id), "create_logical_volume", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100761 LOG.info(json_res)
762
763 if fs_type != None or mount != None:
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200764 ret = create_volume_filesystem(
765 hostname, volume_group + "-" + volume_name, fs_type, mount)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100766
767 return True
768
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100769
azvyagintsevbca1f462018-05-25 19:06:46 +0300770def delete_volume(hostname, volume_name, volume_group):
771 """
772 Delete volume from volume group.
773 Tips: maas always use 'volume_group-volume_name' name schema.Example: 'vg0-glusterfs'
774 This function expexts same format.
775
776 CLI Example:
777
778 .. code-block:: bash
779
780 salt 'maas-node' maasng.delete_volume server_hostname volume_name volume_group
781 salt 'maas-node' maasng.delete_volume server_hostname vg0-vol0 vg0
782 salt-call maasng.delete_volume server_hostname volume_name volume_group
783 """
784
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200785 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +0300786 system_id = get_machine(hostname)["system_id"]
787 LOG.debug('delete_volume:{}'.format(system_id))
788
789 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200790 volume_id = str(_get_volume_id_by_name(
791 hostname, volume_name, volume_group))
azvyagintsevbca1f462018-05-25 19:06:46 +0300792
793 if None in [volume_group_id, volume_id]:
794 return False
795
796 data = {
797 "id": volume_id,
798 }
799
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200800 # TODO validation
801 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
802 system_id, volume_group_id), "delete_logical_volume", **data).read() or 'null')
azvyagintsevbca1f462018-05-25 19:06:46 +0300803 return True
804
805
806def get_volumes(hostname, vg_name):
807 """
808 Get list of volumes in volume group.
809 """
810 volumes = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200811 _volumes = list_volume_groups(
812 hostname)[vg_name].get('logical_volumes', False)
azvyagintsevbca1f462018-05-25 19:06:46 +0300813 if _volumes:
814 for item in _volumes:
815 volumes[item["name"]] = item
816 return volumes
817
818# END LVM
819
820
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200821def create_volume_filesystem(hostname, device, fs_type=None, mount=None):
822
823 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100824 system_id = get_machine(hostname)["system_id"]
825
826 blockdevices_id = _get_blockdevice_id_by_name(hostname, device)
827 data = {}
828 if fs_type != None:
829 data["fstype"] = fs_type
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200830 # TODO validation
831 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
832 system_id, blockdevices_id), "format", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100833 LOG.info(json_res)
834
835 if mount != None:
836 data["mount_point"] = mount
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200837 # TODO validation
838 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
839 system_id, blockdevices_id), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100840 LOG.info(json_res)
841
842 return True
843
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100844
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100845def set_boot_disk(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300846 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100847 Create volume on volume group.
848
849 CLI Example:
850
851 .. code-block:: bash
852
853 salt 'maas-node' maasng.set_boot_disk server_hostname disk_name
854 salt-call maasng.set_boot_disk server_hostname disk_name
azvyagintsevbca1f462018-05-25 19:06:46 +0300855 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100856 data = {}
857 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200858 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100859 system_id = get_machine(hostname)["system_id"]
860 blockdevices_id = _get_blockdevice_id_by_name(hostname, name)
861
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200862 maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
863 system_id, blockdevices_id), "set_boot_disk", **data).read()
864 # TODO validation for error response (disk does not exists and node does not exists)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100865 result["new"] = "Disk {0} was set as bootable".format(name)
866
867 return result
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200868
azvyagintsevbca1f462018-05-25 19:06:46 +0300869# NETWORKING
870
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200871
872def list_fabric():
azvyagintsevbca1f462018-05-25 19:06:46 +0300873 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200874 Get list of all fabric
875
876 CLI Example:
877
878 .. code-block:: bash
879
880 salt 'maas-node' maasng.list_fabric
azvyagintsevbca1f462018-05-25 19:06:46 +0300881 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200882 fabrics = {}
883 maas = _create_maas_client()
884 json_res = json.loads(maas.get(u'api/2.0/fabrics/').read())
885 LOG.info(json_res)
886 for item in json_res:
887 fabrics[item["name"]] = item
888 return fabrics
889
890
891def create_fabric(name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300892 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200893 Create new fabric.
894
895 CLI Example:
896
897 .. code-block:: bash
898
899 salt 'maas-node' maasng.create_fabric
azvyagintsevbca1f462018-05-25 19:06:46 +0300900 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200901 result = {}
902 data = {
903 "name": name,
904 "description": '',
905 "class_type": '',
906
907 }
908
909 maas = _create_maas_client()
910 json_res = json.loads(maas.post(u"api/2.0/fabrics/", None, **data).read())
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200911 LOG.debug("crete_fabric:{}".format(json_res))
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200912 result["new"] = "Fabrics {0} created".format(json_res["name"])
913 return result
914
915
916def list_subnet():
azvyagintsevbca1f462018-05-25 19:06:46 +0300917 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200918 Get list of all subnets
919
920 CLI Example:
921
922 .. code-block:: bash
923
924 salt 'maas-node' maasng.list_subnet
azvyagintsevbca1f462018-05-25 19:06:46 +0300925 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200926 subnets = {}
927 maas = _create_maas_client()
928 json_res = json.loads(maas.get(u'api/2.0/subnets/').read())
929 LOG.info(json_res)
930 for item in json_res:
931 subnets[item["name"]] = item
932 return subnets
933
934
935def list_vlans(fabric):
azvyagintsevbca1f462018-05-25 19:06:46 +0300936 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200937 Get list of all vlans for specific fabric
938
939 CLI Example:
940
941 .. code-block:: bash
942
943 salt 'maas-node' maasng.list_vlans
azvyagintsevbca1f462018-05-25 19:06:46 +0300944 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200945 vlans = {}
946 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200947 fabric_id = get_fabricid(fabric)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200948
949 json_res = json.loads(
950 maas.get(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id)).read())
951 LOG.info(json_res)
952 for item in json_res:
953 vlans[item["name"]] = item
954 return vlans
955
956
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200957def get_fabricid(fabric):
azvyagintsevbca1f462018-05-25 19:06:46 +0300958 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200959 Get id for specific fabric
960
961 CLI Example:
962
963 .. code-block:: bash
964
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200965 salt 'maas-node' maasng.get_fabricid fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +0300966 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200967 try:
968 return list_fabric()[fabric]['id']
969 except KeyError:
970 return {"error": "Frabic not found on MaaS server"}
971
972
Pavel Cizinsky864a3292018-05-25 16:24:48 +0200973def update_vlan(name, fabric, vid, description, primary_rack, dhcp_on=False):
azvyagintsevbca1f462018-05-25 19:06:46 +0300974 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200975 Update vlan
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200976 CLI Example:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200977 .. code-block:: bash
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200978 salt 'maas-node' maasng.update_vlan name, fabric, vid, description, dhcp_on
azvyagintsevbca1f462018-05-25 19:06:46 +0300979 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200980 result = {}
981
982 data = {
983 "name": name,
984 "dhcp_on": str(dhcp_on),
985 "description": description,
Pavel Cizinsky864a3292018-05-25 16:24:48 +0200986 "primary_rack": primary_rack,
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200987 }
988 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200989 fabric_id = get_fabricid(fabric)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200990
991 json_res = json.loads(maas.put(
992 u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id, vid), **data).read())
azvyagintsev3ff2ef12018-06-01 21:30:45 +0300993 LOG.debug("update_vlan:{}".format(json_res))
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200994 result["new"] = "Vlan {0} was updated".format(json_res["name"])
995
996 return result
azvyagintsevbca1f462018-05-25 19:06:46 +0300997
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200998
999def list_subnets():
1000 """
1001 Get list of subnet from maas server
1002
1003 CLI Example:
1004
1005 .. code-block:: bash
1006
1007 salt 'maas-node' maasng.list_subnet
1008 """
1009 subnets = {}
1010 maas = _create_maas_client()
1011 json_res = json.loads(maas.get(u'api/2.0/subnets/').read())
1012 for item in json_res:
1013 subnets[item["name"]] = item
1014 return subnets
1015
1016
1017def create_subnet(cidr, name, fabric, gateway_ip):
1018 """
1019 Create subnet
1020
1021 CLI Example:
1022
1023 .. code-block:: bash
1024
1025 salt 'maas-node' maasng.create_subnet cidr, name, fabric, gateway_ip
1026 """
1027
1028 fabric_id = get_fabricid(fabric)
1029 result = {}
1030
1031 data = {
1032 "cidr": cidr,
1033 "name": name,
1034 "fabric": str(fabric_id),
1035 "gateway_ip": gateway_ip,
1036 }
1037 maas = _create_maas_client()
1038
1039 json_res = json.loads(maas.post(u"api/2.0/subnets/", None, **data).read())
1040 LOG.debug("create_subnet:{}".format(json_res))
1041 result["new"] = "Subnet {0} with CIDR {1} and gateway {2} was created".format(
1042 name, cidr, gateway_ip)
1043
1044 return result
1045
1046
1047def get_subnet(subnet):
1048 """
1049 Get details for specific subnet
1050
1051 CLI Example:
1052
1053 .. code-block:: bash
1054
1055 salt 'maas-node' maasng.get_subnet subnet_name
1056 """
1057 try:
1058 return list_subnet()[subnet]
1059 except KeyError:
1060 return {"error": "Subnet not found on MaaS server"}
1061
1062
1063def get_subnetid(subnet):
1064 """
1065 Get id for specific subnet
1066
1067 CLI Example:
1068
1069 .. code-block:: bash
1070
1071 salt 'maas-node' maasng.get_subnetid subnet_name
1072 """
1073 try:
1074 return list_subnet()[subnet]['id']
1075 except KeyError:
1076 return {"error": "Subnet not found on MaaS server"}
1077
1078
1079def list_ipranges():
1080 """
1081 Get list of all ipranges from maas server
1082
1083 CLI Example:
1084
1085 .. code-block:: bash
1086
1087 salt 'maas-node' maasng.list_ipranges
1088 """
1089 ipranges = {}
1090 maas = _create_maas_client()
1091 json_res = json.loads(maas.get(u'api/2.0/ipranges/').read())
1092 for item in json_res:
1093 ipranges[item["start_ip"]] = item
1094 return ipranges
1095
1096
1097def create_iprange(type_range, start_ip, end_ip, comment):
1098 """
1099 Create ip range
1100
1101 CLI Example:
1102
1103 .. code-block:: bash
1104
1105 salt 'maas-node' maasng.create_iprange type, start ip, end ip, comment
1106 """
1107 result = {}
1108
1109 data = {
1110 "type": type_range,
1111 "start_ip": start_ip,
1112 "end_ip": end_ip,
1113 "comment": comment,
1114 }
1115 maas = _create_maas_client()
1116
1117 json_res = json.loads(maas.post(u"api/2.0/ipranges/", None, **data).read())
1118
1119 LOG.debug("create_iprange:{}".format(json_res))
1120 result["new"] = "Iprange with type {0}, start ip {1}, end ip {2}, was created".format(
1121 type_range, start_ip, end_ip)
1122
1123 return result
1124
1125
1126def get_iprangeid(start_ip):
1127 """
1128 Get id for ip range from maas server
1129
1130 CLI Example:
1131
1132 .. code-block:: bash
1133
1134 salt 'maas-node' maasng.get_iprangeid start_ip
1135 """
1136 try:
1137 return list_ipranges()[start_ip]['id']
1138 except KeyError:
1139 return {"error": "Ip range not found on MaaS server"}
1140
1141
1142def get_startip(start_ip):
1143 """
1144 Get start ip for ip range
1145
1146 CLI Example:
1147
1148 .. code-block:: bash
1149
1150 salt 'maas-node' maasng.get_startip start ip
1151 """
1152 try:
1153 return list_ipranges()[start_ip]
1154 except KeyError:
1155 return {"error": "Ip range not found on MaaS server"}
azvyagintsevbca1f462018-05-25 19:06:46 +03001156# END NETWORKING
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001157
1158# MAAS CONFIG SECTION
1159
1160
azvyagintsev58947072018-06-29 12:09:48 +03001161def _getHTTPCode(url):
1162 code = 404
1163 try:
1164 connection = urllib2.urlopen(url)
1165 code = connection.getcode()
1166 connection.close()
1167 except urllib2.HTTPError as e:
1168 code = e.getcode()
1169 LOG.warning("Catch http code:{} from url:{}".format(code, url))
1170 pass
1171 return code
1172
1173
1174def wait_for_http_code(url=None, expected=[200]):
1175 """
1176 Simple function, which just wait for avaible api, aka wait for 200.
1177
1178 CLI Example:
1179
1180 .. code-block:: bash
1181
1182 salt 'maas-node' maasng.wait_for_http_code url expected=[200]
1183
1184 """
1185 ret = {}
1186 started_at = time.time()
1187 poll_time = 5
1188 timeout = 60 * 2
1189 while _getHTTPCode(url) not in expected:
1190 c_timeout = timeout - (time.time() - started_at)
1191 if c_timeout <= 0:
1192 ret['result'] = False
1193 ret["comment"] = "api:{} not answered up in time".format(url)
1194 return ret
1195 LOG.info(
1196 "Waiting for api:{0}\n"
1197 "sleep for:{1}s "
1198 "Left:{2}/{3}s".format(url, poll_time, round(c_timeout),
1199 timeout))
1200 time.sleep(poll_time)
1201 ret['result'] = True
1202 ret["comment"] = "MAAS API:{} up.".format(url)
1203 return ret
1204
1205
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001206def _get_boot_source_id_by_url(url):
1207 # FIXME: fix ret\validation
1208 try:
1209 bs_id = get_boot_source(url=url)["id"]
1210 except KeyError:
1211 return {"error": "boot-source:{0} not exist!".format(url)}
1212 return bs_id
1213
1214
1215def get_boot_source(url=None):
1216 """
1217 Read a boot source by url. If url not specified - return all.
1218
1219 CLI Example:
1220
1221 .. code-block:: bash
1222
1223 salt 'maas-node' maasng.get_boot_source url
1224
1225 """
1226 boot_sources = {}
1227 maas = _create_maas_client()
1228 json_res = json.loads(maas.get(u'api/2.0/boot-sources/').read() or 'null')
1229 for item in json_res:
1230 boot_sources[str(item["url"])] = item
1231 if url:
1232 return boot_sources.get(url, {})
1233 return boot_sources
1234
1235
1236def delete_boot_source(url, bs_id=None):
1237 """
1238 Delete a boot source by url.
1239
1240 CLI Example:
1241
1242 .. code-block:: bash
1243
1244 sal 'maas-node' maasng.delete url
1245
1246 """
1247 result = {}
1248 if not bs_id:
1249 bs_id = _get_boot_source_id_by_url(url)
1250 maas = _create_maas_client()
1251 json_res = json.loads(maas.delete(
1252 u'/api/2.0/boot-sources/{0}/'.format(bs_id)).read() or 'null')
1253 LOG.debug("delete_boot_source:{}".format(json_res))
1254 result["new"] = "Boot-resource {0} deleted".format(url)
1255 return result
1256
1257
1258def boot_sources_delete_all_others(except_urls=[]):
1259 """
1260 Delete all boot-sources, except defined in 'except_urls' list.
1261 """
1262 result = {}
1263 maas_boot_sources = get_boot_source()
1264 if 0 in [len(except_urls), len(maas_boot_sources)]:
1265 result['result'] = None
1266 result[
1267 "comment"] = "Exclude or maas sources for delete empty. No changes goinng to be."
1268 return result
1269 for url in maas_boot_sources.keys():
1270 if url not in except_urls:
1271 LOG.info("Removing boot-source:{}".format(url))
1272 boot_resources_import(action='stop_import', wait=True)
1273 result["changes"] = delete_boot_source(url)
1274 return result
1275
1276
1277def create_boot_source(url, keyring_filename='', keyring_data='', wait=False):
1278 """
1279 Create and import maas boot-source: link to maas-ephemeral repo
1280 Be aware, those step will import resource to rack ctrl, but you also need to import
1281 them into the region!
1282
1283
1284 :param url: The URL of the BootSource.
1285 :param keyring_filename: The path to the keyring file for this BootSource.
1286 :param keyring_data: The GPG keyring for this BootSource, base64-encoded data.
1287
1288 """
1289
1290 # TODO: not work with 'update' currently => keyring update may fail.
1291 result = {}
1292
1293 data = {
1294 "url": url,
1295 "keyring_filename": keyring_filename,
1296 "keyring_data": str(keyring_data),
1297 }
1298
1299 maas = _create_maas_client()
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001300 if url in get_boot_source():
1301 result['result'] = None
1302 result["comment"] = "boot resource already exist"
1303 return result
1304
1305 # NOTE: maas.post will return 400, if url already defined.
1306 json_res = json.loads(
1307 maas.post(u'api/2.0/boot-sources/', None, **data).read())
1308 if wait:
1309 LOG.debug(
1310 "Sleep for 5s,to get MaaS some time to process previous request")
1311 time.sleep(5)
1312 ret = boot_resources_is_importing(wait=True)
1313 if ret is dict:
1314 return ret
1315 LOG.debug("create_boot_source:{}".format(json_res))
1316 result["new"] = "boot resource {0} was created".format(json_res["url"])
1317
1318 return result
1319
1320
1321def boot_resources_import(action='import', wait=False):
1322 """
1323 import/stop_import the boot resources.
1324
1325 :param action: import\stop_import
1326 :param wait: True\False. Wait till process finished.
1327
1328 CLI Example:
1329
1330 .. code-block:: bash
1331
1332 salt 'maas-node' maasng.boot_resources_import action='import'
1333
1334 """
1335 maas = _create_maas_client()
1336 # Have no idea why, but usual jsonloads not work here..
1337 imp = maas.post(u'api/2.0/boot-resources/', action)
1338 if imp.code == 200:
1339 LOG.debug('boot_resources_import:{}'.format(imp.readline()))
1340 if wait:
1341 boot_resources_is_importing(wait=True)
1342 return True
1343 else:
1344 return False
1345
1346
1347def boot_resources_is_importing(wait=False):
1348 maas = _create_maas_client()
1349 result = {}
1350 if wait:
1351 started_at = time.time()
1352 poll_time = 5
1353 timeout = 60 * 15
1354 while boot_resources_is_importing(wait=False):
1355 c_timeout = timeout - (time.time() - started_at)
1356 if c_timeout <= 0:
1357 result['result'] = False
1358 result["comment"] = "Boot-resources import not finished in time"
1359 return result
1360 LOG.info(
1361 "Waiting boot-resources import done\n"
1362 "sleep for:{}s "
1363 "Left:{}/{}s".format(poll_time, round(c_timeout), timeout))
1364 time.sleep(poll_time)
1365 return json.loads(
1366 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1367 else:
1368 return json.loads(
1369 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1370
1371#####
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001372# def boot_sources_selections_delete_all_others(except_urls=[]):
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001373# """
1374# """
1375# result = {}
1376# return result
1377
1378
1379def is_boot_source_selections_in(dict1, list1):
1380 """
1381 Check that requested boot-selection already in maas bs selections, if True- return bss id.
1382 # FIXME: those hack check doesn't look good.
1383 """
1384 for bs in list1:
1385 same = set(dict1.keys()) & set(bs.keys())
1386 if all(elem in same for elem in
1387 ['os', 'release', 'arches', 'subarches', 'labels']):
1388 LOG.debug(
1389 "boot-selection in maas:{0}\nlooks same to requested:{1}".format(
1390 bs, dict1))
1391 return bs['id']
1392 return False
1393
1394
1395def get_boot_source_selections(bs_url):
1396 """
1397 Get boot-source selections.
1398 """
1399 # check for key_error!
1400 bs_id = _get_boot_source_id_by_url(bs_url)
1401 maas = _create_maas_client()
1402 json_res = json.loads(
1403 maas.get(u'/api/2.0/boot-sources/{0}/selections/'.format(bs_id)).read())
1404 LOG.debug(
1405 "get_boot_source_selections for url:{} \n{}".format(bs_url, json_res))
1406 return json_res
1407
1408
1409def create_boot_source_selections(bs_url, os, release, arches="*",
1410 subarches="*", labels="*", wait=True):
1411 """
1412 Create a new boot source selection for bs_url.
1413 :param os: The OS (e.g. ubuntu, centos) for which to import resources.Required.
1414 :param release: The release for which to import resources. Required.
1415 :param arches: The architecture list for which to import resources.
1416 :param subarches: The subarchitecture list for which to import resources.
1417 :param labels: The label lists for which to import resources.
1418 """
1419
azvyagintsevcb54d142018-06-19 16:18:32 +03001420 result = { "result" : True, 'name' : bs_url, 'changes' : None }
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001421
1422 data = {
1423 "os": os,
1424 "release": release,
1425 "arches": arches,
1426 "subarches": subarches,
1427 "labels": labels,
1428 }
1429
1430 maas = _create_maas_client()
1431 bs_id = _get_boot_source_id_by_url(bs_url)
1432 # TODO add pre-create verify
1433 maas_bs_s = get_boot_source_selections(bs_url)
1434 if is_boot_source_selections_in(data, maas_bs_s):
1435 result["result"] = True
1436 result[
1437 "comment"] = 'Requested boot-source selection for {0} already exist.'.format(
1438 bs_url)
1439 return result
1440
1441 # NOTE: maas.post will return 400, if url already defined.
azvyagintsevcb54d142018-06-19 16:18:32 +03001442 # Also, maas need's some time to import info about stream.
1443 # unfortunatly, maas don't have any call to check stream-import-info - so, we need to implement
1444 # at least simple retry ;(
1445 json_res = False
1446 poll_time = 5
1447 for i in range(0,5):
1448 try:
1449 json_res = json.loads(
1450 maas.post(u'api/2.0/boot-sources/{0}/selections/'.format(bs_id), None,
1451 **data).read())
1452 except Exception as inst:
1453 m = inst.readlines()
1454 LOG.warning("boot_source_selections catch error during processing. Most-probably, streams not imported yet.Sleep:{}s\nRetry:{}/5".format(poll_time,i))
1455 LOG.warning("Message:{0}".format(m))
1456 time.sleep(poll_time)
1457 continue
1458 break
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001459 LOG.debug("create_boot_source_selections:{}".format(json_res))
azvyagintsevcb54d142018-06-19 16:18:32 +03001460 if not json_res:
1461 result["result"] = False
1462 result[
1463 "comment"] = 'Failed to create requested boot-source selection for {0}.'.format(bs_url)
1464 return result
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001465 if wait:
1466 LOG.debug(
1467 "Sleep for 5s,to get MaaS some time to process previous request")
1468 time.sleep(5)
1469 ret = boot_resources_import(action='import', wait=True)
1470 if ret is dict:
1471 return ret
azvyagintsevcb54d142018-06-19 16:18:32 +03001472 result["comment"] = "boot-source selection for {0} was created".format(bs_url)
1473 result["new"] = data
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001474
1475 return result
1476
1477# END MAAS CONFIG SECTION
azvyagintsevcb54d142018-06-19 16:18:32 +03001478
1479# RACK CONTROLLERS SECTION
1480
1481
1482def get_rack(hostname):
1483 """
1484 Get information about specified rackd
1485
1486 CLI Example:
1487
1488 .. code-block:: bash
1489
1490 salt-call maasng.get_rack rack_hostname
1491 """
1492 try:
1493 return list_racks()[hostname]
1494 except KeyError:
1495 return {"error": "rack:{} not found on MaaS server".format(hostname)}
1496
1497
1498def list_racks():
1499 """
1500 Get list of all rack controllers from maas server
1501
1502 CLI Example:
1503
1504 .. code-block:: bash
1505
1506 salt-call maasng.list_racks
1507 """
1508 racks = {}
1509 maas = _create_maas_client()
1510 json_res = json.loads(
1511 maas.get(u"/api/2.0/rackcontrollers/").read() or 'null')
1512 for item in json_res:
1513 racks[item["hostname"]] = item
1514 return racks
1515
1516
1517def sync_bs_to_rack(hostname=None):
1518 """
1519 Sync RACK boot-sources with REGION. If no hostname probided - sync to all.
1520
1521 CLI Example:
1522
1523 .. code-block:: bash
1524
1525 salt-call maasng.sync_bs_to_rack rack_hostname
1526 """
1527 ret = {}
1528 maas = _create_maas_client()
1529 if not hostname:
1530 LOG.info("boot-sources sync initiated for ALL Rack's")
1531 # Convert to json-like format
1532 json_res = json.loads('["{0}"]'.format(
1533 maas.post(u"/api/2.0/rackcontrollers/",
1534 'import_boot_images').read()))
1535 LOG.debug("sync_bs_to_rack:{}".format(json_res))
1536 ret['result'] = True
1537 ret['comment'] = "boot-sources sync initiated for ALL Rack's"
1538 return ret
1539 LOG.info("boot-sources sync initiated for RACK:{0}".format(hostname))
1540 # Convert to json-like format
1541 json_res = json.loads('["{0}"]'.format(maas.post(
1542 u"/api/2.0/rackcontrollers/{0}/".format(
1543 get_rack(hostname)['system_id']),
1544 'import_boot_images').read()))
1545 LOG.debug("sync_bs_to_rack:{}".format(json_res))
1546 ret['result'] = True
1547 ret['comment'] = "boot-sources sync initiated for {0} Rack's".format(
1548 hostname)
1549 return
1550
1551
1552def rack_list_boot_imgs(hostname):
1553 ret = {}
1554 maas = _create_maas_client()
1555 LOG.debug("rack_list_boot_imgs:{}".format(hostname))
1556 ret = json.loads(maas.get(u"/api/2.0/rackcontrollers/{0}/".format(
1557 get_rack(hostname)['system_id']), 'list_boot_images').read() or 'null')
1558 return ret
1559
1560
1561def is_rack_synced(hostname):
1562 rez = rack_list_boot_imgs(hostname)['status']
1563 if rez == 'synced':
1564 return True
1565 return False
1566
1567# TODO do we actually need _exact_ check per-pack?
1568# def wait_for_images_on_rack(hostname):
1569#
1570# """
1571# WA function, to be able check that RACK actually done SYNC images
1572# for REQUIRED images at least.
1573# Required image to be fetched from
1574# reclass:maas:region:boot_sources_selections:[keys]:os/release formation
1575#
1576# CLI Example:
1577#
1578# .. code-block:: bash
1579#
1580# salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
1581# """
1582# try:
1583# bss = __salt__['config.get']('maas')['region']['boot_sources_selections']
1584# except KeyError:
1585# ret['result'] = None
1586# ret['comment'] = "boot_sources_selections definition for sync not found."
1587# return ret
1588# s_names = []
1589# # Format u'name': u'ubuntu/xenial'
1590# for v in bss.values():s_names.append("{0}/{1}".format(v['os'],v['release']))
1591# # Each names, should be in rack and whole rack should be in sync-ed state
1592
1593
1594def sync_and_wait_bs_to_all_racks():
1595 """
1596 Sync ALL rack's with regions source images.
1597
1598 CLI Example:
1599
1600 .. code-block:: bash
1601
1602 salt-call maasng.sync_and_wait_bs_to_all_racks
1603 """
1604 sync_bs_to_rack()
1605 for rack in list_racks().keys():
1606 wait_for_sync_bs_to_rack(hostname=rack)
1607 return True
1608
1609
1610def wait_for_sync_bs_to_rack(hostname=None):
1611 """
1612 Wait for boot images sync finished, on exact rack
1613
1614 CLI Example:
1615
1616 .. code-block:: bash
1617
1618 salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
1619 """
1620 ret = {}
1621 started_at = time.time()
1622 poll_time = 5
1623 timeout = 60 * 15
1624 while not is_rack_synced(hostname):
1625 c_timeout = timeout - (time.time() - started_at)
1626 if c_timeout <= 0:
1627 ret['result'] = False
1628 ret[
1629 "comment"] = "Boot-resources sync on rackd:{0}" \
1630 "not finished in time".format(
1631 hostname)
1632 return ret
1633 LOG.info(
1634 "Waiting boot-resources sync done to rack:{0}\n"
1635 "sleep for:{1}s "
1636 "Left:{2}/{3}s".format(hostname, poll_time, round(c_timeout),
1637 timeout))
1638 time.sleep(poll_time)
1639 ret['result'] = is_rack_synced(hostname)
1640 ret["comment"] = "Boot-resources sync on rackd:{0} finished".format(
1641 hostname)
1642 return ret
1643
1644# END RACK CONTROLLERS SECTION