blob: 65c5a0f70425a5b973271f48be6e3623d697236c [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
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100131def list_machines():
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
azvyagintsevbca1f462018-05-25 19:06:46 +0300140 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200141 machines = {}
142 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100143 json_res = json.loads(maas.get(u'api/2.0/machines/').read())
144 for item in json_res:
145 machines[item["hostname"]] = item
146 return machines
147
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200148
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100149def create_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200150 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100151
152 return False
153
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200154
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100155def update_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200156 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100157
158 return False
159
azvyagintsevbca1f462018-05-25 19:06:46 +0300160# END MACHINE SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200161# RAID SECTION
162
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100163
164def create_raid(hostname, name, level, disks=[], partitions=[], **kwargs):
azvyagintsevbca1f462018-05-25 19:06:46 +0300165 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100166 Create new raid on machine.
167
168 CLI Example:
169
170 .. code-block:: bash
171
172 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 +0300173 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100174
175 result = {}
176
177 if len(disks) == 0 and len(partitions) == 0:
178 result["error"] = "Disks or partitions need to be provided"
179
180 disk_ids = []
181 partition_ids = []
182
183 for disk in disks:
184 try:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200185 disk_ids.append(str(_get_blockdevice_id_by_name(hostname, disk)))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100186 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200187 result["error"] = "Device {0} does not exists on machine {1}".format(
188 disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100189 return result
190
191 for partition in partitions:
192 try:
193 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200194 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100195 partition_ids.append(str(device_part[partition]["id"]))
196 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200197 result["error"] = "Partition {0} does not exists on machine {1}".format(
198 partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100199 return result
200
201 data = {
202 "name": name,
203 "level": RAID[int(level)],
204 "block_devices": disk_ids,
205 "partitions": partition_ids,
206 }
207
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200208 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100209 system_id = get_machine(hostname)["system_id"]
210 LOG.info(system_id)
211
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200212 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100213 LOG.info(data)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200214 json_res = json.loads(
215 maas.post(u"api/2.0/nodes/{0}/raids/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100216 LOG.info(json_res)
217 result["new"] = "Raid {0} created".format(name)
218
219 return result
220
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200221
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100222def list_raids(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300223 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100224 Get list all raids on machine
225
226 CLI Example:
227
228 .. code-block:: bash
229
230 salt-call maasng.list_raids server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300231 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100232
azvyagintsevbca1f462018-05-25 19:06:46 +0300233 raids = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200234 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100235 system_id = get_machine(hostname)["system_id"]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200236 # TODO validation
237 json_res = json.loads(
238 maas.get(u"api/2.0/nodes/{0}/raids/".format(system_id)).read())
azvyagintsevbca1f462018-05-25 19:06:46 +0300239 LOG.debug('list_raids:{} {}'.format(system_id, json_res))
240 for item in json_res:
241 raids[item["name"]] = item
242 return raids
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100243
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200244
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100245def get_raid(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300246 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100247 Get information about specific raid on machine
248
249 CLI Example:
250
251 .. code-block:: bash
252
253 salt-call maasng.get_raids server_hostname md0
azvyagintsevbca1f462018-05-25 19:06:46 +0300254 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100255
256 return list_raids(hostname)[name]
257
258
azvyagintsevbca1f462018-05-25 19:06:46 +0300259def _get_raid_id_by_name(hostname, raid_name):
260 return get_raid(hostname, raid_name)['id']
261
262
263def delete_raid(hostname, raid_name):
264 """
265 Delete RAID on a machine.
266
267 CLI Example:
268
269 .. code-block:: bash
270
271 salt 'maas-node' maasng.delete_raid server_hostname raid_name
272 salt-call maasng.delete_raid server_hostname raid_name
273 """
274 result = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200275 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +0300276 system_id = get_machine(hostname)["system_id"]
277 raid_id = _get_raid_id_by_name(hostname, raid_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200278 LOG.debug('delete_raid: {} {}'.format(system_id, raid_id))
279 maas.delete(
280 u"api/2.0/nodes/{0}/raid/{1}/".format(system_id, raid_id)).read()
azvyagintsevbca1f462018-05-25 19:06:46 +0300281
282 result["new"] = "Raid {0} deleted".format(raid_name)
283 return result
284
285# END RAID SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200286# BLOCKDEVICES SECTION
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100287
azvyagintsevbca1f462018-05-25 19:06:46 +0300288
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100289def list_blockdevices(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300290 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100291 Get list of all blockdevices (disks) on machine
292
293 CLI Example:
294
295 .. code-block:: bash
296
297 salt 'maas-node' maasng.list_blockdevices server_hostname
298 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300299 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100300 ret = {}
301
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200302 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100303 system_id = get_machine(hostname)["system_id"]
304 LOG.info(system_id)
305
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200306 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100307
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200308 json_res = json.loads(
309 maas.get(u"api/2.0/nodes/{0}/blockdevices/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100310 LOG.info(json_res)
311 for item in json_res:
312 ret[item["name"]] = item
313
314 return ret
315
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200316
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100317def get_blockdevice(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300318 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100319 Get information about blockdevice (disk) on machine
320
321 CLI Example:
322
323 .. code-block:: bash
324
325 salt 'maas-node' maasng.get_blockdevice server_hostname sda
326 salt-call maasng.get_blockdevice server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300327 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100328
329 return list_blockdevices(hostname)[name]
330
azvyagintsevbca1f462018-05-25 19:06:46 +0300331# END BLOCKDEVICES SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200332# PARTITIONS
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100333
azvyagintsevbca1f462018-05-25 19:06:46 +0300334
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100335def list_partitions(hostname, device):
azvyagintsevbca1f462018-05-25 19:06:46 +0300336 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100337 Get list of all partitions on specific device located on specific machine
338
339 CLI Example:
340
341 .. code-block:: bash
342
343 salt 'maas-node' maasng.list_partitions server_hostname sda
344 salt-call maasng.list_partitions server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300345 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100346 ret = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200347 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100348 system_id = get_machine(hostname)["system_id"]
349 LOG.info(system_id)
350
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200351 partitions = get_blockdevice(hostname, device)["partitions"]
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100352 LOG.info(partitions)
353
354 #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 +0200355 # LOG.info(json_res)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100356
357 if len(device) > 0:
358 for item in partitions:
359 name = item["path"].split('/')[-1]
360 ret[name] = item
361
362 return ret
363
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200364
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100365def get_partition(hostname, device, partition):
azvyagintsevbca1f462018-05-25 19:06:46 +0300366 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100367 Get information about specific parition on device located on machine
368
369 CLI Example:
370
371 .. code-block:: bash
372
373 salt 'maas-node' maasng.get_partition server_hostname disk_name partition
374 salt-call maasng.get_partition server_hostname disk_name partition
375
376 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300377 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100378
379 return list_partitions(partition)[name]
380
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200381
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100382def create_partition(hostname, disk, size, fs_type=None, mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300383 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100384 Create new partition on device.
385
386 CLI Example:
387
388 .. code-block:: bash
389
390 salt 'maas-node' maasng.create_partition server_hostname disk_name 10 ext4 "/"
391 salt-call maasng.create_partition server_hostname disk_name 10 ext4 "/"
azvyagintsevbca1f462018-05-25 19:06:46 +0300392 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200393 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100394 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200395 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100396 system_id = get_machine(hostname)["system_id"]
397 LOG.info(system_id)
398
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200399 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100400 LOG.info(device_id)
401
402 value, unit = size[:-1], size[-1]
403 calc_size = str(int(value) * SIZE[unit])
404 LOG.info(calc_size)
405
406 data = {
407 "size": calc_size
408 }
409
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200410 # TODO validation
411 partition = json.loads(maas.post(
412 u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100413 LOG.info(partition)
414 result["partition"] = "Partition created on {0}".format(disk)
415
416 if fs_type != None:
417 data_fs_type = {
418 "fstype": fs_type
419 }
420 partition_id = str(partition["id"])
421 LOG.info("Partition id: " + partition_id)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200422 # TODO validation
423 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
424 system_id, device_id, partition_id), "format", **data_fs_type).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100425 LOG.info(json_res)
426 result["filesystem"] = "Filesystem {0} created".format(fs_type)
427
428 if mount != None:
429 data = {
430 "mount_point": mount
431 }
432
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200433 # TODO validation
434 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
435 system_id, device_id, str(partition['id'])), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100436 LOG.info(json_res)
437 result["mount"] = "Mount point {0} created".format(mount)
438
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100439 return result
440
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200441
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100442def delete_partition(hostname, disk, partition_name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300443 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100444 Delete partition on device.
445
446 CLI Example:
447
448 .. code-block:: bash
449
450 salt 'maas-node' maasng.delete_partition server_hostname disk_name partition_name
451 salt-call maasng.delete_partition server_hostname disk_name partition_name
452
453 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300454 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100455 result = {}
456 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200457 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100458 system_id = get_machine(hostname)["system_id"]
459 LOG.info(system_id)
460
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200461 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100462 LOG.info(device_id)
463
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200464 partition_id = _get_partition_id_by_name(hostname, disk, partition_name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100465
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200466 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
467 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100468 result["new"] = "Partition {0} deleted".format(partition_name)
469 return result
470
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200471
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100472def delete_partition_by_id(hostname, disk, partition_id):
azvyagintsevbca1f462018-05-25 19:06:46 +0300473 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100474 Delete partition on device. Partition spefified by id of parition
475
476 CLI Example:
477
478 .. code-block:: bash
479
480 salt 'maas-node' maasng.delete_partition_by_id server_hostname disk_name partition_id
481 salt-call maasng.delete_partition_by_id server_hostname disk_name partition_id
482
483 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300484 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100485 result = {}
486 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200487 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100488 system_id = get_machine(hostname)["system_id"]
489 LOG.info(system_id)
490
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200491 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100492 LOG.info(device_id)
493
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200494 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
495 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100496 result["new"] = "Partition {0} deleted".format(partition_id)
497 return result
azvyagintsevbca1f462018-05-25 19:06:46 +0300498# END PARTITIONS
499# DISK LAYOUT
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100500
azvyagintsevbca1f462018-05-25 19:06:46 +0300501
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200502def drop_storage_schema(hostname, disk=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300503 """
504 #1. Drop lv
505 #2. Drop vg
506 #3. Drop md # need to zero-block?
507 #3. Drop part
508 """
509
510 if __opts__['test']:
511 ret['result'] = None
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200512 ret['comment'] = 'Storage schema on {0} will be removed'.format(
513 hostname)
azvyagintsevbca1f462018-05-25 19:06:46 +0300514 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200515 # TODO validation if exists
azvyagintsevbca1f462018-05-25 19:06:46 +0300516 vgs = list_volume_groups(hostname)
517 for vg in vgs:
518 delete_volume_group(hostname, vg)
519
520 raids = list_raids(hostname)
521 for raid in raids:
522 delete_raid(hostname, raid)
523
524 blocks = list_blockdevices(hostname)
525 for block_d in blocks:
526 partitions = __salt__['maasng.list_partitions'](hostname, block_d)
527 for partition_name, partition in partitions.iteritems():
528 LOG.info('delete partition:\n{}'.format(partition))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200529 __salt__['maasng.delete_partition_by_id'](
530 hostname, block_d, partition["id"])
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200531
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100532
533def 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 +0300534 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100535 Update disk layout. Flat or LVM layout supported.
536
537 CLI Example:
538
539 .. code-block:: bash
540
541 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
542 salt-call maasng.update_disk_layout server_hostname lvm root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None
543
544 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300545 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100546 result = {}
547 data = {
548 "storage_layout": layout,
549 }
550
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200551 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100552 system_id = get_machine(hostname)["system_id"]
553 LOG.info(system_id)
554
azvyagintsevbca1f462018-05-25 19:06:46 +0300555 if layout == 'custom':
556 drop_storage_schema(hostname)
557 result["new"] = {
558 "storage_layout": layout,
559 }
560
561 return result
562
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100563 if root_size != None:
564 bit_size = str(root_size * 1073741824)
565 LOG.info(bit_size)
566 data["root_size"] = bit_size
567
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100568 if root_device != None:
569 LOG.info(root_device)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200570 data["root_device"] = str(
571 _get_blockdevice_id_by_name(hostname, root_device))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100572
573 if layout == 'lvm':
574 if volume_group != None:
575 LOG.info(volume_group)
576 data["vg_name"] = volume_group
577 if volume_name != None:
578 LOG.info(volume_name)
579 data["lv_name"] = volume_name
580 if volume_size != None:
581 vol_size = str(volume_size * 1073741824)
582 LOG.info(vol_size)
583 data["lv_size"] = vol_size
584
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200585 # TODO validation
586 json_res = json.loads(maas.post(
587 u"api/2.0/machines/{0}/".format(system_id), "set_storage_layout", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100588 LOG.info(json_res)
589 result["new"] = {
590 "storage_layout": layout,
591 }
592
593 return result
594
azvyagintsevbca1f462018-05-25 19:06:46 +0300595# END DISK LAYOUT
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200596# LVM
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100597
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200598
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100599def list_volume_groups(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300600 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100601 Get list of all volume group on machine.
602
603 CLI Example:
604
605 .. code-block:: bash
606
607 salt 'maas-node' maasng.list_volume_groups server_hostname
608 salt-call maasng.list_volume_groups server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300609 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100610 volume_groups = {}
611
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200612 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100613 system_id = get_machine(hostname)["system_id"]
614 LOG.info(system_id)
615
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200616 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100617
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200618 json_res = json.loads(
619 maas.get(u"api/2.0/nodes/{0}/volume-groups/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100620 LOG.info(json_res)
621 for item in json_res:
622 volume_groups[item["name"]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200623 # return
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100624 return volume_groups
625
626
627def get_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300628 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100629 Get information about specific volume group on machine.
630
631 CLI Example:
632
633 .. code-block:: bash
634
635 salt 'maas-node' maasng.list_blockdevices server_hostname
636 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300637 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200638 # TODO validation that exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100639 return list_volume_groups(hostname)[name]
640
641
642def create_volume_group(hostname, volume_group_name, disks=[], partitions=[]):
azvyagintsevbca1f462018-05-25 19:06:46 +0300643 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100644 Create new volume group on machine. Disks or partitions needs to be provided.
645
646 CLI Example:
647
648 .. code-block:: bash
649
650 salt 'maas-node' maasng.create_volume_group volume_group_name, disks=[sda,sdb], partitions=[]
651 salt-call maasng.create_volume_group server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300652 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100653 result = {}
654
655 data = {
656 "name": volume_group_name,
657 }
658
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200659 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100660 system_id = get_machine(hostname)["system_id"]
661 LOG.info(system_id)
662
663 disk_ids = []
664 partition_ids = []
665
666 for disk in disks:
667 p_disk = get_blockdevice(hostname, disk)
668 if p_disk["partition_table_type"] == None:
669 disk_ids.append(str(p_disk["id"]))
670 else:
azvyagintsevf3515c82018-06-26 18:59:05 +0300671 result["error"] = "Device {0} on" \
672 "machine {1} cointains partition" \
673 "table".format(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:
azvyagintsevf3515c82018-06-26 18:59:05 +0300682 result["error"] = "Partition {0} does" \
683 "not exists on " \
684 "machine {1}".format(partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100685 return result
686
687 data["block_devices"] = disk_ids
688 data["partitions"] = partition_ids
689 LOG.info(partition_ids)
690 LOG.info(partitions)
691
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200692 # TODO validation
693 json_res = json.loads(maas.post(
694 u"api/2.0/nodes/{0}/volume-groups/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100695 LOG.info(json_res)
696 result["new"] = "Volume group {0} created".format(json_res["name"])
697
698 return result
699
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200700
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100701def delete_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300702 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100703 Delete volume group on machine.
704
705 CLI Example:
706
707 .. code-block:: bash
708
709 salt 'maas-node' maasng.delete_volume_group server_hostname vg0
710 salt-call maasng.delete_volume_group server_hostname vg0
azvyagintsevbca1f462018-05-25 19:06:46 +0300711 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100712
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200713 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100714 system_id = get_machine(hostname)["system_id"]
azvyagintsevbca1f462018-05-25 19:06:46 +0300715 LOG.debug('delete_volume_group:{}'.format(system_id))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100716
azvyagintsevbca1f462018-05-25 19:06:46 +0300717 vg_id = str(_get_volume_group_id_by_name(hostname, name))
718 for vol in get_volumes(hostname, name):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200719 delete_volume(hostname, vol, name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100720
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200721 # TODO validation
722 json_res = json.loads(maas.delete(
723 u"api/2.0/nodes/{0}/volume-group/{1}/".format(system_id, vg_id)).read() or 'null')
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100724 LOG.info(json_res)
725
726 return True
727
728
azvyagintsevf3515c82018-06-26 18:59:05 +0300729def create_volume(hostname, volume_name, volume_group, size, fs_type=None,
730 mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300731 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100732 Create volume on volume group.
733
734 CLI Example:
735
736 .. code-block:: bash
737
738 salt 'maas-node' maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
739 salt-call maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
azvyagintsevbca1f462018-05-25 19:06:46 +0300740 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100741
742 data = {
743 "name": volume_name,
744 }
745
746 value, unit = size[:-1], size[-1]
747 bit_size = str(int(value) * SIZE[unit])
748 LOG.info(bit_size)
749
750 data["size"] = bit_size
751
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200752 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100753 system_id = get_machine(hostname)["system_id"]
754 LOG.info(system_id)
755
756 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
757
758 LOG.info(volume_group_id)
759
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200760 # TODO validation
761 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
762 system_id, volume_group_id), "create_logical_volume", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100763 LOG.info(json_res)
764
765 if fs_type != None or mount != None:
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200766 ret = create_volume_filesystem(
767 hostname, volume_group + "-" + volume_name, fs_type, mount)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100768
769 return True
770
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100771
azvyagintsevbca1f462018-05-25 19:06:46 +0300772def delete_volume(hostname, volume_name, volume_group):
773 """
774 Delete volume from volume group.
775 Tips: maas always use 'volume_group-volume_name' name schema.Example: 'vg0-glusterfs'
776 This function expexts same format.
777
778 CLI Example:
779
780 .. code-block:: bash
781
782 salt 'maas-node' maasng.delete_volume server_hostname volume_name volume_group
783 salt 'maas-node' maasng.delete_volume server_hostname vg0-vol0 vg0
784 salt-call maasng.delete_volume server_hostname volume_name volume_group
785 """
786
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200787 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +0300788 system_id = get_machine(hostname)["system_id"]
789 LOG.debug('delete_volume:{}'.format(system_id))
790
791 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200792 volume_id = str(_get_volume_id_by_name(
793 hostname, volume_name, volume_group))
azvyagintsevbca1f462018-05-25 19:06:46 +0300794
795 if None in [volume_group_id, volume_id]:
796 return False
797
798 data = {
799 "id": volume_id,
800 }
801
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200802 # TODO validation
803 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
804 system_id, volume_group_id), "delete_logical_volume", **data).read() or 'null')
azvyagintsevbca1f462018-05-25 19:06:46 +0300805 return True
806
807
808def get_volumes(hostname, vg_name):
809 """
810 Get list of volumes in volume group.
811 """
812 volumes = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200813 _volumes = list_volume_groups(
814 hostname)[vg_name].get('logical_volumes', False)
azvyagintsevbca1f462018-05-25 19:06:46 +0300815 if _volumes:
816 for item in _volumes:
817 volumes[item["name"]] = item
818 return volumes
819
820# END LVM
821
822
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200823def create_volume_filesystem(hostname, device, fs_type=None, mount=None):
824
825 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100826 system_id = get_machine(hostname)["system_id"]
827
828 blockdevices_id = _get_blockdevice_id_by_name(hostname, device)
829 data = {}
830 if fs_type != None:
831 data["fstype"] = fs_type
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200832 # TODO validation
833 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
834 system_id, blockdevices_id), "format", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100835 LOG.info(json_res)
836
837 if mount != None:
838 data["mount_point"] = mount
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200839 # TODO validation
840 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
841 system_id, blockdevices_id), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100842 LOG.info(json_res)
843
844 return True
845
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100846
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100847def set_boot_disk(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300848 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100849 Create volume on volume group.
850
851 CLI Example:
852
853 .. code-block:: bash
854
855 salt 'maas-node' maasng.set_boot_disk server_hostname disk_name
856 salt-call maasng.set_boot_disk server_hostname disk_name
azvyagintsevbca1f462018-05-25 19:06:46 +0300857 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100858 data = {}
859 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200860 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100861 system_id = get_machine(hostname)["system_id"]
862 blockdevices_id = _get_blockdevice_id_by_name(hostname, name)
863
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200864 maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
865 system_id, blockdevices_id), "set_boot_disk", **data).read()
azvyagintsevf3515c82018-06-26 18:59:05 +0300866 # TODO validation for error response
867 # (disk does not exists and node does not exists)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100868 result["new"] = "Disk {0} was set as bootable".format(name)
869
870 return result
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200871
azvyagintsevbca1f462018-05-25 19:06:46 +0300872# NETWORKING
873
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200874
875def list_fabric():
azvyagintsevbca1f462018-05-25 19:06:46 +0300876 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200877 Get list of all fabric
878
879 CLI Example:
880
881 .. code-block:: bash
882
883 salt 'maas-node' maasng.list_fabric
azvyagintsevbca1f462018-05-25 19:06:46 +0300884 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200885 fabrics = {}
886 maas = _create_maas_client()
887 json_res = json.loads(maas.get(u'api/2.0/fabrics/').read())
888 LOG.info(json_res)
889 for item in json_res:
890 fabrics[item["name"]] = item
891 return fabrics
892
893
azvyagintsevf3515c82018-06-26 18:59:05 +0300894def check_fabric(name):
895 """
896 Simple check that fabric already defined
897 Return format:
898 update - require update
899 correct - fully coincides # not implemented
900 not_exist - need's to be created
901 """
902
903 ret = 'not_exist'
904 fabrics = list_fabric()
905 if name in fabrics.keys():
906 LOG.debug("Requested fabrics with name:{} already exist".format(name))
907 ret = 'update'
908 return ret
909
910
911def check_fabric_guess_with_cidr(name, cidrs):
912 """
913 Check, that fabric already defined OR it was autodiscovered
914 WA to fix issue with hardcoded 'fabric-0'
915 - Find all auto-discovered subnets by cidr
916 - find all subnets, that SHOULD be configured to THIS subent
917 Warning: most probably, will fail if some subnet defined
918 to another fabric :(
919
920 { 'update' : ID } - require update
921 { 'correct' : ID } - fully coincides # not implemented
922 { 'not_exist' : None } - need's to be created
923
924 CLI Example:
925
926 .. code-block:: bash
927
928 salt 'maas-node' maasng.check_fabric_guess_with_cidr name='' cidrs=[]
929 """
930
931 ret = {'not_exist': None}
932 fabrics = list_fabric()
933 # Simple check
934 if name in fabrics:
935 LOG.debug("Requested fabrics with name:{} already exist".format(name))
936 f_id = fabrics[name]['id']
937 ret = {'update': f_id}
938 # Cidr check
939 # All discovered subnets by cidr
940 d_subnets = list_subnets(sort_by='cidr')
941 # Check, that requested cidr already in discovered.
942 # If it is - it would mean that fabric already
943 # exist(fabric-0,most probably) but should be renamed.
944 # Works only for first shot ;(
945 # due curren-single-maas logic for 'vlan-subnet' mapping.
946 # Probably, it will fail with future MAAS releases.
947 for cidr in cidrs:
948 if cidr in d_subnets:
949 f_id = d_subnets[cidr]['vlan']['fabric_id']
950 f_name = d_subnets[cidr]['vlan']['fabric']
951 LOG.warning("Detected cidr:{} in fabric:{}".format(cidr, f_name))
952 LOG.warning("Guessing, that fabric "
953 "with current name:{}\n should be "
954 "renamed to:{}".format(f_name, name))
955 ret = {'update': f_id}
956 return ret
957 return ret
958
959
960def create_fabric(name, fabric_id, description, update=False):
azvyagintsevbca1f462018-05-25 19:06:46 +0300961 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200962 Create new fabric.
963
964 CLI Example:
965
966 .. code-block:: bash
967
azvyagintsevf3515c82018-06-26 18:59:05 +0300968 salt 'maas-node' maasng.create_fabric name='123'
azvyagintsevbca1f462018-05-25 19:06:46 +0300969 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200970 result = {}
971 data = {
972 "name": name,
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200973 "class_type": '',
974
975 }
azvyagintsevf3515c82018-06-26 18:59:05 +0300976 if description:
977 data['description'] = description
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200978
979 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +0300980 try:
981 if update:
982 json_res = json.loads(
983 maas.put(u"api/2.0/fabrics/{0}/".format(fabric_id),
984 **data).read())
985 result["new"] = "Fabric {0} created".format(json_res["name"])
986 else:
987 json_res = json.loads(
988 maas.post(u"api/2.0/fabrics/", None, **data).read())
989 result["changes"] = "Fabric {0} updated".format(json_res["name"])
990 except Exception as inst:
991 LOG.debug("create_fabric data:{}".format(data))
992 try:
993 m = inst.readlines()
994 except:
995 m = inst.message
996 LOG.error("Message:{0}".format(m))
997 result['result'] = False
998 result['comment'] = 'Error creating fabric: {0}'.format(name)
999 result['error'] = m
1000 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001001 LOG.debug("crete_fabric:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001002 result['result'] = True
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001003 return result
1004
1005
azvyagintsevf3515c82018-06-26 18:59:05 +03001006def list_subnets(sort_by='name'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001007 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001008 Get list of subnets from maas server
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001009
1010 CLI Example:
1011
1012 .. code-block:: bash
1013
azvyagintsevf3515c82018-06-26 18:59:05 +03001014 salt 'maas-node' maasng.list_subnets
azvyagintsevbca1f462018-05-25 19:06:46 +03001015 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001016 subnets = {}
1017 maas = _create_maas_client()
1018 json_res = json.loads(maas.get(u'api/2.0/subnets/').read())
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001019 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001020 subnets[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001021 return subnets
1022
1023
azvyagintsevf3515c82018-06-26 18:59:05 +03001024def list_vlans(fabric, sort_by='vid'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001025 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001026 Get list of vlans in fabric
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001027
1028 CLI Example:
1029
1030 .. code-block:: bash
1031
azvyagintsevf3515c82018-06-26 18:59:05 +03001032 salt 'maas-node' maasng.list_vlans fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001033 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001034 vlans = {}
1035 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001036 fabric_id = get_fabricid(fabric)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001037
azvyagintsevf3515c82018-06-26 18:59:05 +03001038 try:
1039 json_res = json.loads(
1040 maas.get(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id)).read())
1041 except Exception as inst:
1042 m = inst.readlines()
1043 LOG.error("Message:{0}".format(m))
1044 LOG.debug(json_res)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001045 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001046 vlans[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001047 return vlans
1048
1049
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001050def get_fabricid(fabric):
azvyagintsevbca1f462018-05-25 19:06:46 +03001051 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001052 Get id for specific fabric
1053
1054 CLI Example:
1055
1056 .. code-block:: bash
1057
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001058 salt 'maas-node' maasng.get_fabricid fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001059 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001060 try:
1061 return list_fabric()[fabric]['id']
1062 except KeyError:
1063 return {"error": "Frabic not found on MaaS server"}
1064
1065
azvyagintsevf3515c82018-06-26 18:59:05 +03001066def check_vlan_in_fabric(fabric, vlan):
1067 """
1068 Check that VLAN exactly defined
1069 Return format:
1070 update - require update
1071 correct - fully coincides # not implemented
1072 not_exist - need's to be created
1073 """
1074
1075 ret = 'not_exist'
1076 vlans = list_vlans(fabric)
1077 if vlan in vlans.keys():
1078 LOG.debug("Requested VLAN:{} already exist"
1079 "in FABRIC:{}".format(vlan, fabric))
1080 ret = 'update'
1081 return ret
1082
1083
1084def create_vlan_in_fabric(name, fabric, vlan, description, primary_rack,
1085 dhcp_on=False, update=False, vlan_id=""):
azvyagintsevbca1f462018-05-25 19:06:46 +03001086 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001087 Update vlan
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001088 CLI Example:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001089 .. code-block:: bash
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001090 salt 'maas-node' maasng.update_vlan name, fabric, vid, description, dhcp_on
azvyagintsevbca1f462018-05-25 19:06:46 +03001091 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001092 result = {}
1093
1094 data = {
1095 "name": name,
1096 "dhcp_on": str(dhcp_on),
1097 "description": description,
azvyagintsev6913e5e2018-07-05 11:42:53 +03001098 "primary_rack": list_racks()[primary_rack]['system_id'],
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001099 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001100 vlan = str(vlan)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001101 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001102 fabric_id = get_fabricid(fabric)
azvyagintsevf3515c82018-06-26 18:59:05 +03001103 try:
1104 if update:
1105 # MAAS have buggy logic here. Fallowing api reference, here should
1106 # be passed VID - which mean, API ID for vlan.
1107 # Otherwise, at least for maas 2.3.3-6498-ge4db91d exactly VLAN
1108 # should be passed. so, make temp.backward-convertation.
1109 # json_res = json.loads(maas.put(u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id,vlan_id), **data).read())
1110 json_res = json.loads(maas.put(
1111 u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id, vlan),
1112 **data).read())
1113 else:
1114 data['vid'] = str(vlan)
1115 json_res = json.loads(maas.post(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id), None, **data).read())
1116 except Exception as inst:
1117 LOG.debug("create_vlan_in_fabric data:{}".format(data))
1118 try:
1119 m = inst.readlines()
1120 except:
1121 m = inst.message
1122 LOG.error("Message:{0}".format(m))
1123 result['result'] = False
1124 result['comment'] = 'Error updating vlan: {0}'.format(name)
1125 result['error'] = m
1126 return result
1127 LOG.debug("create_vlan_in_fabric:{}".format(json_res))
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001128 result["new"] = "Vlan {0} was updated".format(json_res["name"])
1129
1130 return result
azvyagintsevbca1f462018-05-25 19:06:46 +03001131
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001132
azvyagintsevf3515c82018-06-26 18:59:05 +03001133def check_subnet(cidr, name, fabric, gateway_ip):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001134 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001135 Check that subnet exactly defined
1136 Return format:
1137 update - require update
1138 correct - fully coincides # not implemented
1139 not_exist - need's to be created
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001140 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001141
1142 ret = 'not_exist'
1143 subnets = list_subnets(sort_by='cidr')
1144 if cidr in subnets.keys():
1145 LOG.debug("Requested subnet cidr:{} already exist".format(cidr))
1146 ret = 'update'
1147 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001148
1149
azvyagintsevf3515c82018-06-26 18:59:05 +03001150def create_subnet(cidr='', name='', fabric='', gateway_ip='', vlan='',
1151 update=False, subnet_id=''):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001152 """
1153 Create subnet
1154
1155 CLI Example:
1156
1157 .. code-block:: bash
1158
1159 salt 'maas-node' maasng.create_subnet cidr, name, fabric, gateway_ip
1160 """
1161
1162 fabric_id = get_fabricid(fabric)
1163 result = {}
azvyagintsevf3515c82018-06-26 18:59:05 +03001164 vlan=str(vlan)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001165 data = {
1166 "cidr": cidr,
1167 "name": name,
1168 "fabric": str(fabric_id),
1169 "gateway_ip": gateway_ip,
azvyagintsevf3515c82018-06-26 18:59:05 +03001170 'vlan': vlan,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001171 }
1172 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001173 # FIXME: vlan definition not work in 2.3.3-6498-ge4db91d.
1174 LOG.warning("Ignoring parameter vlan:{}".format(vlan))
1175 data.pop('vlan','')
1176 try:
1177 if update:
1178 json_res = json.loads(maas.put(u"api/2.0/subnets/{0}/".format(subnet_id), **data).read())
1179 else:
1180 json_res = json.loads(maas.post(u"api/2.0/subnets/", None, **data).read())
1181 except Exception as inst:
1182 LOG.debug("create_subnet data:{}".format(data))
1183 try:
1184 m = inst.readlines()
1185 except:
1186 m = inst.message
1187 LOG.error("Message:{0}".format(m))
1188 result['result'] = False
1189 result['comment'] = 'Error creating subnet: {0}'.format(name)
1190 result['error'] = m
1191 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001192 LOG.debug("create_subnet:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001193 result["new"] = "Subnet {0} with CIDR {1}" \
1194 "and gateway {2} was created".format(name, cidr, gateway_ip)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001195
1196 return result
1197
1198
1199def get_subnet(subnet):
1200 """
1201 Get details for specific subnet
1202
1203 CLI Example:
1204
1205 .. code-block:: bash
1206
1207 salt 'maas-node' maasng.get_subnet subnet_name
1208 """
1209 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001210 return list_subnets()[subnet]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001211 except KeyError:
1212 return {"error": "Subnet not found on MaaS server"}
1213
1214
1215def get_subnetid(subnet):
1216 """
1217 Get id for specific subnet
1218
1219 CLI Example:
1220
1221 .. code-block:: bash
1222
1223 salt 'maas-node' maasng.get_subnetid subnet_name
1224 """
1225 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001226 return list_subnets()[subnet]['id']
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001227 except KeyError:
1228 return {"error": "Subnet not found on MaaS server"}
1229
1230
1231def list_ipranges():
1232 """
1233 Get list of all ipranges from maas server
1234
1235 CLI Example:
1236
1237 .. code-block:: bash
1238
1239 salt 'maas-node' maasng.list_ipranges
1240 """
1241 ipranges = {}
1242 maas = _create_maas_client()
1243 json_res = json.loads(maas.get(u'api/2.0/ipranges/').read())
1244 for item in json_res:
1245 ipranges[item["start_ip"]] = item
1246 return ipranges
1247
1248
azvyagintsevf3515c82018-06-26 18:59:05 +03001249def create_iprange(type_range, start_ip, end_ip, comment=None):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001250 """
1251 Create ip range
1252
1253 CLI Example:
1254
1255 .. code-block:: bash
1256
1257 salt 'maas-node' maasng.create_iprange type, start ip, end ip, comment
1258 """
1259 result = {}
1260
1261 data = {
1262 "type": type_range,
1263 "start_ip": start_ip,
1264 "end_ip": end_ip,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001265 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001266 if comment:
1267 data['comment'] = comment
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001268 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001269 _name = "Type:{}: {}-{}".format(type_range, start_ip, end_ip)
1270 try:
1271 json_res = json.loads(
1272 maas.post(u"api/2.0/ipranges/", None, **data).read())
1273 except Exception as inst:
1274 try:
1275 m = inst.readlines()
1276 except:
1277 m = inst.message
1278 LOG.error("Message:{0}".format(m))
1279 result['result'] = False
1280 result['comment'] = 'Error creating iprange:{0}'.format(_name)
1281 result['error'] = m
1282 return result
1283 result["new"] = "Iprange: {0} has been created".format(_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001284 LOG.debug("create_iprange:{}".format(json_res))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001285
1286 return result
1287
1288
1289def get_iprangeid(start_ip):
1290 """
1291 Get id for ip range from maas server
1292
1293 CLI Example:
1294
1295 .. code-block:: bash
1296
1297 salt 'maas-node' maasng.get_iprangeid start_ip
1298 """
1299 try:
1300 return list_ipranges()[start_ip]['id']
1301 except KeyError:
1302 return {"error": "Ip range not found on MaaS server"}
1303
1304
1305def get_startip(start_ip):
1306 """
1307 Get start ip for ip range
1308
1309 CLI Example:
1310
1311 .. code-block:: bash
1312
1313 salt 'maas-node' maasng.get_startip start ip
1314 """
1315 try:
1316 return list_ipranges()[start_ip]
1317 except KeyError:
1318 return {"error": "Ip range not found on MaaS server"}
azvyagintsevbca1f462018-05-25 19:06:46 +03001319# END NETWORKING
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001320
1321# MAAS CONFIG SECTION
1322
1323
azvyagintsev58947072018-06-29 12:09:48 +03001324def _getHTTPCode(url):
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001325 code = '003'
1326 m = ''
azvyagintsev58947072018-06-29 12:09:48 +03001327 try:
1328 connection = urllib2.urlopen(url)
1329 code = connection.getcode()
1330 connection.close()
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001331 except (urllib2.HTTPError, urllib2.URLError) as e:
1332 try:
1333 code = e.getcode()
1334 except:
1335 m = e.reason
1336 pass
azvyagintsevf3515c82018-06-26 18:59:05 +03001337 LOG.debug("Unexpected http code:{} from "
1338 "url:{}\nwith message:{}".format(code, url, m))
azvyagintsev58947072018-06-29 12:09:48 +03001339 pass
1340 return code
1341
1342
1343def wait_for_http_code(url=None, expected=[200]):
1344 """
1345 Simple function, which just wait for avaible api, aka wait for 200.
1346
1347 CLI Example:
1348
1349 .. code-block:: bash
1350
1351 salt 'maas-node' maasng.wait_for_http_code url expected=[200]
1352
1353 """
1354 ret = {}
1355 started_at = time.time()
1356 poll_time = 5
1357 timeout = 60 * 2
1358 while _getHTTPCode(url) not in expected:
1359 c_timeout = timeout - (time.time() - started_at)
1360 if c_timeout <= 0:
1361 ret['result'] = False
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001362 ret["comment"] = "api:{} not answered in time".format(url)
azvyagintsev58947072018-06-29 12:09:48 +03001363 return ret
1364 LOG.info(
1365 "Waiting for api:{0}\n"
1366 "sleep for:{1}s "
1367 "Left:{2}/{3}s".format(url, poll_time, round(c_timeout),
1368 timeout))
1369 time.sleep(poll_time)
1370 ret['result'] = True
1371 ret["comment"] = "MAAS API:{} up.".format(url)
1372 return ret
1373
1374
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001375def _get_boot_source_id_by_url(url):
1376 # FIXME: fix ret\validation
1377 try:
1378 bs_id = get_boot_source(url=url)["id"]
1379 except KeyError:
1380 return {"error": "boot-source:{0} not exist!".format(url)}
1381 return bs_id
1382
1383
1384def get_boot_source(url=None):
1385 """
1386 Read a boot source by url. If url not specified - return all.
1387
1388 CLI Example:
1389
1390 .. code-block:: bash
1391
1392 salt 'maas-node' maasng.get_boot_source url
1393
1394 """
1395 boot_sources = {}
1396 maas = _create_maas_client()
1397 json_res = json.loads(maas.get(u'api/2.0/boot-sources/').read() or 'null')
1398 for item in json_res:
1399 boot_sources[str(item["url"])] = item
1400 if url:
1401 return boot_sources.get(url, {})
1402 return boot_sources
1403
1404
1405def delete_boot_source(url, bs_id=None):
1406 """
1407 Delete a boot source by url.
1408
1409 CLI Example:
1410
1411 .. code-block:: bash
1412
1413 sal 'maas-node' maasng.delete url
1414
1415 """
1416 result = {}
1417 if not bs_id:
1418 bs_id = _get_boot_source_id_by_url(url)
1419 maas = _create_maas_client()
1420 json_res = json.loads(maas.delete(
1421 u'/api/2.0/boot-sources/{0}/'.format(bs_id)).read() or 'null')
1422 LOG.debug("delete_boot_source:{}".format(json_res))
1423 result["new"] = "Boot-resource {0} deleted".format(url)
1424 return result
1425
1426
1427def boot_sources_delete_all_others(except_urls=[]):
1428 """
1429 Delete all boot-sources, except defined in 'except_urls' list.
1430 """
1431 result = {}
1432 maas_boot_sources = get_boot_source()
1433 if 0 in [len(except_urls), len(maas_boot_sources)]:
1434 result['result'] = None
1435 result[
1436 "comment"] = "Exclude or maas sources for delete empty. No changes goinng to be."
1437 return result
1438 for url in maas_boot_sources.keys():
1439 if url not in except_urls:
1440 LOG.info("Removing boot-source:{}".format(url))
1441 boot_resources_import(action='stop_import', wait=True)
1442 result["changes"] = delete_boot_source(url)
1443 return result
1444
1445
1446def create_boot_source(url, keyring_filename='', keyring_data='', wait=False):
1447 """
1448 Create and import maas boot-source: link to maas-ephemeral repo
1449 Be aware, those step will import resource to rack ctrl, but you also need to import
1450 them into the region!
1451
1452
1453 :param url: The URL of the BootSource.
1454 :param keyring_filename: The path to the keyring file for this BootSource.
1455 :param keyring_data: The GPG keyring for this BootSource, base64-encoded data.
1456
1457 """
1458
1459 # TODO: not work with 'update' currently => keyring update may fail.
1460 result = {}
1461
1462 data = {
1463 "url": url,
1464 "keyring_filename": keyring_filename,
1465 "keyring_data": str(keyring_data),
1466 }
1467
1468 maas = _create_maas_client()
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001469 if url in get_boot_source():
1470 result['result'] = None
1471 result["comment"] = "boot resource already exist"
1472 return result
1473
1474 # NOTE: maas.post will return 400, if url already defined.
1475 json_res = json.loads(
1476 maas.post(u'api/2.0/boot-sources/', None, **data).read())
1477 if wait:
1478 LOG.debug(
1479 "Sleep for 5s,to get MaaS some time to process previous request")
1480 time.sleep(5)
1481 ret = boot_resources_is_importing(wait=True)
1482 if ret is dict:
1483 return ret
1484 LOG.debug("create_boot_source:{}".format(json_res))
1485 result["new"] = "boot resource {0} was created".format(json_res["url"])
1486
1487 return result
1488
1489
1490def boot_resources_import(action='import', wait=False):
1491 """
1492 import/stop_import the boot resources.
1493
1494 :param action: import\stop_import
1495 :param wait: True\False. Wait till process finished.
1496
1497 CLI Example:
1498
1499 .. code-block:: bash
1500
1501 salt 'maas-node' maasng.boot_resources_import action='import'
1502
1503 """
1504 maas = _create_maas_client()
1505 # Have no idea why, but usual jsonloads not work here..
1506 imp = maas.post(u'api/2.0/boot-resources/', action)
1507 if imp.code == 200:
1508 LOG.debug('boot_resources_import:{}'.format(imp.readline()))
1509 if wait:
1510 boot_resources_is_importing(wait=True)
1511 return True
1512 else:
1513 return False
1514
1515
1516def boot_resources_is_importing(wait=False):
1517 maas = _create_maas_client()
1518 result = {}
1519 if wait:
1520 started_at = time.time()
1521 poll_time = 5
1522 timeout = 60 * 15
1523 while boot_resources_is_importing(wait=False):
1524 c_timeout = timeout - (time.time() - started_at)
1525 if c_timeout <= 0:
1526 result['result'] = False
1527 result["comment"] = "Boot-resources import not finished in time"
1528 return result
1529 LOG.info(
1530 "Waiting boot-resources import done\n"
1531 "sleep for:{}s "
1532 "Left:{}/{}s".format(poll_time, round(c_timeout), timeout))
1533 time.sleep(poll_time)
1534 return json.loads(
1535 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1536 else:
1537 return json.loads(
1538 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1539
1540#####
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001541# def boot_sources_selections_delete_all_others(except_urls=[]):
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001542# """
1543# """
1544# result = {}
1545# return result
1546
1547
1548def is_boot_source_selections_in(dict1, list1):
1549 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001550 Check that requested boot-selection already in maas bs selections,
1551 if True- return bss id.
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001552 # FIXME: those hack check doesn't look good.
1553 """
1554 for bs in list1:
1555 same = set(dict1.keys()) & set(bs.keys())
1556 if all(elem in same for elem in
1557 ['os', 'release', 'arches', 'subarches', 'labels']):
azvyagintsevf3515c82018-06-26 18:59:05 +03001558 LOG.debug("boot-selection in maas:{0}\n"
1559 "looks same to requested:{1}".format(bs, dict1))
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001560 return bs['id']
1561 return False
1562
1563
1564def get_boot_source_selections(bs_url):
1565 """
1566 Get boot-source selections.
1567 """
1568 # check for key_error!
1569 bs_id = _get_boot_source_id_by_url(bs_url)
1570 maas = _create_maas_client()
1571 json_res = json.loads(
1572 maas.get(u'/api/2.0/boot-sources/{0}/selections/'.format(bs_id)).read())
1573 LOG.debug(
1574 "get_boot_source_selections for url:{} \n{}".format(bs_url, json_res))
1575 return json_res
1576
1577
1578def create_boot_source_selections(bs_url, os, release, arches="*",
1579 subarches="*", labels="*", wait=True):
1580 """
1581 Create a new boot source selection for bs_url.
1582 :param os: The OS (e.g. ubuntu, centos) for which to import resources.Required.
1583 :param release: The release for which to import resources. Required.
1584 :param arches: The architecture list for which to import resources.
1585 :param subarches: The subarchitecture list for which to import resources.
1586 :param labels: The label lists for which to import resources.
1587 """
1588
azvyagintsevcb54d142018-06-19 16:18:32 +03001589 result = { "result" : True, 'name' : bs_url, 'changes' : None }
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001590
1591 data = {
1592 "os": os,
1593 "release": release,
1594 "arches": arches,
1595 "subarches": subarches,
1596 "labels": labels,
1597 }
1598
1599 maas = _create_maas_client()
1600 bs_id = _get_boot_source_id_by_url(bs_url)
1601 # TODO add pre-create verify
1602 maas_bs_s = get_boot_source_selections(bs_url)
1603 if is_boot_source_selections_in(data, maas_bs_s):
1604 result["result"] = True
azvyagintsevf3515c82018-06-26 18:59:05 +03001605 result["comment"] = 'Requested boot-source selection ' \
1606 'for {0} already exist.'.format(
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001607 bs_url)
1608 return result
1609
1610 # NOTE: maas.post will return 400, if url already defined.
azvyagintsevcb54d142018-06-19 16:18:32 +03001611 # Also, maas need's some time to import info about stream.
1612 # unfortunatly, maas don't have any call to check stream-import-info - so, we need to implement
1613 # at least simple retry ;(
1614 json_res = False
1615 poll_time = 5
1616 for i in range(0,5):
1617 try:
1618 json_res = json.loads(
1619 maas.post(u'api/2.0/boot-sources/{0}/selections/'.format(bs_id), None,
1620 **data).read())
1621 except Exception as inst:
1622 m = inst.readlines()
azvyagintsevf3515c82018-06-26 18:59:05 +03001623 LOG.warning("boot_source_selections "
1624 "catch error during processing. Most-probably, "
1625 "streams not imported yet.\nSleep:{}s"
1626 "Retry:{}/5".format(poll_time, i))
azvyagintsevcb54d142018-06-19 16:18:32 +03001627 LOG.warning("Message:{0}".format(m))
1628 time.sleep(poll_time)
1629 continue
1630 break
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001631 LOG.debug("create_boot_source_selections:{}".format(json_res))
azvyagintsevcb54d142018-06-19 16:18:32 +03001632 if not json_res:
1633 result["result"] = False
azvyagintsevf3515c82018-06-26 18:59:05 +03001634 result["comment"] = 'Failed to create requested boot-source selection' \
1635 ' for {0}.'.format(bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001636 return result
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001637 if wait:
1638 LOG.debug(
1639 "Sleep for 5s,to get MaaS some time to process previous request")
1640 time.sleep(5)
1641 ret = boot_resources_import(action='import', wait=True)
1642 if ret is dict:
1643 return ret
azvyagintsevcb54d142018-06-19 16:18:32 +03001644 result["comment"] = "boot-source selection for {0} was created".format(bs_url)
1645 result["new"] = data
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001646
1647 return result
1648
1649# END MAAS CONFIG SECTION
azvyagintsevcb54d142018-06-19 16:18:32 +03001650
1651# RACK CONTROLLERS SECTION
1652
1653
1654def get_rack(hostname):
1655 """
1656 Get information about specified rackd
1657
1658 CLI Example:
1659
1660 .. code-block:: bash
1661
1662 salt-call maasng.get_rack rack_hostname
1663 """
1664 try:
1665 return list_racks()[hostname]
1666 except KeyError:
1667 return {"error": "rack:{} not found on MaaS server".format(hostname)}
1668
1669
azvyagintsev6913e5e2018-07-05 11:42:53 +03001670def list_racks(sort_by='hostname'):
azvyagintsevcb54d142018-06-19 16:18:32 +03001671 """
1672 Get list of all rack controllers from maas server
1673
1674 CLI Example:
1675
1676 .. code-block:: bash
1677
1678 salt-call maasng.list_racks
1679 """
1680 racks = {}
1681 maas = _create_maas_client()
1682 json_res = json.loads(
1683 maas.get(u"/api/2.0/rackcontrollers/").read() or 'null')
1684 for item in json_res:
azvyagintsev6913e5e2018-07-05 11:42:53 +03001685 racks[item[sort_by]] = item
azvyagintsevcb54d142018-06-19 16:18:32 +03001686 return racks
1687
1688
1689def sync_bs_to_rack(hostname=None):
1690 """
1691 Sync RACK boot-sources with REGION. If no hostname probided - sync to all.
1692
1693 CLI Example:
1694
1695 .. code-block:: bash
1696
1697 salt-call maasng.sync_bs_to_rack rack_hostname
1698 """
1699 ret = {}
1700 maas = _create_maas_client()
1701 if not hostname:
1702 LOG.info("boot-sources sync initiated for ALL Rack's")
1703 # Convert to json-like format
1704 json_res = json.loads('["{0}"]'.format(
1705 maas.post(u"/api/2.0/rackcontrollers/",
1706 'import_boot_images').read()))
1707 LOG.debug("sync_bs_to_rack:{}".format(json_res))
1708 ret['result'] = True
1709 ret['comment'] = "boot-sources sync initiated for ALL Rack's"
1710 return ret
1711 LOG.info("boot-sources sync initiated for RACK:{0}".format(hostname))
1712 # Convert to json-like format
1713 json_res = json.loads('["{0}"]'.format(maas.post(
1714 u"/api/2.0/rackcontrollers/{0}/".format(
1715 get_rack(hostname)['system_id']),
1716 'import_boot_images').read()))
1717 LOG.debug("sync_bs_to_rack:{}".format(json_res))
1718 ret['result'] = True
1719 ret['comment'] = "boot-sources sync initiated for {0} Rack's".format(
1720 hostname)
1721 return
1722
1723
1724def rack_list_boot_imgs(hostname):
1725 ret = {}
1726 maas = _create_maas_client()
1727 LOG.debug("rack_list_boot_imgs:{}".format(hostname))
1728 ret = json.loads(maas.get(u"/api/2.0/rackcontrollers/{0}/".format(
1729 get_rack(hostname)['system_id']), 'list_boot_images').read() or 'null')
1730 return ret
1731
1732
1733def is_rack_synced(hostname):
1734 rez = rack_list_boot_imgs(hostname)['status']
1735 if rez == 'synced':
1736 return True
1737 return False
1738
1739# TODO do we actually need _exact_ check per-pack?
1740# def wait_for_images_on_rack(hostname):
1741#
1742# """
1743# WA function, to be able check that RACK actually done SYNC images
1744# for REQUIRED images at least.
1745# Required image to be fetched from
1746# reclass:maas:region:boot_sources_selections:[keys]:os/release formation
1747#
1748# CLI Example:
1749#
1750# .. code-block:: bash
1751#
1752# salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
1753# """
1754# try:
1755# bss = __salt__['config.get']('maas')['region']['boot_sources_selections']
1756# except KeyError:
1757# ret['result'] = None
1758# ret['comment'] = "boot_sources_selections definition for sync not found."
1759# return ret
1760# s_names = []
1761# # Format u'name': u'ubuntu/xenial'
1762# for v in bss.values():s_names.append("{0}/{1}".format(v['os'],v['release']))
1763# # Each names, should be in rack and whole rack should be in sync-ed state
1764
1765
1766def sync_and_wait_bs_to_all_racks():
1767 """
1768 Sync ALL rack's with regions source images.
1769
1770 CLI Example:
1771
1772 .. code-block:: bash
1773
1774 salt-call maasng.sync_and_wait_bs_to_all_racks
1775 """
1776 sync_bs_to_rack()
1777 for rack in list_racks().keys():
1778 wait_for_sync_bs_to_rack(hostname=rack)
1779 return True
1780
1781
1782def wait_for_sync_bs_to_rack(hostname=None):
1783 """
1784 Wait for boot images sync finished, on exact rack
1785
1786 CLI Example:
1787
1788 .. code-block:: bash
1789
1790 salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
1791 """
1792 ret = {}
1793 started_at = time.time()
1794 poll_time = 5
1795 timeout = 60 * 15
1796 while not is_rack_synced(hostname):
1797 c_timeout = timeout - (time.time() - started_at)
1798 if c_timeout <= 0:
1799 ret['result'] = False
1800 ret[
1801 "comment"] = "Boot-resources sync on rackd:{0}" \
1802 "not finished in time".format(
1803 hostname)
1804 return ret
1805 LOG.info(
1806 "Waiting boot-resources sync done to rack:{0}\n"
1807 "sleep for:{1}s "
1808 "Left:{2}/{3}s".format(hostname, poll_time, round(c_timeout),
1809 timeout))
1810 time.sleep(poll_time)
1811 ret['result'] = is_rack_synced(hostname)
1812 ret["comment"] = "Boot-resources sync on rackd:{0} finished".format(
1813 hostname)
1814 return ret
1815
1816# END RACK CONTROLLERS SECTION