blob: c2676c1578fa5d6915cd6ef37116f8f573eb31c3 [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
Alexandr Lovtsov4fc89602019-02-22 18:46:59 +030043try:
44 import netaddr
45 HAS_NETADDR = True
46except ImportError:
47 HAS_NETADDR = False
48
Ondrej Smolab57a23b2018-01-24 11:18:24 +010049try:
50 from maas_client import MAASClient, MAASDispatcher, MAASOAuth
51 HAS_MASS = True
52except ImportError:
Alexandr Lovtsov4fc89602019-02-22 18:46:59 +030053 HAS_MASS = False
Ondrej Smolab57a23b2018-01-24 11:18:24 +010054
55
56def __virtual__():
azvyagintsevbca1f462018-05-25 19:06:46 +030057 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +010058 Only load this module if maas-client
59 is installed on this minion.
azvyagintsevbca1f462018-05-25 19:06:46 +030060 """
Alexandr Lovtsov4fc89602019-02-22 18:46:59 +030061 if not HAS_NETADDR:
62 return False, "'netaddr' python library is unavailable"
63 if not HAS_MASS:
64 return False, "MaaS client library is unavailable"
65 return 'maasng'
Ondrej Smolab57a23b2018-01-24 11:18:24 +010066
67
68APIKEY_FILE = '/var/lib/maas/.maas_credentials'
69
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020070
Ondrej Smolab57a23b2018-01-24 11:18:24 +010071def _format_data(data):
72 class Lazy:
73 def __str__(self):
74 return ' '.join(['{0}={1}'.format(k, v)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020075 for k, v in data.iteritems()])
Ondrej Smolab57a23b2018-01-24 11:18:24 +010076 return Lazy()
77
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020078
azvyagintsev58947072018-06-29 12:09:48 +030079def _create_maas_client(api_url=None):
80 if not api_url:
81 api_url = 'http://localhost:5240/MAAS'
Ondrej Smolab57a23b2018-01-24 11:18:24 +010082 global APIKEY_FILE
83 try:
84 api_token = file(APIKEY_FILE).read().splitlines()[-1].strip()\
85 .split(':')
86 except:
87 LOG.exception('token')
88 auth = MAASOAuth(*api_token)
Ondrej Smolab57a23b2018-01-24 11:18:24 +010089 dispatcher = MAASDispatcher()
90 return MAASClient(auth, dispatcher, api_url)
91
Ondrej Smolab57a23b2018-01-24 11:18:24 +010092
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020093def _get_blockdevice_id_by_name(hostname, device):
94
95 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +010096 return list_blockdevices(hostname)[device]["id"]
97
Ondrej Smolab57a23b2018-01-24 11:18:24 +010098
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020099def _get_volume_group_id_by_name(hostname, device):
100
101 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100102 return list_volume_groups(hostname)[device]["id"]
103
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200104
azvyagintsevbca1f462018-05-25 19:06:46 +0300105def _get_volume_id_by_name(hostname, volume_name, volume_group, maas_volname=True):
106
107 if not maas_volname:
108 # MAAS-like name
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200109 volume_name = str("%s-%s" % (volume_group, volume_name))
110 # TODO validation
azvyagintsevbca1f462018-05-25 19:06:46 +0300111 return get_volumes(hostname, volume_group)[volume_name]["id"]
112
113
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100114def _get_partition_id_by_name(hostname, device, partition):
115
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200116 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100117 return list_partitions(hostname, device)[partition]["id"]
118
Dzmitry Stremkouskid95bd2e2018-12-03 17:35:46 +0100119def is_valid_ipv4(address):
120 """Verify that address represents a valid IPv4 address.
121 :param address: Value to verify
122 :type address: string
123 :returns: bool
124 .. versionadded:: 1.1
125 """
126 try:
127 return netaddr.valid_ipv4(address)
128 except netaddr.AddrFormatError:
129 return False
130
131def is_valid_ipv6(address):
132 """Verify that address represents a valid IPv6 address.
133 :param address: Value to verify
134 :type address: string
135 :returns: bool
136 .. versionadded:: 1.1
137 """
138 if not address:
139 return False
140
141 parts = address.rsplit("%", 1)
142 address = parts[0]
143 scope = parts[1] if len(parts) > 1 else None
144 if scope is not None and (len(scope) < 1 or len(scope) > 15):
145 return False
146
147 try:
148 return netaddr.valid_ipv6(address, netaddr.core.INET_PTON)
149 except netaddr.AddrFormatError:
150 return False
151
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200152# MACHINE SECTION
153
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100154
155def get_machine(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300156 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100157 Get information aboout specified machine
158
159 CLI Example:
160
161 .. code-block:: bash
162
163 salt-call maasng.get_machine server_hostname
Alexei Lugovoie5b64122018-11-06 12:30:01 +0100164
165 Error codes:
166 0 : Machine not found
azvyagintsevbca1f462018-05-25 19:06:46 +0300167 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100168 try:
169 return list_machines()[hostname]
170 except KeyError:
Alexei Lugovoie5b64122018-11-06 12:30:01 +0100171 return {"error":
172 { 0: "Machine not found" }
173 }
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100174
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200175
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200176def list_machines(status_filter=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300177 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100178 Get list of all machines from maas server
179
180 CLI Example:
181
182 .. code-block:: bash
183
184 salt 'maas-node' maasng.list_machines
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200185 salt 'maas-node' maasng.list_machines status_filter=[Deployed,Ready]
azvyagintsevbca1f462018-05-25 19:06:46 +0300186 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200187 machines = {}
188 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100189 json_res = json.loads(maas.get(u'api/2.0/machines/').read())
190 for item in json_res:
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200191 if not status_filter or item['status_name'] in status_filter:
192 machines[item["hostname"]] = item
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100193 return machines
194
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200195
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100196def create_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200197 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100198
199 return False
200
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200201
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100202def update_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200203 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100204
205 return False
206
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200207def delete_machine(hostname):
208 """
209 Delete specified machine
210
211 CLI Example:
212
213 .. code-block:: bash
214
215 salt 'maas-node' maasng.delete_machine server_hostname
216 salt-call maasng.delete_machine server_hostname
217 """
218 result = {}
219 maas = _create_maas_client()
220 system_id = get_machine(hostname)["system_id"]
221 LOG.debug('delete_machine: {}'.format(system_id))
222 maas.delete(
223 u"api/2.0/machines/{0}/".format(system_id)).read()
224
225 result["new"] = "Machine {0} deleted".format(hostname)
226 return result
227
Dzmitry Stremkouskid95bd2e2018-12-03 17:35:46 +0100228def machine_power_state(hostname):
229 """
230 Query the power state of a node.
231
232 :param hostname: Node hostname
233
234 CLI Example:
235
236 .. code-block:: bash
237
238 salt 'maas-node' maasng.machine_power_state kvm06
239
240 """
241 result = {}
242 maas = _create_maas_client()
243 system_id = get_machine(hostname)["system_id"]
244 LOG.debug('action_machine: {}'.format(system_id))
245
246 # TODO validation
247 json_res = json.loads(maas.get(
248 u"api/2.0/machines/{0}/".format(system_id), "query_power_state").read())
249 LOG.info(json_res)
250
251 return json_res
252
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200253def action_machine(hostname, action, comment=None):
254 """
255 Send simple action (e.g. mark_broken, mark_fixed) to machine.
256
257 :param action: Action to send for machine (one of MaaS' op codes)
258 :param comment: Optional comment for the event log.
259
260 CLI Example:
261
262 .. code-block:: bash
263
264 salt 'maas-node' maasng.action_machine server_hostname mark_broken comment='dead'
265 """
266 result = {}
267 data = {}
268 maas = _create_maas_client()
269 system_id = get_machine(hostname)["system_id"]
270 LOG.debug('action_machine: {}'.format(system_id))
271
272 # TODO validation
273 if comment:
274 data["comment"] = comment
275 json_res = json.loads(maas.post(
276 u"api/2.0/machines/{0}/".format(system_id), action, **data).read())
277 LOG.info(json_res)
278 result["new"] = "Machine {0} action {1} executed".format(hostname, action)
279
280 return result
281
azvyagintsevbca1f462018-05-25 19:06:46 +0300282# END MACHINE SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200283# RAID SECTION
284
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100285
286def create_raid(hostname, name, level, disks=[], partitions=[], **kwargs):
azvyagintsevbca1f462018-05-25 19:06:46 +0300287 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100288 Create new raid on machine.
289
290 CLI Example:
291
292 .. code-block:: bash
293
294 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 +0300295 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100296
297 result = {}
298
299 if len(disks) == 0 and len(partitions) == 0:
300 result["error"] = "Disks or partitions need to be provided"
301
302 disk_ids = []
303 partition_ids = []
304
305 for disk in disks:
306 try:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200307 disk_ids.append(str(_get_blockdevice_id_by_name(hostname, disk)))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100308 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200309 result["error"] = "Device {0} does not exists on machine {1}".format(
310 disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100311 return result
312
313 for partition in partitions:
314 try:
315 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200316 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100317 partition_ids.append(str(device_part[partition]["id"]))
318 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200319 result["error"] = "Partition {0} does not exists on machine {1}".format(
320 partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100321 return result
322
323 data = {
324 "name": name,
325 "level": RAID[int(level)],
326 "block_devices": disk_ids,
327 "partitions": partition_ids,
328 }
329
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200330 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100331 system_id = get_machine(hostname)["system_id"]
332 LOG.info(system_id)
333
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200334 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100335 LOG.info(data)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200336 json_res = json.loads(
337 maas.post(u"api/2.0/nodes/{0}/raids/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100338 LOG.info(json_res)
339 result["new"] = "Raid {0} created".format(name)
340
341 return result
342
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200343
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100344def list_raids(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300345 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100346 Get list all raids on machine
347
348 CLI Example:
349
350 .. code-block:: bash
351
352 salt-call maasng.list_raids server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300353 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100354
azvyagintsevbca1f462018-05-25 19:06:46 +0300355 raids = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200356 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100357 system_id = get_machine(hostname)["system_id"]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200358 # TODO validation
359 json_res = json.loads(
360 maas.get(u"api/2.0/nodes/{0}/raids/".format(system_id)).read())
azvyagintsevbca1f462018-05-25 19:06:46 +0300361 LOG.debug('list_raids:{} {}'.format(system_id, json_res))
362 for item in json_res:
363 raids[item["name"]] = item
364 return raids
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100365
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200366
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100367def get_raid(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300368 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100369 Get information about specific raid on machine
370
371 CLI Example:
372
373 .. code-block:: bash
374
375 salt-call maasng.get_raids server_hostname md0
azvyagintsevbca1f462018-05-25 19:06:46 +0300376 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100377
378 return list_raids(hostname)[name]
379
380
azvyagintsevbca1f462018-05-25 19:06:46 +0300381def _get_raid_id_by_name(hostname, raid_name):
382 return get_raid(hostname, raid_name)['id']
383
384
385def delete_raid(hostname, raid_name):
386 """
387 Delete RAID on a machine.
388
389 CLI Example:
390
391 .. code-block:: bash
392
393 salt 'maas-node' maasng.delete_raid server_hostname raid_name
394 salt-call maasng.delete_raid server_hostname raid_name
395 """
396 result = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200397 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +0300398 system_id = get_machine(hostname)["system_id"]
399 raid_id = _get_raid_id_by_name(hostname, raid_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200400 LOG.debug('delete_raid: {} {}'.format(system_id, raid_id))
401 maas.delete(
402 u"api/2.0/nodes/{0}/raid/{1}/".format(system_id, raid_id)).read()
azvyagintsevbca1f462018-05-25 19:06:46 +0300403
404 result["new"] = "Raid {0} deleted".format(raid_name)
405 return result
406
407# END RAID SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200408# BLOCKDEVICES SECTION
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100409
azvyagintsevbca1f462018-05-25 19:06:46 +0300410
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100411def list_blockdevices(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300412 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100413 Get list of all blockdevices (disks) on machine
414
415 CLI Example:
416
417 .. code-block:: bash
418
419 salt 'maas-node' maasng.list_blockdevices server_hostname
420 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300421 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100422 ret = {}
423
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200424 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100425 system_id = get_machine(hostname)["system_id"]
426 LOG.info(system_id)
427
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200428 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100429
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200430 json_res = json.loads(
431 maas.get(u"api/2.0/nodes/{0}/blockdevices/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100432 LOG.info(json_res)
433 for item in json_res:
434 ret[item["name"]] = item
435
436 return ret
437
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200438
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100439def get_blockdevice(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300440 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100441 Get information about blockdevice (disk) on machine
442
443 CLI Example:
444
445 .. code-block:: bash
446
447 salt 'maas-node' maasng.get_blockdevice server_hostname sda
448 salt-call maasng.get_blockdevice server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300449 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100450
451 return list_blockdevices(hostname)[name]
452
azvyagintsevbca1f462018-05-25 19:06:46 +0300453# END BLOCKDEVICES SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200454# PARTITIONS
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100455
azvyagintsevbca1f462018-05-25 19:06:46 +0300456
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100457def list_partitions(hostname, device):
azvyagintsevbca1f462018-05-25 19:06:46 +0300458 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100459 Get list of all partitions on specific device located on specific machine
460
461 CLI Example:
462
463 .. code-block:: bash
464
465 salt 'maas-node' maasng.list_partitions server_hostname sda
466 salt-call maasng.list_partitions server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300467 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100468 ret = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200469 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100470 system_id = get_machine(hostname)["system_id"]
471 LOG.info(system_id)
472
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200473 partitions = get_blockdevice(hostname, device)["partitions"]
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100474 LOG.info(partitions)
475
476 #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 +0200477 # LOG.info(json_res)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100478
479 if len(device) > 0:
480 for item in partitions:
481 name = item["path"].split('/')[-1]
482 ret[name] = item
483
484 return ret
485
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200486
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100487def get_partition(hostname, device, partition):
azvyagintsevbca1f462018-05-25 19:06:46 +0300488 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100489 Get information about specific parition on device located on machine
490
491 CLI Example:
492
493 .. code-block:: bash
494
495 salt 'maas-node' maasng.get_partition server_hostname disk_name partition
496 salt-call maasng.get_partition server_hostname disk_name partition
497
498 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300499 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100500
501 return list_partitions(partition)[name]
502
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200503
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100504def create_partition(hostname, disk, size, fs_type=None, mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300505 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100506 Create new partition on device.
507
508 CLI Example:
509
510 .. code-block:: bash
511
512 salt 'maas-node' maasng.create_partition server_hostname disk_name 10 ext4 "/"
513 salt-call maasng.create_partition server_hostname disk_name 10 ext4 "/"
azvyagintsevbca1f462018-05-25 19:06:46 +0300514 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200515 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100516 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200517 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100518 system_id = get_machine(hostname)["system_id"]
519 LOG.info(system_id)
520
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200521 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100522 LOG.info(device_id)
523
524 value, unit = size[:-1], size[-1]
525 calc_size = str(int(value) * SIZE[unit])
526 LOG.info(calc_size)
527
528 data = {
529 "size": calc_size
530 }
531
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200532 # TODO validation
533 partition = json.loads(maas.post(
534 u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100535 LOG.info(partition)
536 result["partition"] = "Partition created on {0}".format(disk)
537
538 if fs_type != None:
539 data_fs_type = {
540 "fstype": fs_type
541 }
542 partition_id = str(partition["id"])
543 LOG.info("Partition id: " + partition_id)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200544 # TODO validation
545 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
546 system_id, device_id, partition_id), "format", **data_fs_type).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100547 LOG.info(json_res)
548 result["filesystem"] = "Filesystem {0} created".format(fs_type)
549
550 if mount != None:
551 data = {
552 "mount_point": mount
553 }
554
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200555 # TODO validation
556 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
557 system_id, device_id, str(partition['id'])), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100558 LOG.info(json_res)
559 result["mount"] = "Mount point {0} created".format(mount)
560
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100561 return result
562
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200563
Dzmitry Stremkouskid95bd2e2018-12-03 17:35:46 +0100564def list_dnsresources():
565 """
566 List DNS resources known to MAAS.
567
568 CLI Example:
569
570 .. code-block:: bash
571
572 salt-call maasng.list_dnsresources
573
574 """
575 result = {}
576 res_json = []
577 maas = _create_maas_client()
578
579 # TODO validation
580 result = json.loads(maas.get(u"api/2.0/dnsresources/").read())
581 for elem in result:
582 ip_addresses = []
583 for ip in elem["ip_addresses"]:
584 ip_addresses.append(ip["ip"])
585 res_json.append(
586 {
587 "ip_addresses": ip_addresses,
588 "id": elem["id"],
589 "fqdn": elem["fqdn"],
590 "hostname": elem["fqdn"].split(".")[0]
591 }
592 )
593
594 LOG.debug(res_json)
595
596 return res_json
597
598
599def list_ipaddresses():
600 """
601 List IP addresses known to MAAS.
602
603 CLI Example:
604
605 .. code-block:: bash
606
607 salt-call maasng.list_ipaddresses
608
609 """
610 result = {}
611 res_json = []
612 maas = _create_maas_client()
613
614 # TODO validation
615 result = json.loads(maas.get(u"api/2.0/ipaddresses/?all").read())
616 for elem in result:
617 res_json.append(
618 {
619 "ip": elem["ip"],
620 "owner": { "username": elem["owner"]["username"] },
621 "created": elem["created"],
622 "alloc_type_name": elem["alloc_type_name"],
623 "alloc_type": elem["alloc_type"],
624 "subnet": {
625 "id": elem["subnet"]["id"],
626 "cidr": elem["subnet"]["cidr"],
627 "name": elem["subnet"]["name"]
628 }
629 }
630 )
631
632 LOG.debug(res_json)
633
634 return res_json
635
636
637def reserve_ipaddress(hostname,subnet,ip=""):
638 """
639 Reserve IP address for specified hostname in specified subnet
640
641 CLI Example:
642
643 .. code-block:: bash
644
645 salt-call maasng.reserve_ipaddress hostname 192.168.0.0/24 192.168.0.254
646 salt-call maasng.reserve_ipaddress hostname 192.168.0.0/24
647
648 """
649 result = {}
650 data = {}
651 maas = _create_maas_client()
652
653 data = {
654 "subnet": subnet,
655 "hostname": hostname
656 }
657
658 if ip:
659 data["ip"] = ip
660
661 # TODO validation
662 result = json.loads(maas.post(u"api/2.0/ipaddresses/", "reserve", **data).read())
663 res_json = {
664 "created": result["created"],
665 "type": "DNS",
666 "hostname": hostname,
667 "ip": result["ip"]
668 }
669
670 LOG.info(res_json)
671
672 return res_json
673
674
675def release_ipaddress(ipaddress):
676 """
677 Release an IP address that was previously reserved by the user.
678
679 CLI Example:
680
681 .. code-block:: bash
682
683 salt-call maasng.release_ipaddress 192.168.2.10
684
685 """
686 result = {}
687 data = {}
688 maas = _create_maas_client()
689
690 data = {
691 "ip": ipaddress
692 }
693
694 # TODO validation
695 return maas.post(u"api/2.0/ipaddresses/", "release", **data).read()
696
697
698def sync_address_pool():
699 """
700 Manage address pool for ext_pillar.
701
702 CLI Example:
703
704 .. code-block:: bash
705
706 salt-call maasng.sync_address_pool
707
708 """
709
710 address_pool = __pillar__["address_pool"]
711 LOG.debug("Address pool:")
712 LOG.debug(address_pool)
713
714 cluster_networks = __pillar__["cluster_networks"]
715 LOG.debug("Cluster networks:")
716 LOG.debug(cluster_networks)
717
718 dnsresources = list_dnsresources()
719 LOG.debug("DNS resources:")
720 LOG.debug(dnsresources)
721
722 machines = list_machines()
723 LOG.debug("Machines:")
724 LOG.debug(machines)
725
726 for net in address_pool:
727 if net == "external":
728 continue
729 for addr in address_pool[net]['pool']:
730 ipaddr = address_pool[net]['pool'][addr]
731 if ipaddr == "":
732 LOG.debug('Releasing IP address for: ' + addr)
733 release_required = False
734 for elem in dnsresources:
735 if elem["hostname"] == addr:
736 release_required = True
737 ip_addresses = elem["ip_addresses"]
738 if release_required:
739 for ip in ip_addresses:
740 res = release_ipaddress(ip)
741 LOG.debug(res)
742 else:
743 LOG.debug('IP for ' + addr + ' already released')
744 elif is_valid_ipv6(ipaddr) or is_valid_ipv4(ipaddr):
745 LOG.debug('Ensure static IP address "' + ipaddr + '" for ' + addr)
746 reserve_required = True
747 for elem in dnsresources:
748 if elem["hostname"] == addr:
749 reserve_required = False
750 for elem, elemval in machines.iteritems():
751 for iface in elemval["interface_set"]:
752 for link in iface["links"]:
753 if "ip_address" in link:
754 if link["ip_address"] == ipaddr:
755 reserve_required = False
756 if reserve_required:
757 res = reserve_ipaddress(addr, cluster_networks[net]['cidr'], ipaddr)
758 reserve_required = False
759 LOG.debug(res)
760 else:
761 LOG.debug('Static IP address "' + ipaddr + '" for ' + addr + ' ensured')
762 else:
763 LOG.debug('Requesting IP address for' + addr)
764 reserve_required = True
765 for elem in dnsresources:
766 if elem["hostname"] == addr:
767 reserve_required = False
768 ip = elem["ip_addresses"][0]
769 if reserve_required:
770 res = reserve_ipaddress(addr, cluster_networks[net]['cidr'])
771 LOG.debug(res)
772 else:
773 LOG.debug(addr + " already has IP " + ip)
774
775 return True
776
777
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100778def delete_partition(hostname, disk, partition_name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300779 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100780 Delete partition on device.
781
782 CLI Example:
783
784 .. code-block:: bash
785
786 salt 'maas-node' maasng.delete_partition server_hostname disk_name partition_name
787 salt-call maasng.delete_partition server_hostname disk_name partition_name
788
789 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300790 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100791 result = {}
792 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200793 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100794 system_id = get_machine(hostname)["system_id"]
795 LOG.info(system_id)
796
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200797 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100798 LOG.info(device_id)
799
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200800 partition_id = _get_partition_id_by_name(hostname, disk, partition_name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100801
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200802 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
803 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100804 result["new"] = "Partition {0} deleted".format(partition_name)
805 return result
806
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200807
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100808def delete_partition_by_id(hostname, disk, partition_id):
azvyagintsevbca1f462018-05-25 19:06:46 +0300809 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100810 Delete partition on device. Partition spefified by id of parition
811
812 CLI Example:
813
814 .. code-block:: bash
815
816 salt 'maas-node' maasng.delete_partition_by_id server_hostname disk_name partition_id
817 salt-call maasng.delete_partition_by_id server_hostname disk_name partition_id
818
819 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300820 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100821 result = {}
822 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200823 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100824 system_id = get_machine(hostname)["system_id"]
825 LOG.info(system_id)
826
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200827 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100828 LOG.info(device_id)
829
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200830 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
831 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100832 result["new"] = "Partition {0} deleted".format(partition_id)
833 return result
azvyagintsevbca1f462018-05-25 19:06:46 +0300834# END PARTITIONS
835# DISK LAYOUT
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100836
azvyagintsevbca1f462018-05-25 19:06:46 +0300837
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200838def drop_storage_schema(hostname, disk=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300839 """
840 #1. Drop lv
841 #2. Drop vg
842 #3. Drop md # need to zero-block?
843 #3. Drop part
844 """
845
846 if __opts__['test']:
847 ret['result'] = None
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200848 ret['comment'] = 'Storage schema on {0} will be removed'.format(
849 hostname)
azvyagintsevbca1f462018-05-25 19:06:46 +0300850 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200851 # TODO validation if exists
azvyagintsevbca1f462018-05-25 19:06:46 +0300852 vgs = list_volume_groups(hostname)
853 for vg in vgs:
854 delete_volume_group(hostname, vg)
855
856 raids = list_raids(hostname)
857 for raid in raids:
858 delete_raid(hostname, raid)
859
860 blocks = list_blockdevices(hostname)
861 for block_d in blocks:
862 partitions = __salt__['maasng.list_partitions'](hostname, block_d)
863 for partition_name, partition in partitions.iteritems():
864 LOG.info('delete partition:\n{}'.format(partition))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200865 __salt__['maasng.delete_partition_by_id'](
866 hostname, block_d, partition["id"])
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200867
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100868
869def 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 +0300870 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100871 Update disk layout. Flat or LVM layout supported.
872
873 CLI Example:
874
875 .. code-block:: bash
876
877 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
878 salt-call maasng.update_disk_layout server_hostname lvm root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None
879
880 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300881 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100882 result = {}
883 data = {
884 "storage_layout": layout,
885 }
886
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200887 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100888 system_id = get_machine(hostname)["system_id"]
889 LOG.info(system_id)
890
azvyagintsevbca1f462018-05-25 19:06:46 +0300891 if layout == 'custom':
892 drop_storage_schema(hostname)
893 result["new"] = {
894 "storage_layout": layout,
895 }
896
897 return result
898
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100899 if root_size != None:
900 bit_size = str(root_size * 1073741824)
901 LOG.info(bit_size)
902 data["root_size"] = bit_size
903
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100904 if root_device != None:
905 LOG.info(root_device)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200906 data["root_device"] = str(
907 _get_blockdevice_id_by_name(hostname, root_device))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100908
909 if layout == 'lvm':
910 if volume_group != None:
911 LOG.info(volume_group)
912 data["vg_name"] = volume_group
913 if volume_name != None:
914 LOG.info(volume_name)
915 data["lv_name"] = volume_name
916 if volume_size != None:
917 vol_size = str(volume_size * 1073741824)
918 LOG.info(vol_size)
919 data["lv_size"] = vol_size
920
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200921 # TODO validation
922 json_res = json.loads(maas.post(
923 u"api/2.0/machines/{0}/".format(system_id), "set_storage_layout", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100924 LOG.info(json_res)
925 result["new"] = {
926 "storage_layout": layout,
927 }
928
929 return result
930
azvyagintsevbca1f462018-05-25 19:06:46 +0300931# END DISK LAYOUT
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200932# LVM
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100933
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200934
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100935def list_volume_groups(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300936 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100937 Get list of all volume group on machine.
938
939 CLI Example:
940
941 .. code-block:: bash
942
943 salt 'maas-node' maasng.list_volume_groups server_hostname
944 salt-call maasng.list_volume_groups server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300945 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100946 volume_groups = {}
947
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200948 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100949 system_id = get_machine(hostname)["system_id"]
950 LOG.info(system_id)
951
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200952 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100953
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200954 json_res = json.loads(
955 maas.get(u"api/2.0/nodes/{0}/volume-groups/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100956 LOG.info(json_res)
957 for item in json_res:
958 volume_groups[item["name"]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200959 # return
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100960 return volume_groups
961
962
963def get_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300964 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100965 Get information about specific volume group on machine.
966
967 CLI Example:
968
969 .. code-block:: bash
970
971 salt 'maas-node' maasng.list_blockdevices server_hostname
972 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300973 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200974 # TODO validation that exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100975 return list_volume_groups(hostname)[name]
976
977
978def create_volume_group(hostname, volume_group_name, disks=[], partitions=[]):
azvyagintsevbca1f462018-05-25 19:06:46 +0300979 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100980 Create new volume group on machine. Disks or partitions needs to be provided.
981
982 CLI Example:
983
984 .. code-block:: bash
985
986 salt 'maas-node' maasng.create_volume_group volume_group_name, disks=[sda,sdb], partitions=[]
987 salt-call maasng.create_volume_group server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300988 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100989 result = {}
990
991 data = {
992 "name": volume_group_name,
993 }
994
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200995 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100996 system_id = get_machine(hostname)["system_id"]
997 LOG.info(system_id)
998
999 disk_ids = []
1000 partition_ids = []
1001
1002 for disk in disks:
1003 p_disk = get_blockdevice(hostname, disk)
1004 if p_disk["partition_table_type"] == None:
1005 disk_ids.append(str(p_disk["id"]))
1006 else:
azvyagintsevf3515c82018-06-26 18:59:05 +03001007 result["error"] = "Device {0} on" \
1008 "machine {1} cointains partition" \
1009 "table".format(disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001010 return result
1011
1012 for partition in partitions:
1013 try:
1014 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001015 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001016 partition_ids.append(str(device_part[partition]["id"]))
1017 except KeyError:
azvyagintsevf3515c82018-06-26 18:59:05 +03001018 result["error"] = "Partition {0} does" \
1019 "not exists on " \
1020 "machine {1}".format(partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001021 return result
1022
1023 data["block_devices"] = disk_ids
1024 data["partitions"] = partition_ids
1025 LOG.info(partition_ids)
1026 LOG.info(partitions)
1027
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001028 # TODO validation
1029 json_res = json.loads(maas.post(
1030 u"api/2.0/nodes/{0}/volume-groups/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001031 LOG.info(json_res)
1032 result["new"] = "Volume group {0} created".format(json_res["name"])
1033
1034 return result
1035
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001036
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001037def delete_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +03001038 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001039 Delete volume group on machine.
1040
1041 CLI Example:
1042
1043 .. code-block:: bash
1044
1045 salt 'maas-node' maasng.delete_volume_group server_hostname vg0
1046 salt-call maasng.delete_volume_group server_hostname vg0
azvyagintsevbca1f462018-05-25 19:06:46 +03001047 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001048
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001049 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001050 system_id = get_machine(hostname)["system_id"]
azvyagintsevbca1f462018-05-25 19:06:46 +03001051 LOG.debug('delete_volume_group:{}'.format(system_id))
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001052
azvyagintsevbca1f462018-05-25 19:06:46 +03001053 vg_id = str(_get_volume_group_id_by_name(hostname, name))
1054 for vol in get_volumes(hostname, name):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001055 delete_volume(hostname, vol, name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001056
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001057 # TODO validation
1058 json_res = json.loads(maas.delete(
1059 u"api/2.0/nodes/{0}/volume-group/{1}/".format(system_id, vg_id)).read() or 'null')
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001060 LOG.info(json_res)
1061
1062 return True
1063
1064
azvyagintsevf3515c82018-06-26 18:59:05 +03001065def create_volume(hostname, volume_name, volume_group, size, fs_type=None,
1066 mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +03001067 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001068 Create volume on volume group.
1069
1070 CLI Example:
1071
1072 .. code-block:: bash
1073
1074 salt 'maas-node' maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
1075 salt-call maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
azvyagintsevbca1f462018-05-25 19:06:46 +03001076 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001077
1078 data = {
1079 "name": volume_name,
1080 }
1081
1082 value, unit = size[:-1], size[-1]
1083 bit_size = str(int(value) * SIZE[unit])
1084 LOG.info(bit_size)
1085
1086 data["size"] = bit_size
1087
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001088 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001089 system_id = get_machine(hostname)["system_id"]
1090 LOG.info(system_id)
1091
1092 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
1093
1094 LOG.info(volume_group_id)
1095
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001096 # TODO validation
1097 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
1098 system_id, volume_group_id), "create_logical_volume", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001099 LOG.info(json_res)
1100
1101 if fs_type != None or mount != None:
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001102 ret = create_volume_filesystem(
1103 hostname, volume_group + "-" + volume_name, fs_type, mount)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001104
1105 return True
1106
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001107
azvyagintsevbca1f462018-05-25 19:06:46 +03001108def delete_volume(hostname, volume_name, volume_group):
1109 """
1110 Delete volume from volume group.
1111 Tips: maas always use 'volume_group-volume_name' name schema.Example: 'vg0-glusterfs'
1112 This function expexts same format.
1113
1114 CLI Example:
1115
1116 .. code-block:: bash
1117
1118 salt 'maas-node' maasng.delete_volume server_hostname volume_name volume_group
1119 salt 'maas-node' maasng.delete_volume server_hostname vg0-vol0 vg0
1120 salt-call maasng.delete_volume server_hostname volume_name volume_group
1121 """
1122
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001123 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +03001124 system_id = get_machine(hostname)["system_id"]
1125 LOG.debug('delete_volume:{}'.format(system_id))
1126
1127 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001128 volume_id = str(_get_volume_id_by_name(
1129 hostname, volume_name, volume_group))
azvyagintsevbca1f462018-05-25 19:06:46 +03001130
1131 if None in [volume_group_id, volume_id]:
1132 return False
1133
1134 data = {
1135 "id": volume_id,
1136 }
1137
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001138 # TODO validation
1139 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
1140 system_id, volume_group_id), "delete_logical_volume", **data).read() or 'null')
azvyagintsevbca1f462018-05-25 19:06:46 +03001141 return True
1142
1143
1144def get_volumes(hostname, vg_name):
1145 """
1146 Get list of volumes in volume group.
1147 """
1148 volumes = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001149 _volumes = list_volume_groups(
1150 hostname)[vg_name].get('logical_volumes', False)
azvyagintsevbca1f462018-05-25 19:06:46 +03001151 if _volumes:
1152 for item in _volumes:
1153 volumes[item["name"]] = item
1154 return volumes
1155
1156# END LVM
1157
1158
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001159def create_volume_filesystem(hostname, device, fs_type=None, mount=None):
1160
1161 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001162 system_id = get_machine(hostname)["system_id"]
1163
1164 blockdevices_id = _get_blockdevice_id_by_name(hostname, device)
1165 data = {}
1166 if fs_type != None:
1167 data["fstype"] = fs_type
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001168 # TODO validation
1169 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
1170 system_id, blockdevices_id), "format", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001171 LOG.info(json_res)
1172
1173 if mount != None:
1174 data["mount_point"] = mount
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001175 # TODO validation
1176 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
1177 system_id, blockdevices_id), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001178 LOG.info(json_res)
1179
1180 return True
1181
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001182
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001183def set_boot_disk(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +03001184 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001185 Create volume on volume group.
1186
1187 CLI Example:
1188
1189 .. code-block:: bash
1190
1191 salt 'maas-node' maasng.set_boot_disk server_hostname disk_name
1192 salt-call maasng.set_boot_disk server_hostname disk_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001193 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001194 data = {}
1195 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001196 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001197 system_id = get_machine(hostname)["system_id"]
1198 blockdevices_id = _get_blockdevice_id_by_name(hostname, name)
1199
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001200 maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
1201 system_id, blockdevices_id), "set_boot_disk", **data).read()
azvyagintsevf3515c82018-06-26 18:59:05 +03001202 # TODO validation for error response
1203 # (disk does not exists and node does not exists)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001204 result["new"] = "Disk {0} was set as bootable".format(name)
1205
1206 return result
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001207
azvyagintsevbca1f462018-05-25 19:06:46 +03001208# NETWORKING
1209
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001210
1211def list_fabric():
azvyagintsevbca1f462018-05-25 19:06:46 +03001212 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001213 Get list of all fabric
1214
1215 CLI Example:
1216
1217 .. code-block:: bash
1218
1219 salt 'maas-node' maasng.list_fabric
azvyagintsevbca1f462018-05-25 19:06:46 +03001220 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001221 fabrics = {}
1222 maas = _create_maas_client()
1223 json_res = json.loads(maas.get(u'api/2.0/fabrics/').read())
1224 LOG.info(json_res)
1225 for item in json_res:
1226 fabrics[item["name"]] = item
1227 return fabrics
1228
1229
azvyagintsevf3515c82018-06-26 18:59:05 +03001230def check_fabric(name):
1231 """
1232 Simple check that fabric already defined
1233 Return format:
1234 update - require update
1235 correct - fully coincides # not implemented
1236 not_exist - need's to be created
1237 """
1238
1239 ret = 'not_exist'
1240 fabrics = list_fabric()
1241 if name in fabrics.keys():
1242 LOG.debug("Requested fabrics with name:{} already exist".format(name))
1243 ret = 'update'
1244 return ret
1245
1246
1247def check_fabric_guess_with_cidr(name, cidrs):
1248 """
1249 Check, that fabric already defined OR it was autodiscovered
1250 WA to fix issue with hardcoded 'fabric-0'
1251 - Find all auto-discovered subnets by cidr
1252 - find all subnets, that SHOULD be configured to THIS subent
1253 Warning: most probably, will fail if some subnet defined
1254 to another fabric :(
1255
1256 { 'update' : ID } - require update
1257 { 'correct' : ID } - fully coincides # not implemented
1258 { 'not_exist' : None } - need's to be created
1259
1260 CLI Example:
1261
1262 .. code-block:: bash
1263
1264 salt 'maas-node' maasng.check_fabric_guess_with_cidr name='' cidrs=[]
1265 """
1266
1267 ret = {'not_exist': None}
1268 fabrics = list_fabric()
1269 # Simple check
1270 if name in fabrics:
1271 LOG.debug("Requested fabrics with name:{} already exist".format(name))
1272 f_id = fabrics[name]['id']
1273 ret = {'update': f_id}
1274 # Cidr check
1275 # All discovered subnets by cidr
1276 d_subnets = list_subnets(sort_by='cidr')
1277 # Check, that requested cidr already in discovered.
1278 # If it is - it would mean that fabric already
1279 # exist(fabric-0,most probably) but should be renamed.
1280 # Works only for first shot ;(
1281 # due curren-single-maas logic for 'vlan-subnet' mapping.
1282 # Probably, it will fail with future MAAS releases.
1283 for cidr in cidrs:
1284 if cidr in d_subnets:
1285 f_id = d_subnets[cidr]['vlan']['fabric_id']
1286 f_name = d_subnets[cidr]['vlan']['fabric']
1287 LOG.warning("Detected cidr:{} in fabric:{}".format(cidr, f_name))
1288 LOG.warning("Guessing, that fabric "
1289 "with current name:{}\n should be "
1290 "renamed to:{}".format(f_name, name))
1291 ret = {'update': f_id}
1292 return ret
1293 return ret
1294
1295
azvyagintsevf0904ac2018-07-05 18:53:26 +03001296def create_fabric(name, description=None, fabric_id=None, update=False):
azvyagintsevbca1f462018-05-25 19:06:46 +03001297 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001298 Create new fabric.
1299
1300 CLI Example:
1301
1302 .. code-block:: bash
1303
azvyagintsevf3515c82018-06-26 18:59:05 +03001304 salt 'maas-node' maasng.create_fabric name='123'
azvyagintsevbca1f462018-05-25 19:06:46 +03001305 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001306 result = {}
1307 data = {
1308 "name": name,
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001309 "class_type": '',
1310
1311 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001312 if description:
1313 data['description'] = description
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001314
1315 maas = _create_maas_client()
azvyagintsevf0904ac2018-07-05 18:53:26 +03001316 json_res = None
azvyagintsevf3515c82018-06-26 18:59:05 +03001317 try:
1318 if update:
1319 json_res = json.loads(
1320 maas.put(u"api/2.0/fabrics/{0}/".format(fabric_id),
1321 **data).read())
1322 result["new"] = "Fabric {0} created".format(json_res["name"])
1323 else:
1324 json_res = json.loads(
1325 maas.post(u"api/2.0/fabrics/", None, **data).read())
1326 result["changes"] = "Fabric {0} updated".format(json_res["name"])
1327 except Exception as inst:
1328 LOG.debug("create_fabric data:{}".format(data))
azvyagintsev06b74932019-04-03 15:27:31 +03001329 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001330 LOG.error("Message:{0}".format(m))
1331 result['result'] = False
1332 result['comment'] = 'Error creating fabric: {0}'.format(name)
1333 result['error'] = m
1334 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001335 LOG.debug("crete_fabric:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001336 result['result'] = True
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001337 return result
1338
1339
azvyagintsevf3515c82018-06-26 18:59:05 +03001340def list_subnets(sort_by='name'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001341 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001342 Get list of subnets from maas server
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001343
1344 CLI Example:
1345
1346 .. code-block:: bash
1347
azvyagintsevf3515c82018-06-26 18:59:05 +03001348 salt 'maas-node' maasng.list_subnets
azvyagintsevbca1f462018-05-25 19:06:46 +03001349 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001350 subnets = {}
1351 maas = _create_maas_client()
1352 json_res = json.loads(maas.get(u'api/2.0/subnets/').read())
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001353 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001354 subnets[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001355 return subnets
1356
1357
azvyagintsevf3515c82018-06-26 18:59:05 +03001358def list_vlans(fabric, sort_by='vid'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001359 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001360 Get list of vlans in fabric
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001361
1362 CLI Example:
1363
1364 .. code-block:: bash
1365
azvyagintsevf3515c82018-06-26 18:59:05 +03001366 salt 'maas-node' maasng.list_vlans fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001367 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001368 vlans = {}
1369 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001370 fabric_id = get_fabricid(fabric)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001371
azvyagintsevf3515c82018-06-26 18:59:05 +03001372 try:
1373 json_res = json.loads(
1374 maas.get(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id)).read())
1375 except Exception as inst:
azvyagintsev06b74932019-04-03 15:27:31 +03001376 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001377 LOG.error("Message:{0}".format(m))
1378 LOG.debug(json_res)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001379 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001380 vlans[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001381 return vlans
1382
1383
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001384def get_fabricid(fabric):
azvyagintsevbca1f462018-05-25 19:06:46 +03001385 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001386 Get id for specific fabric
1387
1388 CLI Example:
1389
1390 .. code-block:: bash
1391
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001392 salt 'maas-node' maasng.get_fabricid fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001393 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001394 try:
1395 return list_fabric()[fabric]['id']
1396 except KeyError:
azvyagintsevefb6f5d2018-07-10 14:16:19 +03001397 return {"error": "Fabric not found on MaaS server"}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001398
1399
azvyagintsevf3515c82018-06-26 18:59:05 +03001400def check_vlan_in_fabric(fabric, vlan):
1401 """
1402 Check that VLAN exactly defined
1403 Return format:
1404 update - require update
1405 correct - fully coincides # not implemented
1406 not_exist - need's to be created
1407 """
1408
1409 ret = 'not_exist'
1410 vlans = list_vlans(fabric)
1411 if vlan in vlans.keys():
1412 LOG.debug("Requested VLAN:{} already exist"
1413 "in FABRIC:{}".format(vlan, fabric))
1414 ret = 'update'
1415 return ret
1416
1417
Petr Ruzicka80471852018-07-13 14:08:27 +02001418def create_vlan_in_fabric(name, fabric, vlan, description, primary_rack, mtu=1500,
azvyagintsevf3515c82018-06-26 18:59:05 +03001419 dhcp_on=False, update=False, vlan_id=""):
azvyagintsevbca1f462018-05-25 19:06:46 +03001420 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001421 Update vlan
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001422 CLI Example:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001423 .. code-block:: bash
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001424 salt 'maas-node' maasng.update_vlan name, fabric, vid, description, dhcp_on
azvyagintsevbca1f462018-05-25 19:06:46 +03001425 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001426 result = {}
1427
1428 data = {
1429 "name": name,
1430 "dhcp_on": str(dhcp_on),
1431 "description": description,
azvyagintsev6913e5e2018-07-05 11:42:53 +03001432 "primary_rack": list_racks()[primary_rack]['system_id'],
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001433 }
Michael Polenchukd25da792018-07-19 18:27:11 +04001434 if mtu:
1435 data['mtu'] = str(mtu)
azvyagintsevf3515c82018-06-26 18:59:05 +03001436 vlan = str(vlan)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001437 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001438 fabric_id = get_fabricid(fabric)
azvyagintsevf3515c82018-06-26 18:59:05 +03001439 try:
1440 if update:
1441 # MAAS have buggy logic here. Fallowing api reference, here should
1442 # be passed VID - which mean, API ID for vlan.
1443 # Otherwise, at least for maas 2.3.3-6498-ge4db91d exactly VLAN
1444 # should be passed. so, make temp.backward-convertation.
1445 # json_res = json.loads(maas.put(u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id,vlan_id), **data).read())
1446 json_res = json.loads(maas.put(
1447 u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id, vlan),
1448 **data).read())
1449 else:
1450 data['vid'] = str(vlan)
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001451 json_res = json.loads(maas.post(
1452 u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id), None, **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001453 except Exception as inst:
1454 LOG.debug("create_vlan_in_fabric data:{}".format(data))
azvyagintsev06b74932019-04-03 15:27:31 +03001455 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001456 LOG.error("Message:{0}".format(m))
1457 result['result'] = False
1458 result['comment'] = 'Error updating vlan: {0}'.format(name)
1459 result['error'] = m
1460 return result
1461 LOG.debug("create_vlan_in_fabric:{}".format(json_res))
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001462 result["new"] = "Vlan {0} was updated".format(json_res["name"])
1463
1464 return result
azvyagintsevbca1f462018-05-25 19:06:46 +03001465
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001466
azvyagintsevf3515c82018-06-26 18:59:05 +03001467def check_subnet(cidr, name, fabric, gateway_ip):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001468 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001469 Check that subnet exactly defined
1470 Return format:
1471 update - require update
1472 correct - fully coincides # not implemented
1473 not_exist - need's to be created
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001474 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001475
1476 ret = 'not_exist'
1477 subnets = list_subnets(sort_by='cidr')
1478 if cidr in subnets.keys():
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001479 LOG.debug("Requested subnet cidr:{} already exist".format(cidr))
1480 ret = 'update'
azvyagintsevf3515c82018-06-26 18:59:05 +03001481 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001482
1483
azvyagintsevf3515c82018-06-26 18:59:05 +03001484def create_subnet(cidr='', name='', fabric='', gateway_ip='', vlan='',
1485 update=False, subnet_id=''):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001486 """
1487 Create subnet
1488
1489 CLI Example:
1490
1491 .. code-block:: bash
1492
1493 salt 'maas-node' maasng.create_subnet cidr, name, fabric, gateway_ip
1494 """
1495
1496 fabric_id = get_fabricid(fabric)
1497 result = {}
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001498 vlan = str(vlan)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001499 data = {
1500 "cidr": cidr,
1501 "name": name,
1502 "fabric": str(fabric_id),
1503 "gateway_ip": gateway_ip,
azvyagintsevf3515c82018-06-26 18:59:05 +03001504 'vlan': vlan,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001505 }
1506 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001507 # FIXME: vlan definition not work in 2.3.3-6498-ge4db91d.
1508 LOG.warning("Ignoring parameter vlan:{}".format(vlan))
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001509 data.pop('vlan', '')
azvyagintsevf3515c82018-06-26 18:59:05 +03001510 try:
1511 if update:
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001512 json_res = json.loads(
1513 maas.put(u"api/2.0/subnets/{0}/".format(subnet_id), **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001514 else:
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001515 json_res = json.loads(
1516 maas.post(u"api/2.0/subnets/", None, **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001517 except Exception as inst:
1518 LOG.debug("create_subnet data:{}".format(data))
azvyagintsev06b74932019-04-03 15:27:31 +03001519 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001520 LOG.error("Message:{0}".format(m))
1521 result['result'] = False
1522 result['comment'] = 'Error creating subnet: {0}'.format(name)
1523 result['error'] = m
1524 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001525 LOG.debug("create_subnet:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001526 result["new"] = "Subnet {0} with CIDR {1}" \
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001527 "and gateway {2} was created".format(
1528 name, cidr, gateway_ip)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001529
1530 return result
1531
1532
1533def get_subnet(subnet):
1534 """
1535 Get details for specific subnet
1536
1537 CLI Example:
1538
1539 .. code-block:: bash
1540
1541 salt 'maas-node' maasng.get_subnet subnet_name
1542 """
1543 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001544 return list_subnets()[subnet]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001545 except KeyError:
1546 return {"error": "Subnet not found on MaaS server"}
1547
1548
1549def get_subnetid(subnet):
1550 """
1551 Get id for specific subnet
1552
1553 CLI Example:
1554
1555 .. code-block:: bash
1556
1557 salt 'maas-node' maasng.get_subnetid subnet_name
1558 """
1559 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001560 return list_subnets()[subnet]['id']
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001561 except KeyError:
1562 return {"error": "Subnet not found on MaaS server"}
1563
1564
1565def list_ipranges():
1566 """
1567 Get list of all ipranges from maas server
1568
1569 CLI Example:
1570
1571 .. code-block:: bash
1572
1573 salt 'maas-node' maasng.list_ipranges
1574 """
1575 ipranges = {}
1576 maas = _create_maas_client()
1577 json_res = json.loads(maas.get(u'api/2.0/ipranges/').read())
1578 for item in json_res:
1579 ipranges[item["start_ip"]] = item
1580 return ipranges
1581
1582
azvyagintsevf0904ac2018-07-05 18:53:26 +03001583def create_iprange(type_range, start_ip, end_ip, subnet=None, comment=None):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001584 """
1585 Create ip range
1586
1587 CLI Example:
1588
1589 .. code-block:: bash
1590
1591 salt 'maas-node' maasng.create_iprange type, start ip, end ip, comment
1592 """
1593 result = {}
1594
1595 data = {
1596 "type": type_range,
1597 "start_ip": start_ip,
1598 "end_ip": end_ip,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001599 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001600 if comment:
1601 data['comment'] = comment
azvyagintsevf0904ac2018-07-05 18:53:26 +03001602 if subnet:
1603 subnet_id = list_subnets()[subnet]['id']
1604 data['subnet'] = str(subnet_id)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001605 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001606 _name = "Type:{}: {}-{}".format(type_range, start_ip, end_ip)
1607 try:
1608 json_res = json.loads(
1609 maas.post(u"api/2.0/ipranges/", None, **data).read())
1610 except Exception as inst:
azvyagintsev06b74932019-04-03 15:27:31 +03001611 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001612 LOG.error("Message:{0}".format(m))
1613 result['result'] = False
1614 result['comment'] = 'Error creating iprange:{0}'.format(_name)
1615 result['error'] = m
1616 return result
1617 result["new"] = "Iprange: {0} has been created".format(_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001618 LOG.debug("create_iprange:{}".format(json_res))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001619
1620 return result
1621
1622
1623def get_iprangeid(start_ip):
1624 """
1625 Get id for ip range from maas server
1626
1627 CLI Example:
1628
1629 .. code-block:: bash
1630
1631 salt 'maas-node' maasng.get_iprangeid start_ip
1632 """
1633 try:
1634 return list_ipranges()[start_ip]['id']
1635 except KeyError:
1636 return {"error": "Ip range not found on MaaS server"}
1637
1638
1639def get_startip(start_ip):
1640 """
1641 Get start ip for ip range
1642
1643 CLI Example:
1644
1645 .. code-block:: bash
1646
1647 salt 'maas-node' maasng.get_startip start ip
1648 """
1649 try:
1650 return list_ipranges()[start_ip]
1651 except KeyError:
1652 return {"error": "Ip range not found on MaaS server"}
azvyagintsevbca1f462018-05-25 19:06:46 +03001653# END NETWORKING
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001654
1655# MAAS CONFIG SECTION
1656
1657
azvyagintsev58947072018-06-29 12:09:48 +03001658def _getHTTPCode(url):
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001659 code = '003'
1660 m = ''
azvyagintsev58947072018-06-29 12:09:48 +03001661 try:
1662 connection = urllib2.urlopen(url)
1663 code = connection.getcode()
1664 connection.close()
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001665 except (urllib2.HTTPError, urllib2.URLError) as e:
1666 try:
1667 code = e.getcode()
1668 except:
1669 m = e.reason
1670 pass
azvyagintsevf3515c82018-06-26 18:59:05 +03001671 LOG.debug("Unexpected http code:{} from "
1672 "url:{}\nwith message:{}".format(code, url, m))
azvyagintsev58947072018-06-29 12:09:48 +03001673 pass
1674 return code
1675
1676
1677def wait_for_http_code(url=None, expected=[200]):
1678 """
1679 Simple function, which just wait for avaible api, aka wait for 200.
1680
1681 CLI Example:
1682
1683 .. code-block:: bash
1684
1685 salt 'maas-node' maasng.wait_for_http_code url expected=[200]
1686
1687 """
1688 ret = {}
1689 started_at = time.time()
1690 poll_time = 5
1691 timeout = 60 * 2
1692 while _getHTTPCode(url) not in expected:
1693 c_timeout = timeout - (time.time() - started_at)
1694 if c_timeout <= 0:
1695 ret['result'] = False
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001696 ret["comment"] = "api:{} not answered in time".format(url)
azvyagintsev58947072018-06-29 12:09:48 +03001697 return ret
1698 LOG.info(
1699 "Waiting for api:{0}\n"
1700 "sleep for:{1}s "
1701 "Left:{2}/{3}s".format(url, poll_time, round(c_timeout),
1702 timeout))
1703 time.sleep(poll_time)
1704 ret['result'] = True
1705 ret["comment"] = "MAAS API:{} up.".format(url)
1706 return ret
1707
1708
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001709def _get_boot_source_id_by_url(url):
1710 # FIXME: fix ret\validation
1711 try:
1712 bs_id = get_boot_source(url=url)["id"]
1713 except KeyError:
1714 return {"error": "boot-source:{0} not exist!".format(url)}
1715 return bs_id
1716
1717
1718def get_boot_source(url=None):
1719 """
1720 Read a boot source by url. If url not specified - return all.
1721
1722 CLI Example:
1723
1724 .. code-block:: bash
1725
1726 salt 'maas-node' maasng.get_boot_source url
1727
1728 """
1729 boot_sources = {}
1730 maas = _create_maas_client()
1731 json_res = json.loads(maas.get(u'api/2.0/boot-sources/').read() or 'null')
1732 for item in json_res:
1733 boot_sources[str(item["url"])] = item
1734 if url:
1735 return boot_sources.get(url, {})
1736 return boot_sources
1737
1738
1739def delete_boot_source(url, bs_id=None):
1740 """
1741 Delete a boot source by url.
1742
1743 CLI Example:
1744
1745 .. code-block:: bash
1746
1747 sal 'maas-node' maasng.delete url
1748
1749 """
1750 result = {}
1751 if not bs_id:
1752 bs_id = _get_boot_source_id_by_url(url)
1753 maas = _create_maas_client()
1754 json_res = json.loads(maas.delete(
1755 u'/api/2.0/boot-sources/{0}/'.format(bs_id)).read() or 'null')
1756 LOG.debug("delete_boot_source:{}".format(json_res))
1757 result["new"] = "Boot-resource {0} deleted".format(url)
1758 return result
1759
1760
1761def boot_sources_delete_all_others(except_urls=[]):
1762 """
1763 Delete all boot-sources, except defined in 'except_urls' list.
1764 """
azvyagintseve2e37a12018-11-01 14:45:49 +02001765 result = {'changes': {}}
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001766 maas_boot_sources = get_boot_source()
1767 if 0 in [len(except_urls), len(maas_boot_sources)]:
1768 result['result'] = None
azvyagintseve2e37a12018-11-01 14:45:49 +02001769 result["comment"] = "'except_urls' or 'maas boot-sources' for " \
1770 "delete empty. No changes goinng to be."
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001771 return result
azvyagintseve2e37a12018-11-01 14:45:49 +02001772 # FIXME: fix 'changes' accumulator
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001773 for url in maas_boot_sources.keys():
1774 if url not in except_urls:
1775 LOG.info("Removing boot-source:{}".format(url))
1776 boot_resources_import(action='stop_import', wait=True)
1777 result["changes"] = delete_boot_source(url)
1778 return result
1779
1780
1781def create_boot_source(url, keyring_filename='', keyring_data='', wait=False):
1782 """
1783 Create and import maas boot-source: link to maas-ephemeral repo
1784 Be aware, those step will import resource to rack ctrl, but you also need to import
1785 them into the region!
1786
1787
1788 :param url: The URL of the BootSource.
1789 :param keyring_filename: The path to the keyring file for this BootSource.
1790 :param keyring_data: The GPG keyring for this BootSource, base64-encoded data.
1791
1792 """
1793
1794 # TODO: not work with 'update' currently => keyring update may fail.
1795 result = {}
1796
1797 data = {
1798 "url": url,
1799 "keyring_filename": keyring_filename,
1800 "keyring_data": str(keyring_data),
1801 }
1802
1803 maas = _create_maas_client()
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001804 if url in get_boot_source():
1805 result['result'] = None
1806 result["comment"] = "boot resource already exist"
1807 return result
1808
1809 # NOTE: maas.post will return 400, if url already defined.
1810 json_res = json.loads(
1811 maas.post(u'api/2.0/boot-sources/', None, **data).read())
1812 if wait:
1813 LOG.debug(
1814 "Sleep for 5s,to get MaaS some time to process previous request")
1815 time.sleep(5)
1816 ret = boot_resources_is_importing(wait=True)
1817 if ret is dict:
1818 return ret
1819 LOG.debug("create_boot_source:{}".format(json_res))
1820 result["new"] = "boot resource {0} was created".format(json_res["url"])
1821
1822 return result
1823
1824
1825def boot_resources_import(action='import', wait=False):
1826 """
1827 import/stop_import the boot resources.
1828
1829 :param action: import\stop_import
1830 :param wait: True\False. Wait till process finished.
1831
1832 CLI Example:
1833
1834 .. code-block:: bash
1835
1836 salt 'maas-node' maasng.boot_resources_import action='import'
1837
1838 """
1839 maas = _create_maas_client()
1840 # Have no idea why, but usual jsonloads not work here..
1841 imp = maas.post(u'api/2.0/boot-resources/', action)
1842 if imp.code == 200:
1843 LOG.debug('boot_resources_import:{}'.format(imp.readline()))
1844 if wait:
1845 boot_resources_is_importing(wait=True)
1846 return True
1847 else:
1848 return False
1849
1850
1851def boot_resources_is_importing(wait=False):
1852 maas = _create_maas_client()
1853 result = {}
1854 if wait:
1855 started_at = time.time()
1856 poll_time = 5
1857 timeout = 60 * 15
1858 while boot_resources_is_importing(wait=False):
1859 c_timeout = timeout - (time.time() - started_at)
1860 if c_timeout <= 0:
1861 result['result'] = False
1862 result["comment"] = "Boot-resources import not finished in time"
1863 return result
1864 LOG.info(
1865 "Waiting boot-resources import done\n"
1866 "sleep for:{}s "
1867 "Left:{}/{}s".format(poll_time, round(c_timeout), timeout))
1868 time.sleep(poll_time)
1869 return json.loads(
1870 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1871 else:
1872 return json.loads(
1873 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1874
1875#####
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001876# def boot_sources_selections_delete_all_others(except_urls=[]):
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001877# """
1878# """
1879# result = {}
1880# return result
1881
1882
1883def is_boot_source_selections_in(dict1, list1):
1884 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001885 Check that requested boot-selection already in maas bs selections,
1886 if True- return bss id.
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001887 # FIXME: those hack check doesn't look good.
1888 """
1889 for bs in list1:
1890 same = set(dict1.keys()) & set(bs.keys())
1891 if all(elem in same for elem in
1892 ['os', 'release', 'arches', 'subarches', 'labels']):
azvyagintsevf3515c82018-06-26 18:59:05 +03001893 LOG.debug("boot-selection in maas:{0}\n"
1894 "looks same to requested:{1}".format(bs, dict1))
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001895 return bs['id']
1896 return False
1897
1898
1899def get_boot_source_selections(bs_url):
1900 """
1901 Get boot-source selections.
1902 """
1903 # check for key_error!
1904 bs_id = _get_boot_source_id_by_url(bs_url)
1905 maas = _create_maas_client()
1906 json_res = json.loads(
1907 maas.get(u'/api/2.0/boot-sources/{0}/selections/'.format(bs_id)).read())
1908 LOG.debug(
1909 "get_boot_source_selections for url:{} \n{}".format(bs_url, json_res))
1910 return json_res
1911
1912
1913def create_boot_source_selections(bs_url, os, release, arches="*",
1914 subarches="*", labels="*", wait=True):
1915 """
1916 Create a new boot source selection for bs_url.
1917 :param os: The OS (e.g. ubuntu, centos) for which to import resources.Required.
1918 :param release: The release for which to import resources. Required.
1919 :param arches: The architecture list for which to import resources.
1920 :param subarches: The subarchitecture list for which to import resources.
1921 :param labels: The label lists for which to import resources.
1922 """
1923
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001924 result = {"result": True, 'name': bs_url, 'changes': None}
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001925
1926 data = {
1927 "os": os,
1928 "release": release,
1929 "arches": arches,
1930 "subarches": subarches,
1931 "labels": labels,
1932 }
1933
1934 maas = _create_maas_client()
1935 bs_id = _get_boot_source_id_by_url(bs_url)
1936 # TODO add pre-create verify
1937 maas_bs_s = get_boot_source_selections(bs_url)
1938 if is_boot_source_selections_in(data, maas_bs_s):
1939 result["result"] = True
azvyagintsevf3515c82018-06-26 18:59:05 +03001940 result["comment"] = 'Requested boot-source selection ' \
1941 'for {0} already exist.'.format(
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001942 bs_url)
1943 return result
1944
1945 # NOTE: maas.post will return 400, if url already defined.
azvyagintsevcb54d142018-06-19 16:18:32 +03001946 # Also, maas need's some time to import info about stream.
1947 # unfortunatly, maas don't have any call to check stream-import-info - so, we need to implement
1948 # at least simple retry ;(
1949 json_res = False
1950 poll_time = 5
azvyagintseve2e37a12018-11-01 14:45:49 +02001951 for i in range(0, 10):
azvyagintsevcb54d142018-06-19 16:18:32 +03001952 try:
1953 json_res = json.loads(
1954 maas.post(u'api/2.0/boot-sources/{0}/selections/'.format(bs_id), None,
1955 **data).read())
1956 except Exception as inst:
azvyagintsev06b74932019-04-03 15:27:31 +03001957 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001958 LOG.warning("boot_source_selections "
1959 "catch error during processing. Most-probably, "
azvyagintseve2e37a12018-11-01 14:45:49 +02001960 "streams data not imported yet.\nSleep:{}s "
1961 "Retry:{}/10".format(poll_time, i))
azvyagintsevcb54d142018-06-19 16:18:32 +03001962 LOG.warning("Message:{0}".format(m))
1963 time.sleep(poll_time)
1964 continue
1965 break
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001966 LOG.debug("create_boot_source_selections:{}".format(json_res))
azvyagintsevcb54d142018-06-19 16:18:32 +03001967 if not json_res:
1968 result["result"] = False
azvyagintsevf3515c82018-06-26 18:59:05 +03001969 result["comment"] = 'Failed to create requested boot-source selection' \
1970 ' for {0}.'.format(bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001971 return result
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001972 if wait:
1973 LOG.debug(
1974 "Sleep for 5s,to get MaaS some time to process previous request")
1975 time.sleep(5)
1976 ret = boot_resources_import(action='import', wait=True)
1977 if ret is dict:
1978 return ret
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001979 result["comment"] = "boot-source selection for {0} was created".format(
1980 bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001981 result["new"] = data
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001982
1983 return result
1984
1985# END MAAS CONFIG SECTION
azvyagintsevcb54d142018-06-19 16:18:32 +03001986
1987# RACK CONTROLLERS SECTION
1988
1989
1990def get_rack(hostname):
1991 """
1992 Get information about specified rackd
1993
1994 CLI Example:
1995
1996 .. code-block:: bash
1997
1998 salt-call maasng.get_rack rack_hostname
1999 """
2000 try:
2001 return list_racks()[hostname]
2002 except KeyError:
2003 return {"error": "rack:{} not found on MaaS server".format(hostname)}
2004
2005
azvyagintsev6913e5e2018-07-05 11:42:53 +03002006def list_racks(sort_by='hostname'):
azvyagintsevcb54d142018-06-19 16:18:32 +03002007 """
2008 Get list of all rack controllers from maas server
2009
2010 CLI Example:
2011
2012 .. code-block:: bash
2013
2014 salt-call maasng.list_racks
2015 """
2016 racks = {}
2017 maas = _create_maas_client()
2018 json_res = json.loads(
2019 maas.get(u"/api/2.0/rackcontrollers/").read() or 'null')
2020 for item in json_res:
azvyagintsev6913e5e2018-07-05 11:42:53 +03002021 racks[item[sort_by]] = item
azvyagintsevcb54d142018-06-19 16:18:32 +03002022 return racks
2023
2024
2025def sync_bs_to_rack(hostname=None):
2026 """
2027 Sync RACK boot-sources with REGION. If no hostname probided - sync to all.
2028
2029 CLI Example:
2030
2031 .. code-block:: bash
2032
2033 salt-call maasng.sync_bs_to_rack rack_hostname
2034 """
2035 ret = {}
2036 maas = _create_maas_client()
2037 if not hostname:
2038 LOG.info("boot-sources sync initiated for ALL Rack's")
2039 # Convert to json-like format
2040 json_res = json.loads('["{0}"]'.format(
2041 maas.post(u"/api/2.0/rackcontrollers/",
2042 'import_boot_images').read()))
2043 LOG.debug("sync_bs_to_rack:{}".format(json_res))
2044 ret['result'] = True
2045 ret['comment'] = "boot-sources sync initiated for ALL Rack's"
2046 return ret
2047 LOG.info("boot-sources sync initiated for RACK:{0}".format(hostname))
2048 # Convert to json-like format
2049 json_res = json.loads('["{0}"]'.format(maas.post(
2050 u"/api/2.0/rackcontrollers/{0}/".format(
2051 get_rack(hostname)['system_id']),
2052 'import_boot_images').read()))
2053 LOG.debug("sync_bs_to_rack:{}".format(json_res))
2054 ret['result'] = True
2055 ret['comment'] = "boot-sources sync initiated for {0} Rack's".format(
2056 hostname)
2057 return
2058
2059
2060def rack_list_boot_imgs(hostname):
2061 ret = {}
2062 maas = _create_maas_client()
2063 LOG.debug("rack_list_boot_imgs:{}".format(hostname))
2064 ret = json.loads(maas.get(u"/api/2.0/rackcontrollers/{0}/".format(
2065 get_rack(hostname)['system_id']), 'list_boot_images').read() or 'null')
2066 return ret
2067
2068
2069def is_rack_synced(hostname):
2070 rez = rack_list_boot_imgs(hostname)['status']
2071 if rez == 'synced':
2072 return True
2073 return False
2074
2075# TODO do we actually need _exact_ check per-pack?
2076# def wait_for_images_on_rack(hostname):
2077#
2078# """
2079# WA function, to be able check that RACK actually done SYNC images
2080# for REQUIRED images at least.
2081# Required image to be fetched from
2082# reclass:maas:region:boot_sources_selections:[keys]:os/release formation
2083#
2084# CLI Example:
2085#
2086# .. code-block:: bash
2087#
2088# salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
2089# """
2090# try:
2091# bss = __salt__['config.get']('maas')['region']['boot_sources_selections']
2092# except KeyError:
2093# ret['result'] = None
2094# ret['comment'] = "boot_sources_selections definition for sync not found."
2095# return ret
2096# s_names = []
2097# # Format u'name': u'ubuntu/xenial'
2098# for v in bss.values():s_names.append("{0}/{1}".format(v['os'],v['release']))
2099# # Each names, should be in rack and whole rack should be in sync-ed state
2100
2101
2102def sync_and_wait_bs_to_all_racks():
2103 """
2104 Sync ALL rack's with regions source images.
2105
2106 CLI Example:
2107
2108 .. code-block:: bash
2109
2110 salt-call maasng.sync_and_wait_bs_to_all_racks
2111 """
2112 sync_bs_to_rack()
2113 for rack in list_racks().keys():
2114 wait_for_sync_bs_to_rack(hostname=rack)
2115 return True
2116
2117
2118def wait_for_sync_bs_to_rack(hostname=None):
2119 """
2120 Wait for boot images sync finished, on exact rack
2121
2122 CLI Example:
2123
2124 .. code-block:: bash
2125
2126 salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
2127 """
2128 ret = {}
2129 started_at = time.time()
2130 poll_time = 5
2131 timeout = 60 * 15
2132 while not is_rack_synced(hostname):
2133 c_timeout = timeout - (time.time() - started_at)
2134 if c_timeout <= 0:
2135 ret['result'] = False
2136 ret[
2137 "comment"] = "Boot-resources sync on rackd:{0}" \
2138 "not finished in time".format(
2139 hostname)
2140 return ret
2141 LOG.info(
2142 "Waiting boot-resources sync done to rack:{0}\n"
2143 "sleep for:{1}s "
2144 "Left:{2}/{3}s".format(hostname, poll_time, round(c_timeout),
2145 timeout))
2146 time.sleep(poll_time)
2147 ret['result'] = is_rack_synced(hostname)
2148 ret["comment"] = "Boot-resources sync on rackd:{0} finished".format(
2149 hostname)
2150 return ret
2151
2152# END RACK CONTROLLERS SECTION
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02002153# SSHKEYS
2154
2155
2156def list_sshkeys():
2157 """
2158 Get list of all sshkeys
2159
2160 CLI Example:
2161
2162 .. code-block:: bash
2163
2164 salt 'maas-node' maasng.list_sshkeys
2165 salt-call maasng.list_sshkeys
2166 """
2167 ssh = {}
2168 maas = _create_maas_client()
2169 json_res = json.loads(maas.get(u'api/2.0/account/prefs/sshkeys/').read())
2170 LOG.info(json_res)
2171 for item in json_res:
2172 ssh[item["key"]] = item
2173 return ssh
2174
2175
2176def add_sshkey(sshkey):
2177 """
2178 Add SSH key for user to MAAS.
2179
2180 CLI Example:
2181
2182 .. code-block:: bash
2183
2184 salt 'maas-node' maasng.add_sshkey sshkey
2185 salt-call maasng.add_sshkey sshkey
2186 """
2187 data = {
2188 "key": sshkey,
2189 }
2190 result = {}
2191 maas = _create_maas_client()
2192
2193 maas.post(u"/api/2.0/account/prefs/sshkeys/", None, **data).read()
2194 result["new"] = "SSH Key {0} was added.".format(sshkey)
2195
2196 return result
2197
2198
2199def get_sshkey(sshkey):
2200 """
2201 Get start ip for ip range
2202
2203 CLI Example:
2204
2205 .. code-block:: bash
2206
2207 salt 'maas-node' maasng.get_sshkey sshkey
2208 salt-call maasng.get_sshkey sshkey
2209 """
2210 try:
2211 return list_sshkeys()[sshkey]
2212 except KeyError:
2213 return {"error": "SSH key not found on MaaS server"}
2214# END SSHKEYS