Merge remote-tracking branch 'origin/pull/500/head'
Merge using existing partitions. Revert to using archive curtin/deps/etc. Signed-off-by: Dimitri John Ledkov <xnox@ubuntu.com>
This commit is contained in:
commit
94de7dc522
|
@ -30,7 +30,7 @@ installer without having access to the machine. A few sample machine
|
|||
profiles are available in the repository at ./examples/ and
|
||||
can be loaded via the MACHINE make variable:
|
||||
|
||||
`make dryrun MACHINE=examples/mwhudson.json`
|
||||
`make dryrun MACHINE=examples/simple.json`
|
||||
|
||||
# Generating machine profiles
|
||||
Machine profiles are generated from the probert tool. To collect a machine profile:
|
||||
|
|
|
@ -18,7 +18,7 @@ Filesystem:
|
|||
obj: [disk index 0]
|
||||
action: PARTITION
|
||||
data:
|
||||
size: 1G
|
||||
size: 0.5G
|
||||
fstype: null
|
||||
- *newpart
|
||||
- *newpart
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,697 @@
|
|||
{
|
||||
"network": {
|
||||
"links": [
|
||||
{
|
||||
"addresses": [
|
||||
{
|
||||
"address": "10.0.2.15/24",
|
||||
"family": 2,
|
||||
"scope": "global",
|
||||
"source": "dhcp"
|
||||
},
|
||||
{
|
||||
"address": "fec0::5054:ff:fe12:3456/64",
|
||||
"family": 10,
|
||||
"scope": "site",
|
||||
"source": "dhcp"
|
||||
},
|
||||
{
|
||||
"address": "fe80::5054:ff:fe12:3456/64",
|
||||
"family": 10,
|
||||
"scope": "link",
|
||||
"source": "static"
|
||||
}
|
||||
],
|
||||
"bond": {
|
||||
"is_master": false,
|
||||
"is_slave": false,
|
||||
"lacp_rate": null,
|
||||
"master": null,
|
||||
"mode": null,
|
||||
"slaves": [],
|
||||
"xmit_hash_policy": null
|
||||
},
|
||||
"bridge": {
|
||||
"interfaces": [],
|
||||
"is_bridge": false,
|
||||
"is_port": false,
|
||||
"options": {}
|
||||
},
|
||||
"netlink_data": {
|
||||
"arptype": 1,
|
||||
"family": 0,
|
||||
"flags": 69699,
|
||||
"ifindex": 2,
|
||||
"is_vlan": false,
|
||||
"name": "ens3"
|
||||
},
|
||||
"type": "eth",
|
||||
"udev_data": {
|
||||
"DEVPATH": "/devices/pci0000:00/0000:00:03.0/net/ens3",
|
||||
"ID_BUS": "pci",
|
||||
"ID_MM_CANDIDATE": "1",
|
||||
"ID_MODEL_FROM_DATABASE": "82540EM Gigabit Ethernet Controller (QEMU Virtual Machine)",
|
||||
"ID_MODEL_ID": "0x100e",
|
||||
"ID_NET_NAME_MAC": "enx525400123456",
|
||||
"ID_NET_NAME_PATH": "enp0s3",
|
||||
"ID_NET_NAME_SLOT": "ens3",
|
||||
"ID_NET_NAMING_SCHEME": "v240",
|
||||
"ID_PATH": "pci-0000:00:03.0",
|
||||
"ID_PATH_TAG": "pci-0000_00_03_0",
|
||||
"ID_PCI_CLASS_FROM_DATABASE": "Network controller",
|
||||
"ID_PCI_SUBCLASS_FROM_DATABASE": "Ethernet controller",
|
||||
"ID_VENDOR_FROM_DATABASE": "Intel Corporation",
|
||||
"ID_VENDOR_ID": "0x8086",
|
||||
"IFINDEX": "2",
|
||||
"INTERFACE": "ens3",
|
||||
"SUBSYSTEM": "net",
|
||||
"SYSTEMD_ALIAS": "/sys/subsystem/net/devices/ens3",
|
||||
"TAGS": ":systemd:",
|
||||
"USEC_INITIALIZED": "82651806",
|
||||
"attrs": {
|
||||
"addr_assign_type": "0",
|
||||
"addr_len": "6",
|
||||
"address": "52:54:00:12:34:56",
|
||||
"broadcast": "ff:ff:ff:ff:ff:ff",
|
||||
"carrier": "1",
|
||||
"carrier_changes": "2",
|
||||
"carrier_down_count": "1",
|
||||
"carrier_up_count": "1",
|
||||
"dev_id": "0x0",
|
||||
"dev_port": "0",
|
||||
"device": null,
|
||||
"dormant": "0",
|
||||
"duplex": "full",
|
||||
"flags": "0x1003",
|
||||
"gro_flush_timeout": "0",
|
||||
"ifalias": "",
|
||||
"ifindex": "2",
|
||||
"iflink": "2",
|
||||
"link_mode": "0",
|
||||
"mtu": "1500",
|
||||
"name_assign_type": "4",
|
||||
"netdev_group": "0",
|
||||
"operstate": "up",
|
||||
"phys_port_id": null,
|
||||
"phys_port_name": null,
|
||||
"phys_switch_id": null,
|
||||
"proto_down": "0",
|
||||
"speed": "1000",
|
||||
"subsystem": "net",
|
||||
"tx_queue_len": "1000",
|
||||
"type": "1",
|
||||
"uevent": "INTERFACE=ens3\nIFINDEX=2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"addresses": [
|
||||
{
|
||||
"address": "127.0.0.1/8",
|
||||
"family": 2,
|
||||
"scope": "host",
|
||||
"source": "static"
|
||||
},
|
||||
{
|
||||
"address": "::1/128",
|
||||
"family": 10,
|
||||
"scope": "host",
|
||||
"source": "static"
|
||||
}
|
||||
],
|
||||
"bond": {
|
||||
"is_master": false,
|
||||
"is_slave": false,
|
||||
"lacp_rate": null,
|
||||
"master": null,
|
||||
"mode": null,
|
||||
"slaves": [],
|
||||
"xmit_hash_policy": null
|
||||
},
|
||||
"bridge": {
|
||||
"interfaces": [],
|
||||
"is_bridge": false,
|
||||
"is_port": false,
|
||||
"options": {}
|
||||
},
|
||||
"netlink_data": {
|
||||
"arptype": 772,
|
||||
"family": 0,
|
||||
"flags": 65609,
|
||||
"ifindex": 1,
|
||||
"is_vlan": false,
|
||||
"name": "lo"
|
||||
},
|
||||
"type": "lo",
|
||||
"udev_data": {
|
||||
"DEVPATH": "/devices/virtual/net/lo",
|
||||
"ID_MM_CANDIDATE": "1",
|
||||
"IFINDEX": "1",
|
||||
"INTERFACE": "lo",
|
||||
"SUBSYSTEM": "net",
|
||||
"USEC_INITIALIZED": "89206115",
|
||||
"attrs": {
|
||||
"addr_assign_type": "0",
|
||||
"addr_len": "6",
|
||||
"address": "00:00:00:00:00:00",
|
||||
"broadcast": "00:00:00:00:00:00",
|
||||
"carrier": "1",
|
||||
"carrier_changes": "0",
|
||||
"carrier_down_count": "0",
|
||||
"carrier_up_count": "0",
|
||||
"dev_id": "0x0",
|
||||
"dev_port": "0",
|
||||
"dormant": "0",
|
||||
"duplex": null,
|
||||
"flags": "0x9",
|
||||
"gro_flush_timeout": "0",
|
||||
"ifalias": "",
|
||||
"ifindex": "1",
|
||||
"iflink": "1",
|
||||
"link_mode": "0",
|
||||
"mtu": "65536",
|
||||
"name_assign_type": null,
|
||||
"netdev_group": "0",
|
||||
"operstate": "unknown",
|
||||
"phys_port_id": null,
|
||||
"phys_port_name": null,
|
||||
"phys_switch_id": null,
|
||||
"proto_down": "0",
|
||||
"speed": null,
|
||||
"subsystem": "net",
|
||||
"tx_queue_len": "1000",
|
||||
"type": "772",
|
||||
"uevent": "INTERFACE=lo\nIFINDEX=1"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"dst": "default",
|
||||
"family": 2,
|
||||
"ifindex": 2,
|
||||
"table": 254,
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"dst": "10.0.2.0/24",
|
||||
"family": 2,
|
||||
"ifindex": 2,
|
||||
"table": 254,
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"dst": "10.0.2.2",
|
||||
"family": 2,
|
||||
"ifindex": 2,
|
||||
"table": 254,
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"dst": "10.0.2.0",
|
||||
"family": 2,
|
||||
"ifindex": 2,
|
||||
"table": 255,
|
||||
"type": 3
|
||||
},
|
||||
{
|
||||
"dst": "10.0.2.15",
|
||||
"family": 2,
|
||||
"ifindex": 2,
|
||||
"table": 255,
|
||||
"type": 2
|
||||
},
|
||||
{
|
||||
"dst": "10.0.2.255",
|
||||
"family": 2,
|
||||
"ifindex": 2,
|
||||
"table": 255,
|
||||
"type": 3
|
||||
},
|
||||
{
|
||||
"dst": "127.0.0.0",
|
||||
"family": 2,
|
||||
"ifindex": 1,
|
||||
"table": 255,
|
||||
"type": 3
|
||||
},
|
||||
{
|
||||
"dst": "127.0.0.0/8",
|
||||
"family": 2,
|
||||
"ifindex": 1,
|
||||
"table": 255,
|
||||
"type": 2
|
||||
},
|
||||
{
|
||||
"dst": "127.0.0.1",
|
||||
"family": 2,
|
||||
"ifindex": 1,
|
||||
"table": 255,
|
||||
"type": 2
|
||||
},
|
||||
{
|
||||
"dst": "127.255.255.255",
|
||||
"family": 2,
|
||||
"ifindex": 1,
|
||||
"table": 255,
|
||||
"type": 3
|
||||
},
|
||||
{
|
||||
"dst": "::1",
|
||||
"family": 10,
|
||||
"ifindex": 1,
|
||||
"table": 254,
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"dst": "fe80::/64",
|
||||
"family": 10,
|
||||
"ifindex": 2,
|
||||
"table": 254,
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"dst": "fec0::/64",
|
||||
"family": 10,
|
||||
"ifindex": 2,
|
||||
"table": 254,
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"dst": "default",
|
||||
"family": 10,
|
||||
"ifindex": 2,
|
||||
"table": 254,
|
||||
"type": 1
|
||||
},
|
||||
{
|
||||
"dst": "::1",
|
||||
"family": 10,
|
||||
"ifindex": 1,
|
||||
"table": 255,
|
||||
"type": 2
|
||||
},
|
||||
{
|
||||
"dst": "fe80::5054:ff:fe12:3456",
|
||||
"family": 10,
|
||||
"ifindex": 2,
|
||||
"table": 255,
|
||||
"type": 2
|
||||
},
|
||||
{
|
||||
"dst": "fec0::5054:ff:fe12:3456",
|
||||
"family": 10,
|
||||
"ifindex": 2,
|
||||
"table": 255,
|
||||
"type": 2
|
||||
},
|
||||
{
|
||||
"dst": "ff00::/8",
|
||||
"family": 10,
|
||||
"ifindex": 2,
|
||||
"table": 255,
|
||||
"type": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"storage": {
|
||||
"bcache": {
|
||||
"backing": {},
|
||||
"caching": {}
|
||||
},
|
||||
"blockdev": {
|
||||
"/dev/fd0": {
|
||||
"DEVNAME": "/dev/fd0",
|
||||
"DEVPATH": "/devices/platform/floppy.0/block/fd0",
|
||||
"DEVTYPE": "disk",
|
||||
"MAJOR": "2",
|
||||
"MINOR": "0",
|
||||
"SUBSYSTEM": "block",
|
||||
"TAGS": ":systemd:",
|
||||
"USEC_INITIALIZED": "82216378",
|
||||
"attrs": {
|
||||
"alignment_offset": "0",
|
||||
"bdi": null,
|
||||
"capability": "11",
|
||||
"dev": "2:0",
|
||||
"device": null,
|
||||
"discard_alignment": "0",
|
||||
"events": "",
|
||||
"events_async": "",
|
||||
"events_poll_msecs": "-1",
|
||||
"ext_range": "1",
|
||||
"hidden": "0",
|
||||
"inflight": " 0 0",
|
||||
"range": "1",
|
||||
"removable": "1",
|
||||
"ro": "0",
|
||||
"size": "4096",
|
||||
"stat": " 1 0 8 68 0 0 0 0 0 8 68 0 0 0 0",
|
||||
"subsystem": "block",
|
||||
"uevent": "MAJOR=2\nMINOR=0\nDEVNAME=fd0\nDEVTYPE=disk"
|
||||
}
|
||||
},
|
||||
"/dev/sda": {
|
||||
"DEVLINKS": "/dev/disk/by-id/scsi-0ATA_QEMU_HARDDISK_QM00001 /dev/disk/by-id/ata-QEMU_HARDDISK_QM00001 /dev/disk/by-id/scsi-1ATA_QEMU_HARDDISK_QM00001 /dev/disk/by-id/scsi-SATA_QEMU_HARDDISK_QM00001 /dev/disk/by-path/pci-0000:00:01.1-ata-1",
|
||||
"DEVNAME": "/dev/sda",
|
||||
"DEVPATH": "/devices/pci0000:00/0000:00:01.1/ata1/host0/target0:0:0/0:0:0:0/block/sda",
|
||||
"DEVTYPE": "disk",
|
||||
"ID_ATA": "1",
|
||||
"ID_BUS": "ata",
|
||||
"ID_MODEL": "QEMU_HARDDISK",
|
||||
"ID_MODEL_ENC": "QEMU\\x20HARDDISK\\x20\\x20\\x20",
|
||||
"ID_PATH": "pci-0000:00:01.1-ata-1",
|
||||
"ID_PATH_TAG": "pci-0000_00_01_1-ata-1",
|
||||
"ID_REVISION": "2.5+",
|
||||
"ID_SCSI": "1",
|
||||
"ID_SCSI_DI": "1",
|
||||
"ID_SCSI_SN": "1",
|
||||
"ID_SERIAL": "QEMU_HARDDISK_QM00001",
|
||||
"ID_SERIAL_SHORT": "QM00001",
|
||||
"ID_TYPE": "disk",
|
||||
"ID_VENDOR": "ATA",
|
||||
"ID_VENDOR_ENC": "ATA\\x20\\x20\\x20\\x20\\x20",
|
||||
"MAJOR": "8",
|
||||
"MINOR": "0",
|
||||
"MPATH_SBIN_PATH": "/sbin",
|
||||
"SCSI_IDENT_LUN_ATA": "QEMU_HARDDISK_QM00001",
|
||||
"SCSI_IDENT_LUN_T10": "ATA_QEMU_HARDDISK_QM00001",
|
||||
"SCSI_IDENT_LUN_VENDOR": "QM00001",
|
||||
"SCSI_IDENT_SERIAL": "QM00001",
|
||||
"SCSI_MODEL": "QEMU_HARDDISK",
|
||||
"SCSI_MODEL_ENC": "QEMU\\x20HARDDISK\\x20\\x20\\x20",
|
||||
"SCSI_REVISION": "2.5+",
|
||||
"SCSI_TPGS": "0",
|
||||
"SCSI_TYPE": "disk",
|
||||
"SCSI_VENDOR": "ATA",
|
||||
"SCSI_VENDOR_ENC": "ATA\\x20\\x20\\x20\\x20\\x20",
|
||||
"SUBSYSTEM": "block",
|
||||
"TAGS": ":systemd:",
|
||||
"USEC_INITIALIZED": "82301144",
|
||||
"attrs": {
|
||||
"alignment_offset": "0",
|
||||
"bdi": null,
|
||||
"capability": "50",
|
||||
"dev": "8:0",
|
||||
"device": null,
|
||||
"discard_alignment": "0",
|
||||
"events": "",
|
||||
"events_async": "",
|
||||
"events_poll_msecs": "-1",
|
||||
"ext_range": "256",
|
||||
"hidden": "0",
|
||||
"inflight": " 0 0",
|
||||
"range": "16",
|
||||
"removable": "0",
|
||||
"ro": "0",
|
||||
"size": "10737418240",
|
||||
"stat": " 411 0 16968 119 0 0 0 0 0 264 0 0 0 0 0",
|
||||
"subsystem": "block",
|
||||
"uevent": "MAJOR=8\nMINOR=0\nDEVNAME=sda\nDEVTYPE=disk"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dmcrypt": {},
|
||||
"filesystem": {
|
||||
},
|
||||
"lvm": {},
|
||||
"mount": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"fstype": "securityfs",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime",
|
||||
"source": "securityfs",
|
||||
"target": "/sys/kernel/security"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"fstype": "cgroup2",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,nsdelegate",
|
||||
"source": "cgroup2",
|
||||
"target": "/sys/fs/cgroup/unified"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,xattr,name=systemd",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/systemd"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,rdma",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/rdma"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,hugetlb",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/hugetlb"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,pids",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/pids"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,freezer",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/freezer"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,memory",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/memory"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,cpu,cpuacct",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/cpu,cpuacct"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,cpuset",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/cpuset"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,blkio",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/blkio"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,perf_event",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/perf_event"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,devices",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/devices"
|
||||
},
|
||||
{
|
||||
"fstype": "cgroup",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,net_cls,net_prio",
|
||||
"source": "cgroup",
|
||||
"target": "/sys/fs/cgroup/net_cls,net_prio"
|
||||
}
|
||||
],
|
||||
"fstype": "tmpfs",
|
||||
"options": "ro,nosuid,nodev,noexec,mode=755",
|
||||
"source": "tmpfs",
|
||||
"target": "/sys/fs/cgroup"
|
||||
},
|
||||
{
|
||||
"fstype": "pstore",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime",
|
||||
"source": "pstore",
|
||||
"target": "/sys/fs/pstore"
|
||||
},
|
||||
{
|
||||
"fstype": "efivarfs",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime",
|
||||
"source": "efivarfs",
|
||||
"target": "/sys/firmware/efi/efivars"
|
||||
},
|
||||
{
|
||||
"fstype": "bpf",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,mode=700",
|
||||
"source": "bpf",
|
||||
"target": "/sys/fs/bpf"
|
||||
},
|
||||
{
|
||||
"fstype": "debugfs",
|
||||
"options": "rw,relatime",
|
||||
"source": "debugfs",
|
||||
"target": "/sys/kernel/debug"
|
||||
},
|
||||
{
|
||||
"fstype": "fusectl",
|
||||
"options": "rw,relatime",
|
||||
"source": "fusectl",
|
||||
"target": "/sys/fs/fuse/connections"
|
||||
},
|
||||
{
|
||||
"fstype": "configfs",
|
||||
"options": "rw,relatime",
|
||||
"source": "configfs",
|
||||
"target": "/sys/kernel/config"
|
||||
}
|
||||
],
|
||||
"fstype": "sysfs",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime",
|
||||
"source": "sysfs",
|
||||
"target": "/sys"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"fstype": "autofs",
|
||||
"options": "rw,relatime,fd=44,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=14037",
|
||||
"source": "systemd-1",
|
||||
"target": "/proc/sys/fs/binfmt_misc"
|
||||
}
|
||||
],
|
||||
"fstype": "proc",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime",
|
||||
"source": "proc",
|
||||
"target": "/proc"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"fstype": "devpts",
|
||||
"options": "rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000",
|
||||
"source": "devpts",
|
||||
"target": "/dev/pts"
|
||||
},
|
||||
{
|
||||
"fstype": "tmpfs",
|
||||
"options": "rw,nosuid,nodev",
|
||||
"source": "tmpfs",
|
||||
"target": "/dev/shm"
|
||||
},
|
||||
{
|
||||
"fstype": "mqueue",
|
||||
"options": "rw,relatime",
|
||||
"source": "mqueue",
|
||||
"target": "/dev/mqueue"
|
||||
},
|
||||
{
|
||||
"fstype": "hugetlbfs",
|
||||
"options": "rw,relatime,pagesize=2M",
|
||||
"source": "hugetlbfs",
|
||||
"target": "/dev/hugepages"
|
||||
}
|
||||
],
|
||||
"fstype": "devtmpfs",
|
||||
"options": "rw,nosuid,relatime,size=465256k,nr_inodes=116314,mode=755",
|
||||
"source": "udev",
|
||||
"target": "/dev"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"fstype": "tmpfs",
|
||||
"options": "rw,nosuid,nodev,noexec,relatime,size=5120k",
|
||||
"source": "tmpfs",
|
||||
"target": "/run/lock"
|
||||
},
|
||||
{
|
||||
"fstype": "tmpfs",
|
||||
"options": "rw,nosuid,nodev,relatime,size=100148k,mode=700,uid=999,gid=999",
|
||||
"source": "tmpfs",
|
||||
"target": "/run/user/999"
|
||||
}
|
||||
],
|
||||
"fstype": "tmpfs",
|
||||
"options": "rw,nosuid,noexec,relatime,size=100152k,mode=755",
|
||||
"source": "tmpfs",
|
||||
"target": "/run"
|
||||
},
|
||||
{
|
||||
"fstype": "squashfs",
|
||||
"options": "ro,noatime",
|
||||
"source": "/dev/loop0",
|
||||
"target": "/rofs"
|
||||
},
|
||||
{
|
||||
"fstype": "squashfs",
|
||||
"options": "ro,relatime",
|
||||
"source": "/dev/loop2",
|
||||
"target": "/usr/lib/modules"
|
||||
},
|
||||
{
|
||||
"fstype": "tmpfs",
|
||||
"options": "rw,nosuid,nodev,relatime",
|
||||
"source": "tmpfs",
|
||||
"target": "/tmp"
|
||||
},
|
||||
{
|
||||
"fstype": "squashfs",
|
||||
"options": "ro,relatime",
|
||||
"source": "/dev/loop3",
|
||||
"target": "/media/rack.lower"
|
||||
},
|
||||
{
|
||||
"fstype": "squashfs",
|
||||
"options": "ro,relatime",
|
||||
"source": "/dev/loop4",
|
||||
"target": "/media/region.lower"
|
||||
},
|
||||
{
|
||||
"fstype": "squashfs",
|
||||
"options": "ro,relatime",
|
||||
"source": "/dev/loop0",
|
||||
"target": "/media/filesystem"
|
||||
},
|
||||
{
|
||||
"fstype": "overlay",
|
||||
"options": "ro,relatime,lowerdir=/media/region.lower:/media/rack.lower:/media/filesystem",
|
||||
"source": "overlay",
|
||||
"target": "/media/region"
|
||||
},
|
||||
{
|
||||
"fstype": "overlay",
|
||||
"options": "ro,relatime,lowerdir=/media/rack.lower:/media/filesystem",
|
||||
"source": "overlay",
|
||||
"target": "/media/rack"
|
||||
},
|
||||
{
|
||||
"fstype": "squashfs",
|
||||
"options": "ro,nodev,relatime",
|
||||
"source": "/dev/loop5",
|
||||
"target": "/snap/core/6673"
|
||||
},
|
||||
{
|
||||
"fstype": "squashfs",
|
||||
"options": "ro,nodev,relatime",
|
||||
"source": "/dev/loop6",
|
||||
"target": "/snap/subiquity/1012"
|
||||
}
|
||||
],
|
||||
"fstype": "overlay",
|
||||
"options": "rw,relatime,lowerdir=//installer.squashfs://filesystem.squashfs,upperdir=/cow/upper,workdir=/cow/work",
|
||||
"source": "/cow",
|
||||
"target": "/"
|
||||
}
|
||||
],
|
||||
"multipath": {},
|
||||
"raid": {},
|
||||
"zfs": {
|
||||
"zpools": {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ for answers in examples/answers*.yaml; do
|
|||
rm -f .subiquity/subiquity-debug.log
|
||||
rm -f .subiquity/run/subiquity/updating
|
||||
# The --foreground is important to avoid subiquity getting SIGTTOU-ed.
|
||||
timeout --foreground 60 sh -c "LANG=C.UTF-8 python3 -m subiquity.cmd.tui --answers $answers --dry-run --snaps-from-examples --machine-config examples/mwhudson.json"
|
||||
timeout --foreground 60 sh -c "LANG=C.UTF-8 python3 -m subiquity.cmd.tui --answers $answers --dry-run --snaps-from-examples --machine-config examples/simple.json"
|
||||
python3 scripts/validate-yaml.py .subiquity/subiquity-curtin-install.conf
|
||||
if grep passw0rd .subiquity/subiquity-debug.log | grep -v "Loaded answers" | grep -v "answers_action"; then
|
||||
echo "password leaked into log file"
|
||||
|
|
|
@ -110,10 +110,22 @@ def main():
|
|||
if opts.snaps_from_examples is None:
|
||||
opts.snaps_from_examples = True
|
||||
LOGFILE = setup_logger(dir=LOGDIR)
|
||||
|
||||
logger = logging.getLogger('subiquity')
|
||||
logger.info("Starting SUbiquity v{}".format(VERSION))
|
||||
logger.info("Arguments passed: {}".format(sys.argv))
|
||||
|
||||
block_log_dir = os.path.join(LOGDIR, "block")
|
||||
os.makedirs(block_log_dir, exist_ok=True)
|
||||
handler = logging.FileHandler(os.path.join(block_log_dir, 'discover.log'))
|
||||
handler.setLevel('DEBUG')
|
||||
handler.setFormatter(
|
||||
logging.Formatter("%(asctime)s %(name)s:%(lineno)d %(message)s"))
|
||||
logging.getLogger('probert').addHandler(handler)
|
||||
handler.addFilter(lambda rec: rec.name != 'probert.network')
|
||||
logging.getLogger('curtin').addHandler(handler)
|
||||
logging.getLogger('block-discover').addHandler(handler)
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
signal.signal(signal.SIGQUIT, signal.SIG_IGN)
|
||||
|
||||
|
@ -140,7 +152,7 @@ def main():
|
|||
ui = SubiquityUI()
|
||||
|
||||
try:
|
||||
subiquity_interface = Subiquity(ui, opts)
|
||||
subiquity_interface = Subiquity(ui, opts, block_log_dir)
|
||||
except ApplicationError as e:
|
||||
logger.exception('Failed to load Subiquity interface')
|
||||
print(e)
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import enum
|
||||
import json
|
||||
import logging
|
||||
|
||||
from probert.storage import StorageInfo
|
||||
import os
|
||||
|
||||
from subiquitycore.controller import BaseController
|
||||
|
||||
|
@ -39,6 +39,7 @@ from subiquity.ui.views.filesystem.probing import (
|
|||
|
||||
|
||||
log = logging.getLogger("subiquitycore.controller.filesystem")
|
||||
block_discover_log = logging.getLogger('block-discover')
|
||||
|
||||
BIOS_GRUB_SIZE_BYTES = 1 * 1024 * 1024 # 1MiB
|
||||
PREP_GRUB_SIZE_BYTES = 8 * 1024 * 1024 # 8MiB
|
||||
|
@ -57,6 +58,7 @@ class FilesystemController(BaseController):
|
|||
|
||||
def __init__(self, common):
|
||||
super().__init__(common)
|
||||
self.block_log_dir = common.get('block_log_dir')
|
||||
self.model = self.base_model.filesystem
|
||||
if self.opts.dry_run and self.opts.bootloader:
|
||||
name = self.opts.bootloader.upper()
|
||||
|
@ -69,28 +71,34 @@ class FilesystemController(BaseController):
|
|||
self._probe_state = ProbeState.NOT_STARTED
|
||||
|
||||
def start(self):
|
||||
block_discover_log.info("starting probe")
|
||||
self._probe_state = ProbeState.PROBING
|
||||
self.run_in_bg(self._bg_probe, self._probed)
|
||||
self.loop.set_alarm_in(
|
||||
5.0, lambda loop, ud: self._check_probe_timeout())
|
||||
|
||||
def _bg_probe(self, probe_types=None):
|
||||
probed_data = self.prober.get_storage(probe_types=probe_types)
|
||||
storage = {}
|
||||
for path, data in probed_data["blockdev"].items():
|
||||
storage[path] = StorageInfo({path: data})
|
||||
return storage
|
||||
return self.prober.get_storage(probe_types=probe_types)
|
||||
|
||||
def _probed(self, fut, restricted=False):
|
||||
if not restricted and self._probe_state != ProbeState.PROBING:
|
||||
log.debug("ignoring result %s for timed out probe", fut)
|
||||
block_discover_log.debug(
|
||||
"ignoring result %s for timed out probe", fut)
|
||||
return
|
||||
try:
|
||||
storage = fut.result()
|
||||
if restricted:
|
||||
fname = 'probe-data-restricted.json'
|
||||
else:
|
||||
fname = 'probe-data.json'
|
||||
with open(os.path.join(self.block_log_dir, fname), 'w') as fp:
|
||||
json.dump(storage, fp)
|
||||
self.model.load_probe_data(storage)
|
||||
except Exception:
|
||||
log.exception("probing failed restricted=%s", restricted)
|
||||
block_discover_log.exception(
|
||||
"probing failed restricted=%s", restricted)
|
||||
if not restricted:
|
||||
log.info("reprobing for blockdev only")
|
||||
block_discover_log.info("reprobing for blockdev only")
|
||||
# Should make a crash file for apport, arrange for it to be
|
||||
# copied onto the installed system and tell user all this
|
||||
# happened!
|
||||
|
@ -100,7 +108,6 @@ class FilesystemController(BaseController):
|
|||
if self.showing:
|
||||
self.default()
|
||||
else:
|
||||
self.model.load_probe_data(storage)
|
||||
self._probe_state = ProbeState.DONE
|
||||
# Should do something here if probing found no devices.
|
||||
if self.showing:
|
||||
|
@ -241,7 +248,7 @@ class FilesystemController(BaseController):
|
|||
if self.answers['guided']:
|
||||
index = self.answers['guided-index']
|
||||
disk = self.model.all_disks()[index]
|
||||
v.choose_disk(None, disk.path)
|
||||
v.choose_disk(None, disk)
|
||||
|
||||
def reset(self):
|
||||
log.info("Resetting Filesystem model")
|
||||
|
@ -264,6 +271,11 @@ class FilesystemController(BaseController):
|
|||
if spec.get('mount') is None:
|
||||
return
|
||||
mount = self.model.add_mount(fs, spec['mount'])
|
||||
if self.model.needs_bootloader_partition():
|
||||
vol = fs.volume
|
||||
if vol.type == "partition" and vol.device.type == "disk":
|
||||
if vol.device._can_be_boot_disk():
|
||||
self.make_boot_disk(vol.device)
|
||||
return mount
|
||||
|
||||
def delete_mount(self, mount):
|
||||
|
@ -273,8 +285,12 @@ class FilesystemController(BaseController):
|
|||
|
||||
def create_filesystem(self, volume, spec):
|
||||
if spec['fstype'] is None:
|
||||
return
|
||||
fs = self.model.add_filesystem(volume, spec['fstype'])
|
||||
fs = volume.original_fs()
|
||||
if fs is None:
|
||||
return
|
||||
self.model.re_add_filesystem(fs)
|
||||
else:
|
||||
fs = self.model.add_filesystem(volume, spec['fstype'])
|
||||
if isinstance(volume, Partition):
|
||||
if spec['fstype'] == "swap":
|
||||
volume.flag = "swap"
|
||||
|
@ -282,6 +298,8 @@ class FilesystemController(BaseController):
|
|||
volume.flag = ""
|
||||
if spec['fstype'] == "swap":
|
||||
self.model.add_mount(fs, "")
|
||||
if spec['fstype'] is None and spec['use_swap']:
|
||||
self.model.add_mount(fs, "")
|
||||
self.create_mount(fs, spec)
|
||||
return fs
|
||||
|
||||
|
@ -391,18 +409,30 @@ class FilesystemController(BaseController):
|
|||
for subobj in obj.fs(), obj.constructed_device():
|
||||
self.delete(subobj)
|
||||
|
||||
def reformat(self, disk):
|
||||
if disk.type == "disk":
|
||||
disk.preserve = False
|
||||
self.clear(disk)
|
||||
for p in list(disk.partitions()):
|
||||
self.delete(p)
|
||||
|
||||
def partition_disk_handler(self, disk, partition, spec):
|
||||
log.debug('partition_disk_handler: %s %s %s', disk, partition, spec)
|
||||
log.debug('disk.freespace: {}'.format(disk.free_for_partitions))
|
||||
|
||||
if partition is not None:
|
||||
partition.size = align_up(spec['size'])
|
||||
if disk.free_for_partitions < 0:
|
||||
raise Exception("partition size too large")
|
||||
if 'size' in spec:
|
||||
partition.size = align_up(spec['size'])
|
||||
if disk.free_for_partitions < 0:
|
||||
raise Exception("partition size too large")
|
||||
self.delete_filesystem(partition.fs())
|
||||
self.create_filesystem(partition, spec)
|
||||
return
|
||||
|
||||
if len(disk.partitions()) == 0:
|
||||
if disk.type == "disk":
|
||||
disk.preserve = False
|
||||
|
||||
needs_boot = self.model.needs_bootloader_partition()
|
||||
log.debug('model needs a bootloader partition? {}'.format(needs_boot))
|
||||
can_be_boot = DeviceAction.MAKE_BOOT in disk.supported_actions
|
||||
|
@ -426,10 +456,12 @@ class FilesystemController(BaseController):
|
|||
log.debug('vg.freespace: {}'.format(vg.free_for_partitions))
|
||||
|
||||
if lv is not None:
|
||||
lv.name = spec['name']
|
||||
lv.size = align_up(spec['size'])
|
||||
if vg.free_for_partitions < 0:
|
||||
raise Exception("lv size too large")
|
||||
if 'name' in spec:
|
||||
lv.name = spec['name']
|
||||
if 'size' in spec:
|
||||
lv.size = align_up(spec['size'])
|
||||
if vg.free_for_partitions < 0:
|
||||
raise Exception("lv size too large")
|
||||
self.delete_filesystem(lv.fs())
|
||||
self.create_filesystem(lv, spec)
|
||||
return
|
||||
|
@ -478,21 +510,48 @@ class FilesystemController(BaseController):
|
|||
|
||||
def make_boot_disk(self, new_boot_disk):
|
||||
boot_partition = None
|
||||
for disk in self.model.all_disks():
|
||||
for part in disk.partitions():
|
||||
if part.flag in ("bios_grub", "boot", "prep"):
|
||||
boot_partition = part
|
||||
if self.model.bootloader == Bootloader.BIOS:
|
||||
install_dev = self.model.grub_install_device
|
||||
if install_dev:
|
||||
boot_partition = install_dev._potential_boot_partition()
|
||||
elif self.model.bootloader == Bootloader.UEFI:
|
||||
mount = self.model._mount_for_path("/boot/efi")
|
||||
if mount is not None:
|
||||
boot_partition = mount.device.volume
|
||||
elif self.model.bootloader == Bootloader.PREP:
|
||||
boot_partition = self.model.grub_install_device
|
||||
if boot_partition is not None:
|
||||
boot_disk = boot_partition.device
|
||||
full = boot_disk.free_for_partitions == 0
|
||||
self.delete_partition(boot_partition)
|
||||
if full:
|
||||
largest_part = max(
|
||||
boot_disk.partitions(), key=lambda p: p.size)
|
||||
largest_part.size += boot_partition.size
|
||||
if new_boot_disk.free_for_partitions < boot_partition.size:
|
||||
largest_part = max(
|
||||
new_boot_disk.partitions(), key=lambda p: p.size)
|
||||
largest_part.size -= (
|
||||
boot_partition.size - new_boot_disk.free_for_partitions)
|
||||
self._create_boot_partition(new_boot_disk)
|
||||
if boot_partition.preserve:
|
||||
if self.model.bootloader == Bootloader.PREP:
|
||||
boot_partition.wipe = None
|
||||
elif self.model.bootloader == Bootloader.UEFI:
|
||||
self.delete_mount(boot_partition.fs().mount())
|
||||
else:
|
||||
boot_disk = boot_partition.device
|
||||
full = boot_disk.free_for_partitions == 0
|
||||
self.delete_partition(boot_partition)
|
||||
if full:
|
||||
largest_part = max(
|
||||
boot_disk.partitions(), key=lambda p: p.size)
|
||||
largest_part.size += boot_partition.size
|
||||
if new_boot_disk.free_for_partitions < boot_partition.size:
|
||||
largest_part = max(
|
||||
new_boot_disk.partitions(), key=lambda p: p.size)
|
||||
largest_part.size -= (
|
||||
boot_partition.size -
|
||||
new_boot_disk.free_for_partitions)
|
||||
if new_boot_disk._has_preexisting_partition():
|
||||
if self.model.bootloader == Bootloader.BIOS:
|
||||
self.model.grub_install_device = new_boot_disk
|
||||
elif self.model.bootloader == Bootloader.UEFI:
|
||||
part = new_boot_disk._potential_boot_partition()
|
||||
if part.fs() is None:
|
||||
self.model.add_filesystem(part, 'fat32')
|
||||
self.model.add_mount(part.fs(), '/boot/efi')
|
||||
elif self.model.bootloader == Bootloader.PREP:
|
||||
part = new_boot_disk._potential_boot_partition()
|
||||
part.wipe = 'zero'
|
||||
self.model.grub_install_device = part
|
||||
else:
|
||||
new_boot_disk.preserve = False
|
||||
self._create_boot_partition(new_boot_disk)
|
||||
|
|
|
@ -83,8 +83,8 @@ class TestFilesystemController(unittest.TestCase):
|
|||
|
||||
def test_make_boot_disk_BIOS(self):
|
||||
controller = make_controller(Bootloader.BIOS)
|
||||
disk1 = make_disk(controller.model)
|
||||
disk2 = make_disk(controller.model)
|
||||
disk1 = make_disk(controller.model, preserve=False)
|
||||
disk2 = make_disk(controller.model, preserve=False)
|
||||
disk2p1 = controller.model.add_partition(
|
||||
disk2, size=disk2.free_for_partitions)
|
||||
|
||||
|
@ -103,10 +103,28 @@ class TestFilesystemController(unittest.TestCase):
|
|||
self.assertEqual(disk2.partitions()[0].flag, "bios_grub")
|
||||
self.assertEqual(controller.model.grub_install_device, disk2)
|
||||
|
||||
def test_make_boot_disk_BIOS_existing(self):
|
||||
controller = make_controller(Bootloader.BIOS)
|
||||
disk1 = make_disk(controller.model, preserve=True)
|
||||
disk1p1 = controller.model.add_partition(
|
||||
disk1, size=1 << 20, flag="bios_grub")
|
||||
disk1p1.preserve = True
|
||||
disk2 = make_disk(controller.model, preserve=False)
|
||||
|
||||
self.assertEqual(disk1.partitions(), [disk1p1])
|
||||
self.assertEqual(controller.model.grub_install_device, None)
|
||||
controller.make_boot_disk(disk1)
|
||||
self.assertEqual(disk1.partitions(), [disk1p1])
|
||||
self.assertEqual(controller.model.grub_install_device, disk1)
|
||||
|
||||
controller.make_boot_disk(disk2)
|
||||
self.assertEqual(disk1.partitions(), [disk1p1])
|
||||
self.assertEqual(controller.model.grub_install_device, disk2)
|
||||
|
||||
def test_make_boot_disk_UEFI(self):
|
||||
controller = make_controller(Bootloader.UEFI)
|
||||
disk1 = make_disk(controller.model)
|
||||
disk2 = make_disk(controller.model)
|
||||
disk1 = make_disk(controller.model, preserve=False)
|
||||
disk2 = make_disk(controller.model, preserve=False)
|
||||
disk2p1 = controller.model.add_partition(
|
||||
disk2, size=disk2.free_for_partitions)
|
||||
|
||||
|
@ -130,10 +148,35 @@ class TestFilesystemController(unittest.TestCase):
|
|||
efi_mnt = controller.model._mount_for_path("/boot/efi")
|
||||
self.assertEqual(efi_mnt.device.volume, disk2.partitions()[0])
|
||||
|
||||
def test_make_boot_disk_UEFI_existing(self):
|
||||
controller = make_controller(Bootloader.UEFI)
|
||||
disk1 = make_disk(controller.model, preserve=True)
|
||||
disk1p1 = controller.model.add_partition(
|
||||
disk1, size=512 << 20, flag="boot")
|
||||
disk1p1.preserve = True
|
||||
disk2 = make_disk(controller.model, preserve=True)
|
||||
|
||||
self.assertEqual(disk1.partitions(), [disk1p1])
|
||||
self.assertEqual(controller.model.grub_install_device, None)
|
||||
efi_mnt = controller.model._mount_for_path("/boot/efi")
|
||||
self.assertEqual(efi_mnt, None)
|
||||
controller.make_boot_disk(disk1)
|
||||
self.assertEqual(disk1.partitions(), [disk1p1])
|
||||
self.assertEqual(controller.model.grub_install_device, None)
|
||||
efi_mnt = controller.model._mount_for_path("/boot/efi")
|
||||
self.assertEqual(efi_mnt.device.volume, disk1p1)
|
||||
self.assertEqual(disk1p1.fs().fstype, "fat32")
|
||||
|
||||
controller.make_boot_disk(disk2)
|
||||
self.assertEqual(disk1.partitions(), [disk1p1])
|
||||
self.assertEqual(controller.model.grub_install_device, None)
|
||||
efi_mnt = controller.model._mount_for_path("/boot/efi")
|
||||
self.assertEqual(efi_mnt.device.volume, disk2.partitions()[0])
|
||||
|
||||
def test_make_boot_disk_PREP(self):
|
||||
controller = make_controller(Bootloader.PREP)
|
||||
disk1 = make_disk(controller.model)
|
||||
disk2 = make_disk(controller.model)
|
||||
disk1 = make_disk(controller.model, preserve=False)
|
||||
disk2 = make_disk(controller.model, preserve=False)
|
||||
disk2p1 = controller.model.add_partition(
|
||||
disk2, size=disk2.free_for_partitions)
|
||||
|
||||
|
@ -157,3 +200,42 @@ class TestFilesystemController(unittest.TestCase):
|
|||
self.assertEqual(
|
||||
controller.model.grub_install_device,
|
||||
disk2.partitions()[0])
|
||||
|
||||
def test_make_boot_disk_PREP_existing(self):
|
||||
controller = make_controller(Bootloader.PREP)
|
||||
disk1 = make_disk(controller.model, preserve=True)
|
||||
disk1p1 = controller.model.add_partition(
|
||||
disk1, size=8 << 20, flag="prep")
|
||||
disk1p1.preserve = True
|
||||
disk2 = make_disk(controller.model, preserve=False)
|
||||
|
||||
self.assertEqual(disk1.partitions(), [disk1p1])
|
||||
self.assertEqual(controller.model.grub_install_device, None)
|
||||
controller.make_boot_disk(disk1)
|
||||
self.assertEqual(disk1.partitions(), [disk1p1])
|
||||
self.assertEqual(controller.model.grub_install_device, disk1p1)
|
||||
self.assertEqual(disk1p1.wipe, 'zero')
|
||||
|
||||
controller.make_boot_disk(disk2)
|
||||
self.assertEqual(disk1.partitions(), [disk1p1])
|
||||
self.assertEqual(disk1p1.wipe, None)
|
||||
self.assertEqual(
|
||||
controller.model.grub_install_device, disk2.partitions()[0])
|
||||
self.assertEqual(disk2.partitions()[0].flag, "prep")
|
||||
self.assertEqual(
|
||||
controller.model.grub_install_device,
|
||||
disk2.partitions()[0])
|
||||
|
||||
def test_mounting_partition_makes_boot_disk(self):
|
||||
controller = make_controller(Bootloader.UEFI)
|
||||
disk1 = make_disk(controller.model, preserve=True)
|
||||
disk1p1 = controller.model.add_partition(
|
||||
disk1, size=512 << 20, flag="boot")
|
||||
disk1p1.preserve = True
|
||||
disk1p2 = controller.model.add_partition(
|
||||
disk1, size=disk1.free_for_partitions)
|
||||
disk1p2.preserve = True
|
||||
controller.partition_disk_handler(
|
||||
disk1, disk1p2, {'fstype': 'ext4', 'mount': '/'})
|
||||
efi_mnt = controller.model._mount_for_path("/boot/efi")
|
||||
self.assertEqual(efi_mnt.device.volume, disk1p1)
|
||||
|
|
|
@ -59,11 +59,14 @@ class Subiquity(Application):
|
|||
"InstallProgress",
|
||||
]
|
||||
|
||||
def __init__(self, ui, opts):
|
||||
|
||||
def __init__(self, ui, opts, block_log_dir):
|
||||
if not opts.bootloader == 'none' and platform.machine() != 's390x':
|
||||
self.controllers.remove("Zdev")
|
||||
|
||||
super().__init__(ui, opts)
|
||||
self.common['ui'].progress_completion += 1
|
||||
self.common['block_log_dir'] = block_log_dir
|
||||
if opts.snaps_from_examples:
|
||||
connection = FakeSnapdConnection(
|
||||
os.path.join(
|
||||
|
|
|
@ -17,15 +17,17 @@ from abc import ABC, abstractmethod
|
|||
import attr
|
||||
import collections
|
||||
import enum
|
||||
import glob
|
||||
import itertools
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from curtin.util import human2bytes
|
||||
from curtin import storage_config
|
||||
|
||||
from probert.storage import StorageInfo
|
||||
|
||||
log = logging.getLogger('subiquity.models.filesystem')
|
||||
|
||||
|
@ -70,6 +72,9 @@ def _remove_backlinks(obj):
|
|||
setattr(vv, backlink, None)
|
||||
|
||||
|
||||
_type_to_cls = {}
|
||||
|
||||
|
||||
def fsobj(typ):
|
||||
def wrapper(c):
|
||||
c.__attrs_post_init__ = _set_backlinks
|
||||
|
@ -77,6 +82,7 @@ def fsobj(typ):
|
|||
c.id = attributes.idfield(typ)
|
||||
c._m = attr.ib(repr=None, default=None)
|
||||
c = attr.s(cmp=False)(c)
|
||||
_type_to_cls[typ] = c
|
||||
return c
|
||||
return wrapper
|
||||
|
||||
|
@ -308,6 +314,7 @@ def asdict(inst):
|
|||
class DeviceAction(enum.Enum):
|
||||
INFO = _("Info")
|
||||
EDIT = _("Edit")
|
||||
REFORMAT = _("Reformat")
|
||||
PARTITION = _("Add Partition")
|
||||
CREATE_LV = _("Create Logical Volume")
|
||||
FORMAT = _("Format")
|
||||
|
@ -332,6 +339,12 @@ def _generic_can_REMOVE(obj):
|
|||
cd = obj.constructed_device()
|
||||
if cd is None:
|
||||
return False
|
||||
if cd.preserve:
|
||||
return _("Cannot remove selflabel from pre-exsting {cdtype} "
|
||||
"{cdlabel}").format(
|
||||
selflabel=obj.label,
|
||||
cdtype=cd.desc(),
|
||||
cdlabel=cd.label)
|
||||
if isinstance(cd, Raid):
|
||||
if obj in cd.spare_devices:
|
||||
return True
|
||||
|
@ -379,10 +392,17 @@ class _Formattable(ABC):
|
|||
|
||||
@property
|
||||
def annotations(self):
|
||||
return []
|
||||
preserve = getattr(self, 'preserve', None)
|
||||
if preserve is None:
|
||||
return []
|
||||
elif preserve:
|
||||
return [_("existing")]
|
||||
else:
|
||||
return [_("new")]
|
||||
|
||||
# Filesystem
|
||||
_fs = attributes.backlink()
|
||||
_original_fs = attributes.backlink()
|
||||
# Raid or LVM_VolGroup for now, but one day ZPool, BCache...
|
||||
_constructed_device = attributes.backlink()
|
||||
|
||||
|
@ -397,13 +417,24 @@ class _Formattable(ABC):
|
|||
]
|
||||
fs = self.fs()
|
||||
if fs is not None:
|
||||
r = [_("formatted as {fstype}").format(fstype=fs.fstype)]
|
||||
if fs.preserve:
|
||||
format_desc = _("already formatted as {fstype}")
|
||||
elif self.original_fs() is not None:
|
||||
format_desc = _("to be reformatted as {fstype}")
|
||||
else:
|
||||
format_desc = _("to be formatted as {fstype}")
|
||||
r = [format_desc.format(fstype=fs.fstype)]
|
||||
if self._m.is_mounted_filesystem(fs.fstype):
|
||||
m = fs.mount()
|
||||
if m:
|
||||
r.append(_("mounted at {path}").format(path=m.path))
|
||||
else:
|
||||
r.append(_("not mounted"))
|
||||
elif fs.preserve:
|
||||
if fs.mount() is None:
|
||||
r.append(_("unused"))
|
||||
else:
|
||||
r.append(_("used"))
|
||||
return r
|
||||
else:
|
||||
return [_("unused")]
|
||||
|
@ -414,6 +445,9 @@ class _Formattable(ABC):
|
|||
def fs(self):
|
||||
return self._fs
|
||||
|
||||
def original_fs(self):
|
||||
return self._original_fs
|
||||
|
||||
def constructed_device(self, skip_dm_crypt=True):
|
||||
cd = self._constructed_device
|
||||
if cd is None:
|
||||
|
@ -499,7 +533,8 @@ class _Device(_Formattable, ABC):
|
|||
if self._fs is not None:
|
||||
return self._fs._available()
|
||||
if self.free_for_partitions > 0:
|
||||
return True
|
||||
if not self._has_preexisting_partition():
|
||||
return True
|
||||
for p in self._partitions:
|
||||
if p.available():
|
||||
return True
|
||||
|
@ -511,6 +546,13 @@ class _Device(_Formattable, ABC):
|
|||
return True
|
||||
return False
|
||||
|
||||
def _has_preexisting_partition(self):
|
||||
for p in self._partitions:
|
||||
if p.preserve:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def _can_DELETE(self):
|
||||
mounted_partitions = 0
|
||||
|
@ -547,21 +589,13 @@ class Disk(_Device):
|
|||
serial = attr.ib(default=None)
|
||||
path = attr.ib(default=None)
|
||||
model = attr.ib(default=None)
|
||||
wipe = attr.ib(default='superblock')
|
||||
wipe = attr.ib(default=None)
|
||||
preserve = attr.ib(default=False)
|
||||
name = attr.ib(default="")
|
||||
grub_device = attr.ib(default=False)
|
||||
|
||||
_info = attr.ib(default=None)
|
||||
|
||||
@classmethod
|
||||
def from_info(self, model, info):
|
||||
d = Disk(m=model, info=info)
|
||||
d.serial = info.serial
|
||||
d.path = info.name
|
||||
d.model = info.model
|
||||
return d
|
||||
|
||||
def info_for_display(self):
|
||||
bus = self._info.raw.get('ID_BUS', None)
|
||||
major = self._info.raw.get('MAJOR', None)
|
||||
|
@ -602,6 +636,10 @@ class Disk(_Device):
|
|||
def size(self):
|
||||
return align_down(self._info.size)
|
||||
|
||||
@property
|
||||
def annotations(self):
|
||||
return []
|
||||
|
||||
def desc(self):
|
||||
return _("local disk")
|
||||
|
||||
|
@ -611,10 +649,38 @@ class Disk(_Device):
|
|||
return self.serial
|
||||
return self.path
|
||||
|
||||
def _potential_boot_partition(self):
|
||||
if self._m.bootloader == Bootloader.NONE:
|
||||
return None
|
||||
if not self._partitions:
|
||||
return None
|
||||
if self._m.bootloader == Bootloader.BIOS:
|
||||
if self._partitions[0].flag == "bios_grub":
|
||||
return self._partitions[0]
|
||||
else:
|
||||
return None
|
||||
flag = {
|
||||
Bootloader.UEFI: "boot",
|
||||
Bootloader.PREP: "prep",
|
||||
}[self._m.bootloader]
|
||||
for p in self._partitions:
|
||||
# XXX should check not extended in the UEFI case too (until we fix
|
||||
# that bug)
|
||||
if p.flag == flag:
|
||||
return p
|
||||
return None
|
||||
|
||||
def _can_be_boot_disk(self):
|
||||
if self._m.bootloader == Bootloader.BIOS and self.ptable == "msdos":
|
||||
return True
|
||||
else:
|
||||
return self._potential_boot_partition() is not None
|
||||
|
||||
@property
|
||||
def supported_actions(self):
|
||||
actions = [
|
||||
DeviceAction.INFO,
|
||||
DeviceAction.REFORMAT,
|
||||
DeviceAction.PARTITION,
|
||||
DeviceAction.FORMAT,
|
||||
DeviceAction.REMOVE,
|
||||
|
@ -624,7 +690,19 @@ class Disk(_Device):
|
|||
return actions
|
||||
|
||||
_can_INFO = True
|
||||
_can_PARTITION = property(lambda self: self.free_for_partitions > 0)
|
||||
|
||||
@property
|
||||
def _can_REFORMAT(self):
|
||||
if len(self._partitions) == 0:
|
||||
return False
|
||||
for p in self._partitions:
|
||||
if p._constructed_device is not None:
|
||||
return False
|
||||
return True
|
||||
|
||||
_can_PARTITION = property(
|
||||
lambda self: not self._has_preexisting_partition() and
|
||||
self.free_for_partitions > 0)
|
||||
_can_FORMAT = property(
|
||||
lambda self: len(self._partitions) == 0 and
|
||||
self._constructed_device is None)
|
||||
|
@ -644,11 +722,16 @@ class Disk(_Device):
|
|||
install_dev = self._m.grub_install_device
|
||||
if install_dev is not None and install_dev.device is self:
|
||||
return False
|
||||
return self._fs is None and self._constructed_device is None
|
||||
if self._has_preexisting_partition():
|
||||
return self._can_be_boot_disk()
|
||||
else:
|
||||
return self._fs is None and self._constructed_device is None
|
||||
|
||||
@property
|
||||
def ok_for_raid(self):
|
||||
if self._fs is not None:
|
||||
if self._fs.preserve:
|
||||
return self._fs._mount is None
|
||||
return False
|
||||
if self._constructed_device is not None:
|
||||
return False
|
||||
|
@ -715,10 +798,14 @@ class Partition(_Formattable):
|
|||
]
|
||||
|
||||
_can_EDIT = property(_generic_can_EDIT)
|
||||
|
||||
_can_REMOVE = property(_generic_can_REMOVE)
|
||||
|
||||
@property
|
||||
def _can_DELETE(self):
|
||||
if self.device._has_preexisting_partition():
|
||||
return _("Cannot delete a single partition from a device that "
|
||||
"already has partitions.")
|
||||
if self.flag in ('boot', 'bios_grub', 'prep'):
|
||||
return _("Cannot delete required bootloader partition")
|
||||
return _generic_can_DELETE(self)
|
||||
|
@ -728,6 +815,8 @@ class Partition(_Formattable):
|
|||
if self.flag in ('boot', 'bios_grub', 'prep'):
|
||||
return False
|
||||
if self._fs is not None:
|
||||
if self._fs.preserve:
|
||||
return self._fs._mount is None
|
||||
return False
|
||||
if self._constructed_device is not None:
|
||||
return False
|
||||
|
@ -774,7 +863,9 @@ class Raid(_Device):
|
|||
|
||||
@property
|
||||
def _can_EDIT(self):
|
||||
if len(self._partitions) > 0:
|
||||
if self.preserve:
|
||||
return _("Cannot edit pre-existing RAIDs.")
|
||||
elif len(self._partitions) > 0:
|
||||
return _(
|
||||
"Cannot edit {selflabel} because it has partitions.").format(
|
||||
selflabel=self.label)
|
||||
|
@ -790,6 +881,8 @@ class Raid(_Device):
|
|||
@property
|
||||
def ok_for_raid(self):
|
||||
if self._fs is not None:
|
||||
if self._fs.preserve:
|
||||
return self._fs._mount is None
|
||||
return False
|
||||
if self._constructed_device is not None:
|
||||
return False
|
||||
|
@ -841,7 +934,9 @@ class LVM_VolGroup(_Device):
|
|||
|
||||
@property
|
||||
def _can_EDIT(self):
|
||||
if len(self._partitions) > 0:
|
||||
if self.preserve:
|
||||
return _("Cannot edit pre-existing volume groups.")
|
||||
elif len(self._partitions) > 0:
|
||||
return _(
|
||||
"Cannot edit {selflabel} because it has logical "
|
||||
"volumes.").format(
|
||||
|
@ -849,7 +944,8 @@ class LVM_VolGroup(_Device):
|
|||
else:
|
||||
return _generic_can_EDIT(self)
|
||||
|
||||
_can_CREATE_LV = Disk._can_PARTITION
|
||||
_can_CREATE_LV = property(
|
||||
lambda self: not self.preserve and self.free_for_partitions > 0)
|
||||
|
||||
ok_for_raid = False
|
||||
ok_for_lvm_vg = False
|
||||
|
@ -895,7 +991,13 @@ class LVM_LogicalVolume(_Formattable):
|
|||
]
|
||||
|
||||
_can_EDIT = True
|
||||
_can_DELETE = True
|
||||
|
||||
@property
|
||||
def _can_DELETE(self):
|
||||
if self.volgroup._has_preexisting_partition():
|
||||
return _("Cannot delete a single logical volume from a volume "
|
||||
"group that already has logical volumes.")
|
||||
return True
|
||||
|
||||
ok_for_raid = False
|
||||
ok_for_lvm_vg = False
|
||||
|
@ -939,7 +1041,10 @@ class Filesystem:
|
|||
def _available(self):
|
||||
# False if mounted or if fs does not require a mount, True otherwise.
|
||||
if self._mount is None:
|
||||
return FilesystemModel.is_mounted_filesystem(self.fstype)
|
||||
if self.preserve:
|
||||
return True
|
||||
else:
|
||||
return FilesystemModel.is_mounted_filesystem(self.fstype)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
@ -1003,14 +1108,100 @@ class FilesystemModel(object):
|
|||
|
||||
def __init__(self):
|
||||
self.bootloader = self._probe_bootloader()
|
||||
self._disk_info = []
|
||||
self._probe_data = None
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self._actions = [
|
||||
Disk.from_info(self, info) for info in self._disk_info]
|
||||
if self._probe_data is not None:
|
||||
config = storage_config.extract_storage_config(self._probe_data)
|
||||
self._actions = self._actions_from_config(
|
||||
config["storage"]["config"],
|
||||
self._probe_data['blockdev'])
|
||||
else:
|
||||
self._actions = []
|
||||
self.grub_install_device = None
|
||||
|
||||
def _actions_from_config(self, config, blockdevs):
|
||||
"""Convert curtin storage config into action instances.
|
||||
|
||||
curtin represents storage "actions" as defined in
|
||||
https://curtin.readthedocs.io/en/latest/topics/storage.html. We
|
||||
convert each action (that we know about) into an instance of
|
||||
Disk, Partition, RAID, etc (unknown actions, e.g. bcache, are
|
||||
just ignored).
|
||||
|
||||
We also filter out anything that can be reached from a currently
|
||||
mounted device. The motivation here is only to exclude the media
|
||||
subiquity is mounted from, so this might be a bit excessive but
|
||||
hey it works.
|
||||
|
||||
Perhaps surprisingly the order of the returned actions matters.
|
||||
The devices are presented in the filesystem view in the reverse
|
||||
of the order they appear in _actions, which means that e.g. a
|
||||
RAID appears higher up the list than the disks is is composed
|
||||
of. This is quite important as it makes "unpeeling" existing
|
||||
compound structures easy, you just delete the top device until
|
||||
you only have disks left.
|
||||
"""
|
||||
byid = {}
|
||||
objs = []
|
||||
exclusions = set()
|
||||
for action in config:
|
||||
if action['type'] == 'mount':
|
||||
exclusions.add(byid[action['device']])
|
||||
continue
|
||||
c = _type_to_cls.get(action['type'], None)
|
||||
if c is None:
|
||||
# Ignore any action we do not know how to process yet
|
||||
# (e.g. bcache)
|
||||
continue
|
||||
kw = {}
|
||||
for f in attr.fields(c):
|
||||
n = f.name
|
||||
if n not in action:
|
||||
continue
|
||||
v = action[n]
|
||||
try:
|
||||
if f.metadata.get('ref', False):
|
||||
kw[n] = byid[v]
|
||||
elif f.metadata.get('reflist', False):
|
||||
kw[n] = [byid[id] for id in v]
|
||||
else:
|
||||
kw[n] = v
|
||||
except KeyError:
|
||||
# If a dependency of the current action has been
|
||||
# ignored, we need to ignore the current action too
|
||||
# (e.g. a bcache's filesystem).
|
||||
continue
|
||||
if kw['type'] == 'disk':
|
||||
path = kw['path']
|
||||
kw['info'] = StorageInfo({path: blockdevs[path]})
|
||||
kw['preserve'] = True
|
||||
obj = byid[action['id']] = c(m=self, **kw)
|
||||
if action['type'] == "format":
|
||||
obj.volume._original_fs = obj
|
||||
objs.append(obj)
|
||||
|
||||
while True:
|
||||
log.debug("exclusions %s", {e.id for e in exclusions})
|
||||
next_exclusions = exclusions.copy()
|
||||
for e in exclusions:
|
||||
next_exclusions.update(itertools.chain(
|
||||
dependencies(e), reverse_dependencies(e)))
|
||||
if len(exclusions) == len(next_exclusions):
|
||||
break
|
||||
exclusions = next_exclusions
|
||||
|
||||
objs = [o for o in objs if o not in exclusions]
|
||||
|
||||
for o in objs:
|
||||
if o.type == "partition" and o.flag == "swap" and o._fs is None:
|
||||
fs = Filesystem(m=self, fstype="swap", volume=o, preserve=True)
|
||||
o._original_fs = fs
|
||||
objs.append(fs)
|
||||
|
||||
return objs
|
||||
|
||||
def _render_actions(self):
|
||||
# The curtin storage config has the constraint that an action must be
|
||||
# preceded by all the things that it depends on. We handle this by
|
||||
|
@ -1023,8 +1214,6 @@ class FilesystemModel(object):
|
|||
emitted_ids = set()
|
||||
|
||||
def emit(obj):
|
||||
if obj.type == 'disk' and not obj.used:
|
||||
return
|
||||
r.append(asdict(obj))
|
||||
emitted_ids.add(obj.id)
|
||||
|
||||
|
@ -1086,45 +1275,9 @@ class FilesystemModel(object):
|
|||
}
|
||||
return config
|
||||
|
||||
def _get_system_mounted_disks(self):
|
||||
# This assumes a fairly vanilla setup. It won't list as
|
||||
# mounted a disk that is only mounted via lvm, for example.
|
||||
mounted_devs = []
|
||||
with open('/proc/mounts', encoding=sys.getfilesystemencoding()) as pm:
|
||||
for line in pm:
|
||||
if line.startswith('/dev/'):
|
||||
mounted_devs.append(line.split()[0][5:])
|
||||
mounted_disks = set()
|
||||
for dev in mounted_devs:
|
||||
if os.path.exists('/sys/block/{}'.format(dev)):
|
||||
mounted_disks.add('/dev/' + dev)
|
||||
else:
|
||||
paths = glob.glob('/sys/block/*/{}/partition'.format(dev))
|
||||
if len(paths) == 1:
|
||||
mounted_disks.add('/dev/' + paths[0].split('/')[3])
|
||||
return mounted_disks
|
||||
|
||||
def load_probe_data(self, storage):
|
||||
currently_mounted = self._get_system_mounted_disks()
|
||||
for path, info in storage.items():
|
||||
log.debug("fs probe %s", path)
|
||||
if path in currently_mounted:
|
||||
continue
|
||||
if info.type == 'disk':
|
||||
if info.is_virtual:
|
||||
continue
|
||||
if info.raw["MAJOR"] in ("2", "11"): # serial and cd devices
|
||||
continue
|
||||
if info.raw['attrs'].get('ro') == "1":
|
||||
continue
|
||||
if "ID_CDROM" in info.raw:
|
||||
continue
|
||||
# log.debug('disk={}\n{}'.format(
|
||||
# path, json.dumps(data, indent=4, sort_keys=True)))
|
||||
if info.size < self.lower_size_limit:
|
||||
continue
|
||||
self._disk_info.append(info)
|
||||
self._actions.append(Disk.from_info(self, info))
|
||||
def load_probe_data(self, probe_data):
|
||||
self._probe_data = probe_data
|
||||
self.reset()
|
||||
|
||||
def disk_by_path(self, path):
|
||||
for a in self._actions:
|
||||
|
@ -1252,6 +1405,10 @@ class FilesystemModel(object):
|
|||
self._actions.append(fs)
|
||||
return fs
|
||||
|
||||
def re_add_filesystem(self, fs):
|
||||
_set_backlinks(fs)
|
||||
self._actions.append(fs)
|
||||
|
||||
def remove_filesystem(self, fs):
|
||||
if fs._mount:
|
||||
raise Exception("can only remove unmounted filesystem")
|
||||
|
|
|
@ -117,12 +117,14 @@ def make_model(bootloader=None):
|
|||
return model
|
||||
|
||||
|
||||
def make_disk(model):
|
||||
def make_disk(model, **kw):
|
||||
serial = 'serial%s' % len(model._actions)
|
||||
model._actions.append(Disk(
|
||||
m=model, serial=serial,
|
||||
info=FakeStorageInfo(size=100*(2**30))))
|
||||
return model._actions[-1]
|
||||
info=FakeStorageInfo(size=100*(2**30)),
|
||||
**kw))
|
||||
disk = model._actions[-1]
|
||||
return disk
|
||||
|
||||
|
||||
def make_model_and_disk(bootloader=None):
|
||||
|
@ -130,12 +132,12 @@ def make_model_and_disk(bootloader=None):
|
|||
return model, make_disk(model)
|
||||
|
||||
|
||||
def make_partition(model, device=None, *, size=None, flag=""):
|
||||
def make_partition(model, device=None, *, size=None, **kw):
|
||||
if device is None:
|
||||
device = make_disk(model)
|
||||
if size is None:
|
||||
size = device.free_for_partitions//2
|
||||
partition = Partition(m=model, device=device, size=size, flag=flag)
|
||||
partition = Partition(m=model, device=device, size=size, **kw)
|
||||
model._actions.append(partition)
|
||||
return partition
|
||||
|
||||
|
@ -180,16 +182,38 @@ def make_model_and_lv(bootloader=None):
|
|||
|
||||
class TestFilesystemModel(unittest.TestCase):
|
||||
|
||||
def test_disk_annotations(self):
|
||||
# disks never have annotations
|
||||
model, disk = make_model_and_disk()
|
||||
self.assertEqual(disk.annotations, [])
|
||||
disk.preserve = True
|
||||
self.assertEqual(disk.annotations, [])
|
||||
|
||||
def test_partition_annotations(self):
|
||||
model, disk = make_model_and_disk()
|
||||
part = model.add_partition(disk, size=disk.free_for_partitions)
|
||||
self.assertEqual(part.annotations, ['new'])
|
||||
part.preserve = True
|
||||
self.assertEqual(part.annotations, ['existing'])
|
||||
part.flag = "boot"
|
||||
self.assertEqual(part.annotations, ['existing', 'ESP'])
|
||||
part.flag = "prep"
|
||||
self.assertEqual(part.annotations, ['existing', 'PReP'])
|
||||
part.flag = "bios_grub"
|
||||
self.assertEqual(part.annotations, ['existing', 'bios_grub'])
|
||||
|
||||
def test_vg_default_annotations(self):
|
||||
model, disk = make_model_and_disk()
|
||||
vg = model.add_volgroup('vg-0', {disk})
|
||||
self.assertEqual(vg.annotations, [])
|
||||
self.assertEqual(vg.annotations, ['new'])
|
||||
vg.preserve = True
|
||||
self.assertEqual(vg.annotations, ['existing'])
|
||||
|
||||
def test_vg_encrypted_annotations(self):
|
||||
model, disk = make_model_and_disk()
|
||||
dm_crypt = model.add_dm_crypt(disk, key='passw0rd')
|
||||
vg = model.add_volgroup('vg-0', {dm_crypt})
|
||||
self.assertEqual(vg.annotations, ['encrypted'])
|
||||
self.assertEqual(vg.annotations, ['new', 'encrypted'])
|
||||
|
||||
def _test_ok_for_xxx(self, model, make_new_device, attr,
|
||||
test_partitions=True):
|
||||
|
@ -205,6 +229,20 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
dev3 = make_new_device()
|
||||
make_partition(model, dev3)
|
||||
self.assertFalse(getattr(dev3, attr))
|
||||
# Empty existing devices are ok
|
||||
dev4 = make_new_device()
|
||||
dev4.preserve = True
|
||||
self.assertTrue(getattr(dev4, attr))
|
||||
# A dev with an existing filesystem is ok (there is no
|
||||
# way to remove the format)
|
||||
dev5 = make_new_device()
|
||||
dev5.preserve = True
|
||||
fs = model.add_filesystem(dev5, 'ext4')
|
||||
fs.preserve = True
|
||||
self.assertTrue(dev5.ok_for_raid)
|
||||
# But a existing, *mounted* filesystem is not.
|
||||
model.add_mount(fs, '/')
|
||||
self.assertFalse(dev5.ok_for_raid)
|
||||
|
||||
def test_disk_ok_for_xxx(self):
|
||||
model = make_model()
|
||||
|
@ -251,10 +289,23 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
self.assertEqual(partition.usage_labels(), ["unused"])
|
||||
fs = model.add_filesystem(partition, 'ext4')
|
||||
self.assertEqual(
|
||||
partition.usage_labels(), ["formatted as ext4", "not mounted"])
|
||||
model.add_mount(fs, '/')
|
||||
partition.usage_labels(),
|
||||
["to be formatted as ext4", "not mounted"])
|
||||
partition._original_fs = fs
|
||||
fs.preserve = True
|
||||
partition.preserve = True
|
||||
self.assertEqual(
|
||||
partition.usage_labels(), ["formatted as ext4", "mounted at /"])
|
||||
partition.usage_labels(),
|
||||
["already formatted as ext4", "not mounted"])
|
||||
model.remove_filesystem(fs)
|
||||
fs2 = model.add_filesystem(partition, 'ext4')
|
||||
self.assertEqual(
|
||||
partition.usage_labels(),
|
||||
["to be reformatted as ext4", "not mounted"])
|
||||
model.add_mount(fs2, '/')
|
||||
self.assertEqual(
|
||||
partition.usage_labels(),
|
||||
["to be reformatted as ext4", "mounted at /"])
|
||||
|
||||
def assertActionNotSupported(self, obj, action):
|
||||
self.assertNotIn(action, obj.supported_actions)
|
||||
|
@ -272,12 +323,24 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
|
||||
vg = model.add_volgroup('vg1', {objects[0], objects[1]})
|
||||
self.assertActionPossible(objects[0], DeviceAction.REMOVE)
|
||||
|
||||
# Cannot remove a device from a preexisting VG
|
||||
vg.preserve = True
|
||||
self.assertActionNotPossible(objects[0], DeviceAction.REMOVE)
|
||||
vg.preserve = False
|
||||
|
||||
# Probably this removal should be a model method?
|
||||
vg.devices.remove(objects[1])
|
||||
objects[1]._constructed_device = None
|
||||
self.assertActionNotPossible(objects[0], DeviceAction.REMOVE)
|
||||
raid = model.add_raid('md0', 'raid1', set(objects[2:]), set())
|
||||
self.assertActionPossible(objects[2], DeviceAction.REMOVE)
|
||||
|
||||
# Cannot remove a device from a preexisting RAID
|
||||
raid.preserve = True
|
||||
self.assertActionNotPossible(objects[2], DeviceAction.REMOVE)
|
||||
raid.preserve = False
|
||||
|
||||
# Probably this removal should be a model method?
|
||||
raid.devices.remove(objects[4])
|
||||
objects[4]._constructed_device = None
|
||||
|
@ -291,6 +354,31 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
model, disk = make_model_and_disk()
|
||||
self.assertActionNotSupported(disk, DeviceAction.EDIT)
|
||||
|
||||
def test_disk_action_REFORMAT(self):
|
||||
model = make_model()
|
||||
|
||||
disk1 = make_disk(model, preserve=False)
|
||||
self.assertActionNotPossible(disk1, DeviceAction.REFORMAT)
|
||||
disk1p1 = make_partition(model, disk1, preserve=False)
|
||||
self.assertActionPossible(disk1, DeviceAction.REFORMAT)
|
||||
model.add_volgroup('vg0', {disk1p1})
|
||||
self.assertActionNotPossible(disk1, DeviceAction.REFORMAT)
|
||||
|
||||
disk2 = make_disk(model, preserve=True)
|
||||
self.assertActionNotPossible(disk2, DeviceAction.REFORMAT)
|
||||
disk2p1 = make_partition(model, disk2, preserve=True)
|
||||
self.assertActionPossible(disk2, DeviceAction.REFORMAT)
|
||||
model.add_volgroup('vg1', {disk2p1})
|
||||
self.assertActionNotPossible(disk2, DeviceAction.REFORMAT)
|
||||
|
||||
disk3 = make_disk(model, preserve=False)
|
||||
model.add_volgroup('vg2', {disk3})
|
||||
self.assertActionNotPossible(disk3, DeviceAction.REFORMAT)
|
||||
|
||||
disk4 = make_disk(model, preserve=True)
|
||||
model.add_volgroup('vg2', {disk4})
|
||||
self.assertActionNotPossible(disk4, DeviceAction.REFORMAT)
|
||||
|
||||
def test_disk_action_PARTITION(self):
|
||||
model, disk = make_model_and_disk()
|
||||
self.assertActionPossible(disk, DeviceAction.PARTITION)
|
||||
|
@ -299,6 +387,14 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
make_partition(model, disk, size=disk.free_for_partitions)
|
||||
self.assertActionNotPossible(disk, DeviceAction.PARTITION)
|
||||
|
||||
# Can partition a disk with .preserve=True
|
||||
disk2 = make_disk(model)
|
||||
disk2.preserve = True
|
||||
self.assertActionPossible(disk2, DeviceAction.PARTITION)
|
||||
# But not if it has a partition.
|
||||
make_partition(model, disk2, preserve=True)
|
||||
self.assertActionNotPossible(disk2, DeviceAction.PARTITION)
|
||||
|
||||
def test_disk_action_CREATE_LV(self):
|
||||
model, disk = make_model_and_disk()
|
||||
self.assertActionNotSupported(disk, DeviceAction.CREATE_LV)
|
||||
|
@ -321,13 +417,81 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
model, disk = make_model_and_disk()
|
||||
self.assertActionNotSupported(disk, DeviceAction.DELETE)
|
||||
|
||||
def test_disk_action_MAKE_BOOT(self):
|
||||
for bl in Bootloader:
|
||||
model, disk = make_model_and_disk(bl)
|
||||
if bl == Bootloader.NONE:
|
||||
self.assertActionNotSupported(disk, DeviceAction.MAKE_BOOT)
|
||||
else:
|
||||
self.assertActionPossible(disk, DeviceAction.MAKE_BOOT)
|
||||
def test_disk_action_MAKE_BOOT_NONE(self):
|
||||
model, disk = make_model_and_disk(Bootloader.NONE)
|
||||
self.assertActionNotSupported(disk, DeviceAction.MAKE_BOOT)
|
||||
|
||||
def test_disk_action_MAKE_BOOT_BIOS(self):
|
||||
model = make_model(Bootloader.BIOS)
|
||||
# Disks with msdos partition tables can always be the BIOS boot disk.
|
||||
dos_disk = make_disk(model, ptable='msdos', preserve=True)
|
||||
self.assertActionPossible(dos_disk, DeviceAction.MAKE_BOOT)
|
||||
# Even if they have existing partitions
|
||||
make_partition(
|
||||
model, dos_disk, size=dos_disk.free_for_partitions, preserve=True)
|
||||
self.assertActionPossible(dos_disk, DeviceAction.MAKE_BOOT)
|
||||
# (we never create dos partition tables so no need to test
|
||||
# preserve=False case).
|
||||
|
||||
# GPT disks with new partition tables can always be the BIOS boot disk
|
||||
gpt_disk = make_disk(model, ptable='gpt', preserve=False)
|
||||
self.assertActionPossible(gpt_disk, DeviceAction.MAKE_BOOT)
|
||||
# Even if they are filled with partitions (we resize partitions to fit)
|
||||
make_partition(model, gpt_disk, size=dos_disk.free_for_partitions)
|
||||
self.assertActionPossible(gpt_disk, DeviceAction.MAKE_BOOT)
|
||||
|
||||
# GPT disks with existing partition tables but no partitions can be the
|
||||
# BIOS boot disk (in general we ignore existing empty partition tables)
|
||||
gpt_disk2 = make_disk(model, ptable='gpt', preserve=True)
|
||||
self.assertActionPossible(gpt_disk2, DeviceAction.MAKE_BOOT)
|
||||
# If there is an existing *partition* though, it cannot be the boot
|
||||
# disk
|
||||
make_partition(model, gpt_disk2, preserve=True)
|
||||
self.assertActionNotPossible(gpt_disk2, DeviceAction.MAKE_BOOT)
|
||||
# Unless there is already a bios_grub partition we can reuse
|
||||
gpt_disk3 = make_disk(model, ptable='gpt', preserve=True)
|
||||
make_partition(
|
||||
model, gpt_disk3, flag="bios_grub", preserve=True)
|
||||
make_partition(
|
||||
model, gpt_disk3, preserve=True)
|
||||
self.assertActionPossible(gpt_disk3, DeviceAction.MAKE_BOOT)
|
||||
# Edge case city: the bios_grub partition has to be first
|
||||
gpt_disk4 = make_disk(model, ptable='gpt', preserve=True)
|
||||
make_partition(
|
||||
model, gpt_disk4, preserve=True)
|
||||
make_partition(
|
||||
model, gpt_disk4, flag="bios_grub", preserve=True)
|
||||
self.assertActionNotPossible(gpt_disk4, DeviceAction.MAKE_BOOT)
|
||||
|
||||
def _test_MAKE_BOOT_boot_partition(self, bl, flag):
|
||||
# The logic for when MAKE_BOOT is enabled for both UEFI and PREP
|
||||
# bootloaders turns out to be the same, modulo the special flag that
|
||||
# has to be present on a partition.
|
||||
model = make_model(bl)
|
||||
# A disk with a new partition table can always be the UEFI/PREP boot
|
||||
# disk.
|
||||
new_disk = make_disk(model, preserve=False)
|
||||
self.assertActionPossible(new_disk, DeviceAction.MAKE_BOOT)
|
||||
# Even if they are filled with partitions (we resize partitions to fit)
|
||||
make_partition(model, new_disk, size=new_disk.free_for_partitions)
|
||||
self.assertActionPossible(new_disk, DeviceAction.MAKE_BOOT)
|
||||
|
||||
# A disk with an existing but empty partitions can also be the
|
||||
# UEFI/PREP boot disk.
|
||||
old_disk = make_disk(model, preserve=True)
|
||||
self.assertActionPossible(old_disk, DeviceAction.MAKE_BOOT)
|
||||
# If there is an existing partition though, it cannot.
|
||||
make_partition(model, old_disk, preserve=True)
|
||||
self.assertActionNotPossible(old_disk, DeviceAction.MAKE_BOOT)
|
||||
# If there is an existing ESP/PReP partition though, fine!
|
||||
make_partition(model, old_disk, flag=flag, preserve=True)
|
||||
self.assertActionPossible(old_disk, DeviceAction.MAKE_BOOT)
|
||||
|
||||
def test_disk_action_MAKE_BOOT_UEFI(self):
|
||||
self._test_MAKE_BOOT_boot_partition(Bootloader.UEFI, "boot")
|
||||
|
||||
def test_disk_action_MAKE_BOOT_PREP(self):
|
||||
self._test_MAKE_BOOT_boot_partition(Bootloader.PREP, "prep")
|
||||
|
||||
def test_partition_action_INFO(self):
|
||||
model, part = make_model_and_partition()
|
||||
|
@ -339,6 +503,10 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
model.add_volgroup('vg1', {part})
|
||||
self.assertActionNotPossible(part, DeviceAction.EDIT)
|
||||
|
||||
def test_partition_action_REFORMAT(self):
|
||||
model, part = make_model_and_partition()
|
||||
self.assertActionNotSupported(part, DeviceAction.REFORMAT)
|
||||
|
||||
def test_partition_action_PARTITION(self):
|
||||
model, part = make_model_and_partition()
|
||||
self.assertActionNotSupported(part, DeviceAction.PARTITION)
|
||||
|
@ -378,6 +546,12 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
part = make_partition(model, flag=flag)
|
||||
self.assertActionNotPossible(part, DeviceAction.DELETE)
|
||||
|
||||
# You cannot delete a partition from a disk that has
|
||||
# pre-existing partitions (only reformat)
|
||||
disk2 = make_disk(model, preserve=True)
|
||||
disk2p1 = make_partition(model, disk2, preserve=True)
|
||||
self.assertActionNotPossible(disk2p1, DeviceAction.DELETE)
|
||||
|
||||
def test_partition_action_MAKE_BOOT(self):
|
||||
model, part = make_model_and_partition()
|
||||
self.assertActionNotSupported(part, DeviceAction.MAKE_BOOT)
|
||||
|
@ -396,6 +570,14 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
make_partition(model, raid2)
|
||||
self.assertActionNotPossible(raid2, DeviceAction.EDIT)
|
||||
|
||||
raid3 = make_raid(model)
|
||||
raid3.preserve = True
|
||||
self.assertActionNotPossible(raid3, DeviceAction.EDIT)
|
||||
|
||||
def test_raid_action_REFORMAT(self):
|
||||
model, raid = make_model_and_raid()
|
||||
self.assertActionNotSupported(raid, DeviceAction.REFORMAT)
|
||||
|
||||
def test_raid_action_PARTITION(self):
|
||||
model, raid = make_model_and_raid()
|
||||
self.assertActionPossible(raid, DeviceAction.PARTITION)
|
||||
|
@ -404,6 +586,14 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
make_partition(model, raid, size=raid.free_for_partitions)
|
||||
self.assertActionNotPossible(raid, DeviceAction.PARTITION)
|
||||
|
||||
# Can partition a raid with .preserve=True
|
||||
raid2 = make_raid(model)
|
||||
raid2.preserve = True
|
||||
self.assertActionPossible(raid2, DeviceAction.PARTITION)
|
||||
# But not if it has a partition.
|
||||
make_partition(model, raid2, preserve=True)
|
||||
self.assertActionNotPossible(raid2, DeviceAction.PARTITION)
|
||||
|
||||
def test_raid_action_CREATE_LV(self):
|
||||
model, raid = make_model_and_raid()
|
||||
self.assertActionNotSupported(raid, DeviceAction.CREATE_LV)
|
||||
|
@ -459,6 +649,14 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
model.add_logical_volume(vg, 'lv1', size=vg.free_for_partitions//2)
|
||||
self.assertActionNotPossible(vg, DeviceAction.EDIT)
|
||||
|
||||
vg2 = make_vg(model)
|
||||
vg2.preserve = True
|
||||
self.assertActionNotPossible(vg2, DeviceAction.EDIT)
|
||||
|
||||
def test_vg_action_REFORMAT(self):
|
||||
model, vg = make_model_and_vg()
|
||||
self.assertActionNotSupported(vg, DeviceAction.REFORMAT)
|
||||
|
||||
def test_vg_action_PARTITION(self):
|
||||
model, vg = make_model_and_vg()
|
||||
self.assertActionNotSupported(vg, DeviceAction.PARTITION)
|
||||
|
@ -470,6 +668,9 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
self.assertActionPossible(vg, DeviceAction.CREATE_LV)
|
||||
model.add_logical_volume(vg, 'lv2', size=vg.free_for_partitions)
|
||||
self.assertActionNotPossible(vg, DeviceAction.CREATE_LV)
|
||||
vg2 = make_vg(model)
|
||||
vg2.preserve = True
|
||||
self.assertActionNotPossible(vg2, DeviceAction.CREATE_LV)
|
||||
|
||||
def test_vg_action_FORMAT(self):
|
||||
model, vg = make_model_and_vg()
|
||||
|
@ -503,6 +704,10 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
model, lv = make_model_and_lv()
|
||||
self.assertActionPossible(lv, DeviceAction.EDIT)
|
||||
|
||||
def test_lv_action_REFORMAT(self):
|
||||
model, lv = make_model_and_lv()
|
||||
self.assertActionNotSupported(lv, DeviceAction.REFORMAT)
|
||||
|
||||
def test_lv_action_PARTITION(self):
|
||||
model, lv = make_model_and_lv()
|
||||
self.assertActionNotSupported(lv, DeviceAction.PARTITION)
|
||||
|
@ -527,6 +732,10 @@ class TestFilesystemModel(unittest.TestCase):
|
|||
model.add_mount(fs, '/')
|
||||
self.assertActionPossible(lv, DeviceAction.DELETE)
|
||||
|
||||
lv2 = make_lv(model)
|
||||
lv2.preserve = lv2.volgroup.preserve = True
|
||||
self.assertActionNotPossible(lv2, DeviceAction.DELETE)
|
||||
|
||||
def test_lv_action_MAKE_BOOT(self):
|
||||
model, lv = make_model_and_lv()
|
||||
self.assertActionNotSupported(lv, DeviceAction.MAKE_BOOT)
|
||||
|
|
|
@ -58,9 +58,9 @@ class MountSelector(WidgetWrap):
|
|||
opts.append((mnt, False))
|
||||
if first_opt is None:
|
||||
first_opt = len(opts)
|
||||
opts.append((_('other'), True, OTHER))
|
||||
opts.append((_('Other'), True, OTHER))
|
||||
opts.append(('---', False)),
|
||||
opts.append((_('leave unmounted'), True, LEAVE_UNMOUNTED))
|
||||
opts.append((_('Leave unmounted'), True, LEAVE_UNMOUNTED))
|
||||
self._selector = Selector(opts, first_opt)
|
||||
connect_signal(self._selector, 'select', self._select_mount)
|
||||
self._other = _MountEditor()
|
||||
|
|
|
@ -96,3 +96,66 @@ class ConfirmDeleteStretchy(Stretchy):
|
|||
|
||||
def cancel(self, sender=None):
|
||||
self.parent.remove_overlay()
|
||||
|
||||
|
||||
class ConfirmReformatStretchy(Stretchy):
|
||||
|
||||
def __init__(self, parent, obj):
|
||||
self.parent = parent
|
||||
self.obj = obj
|
||||
|
||||
fs = obj.fs()
|
||||
if fs is not None:
|
||||
title = _("Remove filesystem from {}").format(obj.desc())
|
||||
lines = [
|
||||
_(
|
||||
"Do you really want to remove the existing filesystem "
|
||||
"from {}?"
|
||||
).format(obj.label),
|
||||
"",
|
||||
]
|
||||
m = fs.mount()
|
||||
if m is not None:
|
||||
lines.append(_(
|
||||
"It is formatted as {fstype} and mounted at "
|
||||
"{path}").format(
|
||||
fstype=fs.fstype,
|
||||
path=m.path))
|
||||
else:
|
||||
lines.append(_(
|
||||
"It is formatted as {fstype} and not mounted.").format(
|
||||
fstype=fs.fstype))
|
||||
else:
|
||||
if obj.type == "lvm_volgroup":
|
||||
things = _("logical volumes")
|
||||
else:
|
||||
things = _("partitions")
|
||||
title = _("Remove all {things} from {obj}").format(
|
||||
things=things, obj=obj.desc())
|
||||
lines = [
|
||||
_(
|
||||
"Do you really want to remove all {things} from "
|
||||
"{obj}?").format(
|
||||
things=things, obj=obj.label),
|
||||
"",
|
||||
]
|
||||
# XXX summarize partitions here?
|
||||
|
||||
delete_btn = danger_btn(label=_("Reformat"), on_press=self.confirm)
|
||||
widgets = [
|
||||
Text("\n".join(lines)),
|
||||
Text(""),
|
||||
button_pile([
|
||||
delete_btn,
|
||||
other_btn(label=_("Cancel"), on_press=self.cancel),
|
||||
]),
|
||||
]
|
||||
super().__init__(title, widgets, 0, 2)
|
||||
|
||||
def confirm(self, sender=None):
|
||||
self.parent.controller.reformat(self.obj)
|
||||
self.parent.refresh_model_inputs()
|
||||
self.parent.remove_overlay()
|
||||
|
||||
def cancel(self, sender=None):
|
||||
self.parent.remove_overlay()
|
||||
|
|
|
@ -67,7 +67,7 @@ from subiquity.models.filesystem import (
|
|||
humanize_size,
|
||||
)
|
||||
|
||||
from .delete import ConfirmDeleteStretchy
|
||||
from .delete import ConfirmDeleteStretchy, ConfirmReformatStretchy
|
||||
from .disk_info import DiskInfoStretchy
|
||||
from .helpers import summarize_device
|
||||
from .lvm import VolGroupStretchy
|
||||
|
@ -289,6 +289,7 @@ class DeviceList(WidgetWrap):
|
|||
super().__init__(self.table)
|
||||
|
||||
_disk_INFO = _stretchy_shower(DiskInfoStretchy)
|
||||
_disk_REFORMAT = _stretchy_shower(ConfirmReformatStretchy)
|
||||
_disk_PARTITION = _stretchy_shower(PartitionStretchy)
|
||||
_disk_FORMAT = _stretchy_shower(FormatEntireStretchy)
|
||||
|
||||
|
@ -353,7 +354,8 @@ class DeviceList(WidgetWrap):
|
|||
else:
|
||||
meth_name = '_{}_{}'.format(device.type, action.name)
|
||||
meth = getattr(self, meth_name)
|
||||
if not whynot and action == DeviceAction.DELETE:
|
||||
if not whynot and action in [DeviceAction.DELETE,
|
||||
DeviceAction.REFORMAT]:
|
||||
label = Color.danger_button(ActionMenuOpenButton(label))
|
||||
device_actions.append(Action(
|
||||
label=label,
|
||||
|
|
|
@ -126,7 +126,7 @@ class GuidedDiskSelectionView(BaseView):
|
|||
if disk.size >= dehumanize_size("6G"):
|
||||
arrow = ClickableIcon(arrow)
|
||||
connect_signal(
|
||||
arrow, 'click', self.choose_disk, disk.path)
|
||||
arrow, 'click', self.choose_disk, disk)
|
||||
wrap = _wrap_button_row
|
||||
else:
|
||||
start, arrow, end = '', '', ''
|
||||
|
@ -152,9 +152,8 @@ class GuidedDiskSelectionView(BaseView):
|
|||
def cancel(self, btn=None):
|
||||
self.controller.default()
|
||||
|
||||
def choose_disk(self, btn, disk_path):
|
||||
self.model.reset()
|
||||
disk = self.model.disk_by_path(disk_path)
|
||||
def choose_disk(self, btn, disk):
|
||||
self.controller.reformat(disk)
|
||||
if self.method == "direct":
|
||||
result = {
|
||||
"size": disk.free_for_partitions,
|
||||
|
|
|
@ -25,6 +25,7 @@ import re
|
|||
from urwid import connect_signal, Text
|
||||
|
||||
from subiquitycore.ui.form import (
|
||||
BooleanField,
|
||||
Form,
|
||||
FormField,
|
||||
simple_field,
|
||||
|
@ -57,14 +58,23 @@ class FSTypeField(FormField):
|
|||
# This will need to do something different for editing an
|
||||
# existing partition that is already formatted.
|
||||
options = [
|
||||
('ext4', True),
|
||||
('xfs', True),
|
||||
('btrfs', True),
|
||||
('---', False),
|
||||
('swap', True),
|
||||
('---', False),
|
||||
('leave unformatted', True, None),
|
||||
('ext4', True),
|
||||
('xfs', True),
|
||||
('btrfs', True),
|
||||
('---', False),
|
||||
('swap', True),
|
||||
]
|
||||
if form.existing_fs_type is None:
|
||||
options = options + [
|
||||
('---', False),
|
||||
(_('Leave unformatted'), True, None),
|
||||
]
|
||||
else:
|
||||
label = _('Leave formatted as {}').format(form.existing_fs_type)
|
||||
options = [
|
||||
(label, True, None),
|
||||
('---', False),
|
||||
] + options
|
||||
sel = Selector(opts=options)
|
||||
sel.value = None
|
||||
return sel
|
||||
|
@ -128,8 +138,14 @@ LVNameField = simple_field(LVNameEditor)
|
|||
|
||||
class PartitionForm(Form):
|
||||
|
||||
def __init__(self, model, max_size, initial, lvm_names):
|
||||
def __init__(self, model, max_size, initial, lvm_names, device):
|
||||
self.model = model
|
||||
self.device = device
|
||||
self.existing_fs_type = None
|
||||
if device:
|
||||
existing_fs = device.original_fs()
|
||||
if existing_fs:
|
||||
self.existing_fs_type = existing_fs.fstype
|
||||
initial_path = initial.get('mount')
|
||||
self.mountpoints = {
|
||||
m.path: m.device.volume for m in self.model.all_mounts()
|
||||
|
@ -143,15 +159,31 @@ class PartitionForm(Form):
|
|||
if max_size is None:
|
||||
self.remove_field('size')
|
||||
connect_signal(self.fstype.widget, 'select', self.select_fstype)
|
||||
self.form_pile = None
|
||||
self.select_fstype(None, self.fstype.widget.value)
|
||||
|
||||
def select_fstype(self, sender, fstype):
|
||||
self.mount.enabled = self.model.is_mounted_filesystem(fstype)
|
||||
show_use = False
|
||||
if fstype is None:
|
||||
if self.existing_fs_type == "swap":
|
||||
show_use = True
|
||||
fstype = self.existing_fs_type
|
||||
if self.form_pile is not None:
|
||||
for i, (w, o) in enumerate(self.form_pile.contents):
|
||||
if w is self.mount._table and show_use:
|
||||
self.form_pile.contents[i] = (self.use_swap._table, o)
|
||||
elif w is self.use_swap._table and not show_use:
|
||||
self.form_pile.contents[i] = (self.mount._table, o)
|
||||
if getattr(self.device, 'flag', None) != "boot":
|
||||
self.mount.enabled = self.model.is_mounted_filesystem(fstype)
|
||||
|
||||
name = LVNameField(_("Name: "))
|
||||
size = SizeField()
|
||||
fstype = FSTypeField(_("Format:"))
|
||||
mount = MountField(_("Mount:"))
|
||||
use_swap = BooleanField(
|
||||
_("Use as swap"),
|
||||
help=_("Use this swap partition in the installed system."))
|
||||
|
||||
def clean_size(self, val):
|
||||
if not val:
|
||||
|
@ -202,6 +234,16 @@ class PartitionForm(Form):
|
|||
return _("{} is already mounted at {}.").format(
|
||||
dev.label.title(), mount)
|
||||
|
||||
def as_rows(self):
|
||||
r = super().as_rows()
|
||||
if self.existing_fs_type == "swap":
|
||||
exclude = self.mount._table
|
||||
else:
|
||||
exclude = self.use_swap._table
|
||||
i = r.index(exclude)
|
||||
del r[i-1:i+1]
|
||||
return r
|
||||
|
||||
|
||||
bios_grub_partition_description = _(
|
||||
"Required bootloader partition\n"
|
||||
|
@ -217,8 +259,14 @@ boot_partition_description = _(
|
|||
"Required bootloader partition\n"
|
||||
"\n"
|
||||
'This is the ESP / "EFI system partition" required by UEFI. Grub will be '
|
||||
'installed onto this partition, which must be formatted as fat32. The '
|
||||
'only aspect of this partition that can be edited is the size.')
|
||||
'installed onto this partition, which must be formatted as fat32.')
|
||||
|
||||
boot_partition_description_size = _(
|
||||
' The only aspect of this partition that can be edited is the size.')
|
||||
|
||||
boot_partition_description_reformat = _(
|
||||
' You can choose whether to use the existing filesystem on this '
|
||||
'partition or reformat it.')
|
||||
|
||||
prep_partition_description = _(
|
||||
"Required bootloader partition\n"
|
||||
|
@ -253,7 +301,11 @@ class PartitionStretchy(Stretchy):
|
|||
max_size += self.partition.size
|
||||
fs = self.partition.fs()
|
||||
if fs is not None:
|
||||
if partition.flag != "boot":
|
||||
if fs.preserve:
|
||||
initial['fstype'] = None
|
||||
if fs.fstype == "swap":
|
||||
initial['use_swap'] = fs.mount() is not None
|
||||
elif partition.flag != "boot":
|
||||
initial['fstype'] = fs.fstype
|
||||
if self.model.is_mounted_filesystem(fs.fstype):
|
||||
mount = fs.mount()
|
||||
|
@ -275,7 +327,8 @@ class PartitionStretchy(Stretchy):
|
|||
x += 1
|
||||
initial['name'] = name
|
||||
|
||||
self.form = PartitionForm(self.model, max_size, initial, lvm_names)
|
||||
self.form = PartitionForm(
|
||||
self.model, max_size, initial, lvm_names, partition)
|
||||
|
||||
if not isinstance(disk, LVM_VolGroup):
|
||||
self.form.remove_field('name')
|
||||
|
@ -288,15 +341,39 @@ class PartitionStretchy(Stretchy):
|
|||
|
||||
if partition is not None:
|
||||
if partition.flag == "boot":
|
||||
opts = [Option(("fat32", True))]
|
||||
self.form.fstype.widget.options = opts
|
||||
self.form.fstype.widget.index = 0
|
||||
self.form.mount.enabled = False
|
||||
self.form.fstype.enabled = False
|
||||
if partition.original_fs():
|
||||
opts = [
|
||||
Option((
|
||||
_("Use existing fat32 filesystem"),
|
||||
True,
|
||||
None
|
||||
)),
|
||||
Option(("---", False)),
|
||||
Option((
|
||||
_("Reformat as fresh fat32 filesystem"),
|
||||
True,
|
||||
"fat32"
|
||||
)),
|
||||
]
|
||||
self.form.fstype.widget.options = opts
|
||||
if partition.fs().preserve:
|
||||
self.form.fstype.widget.index = 0
|
||||
else:
|
||||
self.form.fstype.widget.index = 2
|
||||
self.form.mount.enabled = False
|
||||
else:
|
||||
opts = [Option(("fat32", True))]
|
||||
self.form.fstype.widget.options = opts
|
||||
self.form.fstype.widget.index = 0
|
||||
self.form.mount.enabled = False
|
||||
self.form.fstype.enabled = False
|
||||
elif partition.flag in ["bios_grub", "prep"]:
|
||||
self.form.mount.enabled = False
|
||||
self.form.fstype.enabled = False
|
||||
self.form.size.enabled = False
|
||||
if partition.preserve:
|
||||
self.form.name.enabled = False
|
||||
self.form.size.enabled = False
|
||||
|
||||
connect_signal(self.form, 'submit', self.done)
|
||||
connect_signal(self.form, 'cancel', self.cancel)
|
||||
|
@ -305,8 +382,13 @@ class PartitionStretchy(Stretchy):
|
|||
focus_index = 0
|
||||
if partition is not None:
|
||||
if self.partition.flag == "boot":
|
||||
desc = boot_partition_description
|
||||
if self.partition.preserve:
|
||||
desc += boot_partition_description_reformat
|
||||
else:
|
||||
desc += boot_partition_description_size
|
||||
rows.extend([
|
||||
Text(_(boot_partition_description)),
|
||||
Text(_(desc)),
|
||||
Text(""),
|
||||
])
|
||||
elif self.partition.flag == "bios_grub":
|
||||
|
@ -322,8 +404,9 @@ class PartitionStretchy(Stretchy):
|
|||
])
|
||||
focus_index = 2
|
||||
rows.extend(self.form.as_rows())
|
||||
self.form.form_pile = Pile(rows)
|
||||
widgets = [
|
||||
Pile(rows),
|
||||
self.form.form_pile,
|
||||
Text(""),
|
||||
self.form.buttons,
|
||||
]
|
||||
|
@ -373,6 +456,12 @@ class FormatEntireStretchy(Stretchy):
|
|||
initial = {}
|
||||
fs = device.fs()
|
||||
if fs is not None:
|
||||
if fs.preserve:
|
||||
initial['fstype'] = None
|
||||
if fs.fstype == "swap":
|
||||
initial['use_swap'] = fs.mount() is not None
|
||||
else:
|
||||
initial['fstype'] = fs.fstype
|
||||
initial['fstype'] = fs.fstype
|
||||
if self.model.is_mounted_filesystem(fs.fstype):
|
||||
mount = fs.mount()
|
||||
|
@ -380,7 +469,7 @@ class FormatEntireStretchy(Stretchy):
|
|||
initial['mount'] = mount.path
|
||||
elif not isinstance(device, Disk):
|
||||
initial['fstype'] = 'ext4'
|
||||
self.form = PartitionForm(self.model, 0, initial, None)
|
||||
self.form = PartitionForm(self.model, 0, initial, None, device)
|
||||
self.form.remove_field('size')
|
||||
self.form.remove_field('name')
|
||||
|
||||
|
@ -395,8 +484,9 @@ class FormatEntireStretchy(Stretchy):
|
|||
Text(""),
|
||||
]
|
||||
rows.extend(self.form.as_rows())
|
||||
self.form.form_pile = Pile(rows)
|
||||
widgets = [
|
||||
Pile(rows),
|
||||
self.form.form_pile,
|
||||
Text(""),
|
||||
self.form.buttons,
|
||||
]
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import unittest
|
||||
from unittest import mock
|
||||
from collections import namedtuple
|
||||
|
||||
import urwid
|
||||
|
||||
|
@ -12,14 +11,12 @@ from subiquity.models.filesystem import (
|
|||
Disk,
|
||||
FilesystemModel,
|
||||
)
|
||||
from subiquity.models.tests.test_filesystem import (
|
||||
FakeStorageInfo,
|
||||
)
|
||||
from subiquity.ui.views.filesystem.filesystem import FilesystemView
|
||||
|
||||
|
||||
FakeStorageInfo = namedtuple(
|
||||
'FakeStorageInfo', ['name', 'size', 'free', 'serial', 'model'])
|
||||
FakeStorageInfo.__new__.__defaults__ = (None,) * len(FakeStorageInfo._fields)
|
||||
|
||||
|
||||
class FilesystemViewTests(unittest.TestCase):
|
||||
|
||||
def make_view(self, model, devices=[]):
|
||||
|
@ -35,9 +32,9 @@ class FilesystemViewTests(unittest.TestCase):
|
|||
|
||||
def test_one_disk(self):
|
||||
model = mock.create_autospec(spec=FilesystemModel)
|
||||
disk = Disk.from_info(model, FakeStorageInfo(
|
||||
name='disk-name', size=100*(2**20), free=50*(2**20),
|
||||
serial="DISK-SERIAL"))
|
||||
disk = Disk(
|
||||
m=model, serial="DISK-SERIAL",
|
||||
info=FakeStorageInfo(size=100*(2**20), free=50*(2**20)))
|
||||
view = self.make_view(model, [disk])
|
||||
w = view_helpers.find_with_pred(
|
||||
view,
|
||||
|
|
|
@ -50,6 +50,7 @@ class PartitionViewTests(unittest.TestCase):
|
|||
view_helpers.click(stretchy.form.done_btn.base_widget)
|
||||
valid_data['mount'] = '/'
|
||||
valid_data['size'] = dehumanize_size(valid_data['size'])
|
||||
valid_data['use_swap'] = False
|
||||
view.controller.partition_disk_handler.assert_called_once_with(
|
||||
stretchy.disk, None, valid_data)
|
||||
|
||||
|
@ -69,6 +70,28 @@ class PartitionViewTests(unittest.TestCase):
|
|||
'size': dehumanize_size(form_data['size']),
|
||||
'fstype': 'xfs',
|
||||
'mount': None,
|
||||
'use_swap': False,
|
||||
}
|
||||
view.controller.partition_disk_handler.assert_called_once_with(
|
||||
stretchy.disk, stretchy.partition, expected_data)
|
||||
|
||||
def test_edit_existing_partition(self):
|
||||
form_data = {
|
||||
'fstype': "xfs",
|
||||
}
|
||||
model, disk = make_model_and_disk()
|
||||
partition = model.add_partition(disk, 512*(2**20))
|
||||
partition.preserve = True
|
||||
model.add_filesystem(partition, "ext4")
|
||||
view, stretchy = make_view(model, disk, partition)
|
||||
self.assertFalse(stretchy.form.size.enabled)
|
||||
self.assertTrue(stretchy.form.done_btn.enabled)
|
||||
view_helpers.enter_data(stretchy.form, form_data)
|
||||
view_helpers.click(stretchy.form.done_btn.base_widget)
|
||||
expected_data = {
|
||||
'fstype': 'xfs',
|
||||
'mount': None,
|
||||
'use_swap': False,
|
||||
}
|
||||
view.controller.partition_disk_handler.assert_called_once_with(
|
||||
stretchy.disk, stretchy.partition, expected_data)
|
||||
|
@ -88,6 +111,7 @@ class PartitionViewTests(unittest.TestCase):
|
|||
'size': dehumanize_size(form_data['size']),
|
||||
'fstype': "fat32",
|
||||
'mount': '/boot/efi',
|
||||
'use_swap': False,
|
||||
}
|
||||
view.controller.partition_disk_handler.assert_called_once_with(
|
||||
stretchy.disk, stretchy.partition, expected_data)
|
||||
|
|
|
@ -82,4 +82,6 @@ def get_focus_path(w):
|
|||
|
||||
def enter_data(form, data):
|
||||
for k, v in data.items():
|
||||
getattr(form, k).value = v
|
||||
bf = getattr(form, k)
|
||||
assert bf.enabled, "%s is not enabled" % (k,)
|
||||
bf.value = v
|
||||
|
|
Loading…
Reference in New Issue