Custom storage layout configuration examples

After a few trials and tribulations, I have a well commented script that I think people might find useful. It adheres to a number of standards which our shop uses, and leaves a chunk of disk unused for whatever the purpose of the system may call for.

Here’s what I ended up with:

#!/usr/bin/env python3
####
#
#  Program: 42-custom-disk-layout.sh
#
#  Description:
#  This python script is to specify how to select and partition a disk in MAAS.
#  Inputs: the MAAS_RESOURCES_FILE from previous default scripts is a json
# construct detailing the hardware that is currently being "commissioned".
#  Outputs: the MAAS_STORAGE_CONFIG_FILE should contain a json structure
# that defines the selected hard disk and where to slice it up to the standard
# mountpoints for the OS installation, however that step continually failed.
# Instead this script loads all the data from the MAAS_RESOURCES_FILE, then
# adds the ['storage-extra'] block with the selected disk, partitions, and
# logical volumes.
#
#  Programmer:	Charles Bedford
#  History:
#    2022-09-30 - CHB - Initial revision
#
# --- Start MAAS 1.0 script metadata ---
# name: 42-custom-disk-layout
# title: Set layout for DELL Virtual disks
# description: Set layout for DELL Virtual disks
# script_type: commissioning
# timeout: 60
# --- End MAAS 1.0 script metadata ---
#
####
import json
import os
import sys

# Function definitions
def read_json_file(path):
    try:
        with open(path) as fd:
            return json.load(fd)
    except OSError as e:
        sys.exit(f"Failed to read {path}: {e}")
    except json.JSONDecodeError as e:
        sys.exit(f"Failed to parse {path}: {e}")

# Load the hardware from the json in the MAAS_RESOURCES_FILE
hardware = read_json_file(os.environ['MAAS_RESOURCES_FILE'])

####
#
#  This is the primary datastructure (in json format) with
# placeholders which are easily found and replaced below (DISK & MAX)
#
####
cfg = '''{
    "layout": {
        "DISK": {
            "type": "disk",
            "ptable": "gpt",
            "boot": true,
            "partitions": [
                { "name": "DISK1", "fs": "fat32", "size": "2G", "bootable": true },
                { "name": "DISK2", "size": "MAX" }
            ]
        },
        "vg": {
            "type": "lvm",
            "members": [ "DISK2" ],
            "volumes": [
                { "name": "lv1", "size": "50G", "fs": "ext4" },
                { "name": "lv2", "size": "16G", "fs": "swap" },
                { "name": "lv3", "size": "20G", "fs": "ext4" },
                { "name": "lv4", "size": "20G", "fs": "ext4" },
                { "name": "lv5", "size": "20G", "fs": "ext4" },
                { "name": "lv6", "size": "10G", "fs": "ext4" },
                { "name": "lv7", "size": "20G", "fs": "ext4" },
                { "name": "lv8", "size": "20G", "fs": "ext4" }
            ]
        }
    },
    "mounts": {
        "/": { "device": "lv1" },
        "/boot/efi": { "device": "DISK1" }, 
        "none": { "device": "lv2" },
        "/tmp": { "device": "lv3" },
        "/var": { "device": "lv4" },
        "/var/log": { "device": "lv5" },
        "/var/log/audit": { "device": "lv6" },
        "/var/tmp": { "device": "lv7" },
        "/home": { "device": "lv8" }
    }
}'''

disks = hardware['resources']['storage']['disks']

# Initialize a few variables so we have consistent values below
primary = ''
primaryId = 0
diskSize = 0
index=0

# Iterate through all the disks defined for this machine
for disk in disks:
    # skip virtual mounted drives from maas
    if 'Virtual' in disk["model"] or 'Virtual' in disk['device_id']:
        continue

    # find the PERC or DELLBOSS id to use...
    if 'PERC' in disk["model"] or 'DELLBOSS' in disk["model"]:
            primary = disk['id']
            primaryId = index
            diskSize = disk['size']

    index+=1

