From 02b3719df9a499e49a66f8bf30d85cbd3cca4e81 Mon Sep 17 00:00:00 2001 From: Artem Chernyshev Date: Tue, 16 Feb 2021 22:50:18 +0300 Subject: [PATCH] feat: skip filesystem for state and ephemeral partitions in the installer Filesystem creation step is moved on the later stage: when Talos mounts the partition for the first time. Now it checks if the partition doesn't have any filesystem and formats it right before mounting. Additionally refactored mount options a bit: - replaced separate options with a set of binary flags. - implemented pre-mount and post-unmount hooks. And fixed typos in couple of places and increased timeout for `apid ready`. Signed-off-by: Artem Chernyshev --- cmd/installer/pkg/install/install.go | 47 ++++++- cmd/installer/pkg/install/manifest.go | 27 ++-- cmd/installer/pkg/install/manifest_test.go | 35 ++--- cmd/installer/pkg/install/target.go | 117 +++++----------- go.mod | 2 +- go.sum | 4 +- .../server/v1alpha1/v1alpha1_server.go | 4 +- .../runtime/v1alpha1/v1alpha1_sequencer.go | 2 +- .../v1alpha1/v1alpha1_sequencer_tasks.go | 23 ++-- internal/integration/provision/provision.go | 2 +- internal/integration/provision/upgrade.go | 2 +- internal/pkg/mount/mount.go | 36 +++-- internal/pkg/mount/options.go | 86 +++++++----- internal/pkg/mount/overlay.go | 2 +- internal/pkg/mount/squashfs.go | 2 +- internal/pkg/mount/system.go | 76 +++++++---- .../pkg/partition}/constants.go | 12 +- internal/pkg/partition/format.go | 129 ++++++++++++++++++ internal/pkg/partition/partition.go | 6 + pkg/cluster/check/default.go | 2 +- 20 files changed, 393 insertions(+), 223 deletions(-) rename {cmd/installer/pkg/install => internal/pkg/partition}/constants.go (70%) create mode 100644 internal/pkg/partition/format.go create mode 100644 internal/pkg/partition/partition.go diff --git a/cmd/installer/pkg/install/install.go b/cmd/installer/pkg/install/install.go index 36d23e3de2..3fc95ac75d 100644 --- a/cmd/installer/pkg/install/install.go +++ b/cmd/installer/pkg/install/install.go @@ -182,10 +182,51 @@ func (i *Installer) Install(seq runtime.Sequence) (err error) { } // Mount the partitions. + mountpoints := mount.NewMountPoints() + + for _, label := range []string{constants.BootPartitionLabel, constants.EFIPartitionLabel} { + err = func() error { + var device string + // searching targets for the device to be used + OuterLoop: + for dev, targets := range i.manifest.Targets { + for _, target := range targets { + if target.Label == label { + device = dev + + break OuterLoop + } + } + } - mountpoints, err := i.manifest.SystemMountpoints() - if err != nil { - return err + if device == "" { + return fmt.Errorf("failed to detect %s target device", label) + } + + var bd *blockdevice.BlockDevice + + bd, err = blockdevice.Open(device) + if err != nil { + return err + } + + defer bd.Close() //nolint:errcheck + + var mountpoint *mount.Point + + mountpoint, err = mount.SystemMountPointForLabel(bd, label) + if err != nil { + return err + } + + mountpoints.Set(label, mountpoint) + + return nil + }() + + if err != nil { + return err + } } if err = mount.Mount(mountpoints); err != nil { diff --git a/cmd/installer/pkg/install/manifest.go b/cmd/installer/pkg/install/manifest.go index 67c8535985..a361e9ad6b 100644 --- a/cmd/installer/pkg/install/manifest.go +++ b/cmd/installer/pkg/install/manifest.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/dustin/go-humanize" "github.com/talos-systems/go-blockdevice/blockdevice" "github.com/talos-systems/go-blockdevice/blockdevice/partition/gpt" "github.com/talos-systems/go-blockdevice/blockdevice/util" @@ -22,6 +23,7 @@ import ( "github.com/talos-systems/talos/internal/app/machined/pkg/runtime" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/board" "github.com/talos-systems/talos/internal/pkg/mount" + "github.com/talos-systems/talos/internal/pkg/partition" "github.com/talos-systems/talos/pkg/machinery/constants" ) @@ -131,16 +133,12 @@ func NewManifest(label string, sequence runtime.Sequence, bootPartitionFound boo stateTarget := StateTarget(opts.Disk, &Target{ PreserveContents: bootPartitionFound, - ExtraPreserveSources: []PreserveSource{ - { - Label: constants.LegacyBootPartitionLabel, - FileSystemType: FilesystemTypeVFAT, - FnmatchFilters: []string{"config.yaml"}, - }, + FormatOptions: &partition.FormatOptions{ + FileSystemType: partition.FilesystemTypeNone, }, }) - ephemeralTarget := EphemeralTarget(opts.Disk, nil) + ephemeralTarget := EphemeralTarget(opts.Disk, NoFilesystem) if opts.Force { ephemeralTarget.Force = true @@ -417,7 +415,7 @@ func (m *Manifest) preserveContents(device Device, targets []*Target) (err error var ( sourcePart *gpt.Partition - fileSystemType FileSystemType + fileSystemType partition.FileSystemType fnmatchFilters []string ) @@ -466,11 +464,11 @@ func (m *Manifest) restoreContents(targets []*Target) error { } // SystemMountpoints returns list of system mountpoints for the manifest. -func (m *Manifest) SystemMountpoints() (*mount.Points, error) { +func (m *Manifest) SystemMountpoints(opts ...mount.Option) (*mount.Points, error) { mountpoints := mount.NewMountPoints() for dev := range m.Targets { - mp, err := mount.SystemMountPointsForDevice(dev) + mp, err := mount.SystemMountPointsForDevice(dev, opts...) if err != nil { return nil, err } @@ -511,12 +509,7 @@ func (m *Manifest) zeroDevice(device Device) (err error) { // nolint: dupl, gocyclo func (t *Target) Partition(pt *gpt.GPT, pos int, bd *blockdevice.BlockDevice) (err error) { if t.Skip { - var part *gpt.Partition - - part, err = t.Locate(pt) - if err != nil { - return err - } + part := pt.Partitions().FindByName(t.Label) if part != nil { log.Printf("skipped %s (%s) size %d blocks", t.PartitionName, t.Label, part.Length()) @@ -525,7 +518,7 @@ func (t *Target) Partition(pt *gpt.GPT, pos int, bd *blockdevice.BlockDevice) (e return nil } - log.Printf("partitioning %s - %s\n", t.Device, t.Label) + log.Printf("partitioning %s - %s %q\n", t.Device, t.Label, humanize.Bytes(t.Size)) opts := []gpt.PartitionOption{ gpt.WithPartitionType(t.PartitionType), diff --git a/cmd/installer/pkg/install/manifest_test.go b/cmd/installer/pkg/install/manifest_test.go index 13098eaf98..50bf4adef7 100644 --- a/cmd/installer/pkg/install/manifest_test.go +++ b/cmd/installer/pkg/install/manifest_test.go @@ -22,6 +22,7 @@ import ( "github.com/talos-systems/talos/cmd/installer/pkg/install" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime" "github.com/talos-systems/talos/internal/pkg/mount" + "github.com/talos-systems/talos/internal/pkg/partition" "github.com/talos-systems/talos/pkg/machinery/constants" "github.com/talos-systems/talos/pkg/makefs" ) @@ -45,7 +46,7 @@ const ( ) const ( - legacyBootSize = 512 * install.MiB + legacyBootSize = 512 * partition.MiB legacyEphemeralSize = diskSize - legacyBootSize - gptReserved*lbaSize ) @@ -112,47 +113,47 @@ func (suite *manifestSuite) verifyBlockdevice(manifest *install.Manifest, curren suite.Assert().Len(table.Partitions().Items(), 6) part := table.Partitions().Items()[0] - suite.Assert().Equal(install.EFISystemPartition, strings.ToUpper(part.Type.String())) + suite.Assert().Equal(partition.EFISystemPartition, strings.ToUpper(part.Type.String())) suite.Assert().Equal(constants.EFIPartitionLabel, part.Name) suite.Assert().EqualValues(0, part.Attributes) - suite.Assert().EqualValues(install.EFISize/lbaSize, part.Length()) + suite.Assert().EqualValues(partition.EFISize/lbaSize, part.Length()) part = table.Partitions().Items()[1] - suite.Assert().Equal(install.BIOSBootPartition, strings.ToUpper(part.Type.String())) + suite.Assert().Equal(partition.BIOSBootPartition, strings.ToUpper(part.Type.String())) suite.Assert().Equal(constants.BIOSGrubPartitionLabel, part.Name) suite.Assert().EqualValues(4, part.Attributes) - suite.Assert().EqualValues(install.BIOSGrubSize/lbaSize, part.Length()) + suite.Assert().EqualValues(partition.BIOSGrubSize/lbaSize, part.Length()) part = table.Partitions().Items()[2] - suite.Assert().Equal(install.LinuxFilesystemData, strings.ToUpper(part.Type.String())) + suite.Assert().Equal(partition.LinuxFilesystemData, strings.ToUpper(part.Type.String())) suite.Assert().Equal(constants.BootPartitionLabel, part.Name) suite.Assert().EqualValues(0, part.Attributes) - suite.Assert().EqualValues(install.BootSize/lbaSize, part.Length()) + suite.Assert().EqualValues(partition.BootSize/lbaSize, part.Length()) part = table.Partitions().Items()[3] - suite.Assert().Equal(install.LinuxFilesystemData, strings.ToUpper(part.Type.String())) + suite.Assert().Equal(partition.LinuxFilesystemData, strings.ToUpper(part.Type.String())) suite.Assert().Equal(constants.MetaPartitionLabel, part.Name) suite.Assert().EqualValues(0, part.Attributes) - suite.Assert().EqualValues(install.MetaSize/lbaSize, part.Length()) + suite.Assert().EqualValues(partition.MetaSize/lbaSize, part.Length()) part = table.Partitions().Items()[4] - suite.Assert().Equal(install.LinuxFilesystemData, strings.ToUpper(part.Type.String())) + suite.Assert().Equal(partition.LinuxFilesystemData, strings.ToUpper(part.Type.String())) suite.Assert().Equal(constants.StatePartitionLabel, part.Name) suite.Assert().EqualValues(0, part.Attributes) if !upgradeFromLegacy { - suite.Assert().EqualValues(install.StateSize/lbaSize, part.Length()) + suite.Assert().EqualValues(partition.StateSize/lbaSize, part.Length()) } else { - suite.Assert().EqualValues((diskSize-legacyEphemeralSize-install.EFISize-install.BIOSGrubSize-install.BootSize-install.MetaSize)/lbaSize-gptReserved, part.Length()) + suite.Assert().EqualValues((diskSize-legacyEphemeralSize-partition.EFISize-partition.BIOSGrubSize-partition.BootSize-partition.MetaSize)/lbaSize-gptReserved, part.Length()) } part = table.Partitions().Items()[5] - suite.Assert().Equal(install.LinuxFilesystemData, strings.ToUpper(part.Type.String())) + suite.Assert().Equal(partition.LinuxFilesystemData, strings.ToUpper(part.Type.String())) suite.Assert().Equal(constants.EphemeralPartitionLabel, part.Name) suite.Assert().EqualValues(0, part.Attributes) if !upgradeFromLegacy { - suite.Assert().EqualValues((diskSize-install.EFISize-install.BIOSGrubSize-install.BootSize-install.MetaSize-install.StateSize)/lbaSize-gptReserved, part.Length()) + suite.Assert().EqualValues((diskSize-partition.EFISize-partition.BIOSGrubSize-partition.BootSize-partition.MetaSize-partition.StateSize)/lbaSize-gptReserved, part.Length()) } else { suite.Assert().EqualValues(legacyEphemeralSize/lbaSize, part.Length()) } @@ -399,7 +400,7 @@ func (suite *manifestSuite) createTalosLegacyLayout() { table, err := gpt.New(bd.Device()) suite.Require().NoError(err) - partBoot, err := table.Add(512*install.MiB, + partBoot, err := table.Add(512*partition.MiB, gpt.WithLegacyBIOSBootableAttribute(true), gpt.WithPartitionName(constants.LegacyBootPartitionLabel), gpt.WithPartitionType("28732AC1-1FF8-D211-BA4B-00A0C93EC93B"), @@ -437,8 +438,8 @@ func (suite *manifestSuite) createTalosLegacyLayout() { }() mountpoints := mount.NewMountPoints() - mountpoints.Set(constants.LegacyBootPartitionLabel, mount.NewMountPoint(partBootPath, filepath.Join(tempDir, "boot"), install.FilesystemTypeVFAT, 0, "")) - mountpoints.Set(constants.EphemeralPartitionLabel, mount.NewMountPoint(partEphemeralPath, filepath.Join(tempDir, "var"), install.FilesystemTypeXFS, 0, "")) + mountpoints.Set(constants.LegacyBootPartitionLabel, mount.NewMountPoint(partBootPath, filepath.Join(tempDir, "boot"), partition.FilesystemTypeVFAT, 0, "")) + mountpoints.Set(constants.EphemeralPartitionLabel, mount.NewMountPoint(partEphemeralPath, filepath.Join(tempDir, "var"), partition.FilesystemTypeXFS, 0, "")) err = mount.Mount(mountpoints) suite.Require().NoError(err) diff --git a/cmd/installer/pkg/install/target.go b/cmd/installer/pkg/install/target.go index 73edf6a131..9a2b248e21 100644 --- a/cmd/installer/pkg/install/target.go +++ b/cmd/installer/pkg/install/target.go @@ -19,24 +19,20 @@ import ( "golang.org/x/sys/unix" "github.com/talos-systems/talos/internal/pkg/mount" + "github.com/talos-systems/talos/internal/pkg/partition" "github.com/talos-systems/talos/pkg/archiver" "github.com/talos-systems/talos/pkg/machinery/constants" - "github.com/talos-systems/talos/pkg/makefs" ) // Target represents an installation partition. // //nolint: golint, maligned type Target struct { + *partition.FormatOptions Device string - Label string - PartitionType PartitionType - FileSystemType FileSystemType LegacyBIOSBootable bool - Size uint64 - Force bool Assets []*Asset // Preserve contents of the partition with the same label (if it exists). @@ -67,18 +63,21 @@ type Asset struct { type PreserveSource struct { Label string FnmatchFilters []string - FileSystemType FileSystemType + FileSystemType partition.FileSystemType +} + +// NoFilesystem preset to override default filesystem type to none. +var NoFilesystem = &Target{ + FormatOptions: &partition.FormatOptions{ + FileSystemType: partition.FilesystemTypeNone, + }, } // EFITarget builds the default EFI target. func EFITarget(device string, extra *Target) *Target { target := &Target{ - Device: device, - Label: constants.EFIPartitionLabel, - PartitionType: EFISystemPartition, - FileSystemType: FilesystemTypeVFAT, - Size: EFISize, - Force: true, + FormatOptions: partition.NewFormatOptions(constants.EFIPartitionLabel), + Device: device, } return target.enhance(extra) @@ -87,13 +86,9 @@ func EFITarget(device string, extra *Target) *Target { // BIOSTarget builds the default BIOS target. func BIOSTarget(device string, extra *Target) *Target { target := &Target{ + FormatOptions: partition.NewFormatOptions(constants.BIOSGrubPartitionLabel), Device: device, - Label: constants.BIOSGrubPartitionLabel, - PartitionType: BIOSBootPartition, - FileSystemType: FilesystemTypeNone, LegacyBIOSBootable: true, - Size: BIOSGrubSize, - Force: true, } return target.enhance(extra) @@ -102,12 +97,8 @@ func BIOSTarget(device string, extra *Target) *Target { // BootTarget builds the default boot target. func BootTarget(device string, extra *Target) *Target { target := &Target{ - Device: device, - Label: constants.BootPartitionLabel, - PartitionType: LinuxFilesystemData, - FileSystemType: FilesystemTypeXFS, - Size: BootSize, - Force: true, + FormatOptions: partition.NewFormatOptions(constants.BootPartitionLabel), + Device: device, } return target.enhance(extra) @@ -116,12 +107,8 @@ func BootTarget(device string, extra *Target) *Target { // MetaTarget builds the default meta target. func MetaTarget(device string, extra *Target) *Target { target := &Target{ - Device: device, - Label: constants.MetaPartitionLabel, - PartitionType: LinuxFilesystemData, - FileSystemType: FilesystemTypeNone, - Size: MetaSize, - Force: true, + FormatOptions: partition.NewFormatOptions(constants.MetaPartitionLabel), + Device: device, } return target.enhance(extra) @@ -130,12 +117,8 @@ func MetaTarget(device string, extra *Target) *Target { // StateTarget builds the default state target. func StateTarget(device string, extra *Target) *Target { target := &Target{ - Device: device, - Label: constants.StatePartitionLabel, - PartitionType: LinuxFilesystemData, - FileSystemType: FilesystemTypeXFS, - Size: StateSize, - Force: true, + FormatOptions: partition.NewFormatOptions(constants.StatePartitionLabel), + Device: device, } return target.enhance(extra) @@ -144,12 +127,8 @@ func StateTarget(device string, extra *Target) *Target { // EphemeralTarget builds the default ephemeral target. func EphemeralTarget(device string, extra *Target) *Target { target := &Target{ - Device: device, - Label: constants.EphemeralPartitionLabel, - PartitionType: LinuxFilesystemData, - FileSystemType: FilesystemTypeXFS, - Size: 0, - Force: true, + FormatOptions: partition.NewFormatOptions(constants.EphemeralPartitionLabel), + Device: device, } return target.enhance(extra) @@ -165,6 +144,10 @@ func (t *Target) enhance(extra *Target) *Target { t.ExtraPreserveSources = extra.ExtraPreserveSources t.Skip = extra.Skip + if extra.FormatOptions != nil { + t.FormatOptions.FileSystemType = extra.FormatOptions.FileSystemType + } + return t } @@ -198,22 +181,7 @@ func (t *Target) Format() error { return nil } - if t.FileSystemType == FilesystemTypeNone { - return t.zeroPartition() - } - - log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, t.FileSystemType, t.Label) - - opts := []makefs.Option{makefs.WithForce(t.Force), makefs.WithLabel(t.Label)} - - switch t.FileSystemType { - case FilesystemTypeVFAT: - return makefs.VFAT(t.PartitionName, opts...) - case FilesystemTypeXFS: - return makefs.XFS(t.PartitionName, opts...) - default: - return fmt.Errorf("unsupported filesystem type: %q", t.FileSystemType) - } + return partition.Format(t.PartitionName, t.FormatOptions) } // Save copies the assets to the bootloader partition. @@ -275,7 +243,7 @@ func (t *Target) Save() (err error) { return nil } -func withTemporaryMounted(partPath string, flags uintptr, fileSystemType FileSystemType, label string, f func(mountPath string) error) error { +func withTemporaryMounted(partPath string, flags uintptr, fileSystemType partition.FileSystemType, label string, f func(mountPath string) error) error { mountPath := filepath.Join(constants.SystemPath, "mnt") mountpoints := mount.NewMountPoints() @@ -297,13 +265,13 @@ func withTemporaryMounted(partPath string, flags uintptr, fileSystemType FileSys } // SaveContents saves contents of partition to the target (in-memory). -func (t *Target) SaveContents(device Device, source *gpt.Partition, fileSystemType FileSystemType, fnmatchFilters []string) error { +func (t *Target) SaveContents(device Device, source *gpt.Partition, fileSystemType partition.FileSystemType, fnmatchFilters []string) error { partPath, err := util.PartPath(device.Device, int(source.Number)) if err != nil { return err } - if fileSystemType == FilesystemTypeNone { + if fileSystemType == partition.FilesystemTypeNone { err = t.saveRawContents(partPath) } else { err = t.saveFilesystemContents(partPath, fileSystemType, fnmatchFilters) @@ -341,7 +309,7 @@ func (t *Target) saveRawContents(partPath string) error { return src.Close() } -func (t *Target) saveFilesystemContents(partPath string, fileSystemType FileSystemType, fnmatchFilters []string) error { +func (t *Target) saveFilesystemContents(partPath string, fileSystemType partition.FileSystemType, fnmatchFilters []string) error { t.Contents = bytes.NewBuffer(nil) return withTemporaryMounted(partPath, unix.MS_RDONLY, fileSystemType, t.Label, func(mountPath string) error { @@ -357,7 +325,7 @@ func (t *Target) RestoreContents() error { var err error - if t.FileSystemType == FilesystemTypeNone { + if t.FileSystemType == partition.FilesystemTypeNone { err = t.restoreRawContents() } else { err = t.restoreFilesystemContents() @@ -400,26 +368,3 @@ func (t *Target) restoreFilesystemContents() error { return archiver.UntarGz(context.TODO(), t.Contents, mountPath) }) } - -// zeroPartition fills the partition with zeroes. -func (t *Target) zeroPartition() (err error) { - log.Printf("zeroing out %q", t.PartitionName) - - zeroes, err := os.Open("/dev/zero") - if err != nil { - return err - } - - defer zeroes.Close() //nolint: errcheck - - part, err := os.OpenFile(t.PartitionName, os.O_WRONLY, 0) - if err != nil { - return err - } - - defer part.Close() //nolint: errcheck - - _, err = io.CopyN(part, zeroes, int64(t.Size)) - - return err -} diff --git a/go.mod b/go.mod index 1407861953..e2cdf30f28 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 github.com/talos-systems/crypto v0.2.1-0.20210202170911-39584f1b6e54 - github.com/talos-systems/go-blockdevice v0.2.0 + github.com/talos-systems/go-blockdevice v0.2.1-0.20210216182145-8f976c203110 github.com/talos-systems/go-cmd v0.0.0-20210216164758-68eb0067e0f0 github.com/talos-systems/go-loadbalancer v0.1.0 github.com/talos-systems/go-procfs v0.0.0-20210108152626-8cbc42d3dc24 diff --git a/go.sum b/go.sum index 32de94fbb5..ba342fc481 100644 --- a/go.sum +++ b/go.sum @@ -868,9 +868,9 @@ github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/talos-systems/crypto v0.2.1-0.20210202170911-39584f1b6e54 h1:2IGs3f0qG7f33Fv37XuYzIMxLARl4dcpwahZXLmdT2A= github.com/talos-systems/crypto v0.2.1-0.20210202170911-39584f1b6e54/go.mod h1:OXCK52Q0dzm88YRG4VdTBdidkPUtqrCxCyW7bUs4DAw= -github.com/talos-systems/go-blockdevice v0.2.0 h1:tTu0ak3GfF8iSxNsdsicVhTKebIcyBARQYxhRV86AF0= -github.com/talos-systems/go-blockdevice v0.2.0/go.mod h1:DGbop5CJa0PYdhQK9cNVF61pPJNedas1m7Gi/qAnrsM= github.com/talos-systems/go-cmd v0.0.0-20210216164758-68eb0067e0f0 h1:DI+BjK+fcrLBc70Fi50dZocQcaHosqsuWHrGHKp2NzE= +github.com/talos-systems/go-blockdevice v0.2.1-0.20210216182145-8f976c203110 h1:qyJ+0mNpddRvt2KRGOGU3KFIFFVWuP7KjYgnn3aVbqk= +github.com/talos-systems/go-blockdevice v0.2.1-0.20210216182145-8f976c203110/go.mod h1:Wt0/JMsa+WysYRDlNo6fu9rdn/re73uRnktFWyVUqjs= github.com/talos-systems/go-cmd v0.0.0-20210216164758-68eb0067e0f0/go.mod h1:kf+rZzTEmlDiYQ6ulslvRONnKLQH8x83TowltGMhO+k= github.com/talos-systems/go-loadbalancer v0.1.0 h1:MQFONvSjoleU8RrKq1O1Z8CyTCJGd4SLqdAHDlR6o9s= github.com/talos-systems/go-loadbalancer v0.1.0/go.mod h1:D5Qjfz+29WVjONWECZvOkmaLsBb3f5YeWME0u/5HmIc= diff --git a/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go b/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go index 09a908448f..ecc534893a 100644 --- a/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go +++ b/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go @@ -498,9 +498,9 @@ func (s *Server) Reset(ctx context.Context, in *machine.ResetRequest) (reply *ma case constants.MetaPartitionLabel: target = installer.MetaTarget(bd.Device().Name(), nil) case constants.StatePartitionLabel: - target = installer.StateTarget(bd.Device().Name(), nil) + target = installer.StateTarget(bd.Device().Name(), installer.NoFilesystem) case constants.EphemeralPartitionLabel: - target = installer.EphemeralTarget(bd.Device().Name(), nil) + target = installer.EphemeralTarget(bd.Device().Name(), installer.NoFilesystem) default: return nil, fmt.Errorf("label %q is not supported", spec.Label) } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer.go b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer.go index ed112d3b96..92e9a6432b 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer.go @@ -213,7 +213,7 @@ func (*Sequencer) Boot(r runtime.Runtime) []runtime.Phase { ).AppendWhen( r.State().Platform().Mode() != runtime.ModeContainer, "ephemeral", - MountEphermeralPartition, + MountEphemeralPartition, ).AppendWhen( r.State().Platform().Mode() != runtime.ModeContainer, "verifyInstall", diff --git a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go index 99707cb583..a845bdcebc 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go @@ -54,6 +54,7 @@ import ( "github.com/talos-systems/talos/internal/pkg/kernel/kspp" "github.com/talos-systems/talos/internal/pkg/kmsg" "github.com/talos-systems/talos/internal/pkg/mount" + "github.com/talos-systems/talos/internal/pkg/partition" "github.com/talos-systems/talos/pkg/conditions" "github.com/talos-systems/talos/pkg/images" "github.com/talos-systems/talos/pkg/kubernetes" @@ -882,11 +883,13 @@ func partitionAndFormatDisks(logger *log.Logger, r runtime.Runtime) error { for _, part := range disk.Partitions() { extraTarget := &installer.Target{ - Device: disk.Device(), - Size: part.Size(), - Force: true, - PartitionType: installer.LinuxFilesystemData, - FileSystemType: installer.FilesystemTypeXFS, + Device: disk.Device(), + FormatOptions: &partition.FormatOptions{ + Size: part.Size(), + Force: true, + PartitionType: partition.LinuxFilesystemData, + FileSystemType: partition.FilesystemTypeXFS, + }, } m.Targets[disk.Device()] = append(m.Targets[disk.Device()], extraTarget) @@ -1580,7 +1583,7 @@ func UnmountEFIPartition(seq runtime.Sequence, data interface{}) (runtime.TaskEx // MountStatePartition mounts the system partition. func MountStatePartition(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) { return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) { - return mount.SystemPartitionMount(r, constants.StatePartitionLabel, mount.WithSkipIfMounted(true)) + return mount.SystemPartitionMount(r, constants.StatePartitionLabel, mount.WithFlags(mount.SkipIfMounted)) }, "mountStatePartition" } @@ -1591,11 +1594,11 @@ func UnmountStatePartition(seq runtime.Sequence, data interface{}) (runtime.Task }, "unmountStatePartition" } -// MountEphermeralPartition mounts the ephemeral partition. -func MountEphermeralPartition(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) { +// MountEphemeralPartition mounts the ephemeral partition. +func MountEphemeralPartition(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, string) { return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) error { - return mount.SystemPartitionMount(r, constants.EphemeralPartitionLabel, mount.WithResize(true)) - }, "mountEphermeralPartition" + return mount.SystemPartitionMount(r, constants.EphemeralPartitionLabel, mount.WithFlags(mount.Resize)) + }, "mountEphemeralPartition" } // UnmountEphemeralPartition unmounts the ephemeral partition. diff --git a/internal/integration/provision/provision.go b/internal/integration/provision/provision.go index 61a99d3826..cfb156d160 100644 --- a/internal/integration/provision/provision.go +++ b/internal/integration/provision/provision.go @@ -4,7 +4,7 @@ // +build integration -// Package provision provides integration tests which rely on on provisioning cluster per test. +// Package provision provides integration tests which rely on provisioning cluster per test. package provision import ( diff --git a/internal/integration/provision/upgrade.go b/internal/integration/provision/upgrade.go index 08f9b66d0f..04c3085e09 100644 --- a/internal/integration/provision/upgrade.go +++ b/internal/integration/provision/upgrade.go @@ -403,7 +403,7 @@ func (suite *UpgradeSuite) waitForClusterHealth() { time.Sleep(15 * time.Second) } - checkCtx, checkCtxCancel := context.WithTimeout(suite.ctx, 10*time.Minute) + checkCtx, checkCtxCancel := context.WithTimeout(suite.ctx, 15*time.Minute) defer checkCtxCancel() suite.Require().NoError(check.Wait(checkCtx, suite.clusterAccess, check.DefaultClusterChecks(), check.StderrReporter())) diff --git a/internal/pkg/mount/mount.go b/internal/pkg/mount/mount.go index 72a6c76ca2..8da0236527 100644 --- a/internal/pkg/mount/mount.go +++ b/internal/pkg/mount/mount.go @@ -48,13 +48,13 @@ func mountMountpoint(mountpoint *Point) (err error) { var skipMount bool // Repair the disk's partition table. - if mountpoint.Resize { + if mountpoint.MountFlags.Check(Resize) { if _, err = mountpoint.ResizePartition(); err != nil { return fmt.Errorf("error resizing %w", err) } } - if mountpoint.SkipIfMounted { + if mountpoint.MountFlags.Check(SkipIfMounted) { skipMount, err = mountpoint.IsMounted() if err != nil { return fmt.Errorf("mountpoint is set to skip if mounted, but the mount check failed: %w", err) @@ -71,7 +71,7 @@ func mountMountpoint(mountpoint *Point) (err error) { // // Growfs is called always, even if ResizePartition returns false to workaround failure scenario // when partition was resized, but growfs never got called. - if mountpoint.Resize { + if mountpoint.MountFlags.Check(Resize) { if err = mountpoint.GrowFilesystem(); err != nil { return fmt.Errorf("error resizing filesystem: %w", err) } @@ -226,16 +226,22 @@ func (p *Point) Data() string { func (p *Point) Mount() (err error) { p.target = path.Join(p.Prefix, p.target) + for _, hook := range p.Options.PreMountHooks { + if err = hook(p); err != nil { + return err + } + } + if err = ensureDirectory(p.target); err != nil { return err } - if p.ReadOnly { + if p.MountFlags.Check(ReadOnly) { p.flags |= unix.MS_RDONLY } switch { - case p.Overlay: + case p.MountFlags.Check(Overlay): err = mountRetry(overlay, p, false) default: err = mountRetry(mount, p, false) @@ -245,7 +251,7 @@ func (p *Point) Mount() (err error) { return err } - if p.Shared { + if p.MountFlags.Check(Shared) { if err = mountRetry(share, p, false); err != nil { return fmt.Errorf("error sharing mount point %s: %+v", p.target, err) } @@ -257,11 +263,25 @@ func (p *Point) Mount() (err error) { // Unmount attempts to retry an unmount on EBUSY. It will attempt a // retry every 100 milliseconds over the course of 5 seconds. func (p *Point) Unmount() (err error) { - p.target = path.Join(p.Prefix, p.target) - if err := mountRetry(unmount, p, true); err != nil { + var mounted bool + + if mounted, err = p.IsMounted(); err != nil { return err } + if mounted { + p.target = path.Join(p.Prefix, p.target) + if err = mountRetry(unmount, p, true); err != nil { + return err + } + } + + for _, hook := range p.Options.PostUnmountHooks { + if err = hook(p); err != nil { + return err + } + } + return nil } diff --git a/internal/pkg/mount/options.go b/internal/pkg/mount/options.go index 5acd233833..f55ece25da 100644 --- a/internal/pkg/mount/options.go +++ b/internal/pkg/mount/options.go @@ -4,73 +4,85 @@ package mount +const ( + // ReadOnly is a flag for setting the mount point as readonly. + ReadOnly Flags = 1 << iota + // Shared is a flag for setting the mount point as shared. + Shared + // Resize indicates that a the partition for a given mount point should be + // resized to the maximum allowed. + Resize + // Overlay indicates that a the partition for a given mount point should be + // mounted using overlayfs. + Overlay + // SkipIfMounted is a flag for skipping mount if the mountpoint is already mounted. + SkipIfMounted +) + +// Flags is the mount flags. +type Flags uint + // Options is the functional options struct. type Options struct { - Loopback string - Prefix string - ReadOnly bool - Shared bool - Resize bool - Overlay bool - SkipIfMounted bool + Loopback string + Prefix string + MountFlags Flags + PreMountHooks []Hook + PostUnmountHooks []Hook } // Option is the functional option func. type Option func(*Options) -// WithPrefix is a functional option for setting the mount point prefix. -func WithPrefix(o string) Option { - return func(args *Options) { - args.Prefix = o - } +// Check checks if all provided flags are set. +func (f Flags) Check(flags Flags) bool { + return (f & flags) == flags } -// WithReadOnly is a functional option for setting the mount point as readonly. -func WithReadOnly(o bool) Option { - return func(args *Options) { - args.ReadOnly = o - } +// Intersects checks if at least one flag is set. +func (f Flags) Intersects(flags Flags) bool { + return (f & flags) != 0 } -// WithShared is a functional option for setting the mount point as shared. -func WithShared(o bool) Option { +// WithPrefix is a functional option for setting the mount point prefix. +func WithPrefix(o string) Option { return func(args *Options) { - args.Shared = o + args.Prefix = o } } -// WithSkipIfMounted is a functional option for skipping mount if the mountpoint is already mounted. -func WithSkipIfMounted(o bool) Option { +// WithFlags is a functional option to set up mount flags. +func WithFlags(flags Flags) Option { return func(args *Options) { - args.SkipIfMounted = o + args.MountFlags = flags } } -// WithResize indicates that a the partition for a given mount point should be -// resized to the maximum allowed. -func WithResize(o bool) Option { +// WithPreMountHooks adds functions to be called before mounting the partition. +func WithPreMountHooks(hooks ...Hook) Option { return func(args *Options) { - args.Resize = o + args.PreMountHooks = hooks } } -// WithOverlay indicates that a the partition for a given mount point should be -// mounted using overlayfs. -func WithOverlay(o bool) Option { +// WithPostUnmountHooks adds functions to be called after unmounting the partition. +func WithPostUnmountHooks(hooks ...Hook) Option { return func(args *Options) { - args.Overlay = o + args.PostUnmountHooks = hooks } } +// Hook represents pre/post mount hook. +type Hook func(p *Point) error + // NewDefaultOptions initializes a Options struct with default values. func NewDefaultOptions(setters ...Option) *Options { opts := &Options{ - Loopback: "", - Prefix: "", - ReadOnly: false, - Shared: false, - Resize: false, - Overlay: false, + Loopback: "", + Prefix: "", + MountFlags: 0, + PreMountHooks: []Hook{}, + PostUnmountHooks: []Hook{}, } for _, setter := range setters { diff --git a/internal/pkg/mount/overlay.go b/internal/pkg/mount/overlay.go index c1404aaded..2f421ed042 100644 --- a/internal/pkg/mount/overlay.go +++ b/internal/pkg/mount/overlay.go @@ -22,7 +22,7 @@ func OverlayMountPoints() (mountpoints *Points, err error) { } for _, target := range overlays { - mountpoint := NewMountPoint("", target, "", unix.MS_I_VERSION, "", WithOverlay(true)) + mountpoint := NewMountPoint("", target, "", unix.MS_I_VERSION, "", WithFlags(Overlay)) mountpoints.Set(target, mountpoint) } diff --git a/internal/pkg/mount/squashfs.go b/internal/pkg/mount/squashfs.go index a1e44a75e7..60ca5d64a4 100644 --- a/internal/pkg/mount/squashfs.go +++ b/internal/pkg/mount/squashfs.go @@ -21,7 +21,7 @@ func SquashfsMountPoints(prefix string) (mountpoints *Points, err error) { } squashfs := NewMountPoints() - squashfs.Set("squashfs", NewMountPoint(dev.Path(), "/", "squashfs", unix.MS_RDONLY|unix.MS_I_VERSION, "", WithPrefix(prefix), WithReadOnly(true), WithShared(true))) + squashfs.Set("squashfs", NewMountPoint(dev.Path(), "/", "squashfs", unix.MS_RDONLY|unix.MS_I_VERSION, "", WithPrefix(prefix), WithFlags(ReadOnly|Shared))) return squashfs, nil } diff --git a/internal/pkg/mount/system.go b/internal/pkg/mount/system.go index 535335bc31..35fe2bad15 100644 --- a/internal/pkg/mount/system.go +++ b/internal/pkg/mount/system.go @@ -9,13 +9,17 @@ import ( "os" "github.com/talos-systems/go-blockdevice/blockdevice" + "github.com/talos-systems/go-blockdevice/blockdevice/filesystem" "golang.org/x/sys/unix" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/disk" + "github.com/talos-systems/talos/internal/pkg/partition" "github.com/talos-systems/talos/pkg/machinery/constants" ) +var mountpoints = map[string]*Point{} + // SystemMountPointsForDevice returns the mountpoints required to boot the system. // This function is called exclusively during installations ( both image // creation and bare metall installs ). This is why we want to look up @@ -61,12 +65,12 @@ func SystemMountPointForLabel(device *blockdevice.BlockDevice, label string, opt return nil, fmt.Errorf("unknown label: %q", label) } - partition, err := device.GetPartition(label) + part, err := device.GetPartition(label) if err != nil && err != os.ErrNotExist { return nil, err } - if partition == nil { + if part == nil { // A boot partitition is not required. if label == constants.BootPartitionLabel { return nil, nil @@ -75,16 +79,47 @@ func SystemMountPointForLabel(device *blockdevice.BlockDevice, label string, opt return nil, fmt.Errorf("failed to find device with label %s: %w", label, err) } - fsType, err := partition.Filesystem() + fsType, err := part.Filesystem() if err != nil { return nil, err } - partPath, err := partition.Path() + partPath, err := part.Path() if err != nil { return nil, err } + preMountHooks := []Hook{} + + // Format the partition if it does not have any filesystem + preMountHooks = append(preMountHooks, func(p *Point) error { + sb, err := filesystem.Probe(p.source) + if err != nil { + return err + } + + p.fstype = "" + + // skip formatting the partition if filesystem is detected + // and assign proper fs type to the mountpoint + if sb != nil && sb.Type() != filesystem.Unknown { + p.fstype = sb.Type() + + return nil + } + + opts := partition.NewFormatOptions(part.Name) + if opts == nil { + return fmt.Errorf("failed to determine format options for partition label %s", part.Name) + } + + p.fstype = opts.FileSystemType + + return partition.Format(p.source, opts) + }) + + opts = append(opts, WithPreMountHooks(preMountHooks...)) + mountpoint = NewMountPoint(partPath, target, fsType, unix.MS_NOATIME, "", opts...) return mountpoint, nil @@ -97,8 +132,6 @@ func SystemPartitionMount(r runtime.Runtime, label string, opts ...Option) (err return fmt.Errorf("failed to find device with partition labeled %s", label) } - mountpoints := NewMountPoints() - mountpoint, err := SystemMountPointForLabel(device.BlockDevice, label, opts...) if err != nil { return err @@ -108,37 +141,24 @@ func SystemPartitionMount(r runtime.Runtime, label string, opts ...Option) (err return fmt.Errorf("no mountpoints for label %q", label) } - mountpoints.Set(label, mountpoint) - - if err = Mount(mountpoints); err != nil { + if err = mountMountpoint(mountpoint); err != nil { return err } + mountpoints[label] = mountpoint + return nil } // SystemPartitionUnmount unmounts a system partition by the label. func SystemPartitionUnmount(r runtime.Runtime, label string) (err error) { - device := r.State().Machine().Disk(disk.WithPartitionLabel(label)) - if device == nil { - return fmt.Errorf("failed to find device with partition labeled %s", label) - } - - mountpoints := NewMountPoints() - - mountpoint, err := SystemMountPointForLabel(device.BlockDevice, label) - if err != nil { - return err - } - - if mountpoint == nil { - return fmt.Errorf("no mountpoints for label %q", label) - } - - mountpoints.Set(label, mountpoint) + if mountpoint, ok := mountpoints[label]; ok { + err = mountpoint.Unmount() + if err != nil { + return err + } - if err = Unmount(mountpoints); err != nil { - return err + delete(mountpoints, label) } return nil diff --git a/cmd/installer/pkg/install/constants.go b/internal/pkg/partition/constants.go similarity index 70% rename from cmd/installer/pkg/install/constants.go rename to internal/pkg/partition/constants.go index d3c1f36695..8d9d729e25 100644 --- a/cmd/installer/pkg/install/constants.go +++ b/internal/pkg/partition/constants.go @@ -2,18 +2,18 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -package install +package partition -// PartitionType in partition table. -type PartitionType = string +// Type in partition table. +type Type = string // GPT partition types. // // TODO: should be moved into the blockdevice library. const ( - EFISystemPartition PartitionType = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" - BIOSBootPartition PartitionType = "21686148-6449-6E6F-744E-656564454649" - LinuxFilesystemData PartitionType = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" + EFISystemPartition Type = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" + BIOSBootPartition Type = "21686148-6449-6E6F-744E-656564454649" + LinuxFilesystemData Type = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" ) // FileSystemType is used to format partitions. diff --git a/internal/pkg/partition/format.go b/internal/pkg/partition/format.go new file mode 100644 index 0000000000..ce4a76839e --- /dev/null +++ b/internal/pkg/partition/format.go @@ -0,0 +1,129 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package partition provides common utils for system partition format. +package partition + +import ( + "fmt" + "io" + "log" + "os" + + "github.com/talos-systems/go-blockdevice/blockdevice" + + "github.com/talos-systems/talos/pkg/machinery/constants" + "github.com/talos-systems/talos/pkg/makefs" +) + +// FormatOptions contains format parameters. +type FormatOptions struct { + Label string + PartitionType Type + FileSystemType FileSystemType + Size uint64 + Force bool +} + +// NewFormatOptions creates a new format options. +func NewFormatOptions(label string) *FormatOptions { + opts, ok := systemPartitions[label] + if ok { + return &opts + } + + return nil +} + +// Format zeroes the device and formats it using filesystem type provided. +func Format(devname string, t *FormatOptions) error { + if t.FileSystemType == FilesystemTypeNone { + return zeroPartition(devname, int64(t.Size)) + } + + opts := []makefs.Option{makefs.WithForce(t.Force), makefs.WithLabel(t.Label)} + log.Printf("formatting the partition %q as %q with label %q\n", devname, t.FileSystemType, t.Label) + + switch t.FileSystemType { + case FilesystemTypeVFAT: + return makefs.VFAT(devname, opts...) + case FilesystemTypeXFS: + return makefs.XFS(devname, opts...) + default: + return fmt.Errorf("unsupported filesystem type: %q", t.FileSystemType) + } +} + +// zeroPartition fills the partition with zeroes. +func zeroPartition(devname string, size int64) (err error) { + log.Printf("zeroing out %q", devname) + + zeroes, err := os.Open("/dev/zero") + if err != nil { + return err + } + + defer zeroes.Close() //nolint: errcheck + + part, err := os.OpenFile(devname, os.O_WRONLY, 0) + if err != nil { + return err + } + + defer part.Close() //nolint: errcheck + + // wipe at least minimal header size + if size == 0 { + size = blockdevice.FastWipeRange + } + + _, err = io.CopyN(part, zeroes, size) + + return err +} + +var systemPartitions = map[string]FormatOptions{ + constants.EFIPartitionLabel: { + Label: constants.EFIPartitionLabel, + PartitionType: EFISystemPartition, + FileSystemType: FilesystemTypeVFAT, + Size: EFISize, + Force: true, + }, + constants.BIOSGrubPartitionLabel: { + Label: constants.BIOSGrubPartitionLabel, + PartitionType: BIOSBootPartition, + FileSystemType: FilesystemTypeNone, + Size: BIOSGrubSize, + Force: true, + }, + constants.BootPartitionLabel: { + Label: constants.BootPartitionLabel, + PartitionType: LinuxFilesystemData, + FileSystemType: FilesystemTypeXFS, + Size: BootSize, + Force: true, + }, + constants.MetaPartitionLabel: { + Label: constants.MetaPartitionLabel, + PartitionType: LinuxFilesystemData, + FileSystemType: FilesystemTypeNone, + Size: MetaSize, + Force: true, + }, + constants.StatePartitionLabel: { + Label: constants.StatePartitionLabel, + PartitionType: LinuxFilesystemData, + FileSystemType: FilesystemTypeXFS, + Size: StateSize, + Force: true, + }, + constants.EphemeralPartitionLabel: { + Label: constants.EphemeralPartitionLabel, + PartitionType: LinuxFilesystemData, + FileSystemType: FilesystemTypeXFS, + Size: 0, + Force: true, + }, +} diff --git a/internal/pkg/partition/partition.go b/internal/pkg/partition/partition.go new file mode 100644 index 0000000000..d12fa3bbed --- /dev/null +++ b/internal/pkg/partition/partition.go @@ -0,0 +1,6 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package partition provides common utils for system partition format. +package partition diff --git a/pkg/cluster/check/default.go b/pkg/cluster/check/default.go index b30f1f1b15..badd3839da 100644 --- a/pkg/cluster/check/default.go +++ b/pkg/cluster/check/default.go @@ -41,7 +41,7 @@ func DefaultClusterChecks() []ClusterCheck { func(cluster ClusterInfo) conditions.Condition { return conditions.PollingCondition("apid to be ready", func(ctx context.Context) error { return ApidReadyAssertion(ctx, cluster) - }, 2*time.Minute, 5*time.Second) + }, 5*time.Minute, 5*time.Second) }, // wait for kubelet to be healthy on all func(cluster ClusterInfo) conditions.Condition {