mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-04-27 03:11:14 +00:00
931 lines
33 KiB
Go
931 lines
33 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 (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/diskfs/go-diskfs"
|
|
"golang.org/x/sys/unix"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
sc "syscall"
|
|
"testing"
|
|
|
|
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
|
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"
|
|
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
|
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
|
ghwMock "github.com/kairos-io/kairos-sdk/ghw/mocks"
|
|
sdkTypes "github.com/kairos-io/kairos-sdk/types"
|
|
|
|
fileBackend "github.com/diskfs/go-diskfs/backend/file"
|
|
"github.com/diskfs/go-diskfs/partition/gpt"
|
|
"github.com/gofrs/uuid"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/sanity-io/litter"
|
|
"github.com/twpayne/go-vfs/v5/vfst"
|
|
)
|
|
|
|
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 sdkTypes.KairosLogger
|
|
var syscall *v1mock.FakeSyscall
|
|
var cl *v1mock.FakeHTTPClient
|
|
var mounter *v1mock.ErrorMounter
|
|
var fs *vfst.TestFS
|
|
var cleanup func()
|
|
var extractor *v1mock.FakeImageExtractor
|
|
var memLog *bytes.Buffer
|
|
var devLoopInt int
|
|
|
|
BeforeEach(func() {
|
|
memLog = &bytes.Buffer{}
|
|
logger = sdkTypes.NewBufferLogger(memLog)
|
|
logger.SetLevel("debug")
|
|
runner = v1mock.NewFakeRunner()
|
|
syscall = &v1mock.FakeSyscall{}
|
|
devLoopInt = 44
|
|
syscall.SideEffectSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err sc.Errno) {
|
|
// Trap the call for getting a free loop device number
|
|
if trap == sc.SYS_IOCTL && a2 == unix.LOOP_CTL_GET_FREE {
|
|
// This is a "get free loop device" syscall
|
|
// We return 44 so it gets the /dev/loop44 because we are cool like that
|
|
// Also we can check below that indeed it set that device as expected
|
|
return uintptr(devLoopInt), 0, sc.Errno(syscall.ReturnValue)
|
|
}
|
|
return 0, 0, sc.Errno(syscall.ReturnValue)
|
|
}
|
|
mounter = v1mock.NewErrorMounter()
|
|
cl = &v1mock.FakeHTTPClient{}
|
|
fs, cleanup, _ = vfst.NewTestFS(map[string]interface{}{
|
|
"/dev/loop-control": "",
|
|
fmt.Sprintf("/dev/loop%d", devLoopInt): "",
|
|
})
|
|
extractor = v1mock.NewFakeImageExtractor(logger)
|
|
config = agentConfig.NewConfig(
|
|
agentConfig.WithFs(fs),
|
|
agentConfig.WithRunner(runner),
|
|
agentConfig.WithLogger(logger),
|
|
agentConfig.WithMounter(mounter),
|
|
agentConfig.WithSyscall(syscall),
|
|
agentConfig.WithClient(cl),
|
|
agentConfig.WithImageExtractor(extractor),
|
|
)
|
|
})
|
|
AfterEach(func() { cleanup() })
|
|
Describe("MountRWPartition", Label("mount"), func() {
|
|
var el *elemental.Elemental
|
|
var parts v1.ElementalPartitions
|
|
BeforeEach(func() {
|
|
spec := &v1.InstallSpec{}
|
|
parts = agentConfig.NewInstallElementalPartitions(logger, spec)
|
|
|
|
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() {
|
|
spec := &v1.InstallSpec{}
|
|
parts = agentConfig.NewInstallElementalPartitions(logger, spec)
|
|
|
|
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() {
|
|
spec := &v1.InstallSpec{}
|
|
parts = agentConfig.NewInstallElementalPartitions(logger, spec)
|
|
|
|
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", File: "/image.file"}
|
|
Expect(fs.WriteFile("/image.file", []byte{}, cnst.FilePerm)).To(Succeed())
|
|
})
|
|
|
|
It("Mounts file system image", func() {
|
|
err := el.MountImage(img)
|
|
Expect(err).To(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(fmt.Sprintf("/dev/loop%d", devLoopInt)), litter.Sdump(img))
|
|
})
|
|
|
|
It("Fails to set a loop device", Label("loop"), func() {
|
|
// Return error on syscall call
|
|
syscall.ReturnValue = 10
|
|
Expect(el.MountImage(img)).NotTo(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(""))
|
|
})
|
|
|
|
It("Fails to mount a loop device", Label("loop"), func() {
|
|
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() {
|
|
el = elemental.NewElemental(config)
|
|
img = &v1.Image{MountPoint: "/some/mountpoint", File: "/image.file"}
|
|
Expect(fs.WriteFile("/image.file", []byte{}, cnst.FilePerm)).To(Succeed())
|
|
Expect(el.MountImage(img)).To(BeNil())
|
|
Expect(img.LoopDevice).To(Equal(fmt.Sprintf("/dev/loop%d", devLoopInt)))
|
|
})
|
|
|
|
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() {
|
|
syscall.ReturnValue = 10
|
|
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 := &sdkTypes.Partition{
|
|
Path: "/dev/device1",
|
|
FS: "ext4",
|
|
FilesystemLabel: "MY_LABEL",
|
|
}
|
|
Expect(el.FormatPartition(part)).To(BeNil())
|
|
})
|
|
|
|
})
|
|
Describe("PartitionAndFormatDevice", Label("PartitionAndFormatDevice", "partition", "format"), func() {
|
|
var cInit *v1mock.FakeCloudInitRunner
|
|
var install *v1.InstallSpec
|
|
var err error
|
|
var el *elemental.Elemental
|
|
var tmpDir string
|
|
|
|
BeforeEach(func() {
|
|
cInit = &v1mock.FakeCloudInitRunner{ExecStages: []string{}, Error: false}
|
|
config.CloudInitRunner = cInit
|
|
tmpDir, err = os.MkdirTemp("", "elements-*")
|
|
Expect(err).To(BeNil())
|
|
Expect(os.RemoveAll(filepath.Join(tmpDir, "/test.img"))).ToNot(HaveOccurred())
|
|
// at least 2Gb in size as state is set to 1G
|
|
_, err = fileBackend.CreateFromPath(filepath.Join(tmpDir, "/test.img"), 2*1024*1024*1024)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
config.Install.Device = filepath.Join(tmpDir, "/test.img")
|
|
install, err = agentConfig.NewInstallSpec(config)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
install.Target = filepath.Join(tmpDir, "/test.img")
|
|
el = elemental.NewElemental(config)
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(os.RemoveAll(tmpDir)).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("Successfully creates partitions and formats them, EFI boot", func() {
|
|
install.PartTable = v1.GPT
|
|
install.Firmware = v1.EFI
|
|
Expect(install.Partitions.SetFirmwarePartitions(v1.EFI, v1.GPT)).To(BeNil())
|
|
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
|
|
disk, err := fileBackend.OpenFromPath(filepath.Join(tmpDir, "/test.img"), true)
|
|
defer disk.Close()
|
|
table, err := gpt.Read(disk, int(diskfs.SectorSize512), int(diskfs.SectorSize512))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// check that its type GPT
|
|
Expect(table.Type()).To(Equal("gpt"))
|
|
// Expect the disk UUID to be constant
|
|
Expect(strings.ToLower(table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
|
|
// 5 partitions (boot, oem, recovery, state and persistent)
|
|
Expect(len(table.GetPartitions())).To(Equal(5))
|
|
// Cast the boot partition into specific type to check the type and such
|
|
part := table.GetPartitions()[0]
|
|
partition, ok := part.(*gpt.Partition)
|
|
Expect(ok).To(BeTrue())
|
|
// Should be efi type
|
|
Expect(partition.Type).To(Equal(gpt.EFISystemPartition))
|
|
// should have boot label
|
|
Expect(partition.Name).To(Equal(cnst.EfiPartName))
|
|
// Should have predictable UUID
|
|
Expect(strings.ToLower(partition.UUID())).To(Equal(strings.ToLower(uuid.NewV5(uuid.NamespaceURL, cnst.EfiLabel).String())))
|
|
// Check the rest have the proper types
|
|
for i := 1; i < len(table.GetPartitions()); i++ {
|
|
part := table.GetPartitions()[i]
|
|
partition, ok := part.(*gpt.Partition)
|
|
Expect(ok).To(BeTrue())
|
|
// all of them should have the Linux fs type
|
|
Expect(partition.Type).To(Equal(gpt.LinuxFilesystem))
|
|
}
|
|
})
|
|
It("Successfully creates partitions and formats them, BIOS boot", func() {
|
|
install.PartTable = v1.GPT
|
|
install.Firmware = v1.BIOS
|
|
Expect(install.Partitions.SetFirmwarePartitions(v1.BIOS, v1.GPT)).To(BeNil())
|
|
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
|
|
disk, err := fileBackend.OpenFromPath(filepath.Join(tmpDir, "/test.img"), true)
|
|
defer disk.Close()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
table, err := gpt.Read(disk, int(diskfs.SectorSize512), int(diskfs.SectorSize512))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// check that its type GPT
|
|
Expect(table.Type()).To(Equal("gpt"))
|
|
// Expect the disk UUID to be constant
|
|
Expect(strings.ToLower(table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
|
|
// 5 partitions (boot, oem, recovery, state and persistent)
|
|
Expect(len(table.GetPartitions())).To(Equal(5))
|
|
// Cast the boot partition into specific type to check the type and such
|
|
part := table.GetPartitions()[0]
|
|
partition, ok := part.(*gpt.Partition)
|
|
Expect(ok).To(BeTrue())
|
|
// Should be BIOS boot type
|
|
Expect(partition.Type).To(Equal(gpt.BIOSBoot))
|
|
// should have boot label
|
|
Expect(partition.Name).To(Equal(cnst.BiosPartName))
|
|
// Should have predictable UUID
|
|
Expect(strings.ToLower(partition.UUID())).To(Equal(strings.ToLower(uuid.NewV5(uuid.NamespaceURL, cnst.EfiLabel).String())))
|
|
for i := 1; i < len(table.GetPartitions()); i++ {
|
|
part := table.GetPartitions()[i]
|
|
partition, ok := part.(*gpt.Partition)
|
|
Expect(ok).To(BeTrue())
|
|
// all of them should have the Linux fs type
|
|
Expect(partition.Type).To(Equal(gpt.LinuxFilesystem))
|
|
}
|
|
})
|
|
})
|
|
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: cnst.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 {
|
|
default:
|
|
GinkgoWriter.Println(fmt.Sprintf("Command %s called but we dont catch it", cmd))
|
|
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 = cnst.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 = cnst.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 == cnst.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{{cnst.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 := ghwMock.GhwMock{}
|
|
disk := sdkTypes.Disk{Name: "device", Partitions: []*sdkTypes.Partition{
|
|
{
|
|
Name: "device1",
|
|
FilesystemLabel: cnst.ActiveLabel,
|
|
},
|
|
}}
|
|
ghwTest.AddDisk(disk)
|
|
ghwTest.CreateDevices()
|
|
|
|
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())
|
|
|
|
ghwTest.Clean()
|
|
})
|
|
|
|
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", cnst.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(cnst.SELinuxTargetedPolicyPath, "policy.31")
|
|
err = fsutils.MkdirAll(fs, filepath.Dir(cnst.SELinuxTargetedContextFile), cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
_, err = fs.Create(cnst.SELinuxTargetedContextFile)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
err = fsutils.MkdirAll(fs, cnst.SELinuxTargetedPolicyPath, cnst.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", cnst.SELinuxTargetedContextFile, "/",
|
|
}
|
|
})
|
|
It("does nothing if the context file is not found", func() {
|
|
err := fs.Remove(cnst.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", cnst.SELinuxTargetedContextFile)
|
|
err := fsutils.MkdirAll(fs, filepath.Dir(contextFile), cnst.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", cnst.SELinuxTargetedPolicyPath), cnst.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 := "http://whatever"
|
|
cl.Error = true
|
|
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())
|
|
configFilePath := fmt.Sprintf("%s/90_custom.yaml", cnst.OEMDir)
|
|
copiedFile, err := fs.ReadFile(configFilePath)
|
|
Expect(err).To(BeNil())
|
|
Expect(copiedFile).To(ContainSubstring(testString))
|
|
stat, err := fs.Stat(configFilePath)
|
|
Expect(err).To(BeNil())
|
|
Expect(int(stat.Mode().Perm())).To(Equal(cnst.ConfigPerm))
|
|
|
|
})
|
|
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(config.Fs.Mkdir("/tmp", cnst.DirPerm)).To(BeNil())
|
|
Expect(el.SetDefaultGrubEntry("/tmp", "/imgMountpoint", "dio")).To(BeNil())
|
|
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config)
|
|
Expect(err).To(BeNil())
|
|
Expect(varsParsed["default_menu_entry"]).To(Equal("dio"))
|
|
})
|
|
It("does nothing on empty default entry and no /etc/kairos-release", func() {
|
|
el := elemental.NewElemental(config)
|
|
Expect(config.Fs.Mkdir("/mountpoint", cnst.DirPerm)).To(BeNil())
|
|
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
|
|
_, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config)
|
|
// Because it didnt do anything due to the entry being empty, the file should not be there
|
|
Expect(err).ToNot(BeNil())
|
|
_, err = config.Fs.Stat(filepath.Join("/tmp", cnst.GrubOEMEnv))
|
|
Expect(err).ToNot(BeNil())
|
|
})
|
|
It("loads /etc/kairos-release on empty default entry", func() {
|
|
err := fsutils.MkdirAll(config.Fs, "/imgMountPoint/etc", cnst.DirPerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
err = config.Fs.WriteFile("/imgMountPoint/etc/kairos-release", []byte("GRUB_ENTRY_NAME=test"), cnst.FilePerm)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(config.Fs.Mkdir("/mountpoint", cnst.DirPerm)).To(BeNil())
|
|
|
|
el := elemental.NewElemental(config)
|
|
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
|
|
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/mountpoint", cnst.GrubOEMEnv), config)
|
|
Expect(err).To(BeNil())
|
|
Expect(varsParsed["default_menu_entry"]).To(Equal("test"))
|
|
|
|
})
|
|
It("Fails setting grubenv", func() {
|
|
el := elemental.NewElemental(config)
|
|
Expect(el.SetDefaultGrubEntry("nonexisting", "nonexisting", "default_entry")).NotTo(BeNil())
|
|
})
|
|
})
|
|
Describe("FindKernelInitrd", Label("find"), func() {
|
|
BeforeEach(func() {
|
|
err := fsutils.MkdirAll(fs, "/path/boot", cnst.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())
|
|
})
|
|
})
|
|
})
|