# if the loop didn't find a PERC or DELLBOSS drive to use
# barring that it will have to be #0
if primary == '':
    primary=disks[0]['id']
    primaryId = 0
    diskSize = disks[0]['size']

# The disk ID (sda or whatever) that we want to use for booting...
diskId = hardware["resources"]["storage"]["disks"][primaryId]["id"]

# Load layoutDetail from the above structure
layoutDetail = json.loads(cfg)

# copy the DISK temporary structure over the top of the new diskId name
layoutDetail["layout"][diskId] = layoutDetail["layout"]["DISK"]

# Now delete the DISK temporary structure
del layoutDetail["layout"]["DISK"]

####
#
#  Cleanup the structure details
#
####
# set the names and sizes on the partitions:
# Note: the sizes are NOT based on 1024, but 1000.
# subtract the size of the fat32 partition and a constant...
mySize = ( diskSize - 2000000000 ) / 1000000000
num = 1
for p in layoutDetail["layout"][diskId]["partitions"]:
	p["name"] = diskId + str(num)
	num += 1;
	if p["size"] == "MAX":
		p["size"] = str(int(mySize)) + "G"
		savedP = p

# The Members of the lvm
layoutDetail["layout"]["vg"]["members"] = [ savedP["name"] ]

# last but not least - fix the one mount detail
layoutDetail["mounts"]["/boot/efi"]["device"] = diskId + "1"

####
#
#  Output
#
####
hardware["storage-extra"] = layoutDetail

print('Saving custom storage layout to ' + os.environ['MAAS_RESOURCES_FILE'])
print(json.dumps(hardware))

with open(os.environ['MAAS_RESOURCES_FILE'], 'w') as fd:
    json.dump(hardware, fd)

I hope that’s helpful to someone.

Thanks

~~ Charles

2 Likes

I just came across a machine for which my script unexpectedly fails.

Apparently it’s getting an empty MAAS_RESOURCES_FILE, which of course fails to parse when I attempt to load_json the file.

Has something changed that wouldn’t load the resources file appropriately, or is this just a sole machine acting oddly?

I’m running the 3.2.4 snap.

I also tried to leave the host up so I could ssh in, but every time it powers off when commissionoing completes.

Thanks

~~ Charles

Does the 20-maas-03-machine-resources script succeed and reports output to maas, or is that one failing too?

1 Like

Hey folks. This is a great feature - working on kicking the tires on it now.

Question - I see that Custom is now (in the 3.3 beta) an option for the default storage type in the UI’s Settings page. Can you explain how that setting interacts with custom commissioning scripts?

For example, if I have something else set for storage default layout (let’s say, Flat), and I have a custom storage commissioning script, do I need to pick Custom as my default storage layout for that to trigger? If not, what is the use of picking the Custom storage default layout?

Also - maybe this is part of the updated docs push for 3.3, but is there a list of supported file system types?

Thanks!

I’ve been attempting to get this working too. I need to do a RAID with LVM and I can’t figure out how the scripts that others have posted. @bedfordc or @dadams-fg would it be possible to give me some idea’s on how to use the scripts you posted with a RAID1 config with LVM? Thanks

Okay - that’s a moderately complicated answer.

First of all - you’ll need an example of the json structure you’re trying to create. I’d suggest that you build a host the way you want then interrogate the output of the logs to get the final MAAS_RESOURCES_FILE and then build your python script to make it look like that.

The setup with variables and such you’ll have to figure out on your own tho :slight_smile:

Please post it here when you’re done - I might upgrade my script such that it can figure out when I need to use two disks in a raid configuration when hardware raid is inaccessible. (I think I have 3 hosts like that, so I just did them by hand, but if someone else does the work to figure it out I’ll gladly steal it!)

Thanks

~~ Charles

