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:
Andrey Smirnov 2020-10-14 23:08:31 +03:00 committed by talos-bot
parent 4701a5d40f
commit a12eb76734
3 changed files with 245 additions and 30 deletions

View File

@ -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
}
}

View File

@ -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")

View 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)
}