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