mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 12:26:21 +02:00
test: add unit-test for the installer manifest
This test only works on local machine (see notes in the file). Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
This commit is contained in:
parent
4701a5d40f
commit
a12eb76734
@ -60,7 +60,7 @@ func NewManifest(label string, sequence runtime.Sequence, opts *Options) (manife
|
||||
Targets: map[string][]*Target{},
|
||||
}
|
||||
|
||||
// Verify that the target device(s) can satisify the requested options.
|
||||
// Verify that the target device(s) can satisfy the requested options.
|
||||
|
||||
if sequence != runtime.SequenceUpgrade {
|
||||
if err = VerifyEphemeralPartition(opts); err != nil {
|
||||
@ -72,7 +72,7 @@ func NewManifest(label string, sequence runtime.Sequence, opts *Options) (manife
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize any slices we need. Note that a boot paritition is not
|
||||
// Initialize any slices we need. Note that a boot partition is not
|
||||
// required.
|
||||
|
||||
if manifest.Targets[opts.Disk] == nil {
|
||||
@ -304,42 +304,52 @@ func (t *Target) Format() error {
|
||||
// Save copies the assets to the bootloader partition.
|
||||
func (t *Target) Save() (err error) {
|
||||
for _, asset := range t.Assets {
|
||||
var (
|
||||
sourceFile *os.File
|
||||
destFile *os.File
|
||||
)
|
||||
asset := asset
|
||||
|
||||
if sourceFile, err = os.Open(asset.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
// nolint: errcheck
|
||||
defer sourceFile.Close()
|
||||
err = func() error {
|
||||
var (
|
||||
sourceFile *os.File
|
||||
destFile *os.File
|
||||
)
|
||||
|
||||
if err = os.MkdirAll(filepath.Dir(asset.Destination), os.ModeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if sourceFile, err = os.Open(asset.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
// nolint: errcheck
|
||||
defer sourceFile.Close()
|
||||
|
||||
if destFile, err = os.Create(asset.Destination); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = os.MkdirAll(filepath.Dir(asset.Destination), os.ModeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer destFile.Close()
|
||||
if destFile, err = os.Create(asset.Destination); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("copying %s to %s\n", sourceFile.Name(), destFile.Name())
|
||||
// nolint: errcheck
|
||||
defer destFile.Close()
|
||||
|
||||
if _, err = io.Copy(destFile, sourceFile); err != nil {
|
||||
log.Printf("failed to copy %s to %s\n", sourceFile.Name(), destFile.Name())
|
||||
return err
|
||||
}
|
||||
log.Printf("copying %s to %s\n", sourceFile.Name(), destFile.Name())
|
||||
|
||||
if err = destFile.Close(); err != nil {
|
||||
log.Printf("failed to close %s", destFile.Name())
|
||||
return err
|
||||
}
|
||||
if _, err = io.Copy(destFile, sourceFile); err != nil {
|
||||
log.Printf("failed to copy %s to %s\n", sourceFile.Name(), destFile.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sourceFile.Close(); err != nil {
|
||||
log.Printf("failed to close %s", sourceFile.Name())
|
||||
if err = destFile.Close(); err != nil {
|
||||
log.Printf("failed to close %s", destFile.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sourceFile.Close(); err != nil {
|
||||
log.Printf("failed to close %s", sourceFile.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,17 +13,96 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/talos-systems/go-blockdevice/blockdevice"
|
||||
|
||||
"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/loopback"
|
||||
)
|
||||
|
||||
// Some tests in this package cannot be run under buildkit, as buildkit doesn't propagate partition devices
|
||||
// like /dev/loopXpY into the sandbox. To run the tests on your local computer, do the following:
|
||||
//
|
||||
// sudo go test -v --count 1 ./cmd/installer/pkg/install/
|
||||
|
||||
type manifestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
disk *os.File
|
||||
loopbackDevice *os.File
|
||||
}
|
||||
|
||||
const diskSize = 10 * 1024 * 1024 * 1024 * 1024 // 10 GiB
|
||||
|
||||
func TestManifestSuite(t *testing.T) {
|
||||
suite.Run(t, new(manifestSuite))
|
||||
}
|
||||
|
||||
func (suite *manifestSuite) SetupSuite() {
|
||||
var err error
|
||||
|
||||
suite.disk, err = ioutil.TempFile("", "talos")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().NoError(suite.disk.Truncate(diskSize))
|
||||
|
||||
suite.loopbackDevice, err = loopback.NextLoopDevice()
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.T().Logf("Using %s", suite.loopbackDevice.Name())
|
||||
|
||||
suite.Require().NoError(loopback.Loop(suite.loopbackDevice, suite.disk))
|
||||
|
||||
suite.Require().NoError(loopback.LoopSetReadWrite(suite.loopbackDevice))
|
||||
}
|
||||
|
||||
func (suite *manifestSuite) TearDownSuite() {
|
||||
if suite.loopbackDevice != nil {
|
||||
suite.Assert().NoError(loopback.Unloop(suite.loopbackDevice))
|
||||
}
|
||||
|
||||
if suite.disk != nil {
|
||||
suite.Assert().NoError(os.Remove(suite.disk.Name()))
|
||||
suite.Assert().NoError(suite.disk.Close())
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *manifestSuite) skipUnderBuildkit() {
|
||||
hostname, _ := os.Hostname() //nolint: errcheck
|
||||
|
||||
if hostname == "buildkitsandbox" {
|
||||
suite.T().Skip("test not supported under buildkit as partition devices are not propagated from /dev")
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *manifestSuite) verifyBlockdevice() {
|
||||
bd, err := blockdevice.Open(suite.loopbackDevice.Name())
|
||||
suite.Require().NoError(err)
|
||||
|
||||
defer bd.Close() //nolint: errcheck
|
||||
|
||||
table, err := bd.PartitionTable()
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().Len(table.Partitions(), 5)
|
||||
|
||||
suite.Assert().NoError(bd.Close())
|
||||
}
|
||||
|
||||
func (suite *manifestSuite) TestExecuteManifestClean() {
|
||||
suite.skipUnderBuildkit()
|
||||
|
||||
manifest, err := install.NewManifest("A", runtime.SequenceInstall, &install.Options{
|
||||
Disk: suite.loopbackDevice.Name(),
|
||||
Force: true,
|
||||
})
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().NoError(manifest.ExecuteManifest())
|
||||
|
||||
suite.verifyBlockdevice()
|
||||
}
|
||||
|
||||
func (suite *manifestSuite) TestTargetInstall() {
|
||||
// Create Temp dirname for mountpoint
|
||||
dir, err := ioutil.TempDir("", "talostest")
|
||||
|
||||
126
internal/pkg/loopback/loopback.go
Normal file
126
internal/pkg/loopback/loopback.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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 loopback provides support for disk loopback devices (/dev/loopN).
|
||||
package loopback
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Copyright (c) 2017, Paul R. Tagliamonte <paultag@gmail.com>
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// syscalls will return an errno type (which implements error) for all calls,
|
||||
// including success (errno 0). We only care about non-zero errnos.
|
||||
func errnoIsErr(err error) error {
|
||||
if err.(syscall.Errno) != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Loop given a handle to a Loopback device (such as /dev/loop0), and a handle
|
||||
// to the image to loop mount (such as a squashfs or ext4fs image), performs
|
||||
// the required call to loop the image to the provided block device.
|
||||
func Loop(loopbackDevice, image *os.File) error {
|
||||
_, _, err := syscall.Syscall(
|
||||
syscall.SYS_IOCTL,
|
||||
loopbackDevice.Fd(),
|
||||
unix.LOOP_SET_FD,
|
||||
image.Fd(),
|
||||
)
|
||||
|
||||
return errnoIsErr(err)
|
||||
}
|
||||
|
||||
// LoopSetReadWrite clears the read-only flag on the loop devices.
|
||||
func LoopSetReadWrite(loopbackDevice *os.File) error {
|
||||
var status unix.LoopInfo64
|
||||
|
||||
_, _, err := syscall.Syscall(
|
||||
syscall.SYS_IOCTL,
|
||||
loopbackDevice.Fd(),
|
||||
unix.LOOP_GET_STATUS64,
|
||||
uintptr(unsafe.Pointer(&status)),
|
||||
)
|
||||
|
||||
if e := errnoIsErr(err); e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
status.Flags &= ^uint32(unix.LO_FLAGS_READ_ONLY)
|
||||
|
||||
_, _, err = syscall.Syscall(
|
||||
syscall.SYS_IOCTL,
|
||||
loopbackDevice.Fd(),
|
||||
unix.LOOP_SET_STATUS64,
|
||||
uintptr(unsafe.Pointer(&status)),
|
||||
)
|
||||
|
||||
runtime.KeepAlive(status)
|
||||
|
||||
return errnoIsErr(err)
|
||||
}
|
||||
|
||||
// Unloop given a handle to the Loopback device (such as /dev/loop0), preforms the
|
||||
// required call to the image to unloop the file.
|
||||
func Unloop(loopbackDevice *os.File) error {
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopbackDevice.Fd(), unix.LOOP_CLR_FD, 0)
|
||||
|
||||
return errnoIsErr(err)
|
||||
}
|
||||
|
||||
// NextLoopDevice gets the next loopback device that isn't used.
|
||||
//
|
||||
// Under the hood this will ask loop-control for the LOOP_CTL_GET_FREE value, and interpolate
|
||||
// that into the conventional GNU/Linux naming scheme for loopback devices, and os.Open
|
||||
// that path.
|
||||
func NextLoopDevice() (*os.File, error) {
|
||||
loopInt, err := nextUnallocatedLoop()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return os.OpenFile(fmt.Sprintf("/dev/loop%d", loopInt), os.O_RDWR, 0)
|
||||
}
|
||||
|
||||
// Return the integer of the next loopback device we can use by calling
|
||||
// loop-control with the LOOP_CTL_GET_FREE ioctl.
|
||||
func nextUnallocatedLoop() (int, error) {
|
||||
fd, err := os.OpenFile("/dev/loop-control", os.O_RDONLY, 0o644)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defer fd.Close() //nolint: errcheck
|
||||
|
||||
index, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd.Fd(), unix.LOOP_CTL_GET_FREE, 0)
|
||||
|
||||
return int(index), errnoIsErr(err)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user