blob: 879babece7ab62b1a16ca6932df940fe501f5fc4 [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
Dzmitry Stremkouskid95bd2e2018-12-03 17:35:46 +010024import netaddr
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020025# Salt utils
Ondrej Smolab57a23b2018-01-24 11:18:24 +010026from salt.exceptions import CommandExecutionError, SaltInvocationError
27
28LOG = logging.getLogger(__name__)
29
30SIZE = {
31 "M": 1000000,
32 "G": 1000000000,
33 "T": 1000000000000,
34}
35
36RAID = {
37 0: "raid-0",
38 1: "raid-1",
39 5: "raid-5",
40 10: "raid-10",
41}
42
43# Import third party libs
44HAS_MASS = False
45try:
46 from maas_client import MAASClient, MAASDispatcher, MAASOAuth
47 HAS_MASS = True
48except ImportError:
49 LOG.debug('Missing MaaS client module is Missing. Skipping')
50
51
52def __virtual__():
azvyagintsevbca1f462018-05-25 19:06:46 +030053 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +010054 Only load this module if maas-client
55 is installed on this minion.
azvyagintsevbca1f462018-05-25 19:06:46 +030056 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +010057 if HAS_MASS:
58 return 'maasng'
59 return False
60
61
62APIKEY_FILE = '/var/lib/maas/.maas_credentials'
63
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020064
Ondrej Smolab57a23b2018-01-24 11:18:24 +010065def _format_data(data):
66 class Lazy:
67 def __str__(self):
68 return ' '.join(['{0}={1}'.format(k, v)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020069 for k, v in data.iteritems()])
Ondrej Smolab57a23b2018-01-24 11:18:24 +010070 return Lazy()
71
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020072
azvyagintsev58947072018-06-29 12:09:48 +030073def _create_maas_client(api_url=None):
74 if not api_url:
75 api_url = 'http://localhost:5240/MAAS'
Ondrej Smolab57a23b2018-01-24 11:18:24 +010076 global APIKEY_FILE
77 try:
78 api_token = file(APIKEY_FILE).read().splitlines()[-1].strip()\
79 .split(':')
80 except:
81 LOG.exception('token')
82 auth = MAASOAuth(*api_token)
Ondrej Smolab57a23b2018-01-24 11:18:24 +010083 dispatcher = MAASDispatcher()
84 return MAASClient(auth, dispatcher, api_url)
85
Ondrej Smolab57a23b2018-01-24 11:18:24 +010086
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020087def _get_blockdevice_id_by_name(hostname, device):
88
89 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +010090 return list_blockdevices(hostname)[device]["id"]
91
Ondrej Smolab57a23b2018-01-24 11:18:24 +010092
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020093def _get_volume_group_id_by_name(hostname, device):
94
95 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +010096 return list_volume_groups(hostname)[device]["id"]
97
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +020098
azvyagintsevbca1f462018-05-25 19:06:46 +030099def _get_volume_id_by_name(hostname, volume_name, volume_group, maas_volname=True):
100
101 if not maas_volname:
102 # MAAS-like name
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200103 volume_name = str("%s-%s" % (volume_group, volume_name))
104 # TODO validation
azvyagintsevbca1f462018-05-25 19:06:46 +0300105 return get_volumes(hostname, volume_group)[volume_name]["id"]
106
107
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100108def _get_partition_id_by_name(hostname, device, partition):
109
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200110 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100111 return list_partitions(hostname, device)[partition]["id"]
112
Dzmitry Stremkouskid95bd2e2018-12-03 17:35:46 +0100113def is_valid_ipv4(address):
114 """Verify that address represents a valid IPv4 address.
115 :param address: Value to verify
116 :type address: string
117 :returns: bool
118 .. versionadded:: 1.1
119 """
120 try:
121 return netaddr.valid_ipv4(address)
122 except netaddr.AddrFormatError:
123 return False
124
125def is_valid_ipv6(address):
126 """Verify that address represents a valid IPv6 address.
127 :param address: Value to verify
128 :type address: string
129 :returns: bool
130 .. versionadded:: 1.1
131 """
132 if not address:
133 return False
134
135 parts = address.rsplit("%", 1)
136 address = parts[0]
137 scope = parts[1] if len(parts) > 1 else None
138 if scope is not None and (len(scope) < 1 or len(scope) > 15):
139 return False
140
141 try:
142 return netaddr.valid_ipv6(address, netaddr.core.INET_PTON)
143 except netaddr.AddrFormatError:
144 return False
145
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200146# MACHINE SECTION
147
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100148
149def get_machine(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300150 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100151 Get information aboout specified machine
152
153 CLI Example:
154
155 .. code-block:: bash
156
157 salt-call maasng.get_machine server_hostname
Alexei Lugovoie5b64122018-11-06 12:30:01 +0100158
159 Error codes:
160 0 : Machine not found
azvyagintsevbca1f462018-05-25 19:06:46 +0300161 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100162 try:
163 return list_machines()[hostname]
164 except KeyError:
Alexei Lugovoie5b64122018-11-06 12:30:01 +0100165 return {"error":
166 { 0: "Machine not found" }
167 }
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100168
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200169
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200170def list_machines(status_filter=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300171 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100172 Get list of all machines from maas server
173
174 CLI Example:
175
176 .. code-block:: bash
177
178 salt 'maas-node' maasng.list_machines
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200179 salt 'maas-node' maasng.list_machines status_filter=[Deployed,Ready]
azvyagintsevbca1f462018-05-25 19:06:46 +0300180 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200181 machines = {}
182 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100183 json_res = json.loads(maas.get(u'api/2.0/machines/').read())
184 for item in json_res:
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200185 if not status_filter or item['status_name'] in status_filter:
186 machines[item["hostname"]] = item
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100187 return machines
188
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200189
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100190def create_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200191 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100192
193 return False
194
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200195
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100196def update_machine():
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200197 # TODO
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100198
199 return False
200
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200201def delete_machine(hostname):
202 """
203 Delete specified machine
204
205 CLI Example:
206
207 .. code-block:: bash
208
209 salt 'maas-node' maasng.delete_machine server_hostname
210 salt-call maasng.delete_machine server_hostname
211 """
212 result = {}
213 maas = _create_maas_client()
214 system_id = get_machine(hostname)["system_id"]
215 LOG.debug('delete_machine: {}'.format(system_id))
216 maas.delete(
217 u"api/2.0/machines/{0}/".format(system_id)).read()
218
219 result["new"] = "Machine {0} deleted".format(hostname)
220 return result
221
Dzmitry Stremkouskid95bd2e2018-12-03 17:35:46 +0100222def machine_power_state(hostname):
223 """
224 Query the power state of a node.
225
226 :param hostname: Node hostname
227
228 CLI Example:
229
230 .. code-block:: bash
231
232 salt 'maas-node' maasng.machine_power_state kvm06
233
234 """
235 result = {}
236 maas = _create_maas_client()
237 system_id = get_machine(hostname)["system_id"]
238 LOG.debug('action_machine: {}'.format(system_id))
239
240 # TODO validation
241 json_res = json.loads(maas.get(
242 u"api/2.0/machines/{0}/".format(system_id), "query_power_state").read())
243 LOG.info(json_res)
244
245 return json_res
246
Alexandru Avadanii48c51f42018-09-22 20:14:10 +0200247def action_machine(hostname, action, comment=None):
248 """
249 Send simple action (e.g. mark_broken, mark_fixed) to machine.
250
251 :param action: Action to send for machine (one of MaaS' op codes)
252 :param comment: Optional comment for the event log.
253
254 CLI Example:
255
256 .. code-block:: bash
257
258 salt 'maas-node' maasng.action_machine server_hostname mark_broken comment='dead'
259 """
260 result = {}
261 data = {}
262 maas = _create_maas_client()
263 system_id = get_machine(hostname)["system_id"]
264 LOG.debug('action_machine: {}'.format(system_id))
265
266 # TODO validation
267 if comment:
268 data["comment"] = comment
269 json_res = json.loads(maas.post(
270 u"api/2.0/machines/{0}/".format(system_id), action, **data).read())
271 LOG.info(json_res)
272 result["new"] = "Machine {0} action {1} executed".format(hostname, action)
273
274 return result
275
azvyagintsevbca1f462018-05-25 19:06:46 +0300276# END MACHINE SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200277# RAID SECTION
278
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100279
280def create_raid(hostname, name, level, disks=[], partitions=[], **kwargs):
azvyagintsevbca1f462018-05-25 19:06:46 +0300281 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100282 Create new raid on machine.
283
284 CLI Example:
285
286 .. code-block:: bash
287
288 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 +0300289 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100290
291 result = {}
292
293 if len(disks) == 0 and len(partitions) == 0:
294 result["error"] = "Disks or partitions need to be provided"
295
296 disk_ids = []
297 partition_ids = []
298
299 for disk in disks:
300 try:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200301 disk_ids.append(str(_get_blockdevice_id_by_name(hostname, disk)))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100302 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200303 result["error"] = "Device {0} does not exists on machine {1}".format(
304 disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100305 return result
306
307 for partition in partitions:
308 try:
309 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200310 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100311 partition_ids.append(str(device_part[partition]["id"]))
312 except KeyError:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200313 result["error"] = "Partition {0} does not exists on machine {1}".format(
314 partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100315 return result
316
317 data = {
318 "name": name,
319 "level": RAID[int(level)],
320 "block_devices": disk_ids,
321 "partitions": partition_ids,
322 }
323
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200324 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100325 system_id = get_machine(hostname)["system_id"]
326 LOG.info(system_id)
327
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200328 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100329 LOG.info(data)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200330 json_res = json.loads(
331 maas.post(u"api/2.0/nodes/{0}/raids/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100332 LOG.info(json_res)
333 result["new"] = "Raid {0} created".format(name)
334
335 return result
336
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200337
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100338def list_raids(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300339 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100340 Get list all raids on machine
341
342 CLI Example:
343
344 .. code-block:: bash
345
346 salt-call maasng.list_raids server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300347 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100348
azvyagintsevbca1f462018-05-25 19:06:46 +0300349 raids = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200350 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100351 system_id = get_machine(hostname)["system_id"]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200352 # TODO validation
353 json_res = json.loads(
354 maas.get(u"api/2.0/nodes/{0}/raids/".format(system_id)).read())
azvyagintsevbca1f462018-05-25 19:06:46 +0300355 LOG.debug('list_raids:{} {}'.format(system_id, json_res))
356 for item in json_res:
357 raids[item["name"]] = item
358 return raids
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100359
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200360
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100361def get_raid(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300362 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100363 Get information about specific raid on machine
364
365 CLI Example:
366
367 .. code-block:: bash
368
369 salt-call maasng.get_raids server_hostname md0
azvyagintsevbca1f462018-05-25 19:06:46 +0300370 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100371
372 return list_raids(hostname)[name]
373
374
azvyagintsevbca1f462018-05-25 19:06:46 +0300375def _get_raid_id_by_name(hostname, raid_name):
376 return get_raid(hostname, raid_name)['id']
377
378
379def delete_raid(hostname, raid_name):
380 """
381 Delete RAID on a machine.
382
383 CLI Example:
384
385 .. code-block:: bash
386
387 salt 'maas-node' maasng.delete_raid server_hostname raid_name
388 salt-call maasng.delete_raid server_hostname raid_name
389 """
390 result = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200391 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +0300392 system_id = get_machine(hostname)["system_id"]
393 raid_id = _get_raid_id_by_name(hostname, raid_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200394 LOG.debug('delete_raid: {} {}'.format(system_id, raid_id))
395 maas.delete(
396 u"api/2.0/nodes/{0}/raid/{1}/".format(system_id, raid_id)).read()
azvyagintsevbca1f462018-05-25 19:06:46 +0300397
398 result["new"] = "Raid {0} deleted".format(raid_name)
399 return result
400
401# END RAID SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200402# BLOCKDEVICES SECTION
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100403
azvyagintsevbca1f462018-05-25 19:06:46 +0300404
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100405def list_blockdevices(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300406 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100407 Get list of all blockdevices (disks) on machine
408
409 CLI Example:
410
411 .. code-block:: bash
412
413 salt 'maas-node' maasng.list_blockdevices server_hostname
414 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300415 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100416 ret = {}
417
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200418 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100419 system_id = get_machine(hostname)["system_id"]
420 LOG.info(system_id)
421
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200422 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100423
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200424 json_res = json.loads(
425 maas.get(u"api/2.0/nodes/{0}/blockdevices/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100426 LOG.info(json_res)
427 for item in json_res:
428 ret[item["name"]] = item
429
430 return ret
431
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200432
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100433def get_blockdevice(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300434 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100435 Get information about blockdevice (disk) on machine
436
437 CLI Example:
438
439 .. code-block:: bash
440
441 salt 'maas-node' maasng.get_blockdevice server_hostname sda
442 salt-call maasng.get_blockdevice server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300443 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100444
445 return list_blockdevices(hostname)[name]
446
azvyagintsevbca1f462018-05-25 19:06:46 +0300447# END BLOCKDEVICES SECTION
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200448# PARTITIONS
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100449
azvyagintsevbca1f462018-05-25 19:06:46 +0300450
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100451def list_partitions(hostname, device):
azvyagintsevbca1f462018-05-25 19:06:46 +0300452 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100453 Get list of all partitions on specific device located on specific machine
454
455 CLI Example:
456
457 .. code-block:: bash
458
459 salt 'maas-node' maasng.list_partitions server_hostname sda
460 salt-call maasng.list_partitions server_hostname sda
azvyagintsevbca1f462018-05-25 19:06:46 +0300461 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100462 ret = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200463 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100464 system_id = get_machine(hostname)["system_id"]
465 LOG.info(system_id)
466
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200467 partitions = get_blockdevice(hostname, device)["partitions"]
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100468 LOG.info(partitions)
469
470 #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 +0200471 # LOG.info(json_res)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100472
473 if len(device) > 0:
474 for item in partitions:
475 name = item["path"].split('/')[-1]
476 ret[name] = item
477
478 return ret
479
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200480
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100481def get_partition(hostname, device, partition):
azvyagintsevbca1f462018-05-25 19:06:46 +0300482 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100483 Get information about specific parition on device located on machine
484
485 CLI Example:
486
487 .. code-block:: bash
488
489 salt 'maas-node' maasng.get_partition server_hostname disk_name partition
490 salt-call maasng.get_partition server_hostname disk_name partition
491
492 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300493 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100494
495 return list_partitions(partition)[name]
496
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200497
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100498def create_partition(hostname, disk, size, fs_type=None, mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300499 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100500 Create new partition on device.
501
502 CLI Example:
503
504 .. code-block:: bash
505
506 salt 'maas-node' maasng.create_partition server_hostname disk_name 10 ext4 "/"
507 salt-call maasng.create_partition server_hostname disk_name 10 ext4 "/"
azvyagintsevbca1f462018-05-25 19:06:46 +0300508 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200509 # TODO validation
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100510 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200511 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100512 system_id = get_machine(hostname)["system_id"]
513 LOG.info(system_id)
514
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200515 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100516 LOG.info(device_id)
517
518 value, unit = size[:-1], size[-1]
519 calc_size = str(int(value) * SIZE[unit])
520 LOG.info(calc_size)
521
522 data = {
523 "size": calc_size
524 }
525
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200526 # TODO validation
527 partition = json.loads(maas.post(
528 u"api/2.0/nodes/{0}/blockdevices/{1}/partitions/".format(system_id, device_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100529 LOG.info(partition)
530 result["partition"] = "Partition created on {0}".format(disk)
531
532 if fs_type != None:
533 data_fs_type = {
534 "fstype": fs_type
535 }
536 partition_id = str(partition["id"])
537 LOG.info("Partition id: " + partition_id)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200538 # TODO validation
539 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
540 system_id, device_id, partition_id), "format", **data_fs_type).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100541 LOG.info(json_res)
542 result["filesystem"] = "Filesystem {0} created".format(fs_type)
543
544 if mount != None:
545 data = {
546 "mount_point": mount
547 }
548
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200549 # TODO validation
550 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
551 system_id, device_id, str(partition['id'])), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100552 LOG.info(json_res)
553 result["mount"] = "Mount point {0} created".format(mount)
554
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100555 return result
556
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200557
Dzmitry Stremkouskid95bd2e2018-12-03 17:35:46 +0100558def list_dnsresources():
559 """
560 List DNS resources known to MAAS.
561
562 CLI Example:
563
564 .. code-block:: bash
565
566 salt-call maasng.list_dnsresources
567
568 """
569 result = {}
570 res_json = []
571 maas = _create_maas_client()
572
573 # TODO validation
574 result = json.loads(maas.get(u"api/2.0/dnsresources/").read())
575 for elem in result:
576 ip_addresses = []
577 for ip in elem["ip_addresses"]:
578 ip_addresses.append(ip["ip"])
579 res_json.append(
580 {
581 "ip_addresses": ip_addresses,
582 "id": elem["id"],
583 "fqdn": elem["fqdn"],
584 "hostname": elem["fqdn"].split(".")[0]
585 }
586 )
587
588 LOG.debug(res_json)
589
590 return res_json
591
592
593def list_ipaddresses():
594 """
595 List IP addresses known to MAAS.
596
597 CLI Example:
598
599 .. code-block:: bash
600
601 salt-call maasng.list_ipaddresses
602
603 """
604 result = {}
605 res_json = []
606 maas = _create_maas_client()
607
608 # TODO validation
609 result = json.loads(maas.get(u"api/2.0/ipaddresses/?all").read())
610 for elem in result:
611 res_json.append(
612 {
613 "ip": elem["ip"],
614 "owner": { "username": elem["owner"]["username"] },
615 "created": elem["created"],
616 "alloc_type_name": elem["alloc_type_name"],
617 "alloc_type": elem["alloc_type"],
618 "subnet": {
619 "id": elem["subnet"]["id"],
620 "cidr": elem["subnet"]["cidr"],
621 "name": elem["subnet"]["name"]
622 }
623 }
624 )
625
626 LOG.debug(res_json)
627
628 return res_json
629
630
631def reserve_ipaddress(hostname,subnet,ip=""):
632 """
633 Reserve IP address for specified hostname in specified subnet
634
635 CLI Example:
636
637 .. code-block:: bash
638
639 salt-call maasng.reserve_ipaddress hostname 192.168.0.0/24 192.168.0.254
640 salt-call maasng.reserve_ipaddress hostname 192.168.0.0/24
641
642 """
643 result = {}
644 data = {}
645 maas = _create_maas_client()
646
647 data = {
648 "subnet": subnet,
649 "hostname": hostname
650 }
651
652 if ip:
653 data["ip"] = ip
654
655 # TODO validation
656 result = json.loads(maas.post(u"api/2.0/ipaddresses/", "reserve", **data).read())
657 res_json = {
658 "created": result["created"],
659 "type": "DNS",
660 "hostname": hostname,
661 "ip": result["ip"]
662 }
663
664 LOG.info(res_json)
665
666 return res_json
667
668
669def release_ipaddress(ipaddress):
670 """
671 Release an IP address that was previously reserved by the user.
672
673 CLI Example:
674
675 .. code-block:: bash
676
677 salt-call maasng.release_ipaddress 192.168.2.10
678
679 """
680 result = {}
681 data = {}
682 maas = _create_maas_client()
683
684 data = {
685 "ip": ipaddress
686 }
687
688 # TODO validation
689 return maas.post(u"api/2.0/ipaddresses/", "release", **data).read()
690
691
692def sync_address_pool():
693 """
694 Manage address pool for ext_pillar.
695
696 CLI Example:
697
698 .. code-block:: bash
699
700 salt-call maasng.sync_address_pool
701
702 """
703
704 address_pool = __pillar__["address_pool"]
705 LOG.debug("Address pool:")
706 LOG.debug(address_pool)
707
708 cluster_networks = __pillar__["cluster_networks"]
709 LOG.debug("Cluster networks:")
710 LOG.debug(cluster_networks)
711
712 dnsresources = list_dnsresources()
713 LOG.debug("DNS resources:")
714 LOG.debug(dnsresources)
715
716 machines = list_machines()
717 LOG.debug("Machines:")
718 LOG.debug(machines)
719
720 for net in address_pool:
721 if net == "external":
722 continue
723 for addr in address_pool[net]['pool']:
724 ipaddr = address_pool[net]['pool'][addr]
725 if ipaddr == "":
726 LOG.debug('Releasing IP address for: ' + addr)
727 release_required = False
728 for elem in dnsresources:
729 if elem["hostname"] == addr:
730 release_required = True
731 ip_addresses = elem["ip_addresses"]
732 if release_required:
733 for ip in ip_addresses:
734 res = release_ipaddress(ip)
735 LOG.debug(res)
736 else:
737 LOG.debug('IP for ' + addr + ' already released')
738 elif is_valid_ipv6(ipaddr) or is_valid_ipv4(ipaddr):
739 LOG.debug('Ensure static IP address "' + ipaddr + '" for ' + addr)
740 reserve_required = True
741 for elem in dnsresources:
742 if elem["hostname"] == addr:
743 reserve_required = False
744 for elem, elemval in machines.iteritems():
745 for iface in elemval["interface_set"]:
746 for link in iface["links"]:
747 if "ip_address" in link:
748 if link["ip_address"] == ipaddr:
749 reserve_required = False
750 if reserve_required:
751 res = reserve_ipaddress(addr, cluster_networks[net]['cidr'], ipaddr)
752 reserve_required = False
753 LOG.debug(res)
754 else:
755 LOG.debug('Static IP address "' + ipaddr + '" for ' + addr + ' ensured')
756 else:
757 LOG.debug('Requesting IP address for' + addr)
758 reserve_required = True
759 for elem in dnsresources:
760 if elem["hostname"] == addr:
761 reserve_required = False
762 ip = elem["ip_addresses"][0]
763 if reserve_required:
764 res = reserve_ipaddress(addr, cluster_networks[net]['cidr'])
765 LOG.debug(res)
766 else:
767 LOG.debug(addr + " already has IP " + ip)
768
769 return True
770
771
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100772def delete_partition(hostname, disk, partition_name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300773 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100774 Delete partition on device.
775
776 CLI Example:
777
778 .. code-block:: bash
779
780 salt 'maas-node' maasng.delete_partition server_hostname disk_name partition_name
781 salt-call maasng.delete_partition server_hostname disk_name partition_name
782
783 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300784 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100785 result = {}
786 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200787 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100788 system_id = get_machine(hostname)["system_id"]
789 LOG.info(system_id)
790
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200791 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100792 LOG.info(device_id)
793
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200794 partition_id = _get_partition_id_by_name(hostname, disk, partition_name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100795
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200796 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
797 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100798 result["new"] = "Partition {0} deleted".format(partition_name)
799 return result
800
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200801
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100802def delete_partition_by_id(hostname, disk, partition_id):
azvyagintsevbca1f462018-05-25 19:06:46 +0300803 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100804 Delete partition on device. Partition spefified by id of parition
805
806 CLI Example:
807
808 .. code-block:: bash
809
810 salt 'maas-node' maasng.delete_partition_by_id server_hostname disk_name partition_id
811 salt-call maasng.delete_partition_by_id server_hostname disk_name partition_id
812
813 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300814 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100815 result = {}
816 data = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200817 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100818 system_id = get_machine(hostname)["system_id"]
819 LOG.info(system_id)
820
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200821 device_id = _get_blockdevice_id_by_name(hostname, disk)
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100822 LOG.info(device_id)
823
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200824 maas.delete(u"api/2.0/nodes/{0}/blockdevices/{1}/partition/{2}".format(
825 system_id, device_id, partition_id)).read()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100826 result["new"] = "Partition {0} deleted".format(partition_id)
827 return result
azvyagintsevbca1f462018-05-25 19:06:46 +0300828# END PARTITIONS
829# DISK LAYOUT
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100830
azvyagintsevbca1f462018-05-25 19:06:46 +0300831
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200832def drop_storage_schema(hostname, disk=None):
azvyagintsevbca1f462018-05-25 19:06:46 +0300833 """
834 #1. Drop lv
835 #2. Drop vg
836 #3. Drop md # need to zero-block?
837 #3. Drop part
838 """
839
840 if __opts__['test']:
841 ret['result'] = None
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200842 ret['comment'] = 'Storage schema on {0} will be removed'.format(
843 hostname)
azvyagintsevbca1f462018-05-25 19:06:46 +0300844 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200845 # TODO validation if exists
azvyagintsevbca1f462018-05-25 19:06:46 +0300846 vgs = list_volume_groups(hostname)
847 for vg in vgs:
848 delete_volume_group(hostname, vg)
849
850 raids = list_raids(hostname)
851 for raid in raids:
852 delete_raid(hostname, raid)
853
854 blocks = list_blockdevices(hostname)
855 for block_d in blocks:
856 partitions = __salt__['maasng.list_partitions'](hostname, block_d)
857 for partition_name, partition in partitions.iteritems():
858 LOG.info('delete partition:\n{}'.format(partition))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200859 __salt__['maasng.delete_partition_by_id'](
860 hostname, block_d, partition["id"])
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200861
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100862
863def 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 +0300864 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100865 Update disk layout. Flat or LVM layout supported.
866
867 CLI Example:
868
869 .. code-block:: bash
870
871 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
872 salt-call maasng.update_disk_layout server_hostname lvm root_size=None, root_device=None, volume_group=None, volume_name=None, volume_size=None
873
874 root_size = size in GB
azvyagintsevbca1f462018-05-25 19:06:46 +0300875 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100876 result = {}
877 data = {
878 "storage_layout": layout,
879 }
880
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200881 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100882 system_id = get_machine(hostname)["system_id"]
883 LOG.info(system_id)
884
azvyagintsevbca1f462018-05-25 19:06:46 +0300885 if layout == 'custom':
886 drop_storage_schema(hostname)
887 result["new"] = {
888 "storage_layout": layout,
889 }
890
891 return result
892
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100893 if root_size != None:
894 bit_size = str(root_size * 1073741824)
895 LOG.info(bit_size)
896 data["root_size"] = bit_size
897
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100898 if root_device != None:
899 LOG.info(root_device)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200900 data["root_device"] = str(
901 _get_blockdevice_id_by_name(hostname, root_device))
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100902
903 if layout == 'lvm':
904 if volume_group != None:
905 LOG.info(volume_group)
906 data["vg_name"] = volume_group
907 if volume_name != None:
908 LOG.info(volume_name)
909 data["lv_name"] = volume_name
910 if volume_size != None:
911 vol_size = str(volume_size * 1073741824)
912 LOG.info(vol_size)
913 data["lv_size"] = vol_size
914
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200915 # TODO validation
916 json_res = json.loads(maas.post(
917 u"api/2.0/machines/{0}/".format(system_id), "set_storage_layout", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100918 LOG.info(json_res)
919 result["new"] = {
920 "storage_layout": layout,
921 }
922
923 return result
924
azvyagintsevbca1f462018-05-25 19:06:46 +0300925# END DISK LAYOUT
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200926# LVM
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100927
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +0200928
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100929def list_volume_groups(hostname):
azvyagintsevbca1f462018-05-25 19:06:46 +0300930 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100931 Get list of all volume group on machine.
932
933 CLI Example:
934
935 .. code-block:: bash
936
937 salt 'maas-node' maasng.list_volume_groups server_hostname
938 salt-call maasng.list_volume_groups server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300939 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100940 volume_groups = {}
941
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200942 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100943 system_id = get_machine(hostname)["system_id"]
944 LOG.info(system_id)
945
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200946 # TODO validation if exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100947
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200948 json_res = json.loads(
949 maas.get(u"api/2.0/nodes/{0}/volume-groups/".format(system_id)).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100950 LOG.info(json_res)
951 for item in json_res:
952 volume_groups[item["name"]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200953 # return
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100954 return volume_groups
955
956
957def get_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +0300958 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100959 Get information about specific volume group on machine.
960
961 CLI Example:
962
963 .. code-block:: bash
964
965 salt 'maas-node' maasng.list_blockdevices server_hostname
966 salt-call maasng.list_blockdevices server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300967 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200968 # TODO validation that exists
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100969 return list_volume_groups(hostname)[name]
970
971
972def create_volume_group(hostname, volume_group_name, disks=[], partitions=[]):
azvyagintsevbca1f462018-05-25 19:06:46 +0300973 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100974 Create new volume group on machine. Disks or partitions needs to be provided.
975
976 CLI Example:
977
978 .. code-block:: bash
979
980 salt 'maas-node' maasng.create_volume_group volume_group_name, disks=[sda,sdb], partitions=[]
981 salt-call maasng.create_volume_group server_hostname
azvyagintsevbca1f462018-05-25 19:06:46 +0300982 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100983 result = {}
984
985 data = {
986 "name": volume_group_name,
987 }
988
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +0200989 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +0100990 system_id = get_machine(hostname)["system_id"]
991 LOG.info(system_id)
992
993 disk_ids = []
994 partition_ids = []
995
996 for disk in disks:
997 p_disk = get_blockdevice(hostname, disk)
998 if p_disk["partition_table_type"] == None:
999 disk_ids.append(str(p_disk["id"]))
1000 else:
azvyagintsevf3515c82018-06-26 18:59:05 +03001001 result["error"] = "Device {0} on" \
1002 "machine {1} cointains partition" \
1003 "table".format(disk, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001004 return result
1005
1006 for partition in partitions:
1007 try:
1008 device = partition.split("-")[0]
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001009 device_part = list_partitions(hostname, device)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001010 partition_ids.append(str(device_part[partition]["id"]))
1011 except KeyError:
azvyagintsevf3515c82018-06-26 18:59:05 +03001012 result["error"] = "Partition {0} does" \
1013 "not exists on " \
1014 "machine {1}".format(partition, hostname)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001015 return result
1016
1017 data["block_devices"] = disk_ids
1018 data["partitions"] = partition_ids
1019 LOG.info(partition_ids)
1020 LOG.info(partitions)
1021
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001022 # TODO validation
1023 json_res = json.loads(maas.post(
1024 u"api/2.0/nodes/{0}/volume-groups/".format(system_id), None, **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001025 LOG.info(json_res)
1026 result["new"] = "Volume group {0} created".format(json_res["name"])
1027
1028 return result
1029
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001030
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001031def delete_volume_group(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +03001032 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001033 Delete volume group on machine.
1034
1035 CLI Example:
1036
1037 .. code-block:: bash
1038
1039 salt 'maas-node' maasng.delete_volume_group server_hostname vg0
1040 salt-call maasng.delete_volume_group server_hostname vg0
azvyagintsevbca1f462018-05-25 19:06:46 +03001041 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001042
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001043 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001044 system_id = get_machine(hostname)["system_id"]
azvyagintsevbca1f462018-05-25 19:06:46 +03001045 LOG.debug('delete_volume_group:{}'.format(system_id))
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001046
azvyagintsevbca1f462018-05-25 19:06:46 +03001047 vg_id = str(_get_volume_group_id_by_name(hostname, name))
1048 for vol in get_volumes(hostname, name):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001049 delete_volume(hostname, vol, name)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001050
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001051 # TODO validation
1052 json_res = json.loads(maas.delete(
1053 u"api/2.0/nodes/{0}/volume-group/{1}/".format(system_id, vg_id)).read() or 'null')
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001054 LOG.info(json_res)
1055
1056 return True
1057
1058
azvyagintsevf3515c82018-06-26 18:59:05 +03001059def create_volume(hostname, volume_name, volume_group, size, fs_type=None,
1060 mount=None):
azvyagintsevbca1f462018-05-25 19:06:46 +03001061 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001062 Create volume on volume group.
1063
1064 CLI Example:
1065
1066 .. code-block:: bash
1067
1068 salt 'maas-node' maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
1069 salt-call maasng.create_volume server_hostname volume_name, volume_group, size, fs_type=None, mount=None
azvyagintsevbca1f462018-05-25 19:06:46 +03001070 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001071
1072 data = {
1073 "name": volume_name,
1074 }
1075
1076 value, unit = size[:-1], size[-1]
1077 bit_size = str(int(value) * SIZE[unit])
1078 LOG.info(bit_size)
1079
1080 data["size"] = bit_size
1081
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001082 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001083 system_id = get_machine(hostname)["system_id"]
1084 LOG.info(system_id)
1085
1086 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
1087
1088 LOG.info(volume_group_id)
1089
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001090 # TODO validation
1091 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
1092 system_id, volume_group_id), "create_logical_volume", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001093 LOG.info(json_res)
1094
1095 if fs_type != None or mount != None:
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001096 ret = create_volume_filesystem(
1097 hostname, volume_group + "-" + volume_name, fs_type, mount)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001098
1099 return True
1100
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001101
azvyagintsevbca1f462018-05-25 19:06:46 +03001102def delete_volume(hostname, volume_name, volume_group):
1103 """
1104 Delete volume from volume group.
1105 Tips: maas always use 'volume_group-volume_name' name schema.Example: 'vg0-glusterfs'
1106 This function expexts same format.
1107
1108 CLI Example:
1109
1110 .. code-block:: bash
1111
1112 salt 'maas-node' maasng.delete_volume server_hostname volume_name volume_group
1113 salt 'maas-node' maasng.delete_volume server_hostname vg0-vol0 vg0
1114 salt-call maasng.delete_volume server_hostname volume_name volume_group
1115 """
1116
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001117 maas = _create_maas_client()
azvyagintsevbca1f462018-05-25 19:06:46 +03001118 system_id = get_machine(hostname)["system_id"]
1119 LOG.debug('delete_volume:{}'.format(system_id))
1120
1121 volume_group_id = str(_get_volume_group_id_by_name(hostname, volume_group))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001122 volume_id = str(_get_volume_id_by_name(
1123 hostname, volume_name, volume_group))
azvyagintsevbca1f462018-05-25 19:06:46 +03001124
1125 if None in [volume_group_id, volume_id]:
1126 return False
1127
1128 data = {
1129 "id": volume_id,
1130 }
1131
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001132 # TODO validation
1133 json_res = json.loads(maas.post(u"api/2.0/nodes/{0}/volume-group/{1}/".format(
1134 system_id, volume_group_id), "delete_logical_volume", **data).read() or 'null')
azvyagintsevbca1f462018-05-25 19:06:46 +03001135 return True
1136
1137
1138def get_volumes(hostname, vg_name):
1139 """
1140 Get list of volumes in volume group.
1141 """
1142 volumes = {}
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001143 _volumes = list_volume_groups(
1144 hostname)[vg_name].get('logical_volumes', False)
azvyagintsevbca1f462018-05-25 19:06:46 +03001145 if _volumes:
1146 for item in _volumes:
1147 volumes[item["name"]] = item
1148 return volumes
1149
1150# END LVM
1151
1152
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001153def create_volume_filesystem(hostname, device, fs_type=None, mount=None):
1154
1155 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001156 system_id = get_machine(hostname)["system_id"]
1157
1158 blockdevices_id = _get_blockdevice_id_by_name(hostname, device)
1159 data = {}
1160 if fs_type != None:
1161 data["fstype"] = fs_type
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001162 # TODO validation
1163 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
1164 system_id, blockdevices_id), "format", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001165 LOG.info(json_res)
1166
1167 if mount != None:
1168 data["mount_point"] = mount
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001169 # TODO validation
1170 json_res = json.loads(maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
1171 system_id, blockdevices_id), "mount", **data).read())
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001172 LOG.info(json_res)
1173
1174 return True
1175
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001176
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001177def set_boot_disk(hostname, name):
azvyagintsevbca1f462018-05-25 19:06:46 +03001178 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001179 Create volume on volume group.
1180
1181 CLI Example:
1182
1183 .. code-block:: bash
1184
1185 salt 'maas-node' maasng.set_boot_disk server_hostname disk_name
1186 salt-call maasng.set_boot_disk server_hostname disk_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001187 """
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001188 data = {}
1189 result = {}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001190 maas = _create_maas_client()
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001191 system_id = get_machine(hostname)["system_id"]
1192 blockdevices_id = _get_blockdevice_id_by_name(hostname, name)
1193
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001194 maas.post(u"/api/2.0/nodes/{0}/blockdevices/{1}/".format(
1195 system_id, blockdevices_id), "set_boot_disk", **data).read()
azvyagintsevf3515c82018-06-26 18:59:05 +03001196 # TODO validation for error response
1197 # (disk does not exists and node does not exists)
Ondrej Smolab57a23b2018-01-24 11:18:24 +01001198 result["new"] = "Disk {0} was set as bootable".format(name)
1199
1200 return result
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001201
azvyagintsevbca1f462018-05-25 19:06:46 +03001202# NETWORKING
1203
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001204
1205def list_fabric():
azvyagintsevbca1f462018-05-25 19:06:46 +03001206 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001207 Get list of all fabric
1208
1209 CLI Example:
1210
1211 .. code-block:: bash
1212
1213 salt 'maas-node' maasng.list_fabric
azvyagintsevbca1f462018-05-25 19:06:46 +03001214 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001215 fabrics = {}
1216 maas = _create_maas_client()
1217 json_res = json.loads(maas.get(u'api/2.0/fabrics/').read())
1218 LOG.info(json_res)
1219 for item in json_res:
1220 fabrics[item["name"]] = item
1221 return fabrics
1222
1223
azvyagintsevf3515c82018-06-26 18:59:05 +03001224def check_fabric(name):
1225 """
1226 Simple check that fabric already defined
1227 Return format:
1228 update - require update
1229 correct - fully coincides # not implemented
1230 not_exist - need's to be created
1231 """
1232
1233 ret = 'not_exist'
1234 fabrics = list_fabric()
1235 if name in fabrics.keys():
1236 LOG.debug("Requested fabrics with name:{} already exist".format(name))
1237 ret = 'update'
1238 return ret
1239
1240
1241def check_fabric_guess_with_cidr(name, cidrs):
1242 """
1243 Check, that fabric already defined OR it was autodiscovered
1244 WA to fix issue with hardcoded 'fabric-0'
1245 - Find all auto-discovered subnets by cidr
1246 - find all subnets, that SHOULD be configured to THIS subent
1247 Warning: most probably, will fail if some subnet defined
1248 to another fabric :(
1249
1250 { 'update' : ID } - require update
1251 { 'correct' : ID } - fully coincides # not implemented
1252 { 'not_exist' : None } - need's to be created
1253
1254 CLI Example:
1255
1256 .. code-block:: bash
1257
1258 salt 'maas-node' maasng.check_fabric_guess_with_cidr name='' cidrs=[]
1259 """
1260
1261 ret = {'not_exist': None}
1262 fabrics = list_fabric()
1263 # Simple check
1264 if name in fabrics:
1265 LOG.debug("Requested fabrics with name:{} already exist".format(name))
1266 f_id = fabrics[name]['id']
1267 ret = {'update': f_id}
1268 # Cidr check
1269 # All discovered subnets by cidr
1270 d_subnets = list_subnets(sort_by='cidr')
1271 # Check, that requested cidr already in discovered.
1272 # If it is - it would mean that fabric already
1273 # exist(fabric-0,most probably) but should be renamed.
1274 # Works only for first shot ;(
1275 # due curren-single-maas logic for 'vlan-subnet' mapping.
1276 # Probably, it will fail with future MAAS releases.
1277 for cidr in cidrs:
1278 if cidr in d_subnets:
1279 f_id = d_subnets[cidr]['vlan']['fabric_id']
1280 f_name = d_subnets[cidr]['vlan']['fabric']
1281 LOG.warning("Detected cidr:{} in fabric:{}".format(cidr, f_name))
1282 LOG.warning("Guessing, that fabric "
1283 "with current name:{}\n should be "
1284 "renamed to:{}".format(f_name, name))
1285 ret = {'update': f_id}
1286 return ret
1287 return ret
1288
1289
azvyagintsevf0904ac2018-07-05 18:53:26 +03001290def create_fabric(name, description=None, fabric_id=None, update=False):
azvyagintsevbca1f462018-05-25 19:06:46 +03001291 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001292 Create new fabric.
1293
1294 CLI Example:
1295
1296 .. code-block:: bash
1297
azvyagintsevf3515c82018-06-26 18:59:05 +03001298 salt 'maas-node' maasng.create_fabric name='123'
azvyagintsevbca1f462018-05-25 19:06:46 +03001299 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001300 result = {}
1301 data = {
1302 "name": name,
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001303 "class_type": '',
1304
1305 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001306 if description:
1307 data['description'] = description
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001308
1309 maas = _create_maas_client()
azvyagintsevf0904ac2018-07-05 18:53:26 +03001310 json_res = None
azvyagintsevf3515c82018-06-26 18:59:05 +03001311 try:
1312 if update:
1313 json_res = json.loads(
1314 maas.put(u"api/2.0/fabrics/{0}/".format(fabric_id),
1315 **data).read())
1316 result["new"] = "Fabric {0} created".format(json_res["name"])
1317 else:
1318 json_res = json.loads(
1319 maas.post(u"api/2.0/fabrics/", None, **data).read())
1320 result["changes"] = "Fabric {0} updated".format(json_res["name"])
1321 except Exception as inst:
1322 LOG.debug("create_fabric data:{}".format(data))
azvyagintsevbc421d52019-04-03 15:27:31 +03001323 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001324 LOG.error("Message:{0}".format(m))
1325 result['result'] = False
1326 result['comment'] = 'Error creating fabric: {0}'.format(name)
1327 result['error'] = m
1328 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001329 LOG.debug("crete_fabric:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001330 result['result'] = True
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001331 return result
1332
1333
azvyagintsevf3515c82018-06-26 18:59:05 +03001334def list_subnets(sort_by='name'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001335 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001336 Get list of subnets from maas server
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001337
1338 CLI Example:
1339
1340 .. code-block:: bash
1341
azvyagintsevf3515c82018-06-26 18:59:05 +03001342 salt 'maas-node' maasng.list_subnets
azvyagintsevbca1f462018-05-25 19:06:46 +03001343 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001344 subnets = {}
1345 maas = _create_maas_client()
1346 json_res = json.loads(maas.get(u'api/2.0/subnets/').read())
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001347 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001348 subnets[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001349 return subnets
1350
1351
azvyagintsevf3515c82018-06-26 18:59:05 +03001352def list_vlans(fabric, sort_by='vid'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001353 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001354 Get list of vlans in fabric
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001355
1356 CLI Example:
1357
1358 .. code-block:: bash
1359
azvyagintsevf3515c82018-06-26 18:59:05 +03001360 salt 'maas-node' maasng.list_vlans fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001361 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001362 vlans = {}
1363 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001364 fabric_id = get_fabricid(fabric)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001365
azvyagintsevf3515c82018-06-26 18:59:05 +03001366 try:
1367 json_res = json.loads(
1368 maas.get(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id)).read())
1369 except Exception as inst:
azvyagintsevbc421d52019-04-03 15:27:31 +03001370 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001371 LOG.error("Message:{0}".format(m))
1372 LOG.debug(json_res)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001373 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001374 vlans[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001375 return vlans
1376
1377
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001378def get_fabricid(fabric):
azvyagintsevbca1f462018-05-25 19:06:46 +03001379 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001380 Get id for specific fabric
1381
1382 CLI Example:
1383
1384 .. code-block:: bash
1385
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001386 salt 'maas-node' maasng.get_fabricid fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001387 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001388 try:
1389 return list_fabric()[fabric]['id']
1390 except KeyError:
azvyagintsevefb6f5d2018-07-10 14:16:19 +03001391 return {"error": "Fabric not found on MaaS server"}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001392
1393
azvyagintsevf3515c82018-06-26 18:59:05 +03001394def check_vlan_in_fabric(fabric, vlan):
1395 """
1396 Check that VLAN exactly defined
1397 Return format:
1398 update - require update
1399 correct - fully coincides # not implemented
1400 not_exist - need's to be created
1401 """
1402
1403 ret = 'not_exist'
1404 vlans = list_vlans(fabric)
1405 if vlan in vlans.keys():
1406 LOG.debug("Requested VLAN:{} already exist"
1407 "in FABRIC:{}".format(vlan, fabric))
1408 ret = 'update'
1409 return ret
1410
1411
Petr Ruzicka80471852018-07-13 14:08:27 +02001412def create_vlan_in_fabric(name, fabric, vlan, description, primary_rack, mtu=1500,
azvyagintsevf3515c82018-06-26 18:59:05 +03001413 dhcp_on=False, update=False, vlan_id=""):
azvyagintsevbca1f462018-05-25 19:06:46 +03001414 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001415 Update vlan
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001416 CLI Example:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001417 .. code-block:: bash
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001418 salt 'maas-node' maasng.update_vlan name, fabric, vid, description, dhcp_on
azvyagintsevbca1f462018-05-25 19:06:46 +03001419 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001420 result = {}
1421
1422 data = {
1423 "name": name,
1424 "dhcp_on": str(dhcp_on),
1425 "description": description,
azvyagintsev6913e5e2018-07-05 11:42:53 +03001426 "primary_rack": list_racks()[primary_rack]['system_id'],
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001427 }
Michael Polenchukd25da792018-07-19 18:27:11 +04001428 if mtu:
1429 data['mtu'] = str(mtu)
azvyagintsevf3515c82018-06-26 18:59:05 +03001430 vlan = str(vlan)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001431 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001432 fabric_id = get_fabricid(fabric)
azvyagintsevf3515c82018-06-26 18:59:05 +03001433 try:
1434 if update:
1435 # MAAS have buggy logic here. Fallowing api reference, here should
1436 # be passed VID - which mean, API ID for vlan.
1437 # Otherwise, at least for maas 2.3.3-6498-ge4db91d exactly VLAN
1438 # should be passed. so, make temp.backward-convertation.
1439 # json_res = json.loads(maas.put(u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id,vlan_id), **data).read())
1440 json_res = json.loads(maas.put(
1441 u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id, vlan),
1442 **data).read())
1443 else:
1444 data['vid'] = str(vlan)
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001445 json_res = json.loads(maas.post(
1446 u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id), None, **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001447 except Exception as inst:
1448 LOG.debug("create_vlan_in_fabric data:{}".format(data))
azvyagintsevbc421d52019-04-03 15:27:31 +03001449 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001450 LOG.error("Message:{0}".format(m))
1451 result['result'] = False
1452 result['comment'] = 'Error updating vlan: {0}'.format(name)
1453 result['error'] = m
1454 return result
1455 LOG.debug("create_vlan_in_fabric:{}".format(json_res))
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001456 result["new"] = "Vlan {0} was updated".format(json_res["name"])
1457
1458 return result
azvyagintsevbca1f462018-05-25 19:06:46 +03001459
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001460
azvyagintsevf3515c82018-06-26 18:59:05 +03001461def check_subnet(cidr, name, fabric, gateway_ip):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001462 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001463 Check that subnet exactly defined
1464 Return format:
1465 update - require update
1466 correct - fully coincides # not implemented
1467 not_exist - need's to be created
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001468 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001469
1470 ret = 'not_exist'
1471 subnets = list_subnets(sort_by='cidr')
1472 if cidr in subnets.keys():
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001473 LOG.debug("Requested subnet cidr:{} already exist".format(cidr))
1474 ret = 'update'
azvyagintsevf3515c82018-06-26 18:59:05 +03001475 return ret
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001476
1477
azvyagintsevf3515c82018-06-26 18:59:05 +03001478def create_subnet(cidr='', name='', fabric='', gateway_ip='', vlan='',
1479 update=False, subnet_id=''):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001480 """
1481 Create subnet
1482
1483 CLI Example:
1484
1485 .. code-block:: bash
1486
1487 salt 'maas-node' maasng.create_subnet cidr, name, fabric, gateway_ip
1488 """
1489
1490 fabric_id = get_fabricid(fabric)
1491 result = {}
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001492 vlan = str(vlan)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001493 data = {
1494 "cidr": cidr,
1495 "name": name,
1496 "fabric": str(fabric_id),
1497 "gateway_ip": gateway_ip,
azvyagintsevf3515c82018-06-26 18:59:05 +03001498 'vlan': vlan,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001499 }
1500 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001501 # FIXME: vlan definition not work in 2.3.3-6498-ge4db91d.
1502 LOG.warning("Ignoring parameter vlan:{}".format(vlan))
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001503 data.pop('vlan', '')
azvyagintsevf3515c82018-06-26 18:59:05 +03001504 try:
1505 if update:
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001506 json_res = json.loads(
1507 maas.put(u"api/2.0/subnets/{0}/".format(subnet_id), **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001508 else:
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001509 json_res = json.loads(
1510 maas.post(u"api/2.0/subnets/", None, **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001511 except Exception as inst:
1512 LOG.debug("create_subnet data:{}".format(data))
azvyagintsevbc421d52019-04-03 15:27:31 +03001513 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001514 LOG.error("Message:{0}".format(m))
1515 result['result'] = False
1516 result['comment'] = 'Error creating subnet: {0}'.format(name)
1517 result['error'] = m
1518 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001519 LOG.debug("create_subnet:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001520 result["new"] = "Subnet {0} with CIDR {1}" \
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001521 "and gateway {2} was created".format(
1522 name, cidr, gateway_ip)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001523
1524 return result
1525
1526
1527def get_subnet(subnet):
1528 """
1529 Get details for specific subnet
1530
1531 CLI Example:
1532
1533 .. code-block:: bash
1534
1535 salt 'maas-node' maasng.get_subnet subnet_name
1536 """
1537 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001538 return list_subnets()[subnet]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001539 except KeyError:
1540 return {"error": "Subnet not found on MaaS server"}
1541
1542
1543def get_subnetid(subnet):
1544 """
1545 Get id for specific subnet
1546
1547 CLI Example:
1548
1549 .. code-block:: bash
1550
1551 salt 'maas-node' maasng.get_subnetid subnet_name
1552 """
1553 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001554 return list_subnets()[subnet]['id']
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001555 except KeyError:
1556 return {"error": "Subnet not found on MaaS server"}
1557
1558
1559def list_ipranges():
1560 """
1561 Get list of all ipranges from maas server
1562
1563 CLI Example:
1564
1565 .. code-block:: bash
1566
1567 salt 'maas-node' maasng.list_ipranges
1568 """
1569 ipranges = {}
1570 maas = _create_maas_client()
1571 json_res = json.loads(maas.get(u'api/2.0/ipranges/').read())
1572 for item in json_res:
1573 ipranges[item["start_ip"]] = item
1574 return ipranges
1575
1576
azvyagintsevf0904ac2018-07-05 18:53:26 +03001577def create_iprange(type_range, start_ip, end_ip, subnet=None, comment=None):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001578 """
1579 Create ip range
1580
1581 CLI Example:
1582
1583 .. code-block:: bash
1584
1585 salt 'maas-node' maasng.create_iprange type, start ip, end ip, comment
1586 """
1587 result = {}
1588
1589 data = {
1590 "type": type_range,
1591 "start_ip": start_ip,
1592 "end_ip": end_ip,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001593 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001594 if comment:
1595 data['comment'] = comment
azvyagintsevf0904ac2018-07-05 18:53:26 +03001596 if subnet:
1597 subnet_id = list_subnets()[subnet]['id']
1598 data['subnet'] = str(subnet_id)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001599 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001600 _name = "Type:{}: {}-{}".format(type_range, start_ip, end_ip)
1601 try:
1602 json_res = json.loads(
1603 maas.post(u"api/2.0/ipranges/", None, **data).read())
1604 except Exception as inst:
azvyagintsevbc421d52019-04-03 15:27:31 +03001605 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001606 LOG.error("Message:{0}".format(m))
1607 result['result'] = False
1608 result['comment'] = 'Error creating iprange:{0}'.format(_name)
1609 result['error'] = m
1610 return result
1611 result["new"] = "Iprange: {0} has been created".format(_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001612 LOG.debug("create_iprange:{}".format(json_res))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001613
1614 return result
1615
1616
1617def get_iprangeid(start_ip):
1618 """
1619 Get id for ip range from maas server
1620
1621 CLI Example:
1622
1623 .. code-block:: bash
1624
1625 salt 'maas-node' maasng.get_iprangeid start_ip
1626 """
1627 try:
1628 return list_ipranges()[start_ip]['id']
1629 except KeyError:
1630 return {"error": "Ip range not found on MaaS server"}
1631
1632
1633def get_startip(start_ip):
1634 """
1635 Get start ip for ip range
1636
1637 CLI Example:
1638
1639 .. code-block:: bash
1640
1641 salt 'maas-node' maasng.get_startip start ip
1642 """
1643 try:
1644 return list_ipranges()[start_ip]
1645 except KeyError:
1646 return {"error": "Ip range not found on MaaS server"}
azvyagintsevbca1f462018-05-25 19:06:46 +03001647# END NETWORKING
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001648
1649# MAAS CONFIG SECTION
1650
1651
azvyagintsev58947072018-06-29 12:09:48 +03001652def _getHTTPCode(url):
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001653 code = '003'
1654 m = ''
azvyagintsev58947072018-06-29 12:09:48 +03001655 try:
1656 connection = urllib2.urlopen(url)
1657 code = connection.getcode()
1658 connection.close()
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001659 except (urllib2.HTTPError, urllib2.URLError) as e:
1660 try:
1661 code = e.getcode()
1662 except:
1663 m = e.reason
1664 pass
azvyagintsevf3515c82018-06-26 18:59:05 +03001665 LOG.debug("Unexpected http code:{} from "
1666 "url:{}\nwith message:{}".format(code, url, m))
azvyagintsev58947072018-06-29 12:09:48 +03001667 pass
1668 return code
1669
1670
1671def wait_for_http_code(url=None, expected=[200]):
1672 """
1673 Simple function, which just wait for avaible api, aka wait for 200.
1674
1675 CLI Example:
1676
1677 .. code-block:: bash
1678
1679 salt 'maas-node' maasng.wait_for_http_code url expected=[200]
1680
1681 """
1682 ret = {}
1683 started_at = time.time()
1684 poll_time = 5
1685 timeout = 60 * 2
1686 while _getHTTPCode(url) not in expected:
1687 c_timeout = timeout - (time.time() - started_at)
1688 if c_timeout <= 0:
1689 ret['result'] = False
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001690 ret["comment"] = "api:{} not answered in time".format(url)
azvyagintsev58947072018-06-29 12:09:48 +03001691 return ret
1692 LOG.info(
1693 "Waiting for api:{0}\n"
1694 "sleep for:{1}s "
1695 "Left:{2}/{3}s".format(url, poll_time, round(c_timeout),
1696 timeout))
1697 time.sleep(poll_time)
1698 ret['result'] = True
1699 ret["comment"] = "MAAS API:{} up.".format(url)
1700 return ret
1701
1702
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001703def _get_boot_source_id_by_url(url):
1704 # FIXME: fix ret\validation
1705 try:
1706 bs_id = get_boot_source(url=url)["id"]
1707 except KeyError:
1708 return {"error": "boot-source:{0} not exist!".format(url)}
1709 return bs_id
1710
1711
1712def get_boot_source(url=None):
1713 """
1714 Read a boot source by url. If url not specified - return all.
1715
1716 CLI Example:
1717
1718 .. code-block:: bash
1719
1720 salt 'maas-node' maasng.get_boot_source url
1721
1722 """
1723 boot_sources = {}
1724 maas = _create_maas_client()
1725 json_res = json.loads(maas.get(u'api/2.0/boot-sources/').read() or 'null')
1726 for item in json_res:
1727 boot_sources[str(item["url"])] = item
1728 if url:
1729 return boot_sources.get(url, {})
1730 return boot_sources
1731
1732
1733def delete_boot_source(url, bs_id=None):
1734 """
1735 Delete a boot source by url.
1736
1737 CLI Example:
1738
1739 .. code-block:: bash
1740
1741 sal 'maas-node' maasng.delete url
1742
1743 """
1744 result = {}
1745 if not bs_id:
1746 bs_id = _get_boot_source_id_by_url(url)
1747 maas = _create_maas_client()
1748 json_res = json.loads(maas.delete(
1749 u'/api/2.0/boot-sources/{0}/'.format(bs_id)).read() or 'null')
1750 LOG.debug("delete_boot_source:{}".format(json_res))
1751 result["new"] = "Boot-resource {0} deleted".format(url)
1752 return result
1753
1754
1755def boot_sources_delete_all_others(except_urls=[]):
1756 """
1757 Delete all boot-sources, except defined in 'except_urls' list.
1758 """
azvyagintseve2e37a12018-11-01 14:45:49 +02001759 result = {'changes': {}}
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001760 maas_boot_sources = get_boot_source()
1761 if 0 in [len(except_urls), len(maas_boot_sources)]:
1762 result['result'] = None
azvyagintseve2e37a12018-11-01 14:45:49 +02001763 result["comment"] = "'except_urls' or 'maas boot-sources' for " \
1764 "delete empty. No changes goinng to be."
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001765 return result
azvyagintseve2e37a12018-11-01 14:45:49 +02001766 # FIXME: fix 'changes' accumulator
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001767 for url in maas_boot_sources.keys():
1768 if url not in except_urls:
1769 LOG.info("Removing boot-source:{}".format(url))
1770 boot_resources_import(action='stop_import', wait=True)
1771 result["changes"] = delete_boot_source(url)
1772 return result
1773
1774
1775def create_boot_source(url, keyring_filename='', keyring_data='', wait=False):
1776 """
1777 Create and import maas boot-source: link to maas-ephemeral repo
1778 Be aware, those step will import resource to rack ctrl, but you also need to import
1779 them into the region!
1780
1781
1782 :param url: The URL of the BootSource.
1783 :param keyring_filename: The path to the keyring file for this BootSource.
1784 :param keyring_data: The GPG keyring for this BootSource, base64-encoded data.
1785
1786 """
1787
1788 # TODO: not work with 'update' currently => keyring update may fail.
1789 result = {}
1790
1791 data = {
1792 "url": url,
1793 "keyring_filename": keyring_filename,
1794 "keyring_data": str(keyring_data),
1795 }
1796
1797 maas = _create_maas_client()
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001798 if url in get_boot_source():
1799 result['result'] = None
1800 result["comment"] = "boot resource already exist"
1801 return result
1802
1803 # NOTE: maas.post will return 400, if url already defined.
1804 json_res = json.loads(
1805 maas.post(u'api/2.0/boot-sources/', None, **data).read())
1806 if wait:
1807 LOG.debug(
1808 "Sleep for 5s,to get MaaS some time to process previous request")
1809 time.sleep(5)
1810 ret = boot_resources_is_importing(wait=True)
1811 if ret is dict:
1812 return ret
1813 LOG.debug("create_boot_source:{}".format(json_res))
1814 result["new"] = "boot resource {0} was created".format(json_res["url"])
1815
1816 return result
1817
1818
1819def boot_resources_import(action='import', wait=False):
1820 """
1821 import/stop_import the boot resources.
1822
1823 :param action: import\stop_import
1824 :param wait: True\False. Wait till process finished.
1825
1826 CLI Example:
1827
1828 .. code-block:: bash
1829
1830 salt 'maas-node' maasng.boot_resources_import action='import'
1831
1832 """
1833 maas = _create_maas_client()
1834 # Have no idea why, but usual jsonloads not work here..
1835 imp = maas.post(u'api/2.0/boot-resources/', action)
1836 if imp.code == 200:
1837 LOG.debug('boot_resources_import:{}'.format(imp.readline()))
1838 if wait:
1839 boot_resources_is_importing(wait=True)
1840 return True
1841 else:
1842 return False
1843
1844
1845def boot_resources_is_importing(wait=False):
1846 maas = _create_maas_client()
1847 result = {}
1848 if wait:
1849 started_at = time.time()
1850 poll_time = 5
1851 timeout = 60 * 15
1852 while boot_resources_is_importing(wait=False):
1853 c_timeout = timeout - (time.time() - started_at)
1854 if c_timeout <= 0:
1855 result['result'] = False
1856 result["comment"] = "Boot-resources import not finished in time"
1857 return result
1858 LOG.info(
1859 "Waiting boot-resources import done\n"
1860 "sleep for:{}s "
1861 "Left:{}/{}s".format(poll_time, round(c_timeout), timeout))
1862 time.sleep(poll_time)
1863 return json.loads(
1864 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1865 else:
1866 return json.loads(
1867 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1868
1869#####
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001870# def boot_sources_selections_delete_all_others(except_urls=[]):
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001871# """
1872# """
1873# result = {}
1874# return result
1875
1876
1877def is_boot_source_selections_in(dict1, list1):
1878 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001879 Check that requested boot-selection already in maas bs selections,
1880 if True- return bss id.
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001881 # FIXME: those hack check doesn't look good.
1882 """
1883 for bs in list1:
1884 same = set(dict1.keys()) & set(bs.keys())
1885 if all(elem in same for elem in
1886 ['os', 'release', 'arches', 'subarches', 'labels']):
azvyagintsevf3515c82018-06-26 18:59:05 +03001887 LOG.debug("boot-selection in maas:{0}\n"
1888 "looks same to requested:{1}".format(bs, dict1))
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001889 return bs['id']
1890 return False
1891
1892
1893def get_boot_source_selections(bs_url):
1894 """
1895 Get boot-source selections.
1896 """
1897 # check for key_error!
1898 bs_id = _get_boot_source_id_by_url(bs_url)
1899 maas = _create_maas_client()
1900 json_res = json.loads(
1901 maas.get(u'/api/2.0/boot-sources/{0}/selections/'.format(bs_id)).read())
1902 LOG.debug(
1903 "get_boot_source_selections for url:{} \n{}".format(bs_url, json_res))
1904 return json_res
1905
1906
1907def create_boot_source_selections(bs_url, os, release, arches="*",
1908 subarches="*", labels="*", wait=True):
1909 """
1910 Create a new boot source selection for bs_url.
1911 :param os: The OS (e.g. ubuntu, centos) for which to import resources.Required.
1912 :param release: The release for which to import resources. Required.
1913 :param arches: The architecture list for which to import resources.
1914 :param subarches: The subarchitecture list for which to import resources.
1915 :param labels: The label lists for which to import resources.
1916 """
1917
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001918 result = {"result": True, 'name': bs_url, 'changes': None}
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001919
1920 data = {
1921 "os": os,
1922 "release": release,
1923 "arches": arches,
1924 "subarches": subarches,
1925 "labels": labels,
1926 }
1927
1928 maas = _create_maas_client()
1929 bs_id = _get_boot_source_id_by_url(bs_url)
1930 # TODO add pre-create verify
1931 maas_bs_s = get_boot_source_selections(bs_url)
1932 if is_boot_source_selections_in(data, maas_bs_s):
1933 result["result"] = True
azvyagintsevf3515c82018-06-26 18:59:05 +03001934 result["comment"] = 'Requested boot-source selection ' \
1935 'for {0} already exist.'.format(
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001936 bs_url)
1937 return result
1938
1939 # NOTE: maas.post will return 400, if url already defined.
azvyagintsevcb54d142018-06-19 16:18:32 +03001940 # Also, maas need's some time to import info about stream.
1941 # unfortunatly, maas don't have any call to check stream-import-info - so, we need to implement
1942 # at least simple retry ;(
1943 json_res = False
1944 poll_time = 5
azvyagintseve2e37a12018-11-01 14:45:49 +02001945 for i in range(0, 10):
azvyagintsevcb54d142018-06-19 16:18:32 +03001946 try:
1947 json_res = json.loads(
1948 maas.post(u'api/2.0/boot-sources/{0}/selections/'.format(bs_id), None,
1949 **data).read())
1950 except Exception as inst:
azvyagintsevbc421d52019-04-03 15:27:31 +03001951 m = inst.message
azvyagintsevf3515c82018-06-26 18:59:05 +03001952 LOG.warning("boot_source_selections "
1953 "catch error during processing. Most-probably, "
azvyagintseve2e37a12018-11-01 14:45:49 +02001954 "streams data not imported yet.\nSleep:{}s "
1955 "Retry:{}/10".format(poll_time, i))
azvyagintsevcb54d142018-06-19 16:18:32 +03001956 LOG.warning("Message:{0}".format(m))
1957 time.sleep(poll_time)
1958 continue
1959 break
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001960 LOG.debug("create_boot_source_selections:{}".format(json_res))
azvyagintsevcb54d142018-06-19 16:18:32 +03001961 if not json_res:
1962 result["result"] = False
azvyagintsevf3515c82018-06-26 18:59:05 +03001963 result["comment"] = 'Failed to create requested boot-source selection' \
1964 ' for {0}.'.format(bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001965 return result
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001966 if wait:
1967 LOG.debug(
1968 "Sleep for 5s,to get MaaS some time to process previous request")
1969 time.sleep(5)
1970 ret = boot_resources_import(action='import', wait=True)
1971 if ret is dict:
1972 return ret
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001973 result["comment"] = "boot-source selection for {0} was created".format(
1974 bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001975 result["new"] = data
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001976
1977 return result
1978
1979# END MAAS CONFIG SECTION
azvyagintsevcb54d142018-06-19 16:18:32 +03001980
1981# RACK CONTROLLERS SECTION
1982
1983
1984def get_rack(hostname):
1985 """
1986 Get information about specified rackd
1987
1988 CLI Example:
1989
1990 .. code-block:: bash
1991
1992 salt-call maasng.get_rack rack_hostname
1993 """
1994 try:
1995 return list_racks()[hostname]
1996 except KeyError:
1997 return {"error": "rack:{} not found on MaaS server".format(hostname)}
1998
1999
azvyagintsev6913e5e2018-07-05 11:42:53 +03002000def list_racks(sort_by='hostname'):
azvyagintsevcb54d142018-06-19 16:18:32 +03002001 """
2002 Get list of all rack controllers from maas server
2003
2004 CLI Example:
2005
2006 .. code-block:: bash
2007
2008 salt-call maasng.list_racks
2009 """
2010 racks = {}
2011 maas = _create_maas_client()
2012 json_res = json.loads(
2013 maas.get(u"/api/2.0/rackcontrollers/").read() or 'null')
2014 for item in json_res:
azvyagintsev6913e5e2018-07-05 11:42:53 +03002015 racks[item[sort_by]] = item
azvyagintsevcb54d142018-06-19 16:18:32 +03002016 return racks
2017
2018
2019def sync_bs_to_rack(hostname=None):
2020 """
2021 Sync RACK boot-sources with REGION. If no hostname probided - sync to all.
2022
2023 CLI Example:
2024
2025 .. code-block:: bash
2026
2027 salt-call maasng.sync_bs_to_rack rack_hostname
2028 """
2029 ret = {}
2030 maas = _create_maas_client()
2031 if not hostname:
2032 LOG.info("boot-sources sync initiated for ALL Rack's")
2033 # Convert to json-like format
2034 json_res = json.loads('["{0}"]'.format(
2035 maas.post(u"/api/2.0/rackcontrollers/",
2036 'import_boot_images').read()))
2037 LOG.debug("sync_bs_to_rack:{}".format(json_res))
2038 ret['result'] = True
2039 ret['comment'] = "boot-sources sync initiated for ALL Rack's"
2040 return ret
2041 LOG.info("boot-sources sync initiated for RACK:{0}".format(hostname))
2042 # Convert to json-like format
2043 json_res = json.loads('["{0}"]'.format(maas.post(
2044 u"/api/2.0/rackcontrollers/{0}/".format(
2045 get_rack(hostname)['system_id']),
2046 'import_boot_images').read()))
2047 LOG.debug("sync_bs_to_rack:{}".format(json_res))
2048 ret['result'] = True
2049 ret['comment'] = "boot-sources sync initiated for {0} Rack's".format(
2050 hostname)
2051 return
2052
2053
2054def rack_list_boot_imgs(hostname):
2055 ret = {}
2056 maas = _create_maas_client()
2057 LOG.debug("rack_list_boot_imgs:{}".format(hostname))
2058 ret = json.loads(maas.get(u"/api/2.0/rackcontrollers/{0}/".format(
2059 get_rack(hostname)['system_id']), 'list_boot_images').read() or 'null')
2060 return ret
2061
2062
2063def is_rack_synced(hostname):
2064 rez = rack_list_boot_imgs(hostname)['status']
2065 if rez == 'synced':
2066 return True
2067 return False
2068
2069# TODO do we actually need _exact_ check per-pack?
2070# def wait_for_images_on_rack(hostname):
2071#
2072# """
2073# WA function, to be able check that RACK actually done SYNC images
2074# for REQUIRED images at least.
2075# Required image to be fetched from
2076# reclass:maas:region:boot_sources_selections:[keys]:os/release formation
2077#
2078# CLI Example:
2079#
2080# .. code-block:: bash
2081#
2082# salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
2083# """
2084# try:
2085# bss = __salt__['config.get']('maas')['region']['boot_sources_selections']
2086# except KeyError:
2087# ret['result'] = None
2088# ret['comment'] = "boot_sources_selections definition for sync not found."
2089# return ret
2090# s_names = []
2091# # Format u'name': u'ubuntu/xenial'
2092# for v in bss.values():s_names.append("{0}/{1}".format(v['os'],v['release']))
2093# # Each names, should be in rack and whole rack should be in sync-ed state
2094
2095
2096def sync_and_wait_bs_to_all_racks():
2097 """
2098 Sync ALL rack's with regions source images.
2099
2100 CLI Example:
2101
2102 .. code-block:: bash
2103
2104 salt-call maasng.sync_and_wait_bs_to_all_racks
2105 """
2106 sync_bs_to_rack()
2107 for rack in list_racks().keys():
2108 wait_for_sync_bs_to_rack(hostname=rack)
2109 return True
2110
2111
2112def wait_for_sync_bs_to_rack(hostname=None):
2113 """
2114 Wait for boot images sync finished, on exact rack
2115
2116 CLI Example:
2117
2118 .. code-block:: bash
2119
2120 salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
2121 """
2122 ret = {}
2123 started_at = time.time()
2124 poll_time = 5
2125 timeout = 60 * 15
2126 while not is_rack_synced(hostname):
2127 c_timeout = timeout - (time.time() - started_at)
2128 if c_timeout <= 0:
2129 ret['result'] = False
2130 ret[
2131 "comment"] = "Boot-resources sync on rackd:{0}" \
2132 "not finished in time".format(
2133 hostname)
2134 return ret
2135 LOG.info(
2136 "Waiting boot-resources sync done to rack:{0}\n"
2137 "sleep for:{1}s "
2138 "Left:{2}/{3}s".format(hostname, poll_time, round(c_timeout),
2139 timeout))
2140 time.sleep(poll_time)
2141 ret['result'] = is_rack_synced(hostname)
2142 ret["comment"] = "Boot-resources sync on rackd:{0} finished".format(
2143 hostname)
2144 return ret
2145
2146# END RACK CONTROLLERS SECTION
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02002147# SSHKEYS
2148
2149
2150def list_sshkeys():
2151 """
2152 Get list of all sshkeys
2153
2154 CLI Example:
2155
2156 .. code-block:: bash
2157
2158 salt 'maas-node' maasng.list_sshkeys
2159 salt-call maasng.list_sshkeys
2160 """
2161 ssh = {}
2162 maas = _create_maas_client()
2163 json_res = json.loads(maas.get(u'api/2.0/account/prefs/sshkeys/').read())
2164 LOG.info(json_res)
2165 for item in json_res:
2166 ssh[item["key"]] = item
2167 return ssh
2168
2169
2170def add_sshkey(sshkey):
2171 """
2172 Add SSH key for user to MAAS.
2173
2174 CLI Example:
2175
2176 .. code-block:: bash
2177
2178 salt 'maas-node' maasng.add_sshkey sshkey
2179 salt-call maasng.add_sshkey sshkey
2180 """
2181 data = {
2182 "key": sshkey,
2183 }
2184 result = {}
2185 maas = _create_maas_client()
2186
2187 maas.post(u"/api/2.0/account/prefs/sshkeys/", None, **data).read()
2188 result["new"] = "SSH Key {0} was added.".format(sshkey)
2189
2190 return result
2191
2192
2193def get_sshkey(sshkey):
2194 """
2195 Get start ip for ip range
2196
2197 CLI Example:
2198
2199 .. code-block:: bash
2200
2201 salt 'maas-node' maasng.get_sshkey sshkey
2202 salt-call maasng.get_sshkey sshkey
2203 """
2204 try:
2205 return list_sshkeys()[sshkey]
2206 except KeyError:
2207 return {"error": "SSH key not found on MaaS server"}
2208# END SSHKEYS