mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-09-19 17:25:01 +00:00
* Fix config loading, install device and call sanitize Syncs the cfg.install.device to the installspec.target As on manual-install the device can actually come from a flag and its not on the cloud-config, we need to initialize the InstallSpec with the value of the cfg.Install.Device as by that time we already have the final value of the install target. Also calls sanitize on config and specs and warns if anything goes wrong Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> * Fix platform override and tests The sanitize method for Config was never called so we never noticed that its overriding the platform, even if we pass the WithPLatform option. This patch fixes it by only overriding platform by default on sanitize if the platform is empty, otherwise leave the platform on it values. Also fixes the tests to use test facilities and a small fix to not try to load the platform values form yaml Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> * Init the Install in the Config init Just in case something tries to access it so they dont find a nil Also adjusts some more tests Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com> --------- Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com>
965 lines
32 KiB
Go
965 lines
32 KiB
Go
/*
|
|
Copyright © 2021 SUSE LLC
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package elemental_test
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/jaypipes/ghw/pkg/block"
|
|
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
|
cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/elemental"
|
|
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
|
"github.com/kairos-io/kairos-agent/v2/pkg/utils"
|
|
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/twpayne/go-vfs/vfst"
|
|
"k8s.io/mount-utils"
|
|
)
|
|
|
|
const printOutput = `BYT;
|
|
/dev/loop0:50593792s:loopback:512:512:gpt:Loopback device:;`
|
|
const partTmpl = `
|
|
%d:%ss:%ss:2048s:ext4::type=83;`
|
|
|
|
func TestElementalSuite(t *testing.T) {
|
|
RegisterFailHandler(Fail)
|
|
RunSpecs(t, "Elemental test suite")
|
|
}
|
|
|
|
var _ = Describe("Elemental", Label("elemental"), func() {
|
|
var config *agentConfig.Config
|
|
var runner *v1mock.FakeRunner
|
|
var logger v1.Logger
|
|
var syscall v1.SyscallInterface
|
|
var client *v1mock.FakeHTTPClient
|
|
var mounter *v1mock.ErrorMounter
|
|
var fs *vfst.TestFS
|
|
var cleanup func()
|
|
var extractor *v1mock.FakeImageExtractor
|
|
|
|
BeforeEach(func() {
|
|
runner = v1mock.NewFakeRunner()
|
|
syscall = &v1mock.FakeSyscall{}
|
|
mounter = v1mock.NewErrorMounter()
|
|
client = &v1mock.FakeHTTPClient{}
|
|
logger = v1.NewNullLogger()
|
|
fs, cleanup, _ = vfst.NewTestFS(nil)
|
|
extractor = v1mock.NewFakeImageExtractor(logger)
|
|
config = agentConfig.NewConfig(
|
|
agentConfig.WithFs(fs),
|
|
agentConfig.WithRunner(runner),
|
|
agentConfig.WithLogger(logger),
|
|
agentConfig.WithMounter(mounter),
|
|
agentConfig.WithSyscall(syscall),
|
|
agentConfig.WithClient(client),
|
|
agentConfig.WithImageExtractor(extractor),
|
|
)
|
|
})
|
|
AfterEach(func() { cleanup() })
|
|
Describe("MountRWPartition", Label("mount"), func() {
|
|
var el *elemental.Elemental
|
|
var parts v1.ElementalPartitions
|
|
BeforeEach(func() {
|
|
parts = agentConfig.NewInstallElementalPartitions()
|
|
|
|
err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = fs.Create("/some/device")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
parts.OEM.Path = "/dev/device1"
|
|
|
|
el = elemental.NewElemental(config)
|
|
})
|
|
|
|
It("Mounts and umounts a partition with RW", func() {
|
|
umount, err := el.MountRWPartition(parts.OEM)
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
Expect(len(lst)).To(Equal(1))
|
|
Expect(lst[0].Opts).To(Equal([]string{"rw"}))
|
|
|
|
Expect(umount()).ShouldNot(HaveOccurred())
|
|
lst, _ = mounter.List()
|
|
Expect(len(lst)).To(Equal(0))
|
|
})
|
|
It("Remounts a partition with RW", func() {
|
|
err := el.MountPartition(parts.OEM)
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
Expect(len(lst)).To(Equal(1))
|
|
|
|
umount, err := el.MountRWPartition(parts.OEM)
|
|
Expect(err).To(BeNil())
|
|
lst, _ = mounter.List()
|
|
// fake mounter is not merging remounts it just appends
|
|
Expect(len(lst)).To(Equal(2))
|
|
Expect(lst[1].Opts).To(Equal([]string{"remount", "rw"}))
|
|
|
|
Expect(umount()).ShouldNot(HaveOccurred())
|
|
lst, _ = mounter.List()
|
|
// Increased once more to remount read-onply
|
|
Expect(len(lst)).To(Equal(3))
|
|
Expect(lst[2].Opts).To(Equal([]string{"remount", "ro"}))
|
|
})
|
|
It("Fails to mount a partition", func() {
|
|
mounter.ErrorOnMount = true
|
|
_, err := el.MountRWPartition(parts.OEM)
|
|
Expect(err).Should(HaveOccurred())
|
|
})
|
|
It("Fails to remount a partition", func() {
|
|
err := el.MountPartition(parts.OEM)
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
Expect(len(lst)).To(Equal(1))
|
|
|
|
mounter.ErrorOnMount = true
|
|
_, err = el.MountRWPartition(parts.OEM)
|
|
Expect(err).Should(HaveOccurred())
|
|
lst, _ = mounter.List()
|
|
Expect(len(lst)).To(Equal(1))
|
|
})
|
|
})
|
|
Describe("MountPartitions", Label("MountPartitions", "disk", "partition", "mount"), func() {
|
|
var el *elemental.Elemental
|
|
var parts v1.ElementalPartitions
|
|
BeforeEach(func() {
|
|
parts = agentConfig.NewInstallElementalPartitions()
|
|
|
|
err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = fs.Create("/some/device")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
parts.OEM.Path = "/dev/device2"
|
|
parts.Recovery.Path = "/dev/device3"
|
|
parts.State.Path = "/dev/device4"
|
|
parts.Persistent.Path = "/dev/device5"
|
|
|
|
el = elemental.NewElemental(config)
|
|
})
|
|
|
|
It("Mounts disk partitions", func() {
|
|
err := el.MountPartitions(parts.PartitionsByMountPoint(false))
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
Expect(len(lst)).To(Equal(4))
|
|
})
|
|
|
|
It("Mounts disk partitions excluding recovery", func() {
|
|
err := el.MountPartitions(parts.PartitionsByMountPoint(false, parts.Recovery))
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
for _, i := range lst {
|
|
Expect(i.Path).NotTo(Equal("/dev/device3"))
|
|
}
|
|
})
|
|
|
|
It("Fails if some partition resists to mount ", func() {
|
|
mounter.ErrorOnMount = true
|
|
err := el.MountPartitions(parts.PartitionsByMountPoint(false))
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
|
|
It("Fails if oem partition is not found ", func() {
|
|
parts.OEM.Path = ""
|
|
err := el.MountPartitions(parts.PartitionsByMountPoint(false))
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
})
|
|
|
|
Describe("UnmountPartitions", Label("UnmountPartitions", "disk", "partition", "unmount"), func() {
|
|
var el *elemental.Elemental
|
|
var parts v1.ElementalPartitions
|
|
BeforeEach(func() {
|
|
parts = agentConfig.NewInstallElementalPartitions()
|
|
|
|
err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = fs.Create("/some/device")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
parts.OEM.Path = "/dev/device2"
|
|
parts.Recovery.Path = "/dev/device3"
|
|
parts.State.Path = "/dev/device4"
|
|
parts.Persistent.Path = "/dev/device5"
|
|
|
|
el = elemental.NewElemental(config)
|
|
err = el.MountPartitions(parts.PartitionsByMountPoint(false))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("Unmounts disk partitions", func() {
|
|
err := el.UnmountPartitions(parts.PartitionsByMountPoint(true))
|
|
Expect(err).To(BeNil())
|
|
lst, _ := mounter.List()
|
|
Expect(len(lst)).To(Equal(0))
|
|
})
|
|
|
|
It("Fails to unmount disk partitions", func() {
|
|
mounter.ErrorOnUnmount = true
|
|
err := el.UnmountPartitions(parts.PartitionsByMountPoint(true))
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
})
|
|
|
|
Describe("MountImage", Label("MountImage", "mount", "image"), func() {
|
|
var el *elemental.Elemental
|
|
var img *v1.Image
|
|
BeforeEach(func() {
|
|
el = elemental.NewElemental(config)
|
|
img = &v1.Image{MountPoint: "/some/mountpoint"}
|
|
})
|
|
|
|
It("Mounts file system image", func() {
|
|
runner.ReturnValue = []byte("/dev/loop")
|
|
Expect(el.MountImage(img)).To(BeNil())
|
|
Expect(img.LoopDevice).To(Equal("/dev/loop"))
|
|
})
|
|
|
|
It("Fails to set a loop device", Label("loop"), func() {
|
|
runner.ReturnError = errors.New("failed to set a loop device")
|
|
Expect(el.MountImage(img)).NotTo(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(""))
|
|
})
|
|
|
|
It("Fails to mount a loop device", Label("loop"), func() {
|
|
runner.ReturnValue = []byte("/dev/loop")
|
|
mounter.ErrorOnMount = true
|
|
Expect(el.MountImage(img)).NotTo(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(""))
|
|
})
|
|
})
|
|
|
|
Describe("UnmountImage", Label("UnmountImage", "mount", "image"), func() {
|
|
var el *elemental.Elemental
|
|
var img *v1.Image
|
|
BeforeEach(func() {
|
|
runner.ReturnValue = []byte("/dev/loop")
|
|
el = elemental.NewElemental(config)
|
|
img = &v1.Image{MountPoint: "/some/mountpoint"}
|
|
Expect(el.MountImage(img)).To(BeNil())
|
|
Expect(img.LoopDevice).To(Equal("/dev/loop"))
|
|
})
|
|
|
|
It("Unmounts file system image", func() {
|
|
Expect(el.UnmountImage(img)).To(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(""))
|
|
})
|
|
|
|
It("Fails to unmount a mountpoint", func() {
|
|
mounter.ErrorOnUnmount = true
|
|
Expect(el.UnmountImage(img)).NotTo(BeNil())
|
|
})
|
|
|
|
It("Fails to unset a loop device", Label("loop"), func() {
|
|
runner.ReturnError = errors.New("failed to unset a loop device")
|
|
Expect(el.UnmountImage(img)).NotTo(BeNil())
|
|
})
|
|
})
|
|
|
|
Describe("CreateFileSystemImage", Label("CreateFileSystemImage", "image"), func() {
|
|
var el *elemental.Elemental
|
|
var img *v1.Image
|
|
BeforeEach(func() {
|
|
img = &v1.Image{
|
|
Label: cnst.ActiveLabel,
|
|
Size: 32,
|
|
File: filepath.Join(cnst.StateDir, "cOS", cnst.ActiveImgFile),
|
|
FS: cnst.LinuxImgFs,
|
|
MountPoint: cnst.ActiveDir,
|
|
Source: v1.NewDirSrc(cnst.IsoBaseTree),
|
|
}
|
|
_ = fsutils.MkdirAll(fs, cnst.IsoBaseTree, cnst.DirPerm)
|
|
el = elemental.NewElemental(config)
|
|
})
|
|
|
|
It("Creates a new file system image", func() {
|
|
_, err := fs.Stat(img.File)
|
|
Expect(err).NotTo(BeNil())
|
|
err = el.CreateFileSystemImage(img)
|
|
Expect(err).To(BeNil())
|
|
stat, err := fs.Stat(img.File)
|
|
Expect(err).To(BeNil())
|
|
Expect(stat.Size()).To(Equal(int64(32 * 1024 * 1024)))
|
|
})
|
|
|
|
It("Fails formatting a file system image", Label("format"), func() {
|
|
runner.ReturnError = errors.New("run error")
|
|
_, err := fs.Stat(img.File)
|
|
Expect(err).NotTo(BeNil())
|
|
err = el.CreateFileSystemImage(img)
|
|
Expect(err).NotTo(BeNil())
|
|
_, err = fs.Stat(img.File)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
})
|
|
|
|
Describe("FormatPartition", Label("FormatPartition", "partition", "format"), func() {
|
|
It("Reformats an already existing partition", func() {
|
|
el := elemental.NewElemental(config)
|
|
part := &v1.Partition{
|
|
Path: "/dev/device1",
|
|
FS: "ext4",
|
|
FilesystemLabel: "MY_LABEL",
|
|
}
|
|
Expect(el.FormatPartition(part)).To(BeNil())
|
|
})
|
|
|
|
})
|
|
Describe("PartitionAndFormatDevice", Label("PartitionAndFormatDevice", "partition", "format"), func() {
|
|
var el *elemental.Elemental
|
|
var cInit *v1mock.FakeCloudInitRunner
|
|
var partNum int
|
|
var printOut string
|
|
var failPart bool
|
|
var install *v1.InstallSpec
|
|
|
|
BeforeEach(func() {
|
|
cInit = &v1mock.FakeCloudInitRunner{ExecStages: []string{}, Error: false}
|
|
config.CloudInitRunner = cInit
|
|
config.Install.Device = "/some/device"
|
|
el = elemental.NewElemental(config)
|
|
install = agentConfig.NewInstallSpec(config)
|
|
install.Target = "/some/device"
|
|
|
|
err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = fs.Create("/some/device")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
Describe("Successful run", func() {
|
|
var runFunc func(cmd string, args ...string) ([]byte, error)
|
|
var efiPartCmds, partCmds, biosPartCmds [][]string
|
|
BeforeEach(func() {
|
|
partNum, printOut = 0, printOutput
|
|
err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm)
|
|
Expect(err).To(BeNil())
|
|
efiPartCmds = [][]string{
|
|
{
|
|
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
|
|
"mklabel", "gpt",
|
|
}, {
|
|
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
|
|
"mkpart", "efi", "fat32", "2048", "133119", "set", "1", "esp", "on",
|
|
}, {"mkfs.vfat", "-n", "COS_GRUB", "/some/device1"},
|
|
}
|
|
biosPartCmds = [][]string{
|
|
{
|
|
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
|
|
"mklabel", "gpt",
|
|
}, {
|
|
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
|
|
"mkpart", "bios", "", "2048", "4095", "set", "1", "bios_grub", "on",
|
|
}, {"wipefs", "--all", "/some/device1"},
|
|
}
|
|
// These commands are only valid for EFI case
|
|
partCmds = [][]string{
|
|
{
|
|
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
|
|
"mkpart", "oem", "ext4", "133120", "264191",
|
|
}, {"mkfs.ext4", "-L", "COS_OEM", "/some/device2"}, {
|
|
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
|
|
"mkpart", "recovery", "ext4", "264192", "17041407",
|
|
}, {"mkfs.ext4", "-L", "COS_RECOVERY", "/some/device3"}, {
|
|
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
|
|
"mkpart", "state", "ext4", "17041408", "48498687",
|
|
}, {"mkfs.ext4", "-L", "COS_STATE", "/some/device4"}, {
|
|
"parted", "--script", "--machine", "--", "/some/device", "unit", "s",
|
|
"mkpart", "persistent", "ext4", "48498688", "100%",
|
|
}, {"mkfs.ext4", "-L", "COS_PERSISTENT", "/some/device5"},
|
|
}
|
|
|
|
runFunc = func(cmd string, args ...string) ([]byte, error) {
|
|
switch cmd {
|
|
case "parted":
|
|
idx := 0
|
|
for i, arg := range args {
|
|
if arg == "mkpart" {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
if idx > 0 {
|
|
partNum++
|
|
printOut += fmt.Sprintf(partTmpl, partNum, args[idx+3], args[idx+4])
|
|
_, _ = fs.Create(fmt.Sprintf("/some/device%d", partNum))
|
|
}
|
|
return []byte(printOut), nil
|
|
default:
|
|
return []byte{}, nil
|
|
}
|
|
}
|
|
runner.SideEffect = runFunc
|
|
})
|
|
|
|
It("Successfully creates partitions and formats them, EFI boot", func() {
|
|
install.PartTable = v1.GPT
|
|
install.Firmware = v1.EFI
|
|
install.Partitions.SetFirmwarePartitions(v1.EFI, v1.GPT)
|
|
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
|
|
Expect(runner.MatchMilestones(append(efiPartCmds, partCmds...))).To(BeNil())
|
|
})
|
|
|
|
It("Successfully creates partitions and formats them, BIOS boot", func() {
|
|
install.PartTable = v1.GPT
|
|
install.Firmware = v1.BIOS
|
|
install.Partitions.SetFirmwarePartitions(v1.BIOS, v1.GPT)
|
|
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
|
|
Expect(runner.MatchMilestones(biosPartCmds)).To(BeNil())
|
|
})
|
|
})
|
|
|
|
Describe("Run with failures", func() {
|
|
var runFunc func(cmd string, args ...string) ([]byte, error)
|
|
BeforeEach(func() {
|
|
err := fsutils.MkdirAll(fs, "/some", cnst.DirPerm)
|
|
Expect(err).To(BeNil())
|
|
partNum, printOut = 0, printOutput
|
|
runFunc = func(cmd string, args ...string) ([]byte, error) {
|
|
switch cmd {
|
|
case "parted":
|
|
idx := 0
|
|
for i, arg := range args {
|
|
if arg == "mkpart" {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
if idx > 0 {
|
|
partNum++
|
|
printOut += fmt.Sprintf(partTmpl, partNum, args[idx+3], args[idx+4])
|
|
if failPart {
|
|
return []byte{}, errors.New("Failure")
|
|
}
|
|
_, _ = fs.Create(fmt.Sprintf("/some/device%d", partNum))
|
|
}
|
|
return []byte(printOut), nil
|
|
case "mkfs.ext4", "wipefs", "mkfs.vfat":
|
|
return []byte{}, errors.New("Failure")
|
|
default:
|
|
return []byte{}, nil
|
|
}
|
|
}
|
|
runner.SideEffect = runFunc
|
|
})
|
|
|
|
It("Fails creating efi partition", func() {
|
|
failPart = true
|
|
Expect(el.PartitionAndFormatDevice(install)).NotTo(BeNil())
|
|
// Failed to create first partition
|
|
Expect(partNum).To(Equal(1))
|
|
})
|
|
|
|
It("Fails formatting efi partition", func() {
|
|
failPart = false
|
|
Expect(el.PartitionAndFormatDevice(install)).NotTo(BeNil())
|
|
// Failed to format first partition
|
|
Expect(partNum).To(Equal(1))
|
|
})
|
|
})
|
|
})
|
|
Describe("DeployImage", Label("DeployImage"), func() {
|
|
var el *elemental.Elemental
|
|
var img *v1.Image
|
|
var cmdFail string
|
|
BeforeEach(func() {
|
|
sourceDir, err := fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
destDir, err := fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
cmdFail = ""
|
|
el = elemental.NewElemental(config)
|
|
img = &v1.Image{
|
|
FS: constants.LinuxImgFs,
|
|
Size: 16,
|
|
Source: v1.NewDirSrc(sourceDir),
|
|
MountPoint: destDir,
|
|
File: filepath.Join(destDir, "image.img"),
|
|
Label: "some_label",
|
|
}
|
|
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
|
|
if cmdFail == cmd {
|
|
return []byte{}, errors.New("Command failed")
|
|
}
|
|
switch cmd {
|
|
case "losetup":
|
|
return []byte("/dev/loop"), nil
|
|
default:
|
|
return []byte{}, nil
|
|
}
|
|
}
|
|
})
|
|
It("Deploys an image from a directory and leaves it mounted", func() {
|
|
Expect(el.DeployImage(img, true)).To(BeNil())
|
|
})
|
|
It("Deploys an image from a directory and leaves it unmounted", func() {
|
|
Expect(el.DeployImage(img, false)).To(BeNil())
|
|
})
|
|
It("Deploys an squashfs image from a directory", func() {
|
|
img.FS = constants.SquashFs
|
|
Expect(el.DeployImage(img, true)).To(BeNil())
|
|
Expect(runner.MatchMilestones([][]string{
|
|
{
|
|
"mksquashfs", "/tmp/elemental-tmp", "/tmp/elemental/image.img",
|
|
"-b", "1024k", "-comp", "gzip",
|
|
},
|
|
}))
|
|
})
|
|
It("Deploys a file image and mounts it", func() {
|
|
sourceImg := "/source.img"
|
|
_, err := fs.Create(sourceImg)
|
|
Expect(err).To(BeNil())
|
|
destDir, err := fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).To(BeNil())
|
|
img.Source = v1.NewFileSrc(sourceImg)
|
|
img.MountPoint = destDir
|
|
Expect(el.DeployImage(img, true)).To(BeNil())
|
|
})
|
|
It("Deploys a file image and fails to mount it", func() {
|
|
sourceImg := "/source.img"
|
|
_, err := fs.Create(sourceImg)
|
|
Expect(err).To(BeNil())
|
|
destDir, err := fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).To(BeNil())
|
|
img.Source = v1.NewFileSrc(sourceImg)
|
|
img.MountPoint = destDir
|
|
mounter.ErrorOnMount = true
|
|
_, err = el.DeployImage(img, true)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
It("Deploys a file image and fails to label it", func() {
|
|
sourceImg := "/source.img"
|
|
_, err := fs.Create(sourceImg)
|
|
Expect(err).To(BeNil())
|
|
destDir, err := fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).To(BeNil())
|
|
img.Source = v1.NewFileSrc(sourceImg)
|
|
img.MountPoint = destDir
|
|
cmdFail = "tune2fs"
|
|
_, err = el.DeployImage(img, true)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
It("Fails creating the squashfs filesystem", func() {
|
|
cmdFail = "mksquashfs"
|
|
img.FS = constants.SquashFs
|
|
_, err := el.DeployImage(img, true)
|
|
Expect(err).NotTo(BeNil())
|
|
Expect(runner.MatchMilestones([][]string{
|
|
{
|
|
"mksquashfs", "/tmp/elemental-tmp", "/tmp/elemental/image.img",
|
|
"-b", "1024k", "-comp", "gzip",
|
|
},
|
|
}))
|
|
})
|
|
It("Fails formatting the image", func() {
|
|
cmdFail = "mkfs.ext2"
|
|
_, err := el.DeployImage(img, true)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
It("Fails mounting the image", func() {
|
|
mounter.ErrorOnMount = true
|
|
_, err := el.DeployImage(img, true)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
It("Fails unmounting the image after copying", func() {
|
|
mounter.ErrorOnUnmount = true
|
|
_, err := el.DeployImage(img, false)
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("DumpSource", Label("dump"), func() {
|
|
var e *elemental.Elemental
|
|
var destDir string
|
|
BeforeEach(func() {
|
|
var err error
|
|
e = elemental.NewElemental(config)
|
|
destDir, err = fsutils.TempDir(fs, "", "elemental")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
})
|
|
It("Copies files from a directory source", func() {
|
|
rsyncCount := 0
|
|
src := ""
|
|
dest := ""
|
|
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
|
|
if cmd == constants.Rsync {
|
|
rsyncCount += 1
|
|
src = args[len(args)-2]
|
|
dest = args[len(args)-1]
|
|
|
|
}
|
|
return []byte{}, nil
|
|
}
|
|
_, err := e.DumpSource("/dest", v1.NewDirSrc("/source"))
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(rsyncCount).To(Equal(1))
|
|
Expect(src).To(HaveSuffix("/source/"))
|
|
Expect(dest).To(HaveSuffix("/dest/"))
|
|
})
|
|
It("Unpacks a docker image to target", Label("docker"), func() {
|
|
_, err := e.DumpSource(destDir, v1.NewDockerSrc("docker/image:latest"))
|
|
Expect(err).To(BeNil())
|
|
})
|
|
It("Unpacks a docker image to target with cosign validation", Label("docker", "cosign"), func() {
|
|
config.Cosign = true
|
|
_, err := e.DumpSource(destDir, v1.NewDockerSrc("docker/image:latest"))
|
|
Expect(err).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{{"cosign", "verify", "docker/image:latest"}}))
|
|
})
|
|
It("Fails cosign validation", Label("cosign"), func() {
|
|
runner.ReturnError = errors.New("cosign error")
|
|
config.Cosign = true
|
|
_, err := e.DumpSource(destDir, v1.NewDockerSrc("docker/image:latest"))
|
|
Expect(err).NotTo(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{{"cosign", "verify", "docker/image:latest"}}))
|
|
})
|
|
It("Copies image file to target", func() {
|
|
sourceImg := "/source.img"
|
|
destFile := filepath.Join(destDir, "active.img")
|
|
_, err := fs.Create(sourceImg)
|
|
Expect(err).To(BeNil())
|
|
_, err = e.DumpSource(destFile, v1.NewFileSrc(sourceImg))
|
|
Expect(err).To(BeNil())
|
|
Expect(runner.IncludesCmds([][]string{{constants.Rsync}}))
|
|
})
|
|
It("Fails to copy, source file is not present", func() {
|
|
_, err := e.DumpSource("whatever", v1.NewFileSrc("/source.img"))
|
|
Expect(err).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("CheckActiveDeployment", Label("check"), func() {
|
|
It("deployment found", func() {
|
|
ghwTest := v1mock.GhwMock{}
|
|
disk := block.Disk{Name: "device", Partitions: []*block.Partition{
|
|
{
|
|
Name: "device1",
|
|
FilesystemLabel: cnst.ActiveLabel,
|
|
},
|
|
}}
|
|
ghwTest.AddDisk(disk)
|
|
ghwTest.CreateDevices()
|
|
defer ghwTest.Clean()
|
|
runner.ReturnValue = []byte(
|
|
fmt.Sprintf(
|
|
`{"blockdevices": [{"label": "%s", "type": "loop", "path": "/some/device"}]}`,
|
|
cnst.ActiveLabel,
|
|
),
|
|
)
|
|
e := elemental.NewElemental(config)
|
|
Expect(e.CheckActiveDeployment([]string{cnst.ActiveLabel, cnst.PassiveLabel})).To(BeTrue())
|
|
})
|
|
|
|
It("Should not error out", func() {
|
|
runner.ReturnValue = []byte("")
|
|
e := elemental.NewElemental(config)
|
|
Expect(e.CheckActiveDeployment([]string{cnst.ActiveLabel, cnst.PassiveLabel})).To(BeFalse())
|
|
})
|
|
})
|
|
Describe("SelinuxRelabel", Label("SelinuxRelabel", "selinux"), func() {
|
|
var policyFile string
|
|
var relabelCmd []string
|
|
BeforeEach(func() {
|
|
// to mock the existance of setfiles command on non selinux hosts
|
|
err := fsutils.MkdirAll(fs, "/usr/sbin", constants.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
sbin, err := fs.RawPath("/usr/sbin")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
path := os.Getenv("PATH")
|
|
os.Setenv("PATH", fmt.Sprintf("%s:%s", sbin, path))
|
|
_, err = fs.Create("/usr/sbin/setfiles")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
err = fs.Chmod("/usr/sbin/setfiles", 0777)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
// to mock SELinux policy files
|
|
policyFile = filepath.Join(constants.SELinuxTargetedPolicyPath, "policy.31")
|
|
err = fsutils.MkdirAll(fs, filepath.Dir(constants.SELinuxTargetedContextFile), constants.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
_, err = fs.Create(constants.SELinuxTargetedContextFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
err = fsutils.MkdirAll(fs, constants.SELinuxTargetedPolicyPath, constants.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
_, err = fs.Create(policyFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
relabelCmd = []string{
|
|
"setfiles", "-c", policyFile, "-e", "/dev", "-e", "/proc", "-e", "/sys",
|
|
"-F", constants.SELinuxTargetedContextFile, "/",
|
|
}
|
|
})
|
|
It("does nothing if the context file is not found", func() {
|
|
err := fs.Remove(constants.SELinuxTargetedContextFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("/", true)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{{}}))
|
|
})
|
|
It("does nothing if the policy file is not found", func() {
|
|
err := fs.Remove(policyFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("/", true)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{{}}))
|
|
})
|
|
It("relabels the current root", func() {
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("", true)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
|
|
|
runner.ClearCmds()
|
|
Expect(c.SelinuxRelabel("/", true)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
|
})
|
|
It("fails to relabel the current root", func() {
|
|
runner.ReturnError = errors.New("setfiles failure")
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("", true)).NotTo(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
|
})
|
|
It("ignores relabel failures", func() {
|
|
runner.ReturnError = errors.New("setfiles failure")
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("", false)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
|
})
|
|
It("relabels the given root-tree path", func() {
|
|
contextFile := filepath.Join("/root", constants.SELinuxTargetedContextFile)
|
|
err := fsutils.MkdirAll(fs, filepath.Dir(contextFile), constants.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
_, err = fs.Create(contextFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
policyFile = filepath.Join("/root", policyFile)
|
|
err = fsutils.MkdirAll(fs, filepath.Join("/root", constants.SELinuxTargetedPolicyPath), constants.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
_, err = fs.Create(policyFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
relabelCmd = []string{
|
|
"setfiles", "-c", policyFile, "-F", "-r", "/root", contextFile, "/root",
|
|
}
|
|
|
|
c := elemental.NewElemental(config)
|
|
Expect(c.SelinuxRelabel("/root", true)).To(BeNil())
|
|
Expect(runner.CmdsMatch([][]string{relabelCmd})).To(BeNil())
|
|
})
|
|
})
|
|
Describe("GetIso", Label("GetIso", "iso"), func() {
|
|
var e *elemental.Elemental
|
|
BeforeEach(func() {
|
|
e = elemental.NewElemental(config)
|
|
})
|
|
It("Gets the iso and returns the temporary where it is stored", func() {
|
|
tmpDir, err := fsutils.TempDir(fs, "", "elemental-test")
|
|
Expect(err).To(BeNil())
|
|
err = fs.WriteFile(fmt.Sprintf("%s/fake.iso", tmpDir), []byte("Hi"), cnst.FilePerm)
|
|
Expect(err).To(BeNil())
|
|
iso := fmt.Sprintf("%s/fake.iso", tmpDir)
|
|
isoDir, err := e.GetIso(iso)
|
|
Expect(err).To(BeNil())
|
|
// Confirm that the iso is stored in isoDir
|
|
fsutils.Exists(fs, filepath.Join(isoDir, "cOs.iso"))
|
|
})
|
|
It("Fails if it cant find the iso", func() {
|
|
iso := "whatever"
|
|
e := elemental.NewElemental(config)
|
|
_, err := e.GetIso(iso)
|
|
Expect(err).ToNot(BeNil())
|
|
})
|
|
It("Fails if it cannot mount the iso", func() {
|
|
mounter.ErrorOnMount = true
|
|
tmpDir, err := fsutils.TempDir(fs, "", "elemental-test")
|
|
Expect(err).To(BeNil())
|
|
err = fs.WriteFile(fmt.Sprintf("%s/fake.iso", tmpDir), []byte("Hi"), cnst.FilePerm)
|
|
Expect(err).To(BeNil())
|
|
iso := fmt.Sprintf("%s/fake.iso", tmpDir)
|
|
_, err = e.GetIso(iso)
|
|
Expect(err).ToNot(BeNil())
|
|
Expect(err.Error()).To(ContainSubstring("mount error"))
|
|
})
|
|
})
|
|
Describe("UpdateSourcesFormDownloadedISO", Label("iso"), func() {
|
|
var e *elemental.Elemental
|
|
var activeImg, recoveryImg *v1.Image
|
|
BeforeEach(func() {
|
|
activeImg, recoveryImg = nil, nil
|
|
e = elemental.NewElemental(config)
|
|
})
|
|
It("updates active image", func() {
|
|
activeImg = &v1.Image{}
|
|
err := e.UpdateSourcesFormDownloadedISO("/some/dir", activeImg, recoveryImg)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(activeImg.Source.IsDir()).To(BeTrue())
|
|
Expect(activeImg.Source.Value()).To(Equal("/some/dir/rootfs"))
|
|
Expect(recoveryImg).To(BeNil())
|
|
})
|
|
It("updates active and recovery image", func() {
|
|
activeImg = &v1.Image{File: "activeFile"}
|
|
recoveryImg = &v1.Image{}
|
|
err := e.UpdateSourcesFormDownloadedISO("/some/dir", activeImg, recoveryImg)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(recoveryImg.Source.IsFile()).To(BeTrue())
|
|
Expect(recoveryImg.Source.Value()).To(Equal("activeFile"))
|
|
Expect(recoveryImg.Label).To(Equal(cnst.SystemLabel))
|
|
Expect(activeImg.Source.IsDir()).To(BeTrue())
|
|
Expect(activeImg.Source.Value()).To(Equal("/some/dir/rootfs"))
|
|
})
|
|
It("updates recovery only image", func() {
|
|
recoveryImg = &v1.Image{}
|
|
isoMnt := "/some/dir/iso"
|
|
err := fsutils.MkdirAll(fs, isoMnt, cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
recoverySquash := filepath.Join(isoMnt, cnst.RecoverySquashFile)
|
|
_, err = fs.Create(recoverySquash)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
err = e.UpdateSourcesFormDownloadedISO("/some/dir", activeImg, recoveryImg)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(recoveryImg.Source.IsFile()).To(BeTrue())
|
|
Expect(recoveryImg.Source.Value()).To(Equal(recoverySquash))
|
|
Expect(activeImg).To(BeNil())
|
|
})
|
|
It("fails to update recovery from active file", func() {
|
|
recoveryImg = &v1.Image{}
|
|
err := e.UpdateSourcesFormDownloadedISO("/some/dir", activeImg, recoveryImg)
|
|
Expect(err).Should(HaveOccurred())
|
|
})
|
|
})
|
|
Describe("CloudConfig", Label("CloudConfig", "cloud-config"), func() {
|
|
var e *elemental.Elemental
|
|
BeforeEach(func() {
|
|
e = elemental.NewElemental(config)
|
|
})
|
|
It("Copies the cloud config file", func() {
|
|
testString := "In a galaxy far far away..."
|
|
cloudInit := []string{"/config.yaml"}
|
|
err := fs.WriteFile(cloudInit[0], []byte(testString), cnst.FilePerm)
|
|
Expect(err).To(BeNil())
|
|
Expect(err).To(BeNil())
|
|
|
|
err = e.CopyCloudConfig(cloudInit)
|
|
Expect(err).To(BeNil())
|
|
copiedFile, err := fs.ReadFile(fmt.Sprintf("%s/90_custom.yaml", cnst.OEMDir))
|
|
Expect(err).To(BeNil())
|
|
Expect(copiedFile).To(ContainSubstring(testString))
|
|
})
|
|
It("Doesnt do anything if the config file is not set", func() {
|
|
err := e.CopyCloudConfig([]string{})
|
|
Expect(err).To(BeNil())
|
|
})
|
|
})
|
|
Describe("SetDefaultGrubEntry", Label("SetDefaultGrubEntry", "grub"), func() {
|
|
It("Sets the default grub entry without issues", func() {
|
|
el := elemental.NewElemental(config)
|
|
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountpoint", "default_entry")).To(BeNil())
|
|
})
|
|
It("does nothing on empty default entry and no /etc/os-release", func() {
|
|
el := elemental.NewElemental(config)
|
|
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
|
|
// No grub2-editenv command called
|
|
Expect(runner.CmdsMatch([][]string{{"grub2-editenv"}})).NotTo(BeNil())
|
|
})
|
|
It("loads /etc/os-release on empty default entry", func() {
|
|
err := fsutils.MkdirAll(config.Fs, "/imgMountPoint/etc", constants.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
err = config.Fs.WriteFile("/imgMountPoint/etc/os-release", []byte("GRUB_ENTRY_NAME=test"), constants.FilePerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
el := elemental.NewElemental(config)
|
|
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
|
|
// Calls grub2-editenv with the loaded content from /etc/os-release
|
|
editEnv := utils.FindCommand("grub2-editenv", []string{"grub2-editenv", "grub-editenv"})
|
|
Expect(runner.CmdsMatch([][]string{
|
|
{editEnv, "/mountpoint/grub_oem_env", "set", "default_menu_entry=test"},
|
|
})).To(BeNil())
|
|
})
|
|
It("Fails setting grubenv", func() {
|
|
runner.ReturnError = errors.New("failure")
|
|
el := elemental.NewElemental(config)
|
|
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "default_entry")).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("FindKernelInitrd", Label("find"), func() {
|
|
BeforeEach(func() {
|
|
err := fsutils.MkdirAll(fs, "/path/boot", constants.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
})
|
|
It("finds kernel and initrd files", func() {
|
|
_, err := fs.Create("/path/boot/initrd")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
_, err = fs.Create("/path/boot/vmlinuz")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
el := elemental.NewElemental(config)
|
|
k, i, err := el.FindKernelInitrd("/path")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(k).To(Equal("/path/boot/vmlinuz"))
|
|
Expect(i).To(Equal("/path/boot/initrd"))
|
|
})
|
|
It("fails if no initrd is found", func() {
|
|
_, err := fs.Create("/path/boot/vmlinuz")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
el := elemental.NewElemental(config)
|
|
_, _, err = el.FindKernelInitrd("/path")
|
|
Expect(err).Should(HaveOccurred())
|
|
})
|
|
It("fails if no kernel is found", func() {
|
|
_, err := fs.Create("/path/boot/initrd")
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
|
|
el := elemental.NewElemental(config)
|
|
_, _, err = el.FindKernelInitrd("/path")
|
|
Expect(err).Should(HaveOccurred())
|
|
})
|
|
})
|
|
Describe("DeactivateDevices", Label("blkdeactivate"), func() {
|
|
It("calls blkdeactivat", func() {
|
|
el := elemental.NewElemental(config)
|
|
err := el.DeactivateDevices()
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(runner.CmdsMatch([][]string{{
|
|
"blkdeactivate", "--lvmoptions", "retry,wholevg",
|
|
"--dmoptions", "force,retry", "--errors",
|
|
}})).To(BeNil())
|
|
})
|
|
})
|
|
})
|
|
|
|
// PathInMountPoints will check if the given path is in the mountPoints list
|
|
func pathInMountPoints(mounter mount.Interface, path string) bool {
|
|
mountPoints, _ := mounter.List()
|
|
for _, m := range mountPoints {
|
|
if path == m.Path {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|