Late response here. I tried parsing the output of MAAS_RESOURCES_FILE and it really wasn’t usable. At least not in any way that I could easily understand and then build from. But, I’ve managed to come up with something that is ‘somewhat’ working. Although it’s such a hack I’m a bit embarassed to post what I have right now. I’m going to try and clean it up and post here what I’ve got working so far.

We’re currently looking at moving to MAAS from an in-house solution based (partially) on autoinstall. Autoinstall (which I’ll note is a also Canonical tool) has a really nice feature where you can specify a disk size of -1, which will use all of the remaining space. Is there any way to utilize autoinstall scripts in MAAS?

@bedfordc, this is indeed very useful! In fact, some of the questions you’ve asked in this post were the same questions I had, even after reading through a bunch of the documentation. (Googling for answers to this stuff is pretty fruitless)

1 Like

Most of the “documentation” is wrapped up in the code surrounding data manipulation. Just start with the details in the JSON files produced by MAAS for each host, and work backward from there.

If you know where you want to finish, that makes it an easy test case for your code :-p

Once I realized that, then the rest of my work to produce the configuration I required was a lot simpler - just looking up how to make Python do what I wanted to do with any data structures I maintained within the code as it ran.

I hope that answers some additional questions you may have :innocent:

Thanks

~~ Charles

1 Like

Is there any real documentation about this feature? Can dm_crypt devices be configured here? Is there no way to set a partition to 100% of remaining free space?

Resurrecting an older thread here, but an important one as I try and get MAAS working in my environment. It would be good to see some more documentation about how to define custom layouts, and moving further, it makes sense that all layouts would be editable from the MAAS UI, or even the CLI.

We are testing this procedure but so far it’s not working for us and in case we get it working it seems quite complex to assign different partition layouts to different machines, which is something we need.

Did anyone manage to define custom partition layouts using cloud-init ? cloud-init seems more flexible but we haven’t made it work either. Should it be possible to use cloud-init during the deployment step to define custom partitions layouts?

Hi @pescobar-scicore ,

nope, you can’t use cloud-init for that in MAAS.

1 Like

thanks @r00ta for confirming that we cannot use cloud-init. At least we won’t invest more time trying that approach

If I understand correctly the only approach for our use case (many different server models with different partitions layouts) is to write a commissioning script where we define the different partitions layout depending on the information in $MAAS_RESOURCES_FILE , right?

To be honest I don’t like the idea of “hiding” all this logic inside a commissioning script . I was expecting that MAAS would provide a simpler solution to assign different partition layouts to different nodes.

there are multiple ways. For example

  • you can do it in the way you suggested
  • or you can have multiple commissioning script and you select the right one to use for a specific machine when you take an action (you can select it manually or you can make it part of your automation built on top of MAAS, if you have any)

Thanks for your feedback. Currently this is the only supported way to do it

I ended up writing a script, run manually, once the system has been commissioned and is in a “ready” state that runs various partitioning, formatting, and mounting commands based on what storage profile I want the system to have. pescobar-scicore, if you want to message me I can send it to you if you’d find it useful.

thanks for offer @bmcnally-uw but we already got a working solution

We wrote some commissioning scripts based on the scripts posted in this thread. Each of the commissioning scripts defines a different partition layout and we apply the right partition layout based on the maas tags we apply to the machines as described in section Tagging and hardware specification in https://maas.io/docs/using-commissioning-scripts

This works for us and once it’s configured allows us to choose the right partition layout by assigning the right maas tag. Something that requires a manual step would not work in our terraform CI pipeline.

2 Likes

Excellent, glad you found something that works for you! I may eventually want to use tagging so this is a useful tip. Canonical needs to improve how partitioning is done to save everyone a lot of time and hassle. It’s a major missing piece of the GUI currently.

I fully agree. IMHO defining a custom partition layout is a basic functionality in a deployment tool and doing it with MAAS requires writing a python script to update a json and this is “hidden” in a commissioning script. I think MAAS should allow to do this in a simpler way.