fix(machined): Clean up installation process

This also includes a fix for #955 which had the unintended side effect
of breaking image creation ( since it would attempt to grow the filesystem
always ).

The refactor standardizes around looking for the DATA and ESP labels to
discover any existing installations/filesystems. If none are found, an
installation will proceed -- for both image creation and bare metal.
During bootup, the DATA partition will always attempt to expand/grow.

This also introduces a new phase to verify the installation through the
existance of /boot/installed ( migrated from install stage ).

Signed-off-by: Brad Beam <brad.beam@talos-systems.com>
This commit is contained in:
Brad Beam 2019-08-08 00:16:43 -05:00 committed by Brad Beam
parent 3383e72d37
commit da1f73249f
9 changed files with 120 additions and 81 deletions

View File

@ -25,6 +25,7 @@ policies:
- init
- initramfs
- kernel
- machined
- proxyd
- osctl
- osd

View File

@ -0,0 +1,39 @@
/* 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 rootfs
import (
"os"
"path/filepath"
"github.com/talos-systems/talos/internal/app/machined/internal/phase"
"github.com/talos-systems/talos/internal/app/machined/internal/platform"
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
"github.com/talos-systems/talos/pkg/constants"
"github.com/talos-systems/talos/pkg/userdata"
)
// CheckInstall represents the CheckInstall task.
type CheckInstall struct{}
// NewCheckInstallTask initializes and returns a CheckInstall task.
func NewCheckInstallTask() phase.Task {
return &CheckInstall{}
}
// RuntimeFunc returns the runtime function.
func (task *CheckInstall) RuntimeFunc(mode runtime.Mode) phase.RuntimeFunc {
switch mode {
case runtime.Standard:
return task.standard
default:
return nil
}
}
func (task *CheckInstall) standard(platform platform.Platform, data *userdata.UserData) (err error) {
_, err = os.Stat(filepath.Join(constants.BootMountPoint, "installed"))
return err
}

View File

@ -13,6 +13,9 @@ import (
"github.com/pkg/errors"
"github.com/talos-systems/talos/internal/pkg/installer"
"github.com/talos-systems/talos/internal/pkg/kernel"
"github.com/talos-systems/talos/internal/pkg/mount"
"github.com/talos-systems/talos/internal/pkg/mount/manager"
"github.com/talos-systems/talos/internal/pkg/mount/manager/owned"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/talos/pkg/constants"
"github.com/talos-systems/talos/pkg/userdata"
@ -88,9 +91,27 @@ func (b *BareMetal) Initialize(data *userdata.UserData) (err error) {
return err
}
i := installer.NewInstaller(cmdline, data)
if err = i.Install(); err != nil {
return errors.Wrap(err, "failed to install")
// Attempt to discover a previous installation
// An err case should only happen if no partitions
// with matching labels were found
var mountpoints *mount.Points
mountpoints, err = owned.MountPointsFromLabels()
if err != nil {
// No previous installation was found, attempt an install
i := installer.NewInstaller(cmdline, data)
if err = i.Install(); err != nil {
return errors.Wrap(err, "failed to install")
}
mountpoints, err = owned.MountPointsFromLabels()
if err != nil {
return err
}
}
m := manager.NewManager(mountpoints)
if err = m.MountAll(); err != nil {
return err
}
return nil

View File

@ -10,6 +10,9 @@ import (
"github.com/pkg/errors"
"github.com/talos-systems/talos/internal/pkg/installer"
"github.com/talos-systems/talos/internal/pkg/kernel"
"github.com/talos-systems/talos/internal/pkg/mount"
"github.com/talos-systems/talos/internal/pkg/mount/manager"
"github.com/talos-systems/talos/internal/pkg/mount/manager/owned"
"github.com/talos-systems/talos/pkg/constants"
"github.com/talos-systems/talos/pkg/userdata"
)
@ -49,9 +52,27 @@ func (p *Packet) Initialize(data *userdata.UserData) (err error) {
return err
}
i := installer.NewInstaller(cmdline, data)
if err = i.Install(); err != nil {
return errors.Wrap(err, "failed to install")
// Attempt to discover a previous installation
// An err case should only happen if no partitions
// with matching labels were found
var mountpoints *mount.Points
mountpoints, err = owned.MountPointsFromLabels()
if err != nil {
// No previous installation was found, attempt an install
i := installer.NewInstaller(cmdline, data)
if err = i.Install(); err != nil {
return errors.Wrap(err, "failed to install")
}
mountpoints, err = owned.MountPointsFromLabels()
if err != nil {
return err
}
}
m := manager.NewManager(mountpoints)
if err = m.MountAll(); err != nil {
return err
}
return nil

View File

@ -77,6 +77,10 @@ func run() (err error) {
"platform tasks",
platform.NewPlatformTask(),
),
phase.NewPhase(
"installation verification",
rootfs.NewCheckInstallTask(),
),
phase.NewPhase(
"overlay mounts",
rootfs.NewMountOverlayTask(),

View File

@ -132,7 +132,7 @@ func WriteSyslinuxCfg(base, path string, syslinuxcfg *Cfg) (err error) {
return err
}
log.Println("writing syslinux.cfg to disk")
log.Printf("writing %s to disk", path)
if err = ioutil.WriteFile(path, wr.Bytes(), 0600); err != nil {
return err
}

View File

@ -7,7 +7,6 @@ package installer
import (
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"unsafe"
@ -19,7 +18,6 @@ import (
"github.com/talos-systems/talos/internal/pkg/mount"
"github.com/talos-systems/talos/internal/pkg/mount/manager"
"github.com/talos-systems/talos/internal/pkg/mount/manager/owned"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/talos/pkg/constants"
"github.com/talos-systems/talos/pkg/userdata"
"github.com/talos-systems/talos/pkg/version"
@ -72,34 +70,12 @@ func (i *Installer) Install() (err error) {
return nil
}
if i.data.Install.Boot != nil {
var ok bool
if ok, err = exists(i.data.Install.Boot.InstallDevice.Device); err != nil {
return err
}
if ok {
log.Println("found existing installation")
var mountpoints *mount.Points
mountpoints, err = owned.MountPointsFromLabels()
if err != nil {
return err
}
m := manager.NewManager(mountpoints)
if err = m.MountAll(); err != nil {
return err
}
return nil
}
if err = VerifyBootDevice(i.data); err != nil {
return errors.Wrap(err, "failed to prepare boot device")
}
}
// Verify that the target device(s) can satisify the requested options.
if err = VerifyBootDevice(i.data); err != nil {
return errors.Wrap(err, "failed to prepare boot device")
}
if err = VerifyDataDevice(i.data); err != nil {
return errors.Wrap(err, "failed to prepare data device")
}
@ -118,16 +94,18 @@ func (i *Installer) Install() (err error) {
// Mount the partitions.
var mountpoints *mount.Points
if i.data.Install.Boot != nil {
mountpoints, err = owned.MountPointsForDevice(i.data.Install.Boot.InstallDevice.Device)
mountpoints := mount.NewMountPoints()
// look for mountpoints across all target devices
for dev := range i.manifest.Targets {
var mp *mount.Points
mp, err = owned.MountPointsForDevice(dev)
if err != nil {
return err
}
} else {
mountpoints, err = owned.MountPointsFromLabels()
if err != nil {
return err
iter := mp.Iter()
for iter.Next() {
mountpoints.Set(iter.Key(), iter.Value())
}
}
@ -136,6 +114,9 @@ func (i *Installer) Install() (err error) {
return err
}
// nolint: errcheck
defer m.UnmountAll()
// Install the assets.
for _, targets := range i.manifest.Targets {
@ -216,37 +197,3 @@ func wipe(manifest *manifest.Manifest) (err error) {
return nil
}
func exists(devpath string) (bool, error) {
var (
err error
dev *probe.ProbedBlockDevice
)
if dev, err = probe.DevForFileSystemLabel(devpath, constants.BootPartitionLabel); err == nil {
// nolint: errcheck
defer dev.Close()
if dev.SuperBlock != nil {
mountpoint := mount.NewMountPoint(dev.Path, "/tmp", dev.SuperBlock.Type(), 0, "")
if err = mountpoint.Mount(); err != nil {
return false, err
}
defer func() {
if err = mountpoint.Unmount(); err != nil {
log.Printf("WARNING: failed to unmount %s from /tmp", dev.Path)
}
}()
_, err = os.Stat(filepath.Join("tmp", "installed"))
switch {
case err == nil:
return true, nil
case os.IsNotExist(err):
return false, nil
default:
return false, err
}
}
}
return false, nil
}

View File

@ -205,10 +205,11 @@ func (t *Target) Partition(bd *blockdevice.BlockDevice) (err error) {
// Format creates a filesystem on the device/partition.
func (t *Target) Format() error {
log.Printf("formatting partition %s - %s\n", t.PartitionName, t.Label)
if t.Label == constants.BootPartitionLabel {
log.Printf("formatting partition %s - %s as %s\n", t.PartitionName, t.Label, "fat")
return vfat.MakeFS(t.PartitionName, vfat.WithLabel(t.Label))
}
log.Printf("formatting partition %s - %s as %s\n", t.PartitionName, t.Label, "xfs")
opts := []xfs.Option{xfs.WithForce(t.Force)}
if t.Label != "" {
opts = append(opts, xfs.WithLabel(t.Label))

View File

@ -15,15 +15,17 @@ import (
)
// MountPointsForDevice 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
// device by specified disk as well as why we don't want to grow any
// filesystems.
func MountPointsForDevice(devpath string) (mountpoints *mount.Points, err error) {
mountpoints = mount.NewMountPoints()
for _, name := range []string{constants.DataPartitionLabel, constants.BootPartitionLabel} {
opts := []mount.Option{}
var target string
switch name {
case constants.DataPartitionLabel:
target = constants.DataMountPoint
opts = append(opts, mount.WithResize(true))
case constants.BootPartitionLabel:
target = constants.BootMountPoint
}
@ -37,7 +39,7 @@ func MountPointsForDevice(devpath string) (mountpoints *mount.Points, err error)
}
return nil, errors.Errorf("probe device for filesystem %s: %v", name, err)
}
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "", opts...)
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "")
mountpoints.Set(name, mountpoint)
}
@ -45,6 +47,8 @@ func MountPointsForDevice(devpath string) (mountpoints *mount.Points, err error)
}
// MountPointsFromLabels returns the mountpoints required to boot the system.
// Since this function is called exclusively during boot time, this is when
// we want to grow the data filesystem.
func MountPointsFromLabels() (mountpoints *mount.Points, err error) {
mountpoints = mount.NewMountPoints()
for _, name := range []string{constants.DataPartitionLabel, constants.BootPartitionLabel} {
@ -60,13 +64,14 @@ func MountPointsFromLabels() (mountpoints *mount.Points, err error) {
var dev *probe.ProbedBlockDevice
if dev, err = probe.GetDevWithFileSystemLabel(name); err != nil {
// A bootloader is not always required.
if name == constants.BootPartitionLabel {
// A bootloader is not always required.
log.Println("WARNING: no ESP partition was found")
continue
}
return nil, errors.Errorf("find device with label %s: %v", name, err)
}
mountpoint := mount.NewMountPoint(dev.Path, target, dev.SuperBlock.Type(), unix.MS_NOATIME, "", opts...)
mountpoints.Set(name, mountpoint)
}