blob: 29a2c07227c7f2fd9b082cda0b235baa9137a6f2 [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))
1323 try:
1324 m = inst.readlines()
1325 except:
1326 m = inst.message
1327 LOG.error("Message:{0}".format(m))
1328 result['result'] = False
1329 result['comment'] = 'Error creating fabric: {0}'.format(name)
1330 result['error'] = m
1331 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001332 LOG.debug("crete_fabric:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001333 result['result'] = True
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001334 return result
1335
1336
azvyagintsevf3515c82018-06-26 18:59:05 +03001337def list_subnets(sort_by='name'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001338 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001339 Get list of subnets from maas server
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001340
1341 CLI Example:
1342
1343 .. code-block:: bash
1344
azvyagintsevf3515c82018-06-26 18:59:05 +03001345 salt 'maas-node' maasng.list_subnets
azvyagintsevbca1f462018-05-25 19:06:46 +03001346 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001347 subnets = {}
1348 maas = _create_maas_client()
1349 json_res = json.loads(maas.get(u'api/2.0/subnets/').read())
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001350 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001351 subnets[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001352 return subnets
1353
1354
azvyagintsevf3515c82018-06-26 18:59:05 +03001355def list_vlans(fabric, sort_by='vid'):
azvyagintsevbca1f462018-05-25 19:06:46 +03001356 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001357 Get list of vlans in fabric
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001358
1359 CLI Example:
1360
1361 .. code-block:: bash
1362
azvyagintsevf3515c82018-06-26 18:59:05 +03001363 salt 'maas-node' maasng.list_vlans fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001364 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001365 vlans = {}
1366 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001367 fabric_id = get_fabricid(fabric)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001368
azvyagintsevf3515c82018-06-26 18:59:05 +03001369 try:
1370 json_res = json.loads(
1371 maas.get(u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id)).read())
1372 except Exception as inst:
1373 m = inst.readlines()
1374 LOG.error("Message:{0}".format(m))
1375 LOG.debug(json_res)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001376 for item in json_res:
azvyagintsevf3515c82018-06-26 18:59:05 +03001377 vlans[item[sort_by]] = item
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001378 return vlans
1379
1380
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001381def get_fabricid(fabric):
azvyagintsevbca1f462018-05-25 19:06:46 +03001382 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001383 Get id for specific fabric
1384
1385 CLI Example:
1386
1387 .. code-block:: bash
1388
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001389 salt 'maas-node' maasng.get_fabricid fabric_name
azvyagintsevbca1f462018-05-25 19:06:46 +03001390 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001391 try:
1392 return list_fabric()[fabric]['id']
1393 except KeyError:
azvyagintsevefb6f5d2018-07-10 14:16:19 +03001394 return {"error": "Fabric not found on MaaS server"}
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001395
1396
azvyagintsevf3515c82018-06-26 18:59:05 +03001397def check_vlan_in_fabric(fabric, vlan):
1398 """
1399 Check that VLAN exactly defined
1400 Return format:
1401 update - require update
1402 correct - fully coincides # not implemented
1403 not_exist - need's to be created
1404 """
1405
1406 ret = 'not_exist'
1407 vlans = list_vlans(fabric)
1408 if vlan in vlans.keys():
1409 LOG.debug("Requested VLAN:{} already exist"
1410 "in FABRIC:{}".format(vlan, fabric))
1411 ret = 'update'
1412 return ret
1413
1414
Petr Ruzicka80471852018-07-13 14:08:27 +02001415def create_vlan_in_fabric(name, fabric, vlan, description, primary_rack, mtu=1500,
azvyagintsevf3515c82018-06-26 18:59:05 +03001416 dhcp_on=False, update=False, vlan_id=""):
azvyagintsevbca1f462018-05-25 19:06:46 +03001417 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001418 Update vlan
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001419 CLI Example:
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001420 .. code-block:: bash
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001421 salt 'maas-node' maasng.update_vlan name, fabric, vid, description, dhcp_on
azvyagintsevbca1f462018-05-25 19:06:46 +03001422 """
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001423 result = {}
1424
1425 data = {
1426 "name": name,
1427 "dhcp_on": str(dhcp_on),
1428 "description": description,
azvyagintsev6913e5e2018-07-05 11:42:53 +03001429 "primary_rack": list_racks()[primary_rack]['system_id'],
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001430 }
Michael Polenchukd25da792018-07-19 18:27:11 +04001431 if mtu:
1432 data['mtu'] = str(mtu)
azvyagintsevf3515c82018-06-26 18:59:05 +03001433 vlan = str(vlan)
Pavel Cizinsky0995e8f2018-05-04 17:10:37 +02001434 maas = _create_maas_client()
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001435 fabric_id = get_fabricid(fabric)
azvyagintsevf3515c82018-06-26 18:59:05 +03001436 try:
1437 if update:
1438 # MAAS have buggy logic here. Fallowing api reference, here should
1439 # be passed VID - which mean, API ID for vlan.
1440 # Otherwise, at least for maas 2.3.3-6498-ge4db91d exactly VLAN
1441 # should be passed. so, make temp.backward-convertation.
1442 # json_res = json.loads(maas.put(u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id,vlan_id), **data).read())
1443 json_res = json.loads(maas.put(
1444 u'api/2.0/fabrics/{0}/vlans/{1}/'.format(fabric_id, vlan),
1445 **data).read())
1446 else:
1447 data['vid'] = str(vlan)
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001448 json_res = json.loads(maas.post(
1449 u'api/2.0/fabrics/{0}/vlans/'.format(fabric_id), None, **data).read())
azvyagintsevf3515c82018-06-26 18:59:05 +03001450 except Exception as inst:
1451 LOG.debug("create_vlan_in_fabric data:{}".format(data))
1452 try:
1453 m = inst.readlines()
1454 except:
1455 m = inst.message
1456 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))
1519 try:
1520 m = inst.readlines()
1521 except:
1522 m = inst.message
1523 LOG.error("Message:{0}".format(m))
1524 result['result'] = False
1525 result['comment'] = 'Error creating subnet: {0}'.format(name)
1526 result['error'] = m
1527 return result
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001528 LOG.debug("create_subnet:{}".format(json_res))
azvyagintsevf3515c82018-06-26 18:59:05 +03001529 result["new"] = "Subnet {0} with CIDR {1}" \
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001530 "and gateway {2} was created".format(
1531 name, cidr, gateway_ip)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001532
1533 return result
1534
1535
1536def get_subnet(subnet):
1537 """
1538 Get details for specific subnet
1539
1540 CLI Example:
1541
1542 .. code-block:: bash
1543
1544 salt 'maas-node' maasng.get_subnet subnet_name
1545 """
1546 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001547 return list_subnets()[subnet]
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001548 except KeyError:
1549 return {"error": "Subnet not found on MaaS server"}
1550
1551
1552def get_subnetid(subnet):
1553 """
1554 Get id for specific subnet
1555
1556 CLI Example:
1557
1558 .. code-block:: bash
1559
1560 salt 'maas-node' maasng.get_subnetid subnet_name
1561 """
1562 try:
azvyagintsevf3515c82018-06-26 18:59:05 +03001563 return list_subnets()[subnet]['id']
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001564 except KeyError:
1565 return {"error": "Subnet not found on MaaS server"}
1566
1567
1568def list_ipranges():
1569 """
1570 Get list of all ipranges from maas server
1571
1572 CLI Example:
1573
1574 .. code-block:: bash
1575
1576 salt 'maas-node' maasng.list_ipranges
1577 """
1578 ipranges = {}
1579 maas = _create_maas_client()
1580 json_res = json.loads(maas.get(u'api/2.0/ipranges/').read())
1581 for item in json_res:
1582 ipranges[item["start_ip"]] = item
1583 return ipranges
1584
1585
azvyagintsevf0904ac2018-07-05 18:53:26 +03001586def create_iprange(type_range, start_ip, end_ip, subnet=None, comment=None):
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001587 """
1588 Create ip range
1589
1590 CLI Example:
1591
1592 .. code-block:: bash
1593
1594 salt 'maas-node' maasng.create_iprange type, start ip, end ip, comment
1595 """
1596 result = {}
1597
1598 data = {
1599 "type": type_range,
1600 "start_ip": start_ip,
1601 "end_ip": end_ip,
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001602 }
azvyagintsevf3515c82018-06-26 18:59:05 +03001603 if comment:
1604 data['comment'] = comment
azvyagintsevf0904ac2018-07-05 18:53:26 +03001605 if subnet:
1606 subnet_id = list_subnets()[subnet]['id']
1607 data['subnet'] = str(subnet_id)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001608 maas = _create_maas_client()
azvyagintsevf3515c82018-06-26 18:59:05 +03001609 _name = "Type:{}: {}-{}".format(type_range, start_ip, end_ip)
1610 try:
1611 json_res = json.loads(
1612 maas.post(u"api/2.0/ipranges/", None, **data).read())
1613 except Exception as inst:
1614 try:
1615 m = inst.readlines()
1616 except:
1617 m = inst.message
1618 LOG.error("Message:{0}".format(m))
1619 result['result'] = False
1620 result['comment'] = 'Error creating iprange:{0}'.format(_name)
1621 result['error'] = m
1622 return result
1623 result["new"] = "Iprange: {0} has been created".format(_name)
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001624 LOG.debug("create_iprange:{}".format(json_res))
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001625
1626 return result
1627
1628
1629def get_iprangeid(start_ip):
1630 """
1631 Get id for ip range from maas server
1632
1633 CLI Example:
1634
1635 .. code-block:: bash
1636
1637 salt 'maas-node' maasng.get_iprangeid start_ip
1638 """
1639 try:
1640 return list_ipranges()[start_ip]['id']
1641 except KeyError:
1642 return {"error": "Ip range not found on MaaS server"}
1643
1644
1645def get_startip(start_ip):
1646 """
1647 Get start ip for ip range
1648
1649 CLI Example:
1650
1651 .. code-block:: bash
1652
1653 salt 'maas-node' maasng.get_startip start ip
1654 """
1655 try:
1656 return list_ipranges()[start_ip]
1657 except KeyError:
1658 return {"error": "Ip range not found on MaaS server"}
azvyagintsevbca1f462018-05-25 19:06:46 +03001659# END NETWORKING
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001660
1661# MAAS CONFIG SECTION
1662
1663
azvyagintsev58947072018-06-29 12:09:48 +03001664def _getHTTPCode(url):
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001665 code = '003'
1666 m = ''
azvyagintsev58947072018-06-29 12:09:48 +03001667 try:
1668 connection = urllib2.urlopen(url)
1669 code = connection.getcode()
1670 connection.close()
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001671 except (urllib2.HTTPError, urllib2.URLError) as e:
1672 try:
1673 code = e.getcode()
1674 except:
1675 m = e.reason
1676 pass
azvyagintsevf3515c82018-06-26 18:59:05 +03001677 LOG.debug("Unexpected http code:{} from "
1678 "url:{}\nwith message:{}".format(code, url, m))
azvyagintsev58947072018-06-29 12:09:48 +03001679 pass
1680 return code
1681
1682
1683def wait_for_http_code(url=None, expected=[200]):
1684 """
1685 Simple function, which just wait for avaible api, aka wait for 200.
1686
1687 CLI Example:
1688
1689 .. code-block:: bash
1690
1691 salt 'maas-node' maasng.wait_for_http_code url expected=[200]
1692
1693 """
1694 ret = {}
1695 started_at = time.time()
1696 poll_time = 5
1697 timeout = 60 * 2
1698 while _getHTTPCode(url) not in expected:
1699 c_timeout = timeout - (time.time() - started_at)
1700 if c_timeout <= 0:
1701 ret['result'] = False
azvyagintsevfc1fcff2018-06-29 16:44:54 +03001702 ret["comment"] = "api:{} not answered in time".format(url)
azvyagintsev58947072018-06-29 12:09:48 +03001703 return ret
1704 LOG.info(
1705 "Waiting for api:{0}\n"
1706 "sleep for:{1}s "
1707 "Left:{2}/{3}s".format(url, poll_time, round(c_timeout),
1708 timeout))
1709 time.sleep(poll_time)
1710 ret['result'] = True
1711 ret["comment"] = "MAAS API:{} up.".format(url)
1712 return ret
1713
1714
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001715def _get_boot_source_id_by_url(url):
1716 # FIXME: fix ret\validation
1717 try:
1718 bs_id = get_boot_source(url=url)["id"]
1719 except KeyError:
1720 return {"error": "boot-source:{0} not exist!".format(url)}
1721 return bs_id
1722
1723
1724def get_boot_source(url=None):
1725 """
1726 Read a boot source by url. If url not specified - return all.
1727
1728 CLI Example:
1729
1730 .. code-block:: bash
1731
1732 salt 'maas-node' maasng.get_boot_source url
1733
1734 """
1735 boot_sources = {}
1736 maas = _create_maas_client()
1737 json_res = json.loads(maas.get(u'api/2.0/boot-sources/').read() or 'null')
1738 for item in json_res:
1739 boot_sources[str(item["url"])] = item
1740 if url:
1741 return boot_sources.get(url, {})
1742 return boot_sources
1743
1744
1745def delete_boot_source(url, bs_id=None):
1746 """
1747 Delete a boot source by url.
1748
1749 CLI Example:
1750
1751 .. code-block:: bash
1752
1753 sal 'maas-node' maasng.delete url
1754
1755 """
1756 result = {}
1757 if not bs_id:
1758 bs_id = _get_boot_source_id_by_url(url)
1759 maas = _create_maas_client()
1760 json_res = json.loads(maas.delete(
1761 u'/api/2.0/boot-sources/{0}/'.format(bs_id)).read() or 'null')
1762 LOG.debug("delete_boot_source:{}".format(json_res))
1763 result["new"] = "Boot-resource {0} deleted".format(url)
1764 return result
1765
1766
1767def boot_sources_delete_all_others(except_urls=[]):
1768 """
1769 Delete all boot-sources, except defined in 'except_urls' list.
1770 """
azvyagintseve2e37a12018-11-01 14:45:49 +02001771 result = {'changes': {}}
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001772 maas_boot_sources = get_boot_source()
1773 if 0 in [len(except_urls), len(maas_boot_sources)]:
1774 result['result'] = None
azvyagintseve2e37a12018-11-01 14:45:49 +02001775 result["comment"] = "'except_urls' or 'maas boot-sources' for " \
1776 "delete empty. No changes goinng to be."
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001777 return result
azvyagintseve2e37a12018-11-01 14:45:49 +02001778 # FIXME: fix 'changes' accumulator
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001779 for url in maas_boot_sources.keys():
1780 if url not in except_urls:
1781 LOG.info("Removing boot-source:{}".format(url))
1782 boot_resources_import(action='stop_import', wait=True)
1783 result["changes"] = delete_boot_source(url)
1784 return result
1785
1786
1787def create_boot_source(url, keyring_filename='', keyring_data='', wait=False):
1788 """
1789 Create and import maas boot-source: link to maas-ephemeral repo
1790 Be aware, those step will import resource to rack ctrl, but you also need to import
1791 them into the region!
1792
1793
1794 :param url: The URL of the BootSource.
1795 :param keyring_filename: The path to the keyring file for this BootSource.
1796 :param keyring_data: The GPG keyring for this BootSource, base64-encoded data.
1797
1798 """
1799
1800 # TODO: not work with 'update' currently => keyring update may fail.
1801 result = {}
1802
1803 data = {
1804 "url": url,
1805 "keyring_filename": keyring_filename,
1806 "keyring_data": str(keyring_data),
1807 }
1808
1809 maas = _create_maas_client()
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001810 if url in get_boot_source():
1811 result['result'] = None
1812 result["comment"] = "boot resource already exist"
1813 return result
1814
1815 # NOTE: maas.post will return 400, if url already defined.
1816 json_res = json.loads(
1817 maas.post(u'api/2.0/boot-sources/', None, **data).read())
1818 if wait:
1819 LOG.debug(
1820 "Sleep for 5s,to get MaaS some time to process previous request")
1821 time.sleep(5)
1822 ret = boot_resources_is_importing(wait=True)
1823 if ret is dict:
1824 return ret
1825 LOG.debug("create_boot_source:{}".format(json_res))
1826 result["new"] = "boot resource {0} was created".format(json_res["url"])
1827
1828 return result
1829
1830
1831def boot_resources_import(action='import', wait=False):
1832 """
1833 import/stop_import the boot resources.
1834
1835 :param action: import\stop_import
1836 :param wait: True\False. Wait till process finished.
1837
1838 CLI Example:
1839
1840 .. code-block:: bash
1841
1842 salt 'maas-node' maasng.boot_resources_import action='import'
1843
1844 """
1845 maas = _create_maas_client()
1846 # Have no idea why, but usual jsonloads not work here..
1847 imp = maas.post(u'api/2.0/boot-resources/', action)
1848 if imp.code == 200:
1849 LOG.debug('boot_resources_import:{}'.format(imp.readline()))
1850 if wait:
1851 boot_resources_is_importing(wait=True)
1852 return True
1853 else:
1854 return False
1855
1856
1857def boot_resources_is_importing(wait=False):
1858 maas = _create_maas_client()
1859 result = {}
1860 if wait:
1861 started_at = time.time()
1862 poll_time = 5
1863 timeout = 60 * 15
1864 while boot_resources_is_importing(wait=False):
1865 c_timeout = timeout - (time.time() - started_at)
1866 if c_timeout <= 0:
1867 result['result'] = False
1868 result["comment"] = "Boot-resources import not finished in time"
1869 return result
1870 LOG.info(
1871 "Waiting boot-resources import done\n"
1872 "sleep for:{}s "
1873 "Left:{}/{}s".format(poll_time, round(c_timeout), timeout))
1874 time.sleep(poll_time)
1875 return json.loads(
1876 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1877 else:
1878 return json.loads(
1879 maas.get(u'api/2.0/boot-resources/', 'is_importing').read())
1880
1881#####
Pavel Cizinsky8dd85b52018-06-18 21:40:13 +02001882# def boot_sources_selections_delete_all_others(except_urls=[]):
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001883# """
1884# """
1885# result = {}
1886# return result
1887
1888
1889def is_boot_source_selections_in(dict1, list1):
1890 """
azvyagintsevf3515c82018-06-26 18:59:05 +03001891 Check that requested boot-selection already in maas bs selections,
1892 if True- return bss id.
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001893 # FIXME: those hack check doesn't look good.
1894 """
1895 for bs in list1:
1896 same = set(dict1.keys()) & set(bs.keys())
1897 if all(elem in same for elem in
1898 ['os', 'release', 'arches', 'subarches', 'labels']):
azvyagintsevf3515c82018-06-26 18:59:05 +03001899 LOG.debug("boot-selection in maas:{0}\n"
1900 "looks same to requested:{1}".format(bs, dict1))
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001901 return bs['id']
1902 return False
1903
1904
1905def get_boot_source_selections(bs_url):
1906 """
1907 Get boot-source selections.
1908 """
1909 # check for key_error!
1910 bs_id = _get_boot_source_id_by_url(bs_url)
1911 maas = _create_maas_client()
1912 json_res = json.loads(
1913 maas.get(u'/api/2.0/boot-sources/{0}/selections/'.format(bs_id)).read())
1914 LOG.debug(
1915 "get_boot_source_selections for url:{} \n{}".format(bs_url, json_res))
1916 return json_res
1917
1918
1919def create_boot_source_selections(bs_url, os, release, arches="*",
1920 subarches="*", labels="*", wait=True):
1921 """
1922 Create a new boot source selection for bs_url.
1923 :param os: The OS (e.g. ubuntu, centos) for which to import resources.Required.
1924 :param release: The release for which to import resources. Required.
1925 :param arches: The architecture list for which to import resources.
1926 :param subarches: The subarchitecture list for which to import resources.
1927 :param labels: The label lists for which to import resources.
1928 """
1929
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001930 result = {"result": True, 'name': bs_url, 'changes': None}
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001931
1932 data = {
1933 "os": os,
1934 "release": release,
1935 "arches": arches,
1936 "subarches": subarches,
1937 "labels": labels,
1938 }
1939
1940 maas = _create_maas_client()
1941 bs_id = _get_boot_source_id_by_url(bs_url)
1942 # TODO add pre-create verify
1943 maas_bs_s = get_boot_source_selections(bs_url)
1944 if is_boot_source_selections_in(data, maas_bs_s):
1945 result["result"] = True
azvyagintsevf3515c82018-06-26 18:59:05 +03001946 result["comment"] = 'Requested boot-source selection ' \
1947 'for {0} already exist.'.format(
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001948 bs_url)
1949 return result
1950
1951 # NOTE: maas.post will return 400, if url already defined.
azvyagintsevcb54d142018-06-19 16:18:32 +03001952 # Also, maas need's some time to import info about stream.
1953 # unfortunatly, maas don't have any call to check stream-import-info - so, we need to implement
1954 # at least simple retry ;(
1955 json_res = False
1956 poll_time = 5
azvyagintseve2e37a12018-11-01 14:45:49 +02001957 for i in range(0, 10):
azvyagintsevcb54d142018-06-19 16:18:32 +03001958 try:
1959 json_res = json.loads(
1960 maas.post(u'api/2.0/boot-sources/{0}/selections/'.format(bs_id), None,
1961 **data).read())
1962 except Exception as inst:
1963 m = inst.readlines()
azvyagintsevf3515c82018-06-26 18:59:05 +03001964 LOG.warning("boot_source_selections "
1965 "catch error during processing. Most-probably, "
azvyagintseve2e37a12018-11-01 14:45:49 +02001966 "streams data not imported yet.\nSleep:{}s "
1967 "Retry:{}/10".format(poll_time, i))
azvyagintsevcb54d142018-06-19 16:18:32 +03001968 LOG.warning("Message:{0}".format(m))
1969 time.sleep(poll_time)
1970 continue
1971 break
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001972 LOG.debug("create_boot_source_selections:{}".format(json_res))
azvyagintsevcb54d142018-06-19 16:18:32 +03001973 if not json_res:
1974 result["result"] = False
azvyagintsevf3515c82018-06-26 18:59:05 +03001975 result["comment"] = 'Failed to create requested boot-source selection' \
1976 ' for {0}.'.format(bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001977 return result
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001978 if wait:
1979 LOG.debug(
1980 "Sleep for 5s,to get MaaS some time to process previous request")
1981 time.sleep(5)
1982 ret = boot_resources_import(action='import', wait=True)
1983 if ret is dict:
1984 return ret
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02001985 result["comment"] = "boot-source selection for {0} was created".format(
1986 bs_url)
azvyagintsevcb54d142018-06-19 16:18:32 +03001987 result["new"] = data
azvyagintsev3ff2ef12018-06-01 21:30:45 +03001988
1989 return result
1990
1991# END MAAS CONFIG SECTION
azvyagintsevcb54d142018-06-19 16:18:32 +03001992
1993# RACK CONTROLLERS SECTION
1994
1995
1996def get_rack(hostname):
1997 """
1998 Get information about specified rackd
1999
2000 CLI Example:
2001
2002 .. code-block:: bash
2003
2004 salt-call maasng.get_rack rack_hostname
2005 """
2006 try:
2007 return list_racks()[hostname]
2008 except KeyError:
2009 return {"error": "rack:{} not found on MaaS server".format(hostname)}
2010
2011
azvyagintsev6913e5e2018-07-05 11:42:53 +03002012def list_racks(sort_by='hostname'):
azvyagintsevcb54d142018-06-19 16:18:32 +03002013 """
2014 Get list of all rack controllers from maas server
2015
2016 CLI Example:
2017
2018 .. code-block:: bash
2019
2020 salt-call maasng.list_racks
2021 """
2022 racks = {}
2023 maas = _create_maas_client()
2024 json_res = json.loads(
2025 maas.get(u"/api/2.0/rackcontrollers/").read() or 'null')
2026 for item in json_res:
azvyagintsev6913e5e2018-07-05 11:42:53 +03002027 racks[item[sort_by]] = item
azvyagintsevcb54d142018-06-19 16:18:32 +03002028 return racks
2029
2030
2031def sync_bs_to_rack(hostname=None):
2032 """
2033 Sync RACK boot-sources with REGION. If no hostname probided - sync to all.
2034
2035 CLI Example:
2036
2037 .. code-block:: bash
2038
2039 salt-call maasng.sync_bs_to_rack rack_hostname
2040 """
2041 ret = {}
2042 maas = _create_maas_client()
2043 if not hostname:
2044 LOG.info("boot-sources sync initiated for ALL Rack's")
2045 # Convert to json-like format
2046 json_res = json.loads('["{0}"]'.format(
2047 maas.post(u"/api/2.0/rackcontrollers/",
2048 'import_boot_images').read()))
2049 LOG.debug("sync_bs_to_rack:{}".format(json_res))
2050 ret['result'] = True
2051 ret['comment'] = "boot-sources sync initiated for ALL Rack's"
2052 return ret
2053 LOG.info("boot-sources sync initiated for RACK:{0}".format(hostname))
2054 # Convert to json-like format
2055 json_res = json.loads('["{0}"]'.format(maas.post(
2056 u"/api/2.0/rackcontrollers/{0}/".format(
2057 get_rack(hostname)['system_id']),
2058 'import_boot_images').read()))
2059 LOG.debug("sync_bs_to_rack:{}".format(json_res))
2060 ret['result'] = True
2061 ret['comment'] = "boot-sources sync initiated for {0} Rack's".format(
2062 hostname)
2063 return
2064
2065
2066def rack_list_boot_imgs(hostname):
2067 ret = {}
2068 maas = _create_maas_client()
2069 LOG.debug("rack_list_boot_imgs:{}".format(hostname))
2070 ret = json.loads(maas.get(u"/api/2.0/rackcontrollers/{0}/".format(
2071 get_rack(hostname)['system_id']), 'list_boot_images').read() or 'null')
2072 return ret
2073
2074
2075def is_rack_synced(hostname):
2076 rez = rack_list_boot_imgs(hostname)['status']
2077 if rez == 'synced':
2078 return True
2079 return False
2080
2081# TODO do we actually need _exact_ check per-pack?
2082# def wait_for_images_on_rack(hostname):
2083#
2084# """
2085# WA function, to be able check that RACK actually done SYNC images
2086# for REQUIRED images at least.
2087# Required image to be fetched from
2088# reclass:maas:region:boot_sources_selections:[keys]:os/release formation
2089#
2090# CLI Example:
2091#
2092# .. code-block:: bash
2093#
2094# salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
2095# """
2096# try:
2097# bss = __salt__['config.get']('maas')['region']['boot_sources_selections']
2098# except KeyError:
2099# ret['result'] = None
2100# ret['comment'] = "boot_sources_selections definition for sync not found."
2101# return ret
2102# s_names = []
2103# # Format u'name': u'ubuntu/xenial'
2104# for v in bss.values():s_names.append("{0}/{1}".format(v['os'],v['release']))
2105# # Each names, should be in rack and whole rack should be in sync-ed state
2106
2107
2108def sync_and_wait_bs_to_all_racks():
2109 """
2110 Sync ALL rack's with regions source images.
2111
2112 CLI Example:
2113
2114 .. code-block:: bash
2115
2116 salt-call maasng.sync_and_wait_bs_to_all_racks
2117 """
2118 sync_bs_to_rack()
2119 for rack in list_racks().keys():
2120 wait_for_sync_bs_to_rack(hostname=rack)
2121 return True
2122
2123
2124def wait_for_sync_bs_to_rack(hostname=None):
2125 """
2126 Wait for boot images sync finished, on exact rack
2127
2128 CLI Example:
2129
2130 .. code-block:: bash
2131
2132 salt-call maasng.wait_for_sync_bs_to_rack rack_hostname
2133 """
2134 ret = {}
2135 started_at = time.time()
2136 poll_time = 5
2137 timeout = 60 * 15
2138 while not is_rack_synced(hostname):
2139 c_timeout = timeout - (time.time() - started_at)
2140 if c_timeout <= 0:
2141 ret['result'] = False
2142 ret[
2143 "comment"] = "Boot-resources sync on rackd:{0}" \
2144 "not finished in time".format(
2145 hostname)
2146 return ret
2147 LOG.info(
2148 "Waiting boot-resources sync done to rack:{0}\n"
2149 "sleep for:{1}s "
2150 "Left:{2}/{3}s".format(hostname, poll_time, round(c_timeout),
2151 timeout))
2152 time.sleep(poll_time)
2153 ret['result'] = is_rack_synced(hostname)
2154 ret["comment"] = "Boot-resources sync on rackd:{0} finished".format(
2155 hostname)
2156 return ret
2157
2158# END RACK CONTROLLERS SECTION
Pavel Cizinsky8f9ba8e2018-09-10 14:31:49 +02002159# SSHKEYS
2160
2161
2162def list_sshkeys():
2163 """
2164 Get list of all sshkeys
2165
2166 CLI Example:
2167
2168 .. code-block:: bash
2169
2170 salt 'maas-node' maasng.list_sshkeys
2171 salt-call maasng.list_sshkeys
2172 """
2173 ssh = {}
2174 maas = _create_maas_client()
2175 json_res = json.loads(maas.get(u'api/2.0/account/prefs/sshkeys/').read())
2176 LOG.info(json_res)
2177 for item in json_res:
2178 ssh[item["key"]] = item
2179 return ssh
2180
2181
2182def add_sshkey(sshkey):
2183 """
2184 Add SSH key for user to MAAS.
2185
2186 CLI Example:
2187
2188 .. code-block:: bash
2189
2190 salt 'maas-node' maasng.add_sshkey sshkey
2191 salt-call maasng.add_sshkey sshkey
2192 """
2193 data = {
2194 "key": sshkey,
2195 }
2196 result = {}
2197 maas = _create_maas_client()
2198
2199 maas.post(u"/api/2.0/account/prefs/sshkeys/", None, **data).read()
2200 result["new"] = "SSH Key {0} was added.".format(sshkey)
2201
2202 return result
2203
2204
2205def get_sshkey(sshkey):
2206 """
2207 Get start ip for ip range
2208
2209 CLI Example:
2210
2211 .. code-block:: bash
2212
2213 salt 'maas-node' maasng.get_sshkey sshkey
2214 salt-call maasng.get_sshkey sshkey
2215 """
2216 try:
2217 return list_sshkeys()[sshkey]
2218 except KeyError:
2219 return {"error": "SSH key not found on MaaS server"}
2220# END SSHKEYS