mirror of
https://github.com/coreos/ignition.git
synced 2026-02-06 09:47:17 +01:00
Addresses the staticcheck lint error by replacing all instances of strings.Replace(s, old, new, -1) with the more explicit strings.ReplaceAll(s, old, new). Fixes lint: ``` QF1004: could use strings.ReplaceAll instead (staticcheck) ```
316 lines
9.1 KiB
Go
316 lines
9.1 KiB
Go
// Copyright 2017 CoreOS, Inc.
|
|
//
|
|
// 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 blackbox
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/coreos/ignition/v2/internal/exec/util"
|
|
"github.com/coreos/ignition/v2/tests/types"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func regexpSearch(itemName, pattern string, data []byte) (string, error) {
|
|
re := regexp.MustCompile(pattern)
|
|
match := re.FindSubmatch(data)
|
|
if len(match) < 2 {
|
|
return "", fmt.Errorf("couldn't find %s", itemName)
|
|
}
|
|
return string(match[1]), nil
|
|
}
|
|
|
|
func getPartitionSet(device string) (map[int]struct{}, error) {
|
|
sgdiskOverview, err := exec.Command("sgdisk", "-p", device).CombinedOutput()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sgdisk -p %s failed: %v", device, err)
|
|
}
|
|
|
|
//What this regex means: num start end size,code,name
|
|
re := regexp.MustCompile("\n\\W+(\\d+)\\W+\\d+\\W+\\d+\\W+\\d+.*")
|
|
ret := map[int]struct{}{}
|
|
for _, match := range re.FindAllStringSubmatch(string(sgdiskOverview), -1) {
|
|
if len(match) == 0 {
|
|
continue
|
|
}
|
|
if len(match) != 2 {
|
|
return nil, fmt.Errorf("invalid regex result from parsing sgdisk")
|
|
}
|
|
num, err := strconv.Atoi(match[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret[num] = struct{}{}
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func validateDisk(t *testing.T, d types.Disk) error {
|
|
partitionSet, err := getPartitionSet(d.Device)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, e := range d.Partitions {
|
|
if e.TypeCode == "blank" {
|
|
continue
|
|
}
|
|
|
|
if _, ok := partitionSet[e.Number]; !ok {
|
|
t.Errorf("Partition %d is missing", e.Number)
|
|
}
|
|
delete(partitionSet, e.Number)
|
|
|
|
sgdiskInfo, err := exec.Command(
|
|
"sgdisk", "-i", strconv.Itoa(e.Number),
|
|
d.Device).CombinedOutput()
|
|
if err != nil {
|
|
t.Error("sgdisk -i", strconv.Itoa(e.Number), err)
|
|
return nil
|
|
}
|
|
|
|
actualGUID, err := regexpSearch("GUID", "Partition unique GUID: (?P<partition_guid>[\\d\\w-]+)", sgdiskInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
actualTypeGUID, err := regexpSearch("type GUID", "Partition GUID code: (?P<partition_code>[\\d\\w-]+)", sgdiskInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
actualSectors, err := regexpSearch("partition size", "Partition size: (?P<sectors>\\d+) sectors", sgdiskInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
actualLabel, err := regexpSearch("partition name", "Partition name: '(?P<name>[\\d\\w-_]+)'", sgdiskInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// have to align the size to the nearest sector alignment boundary first
|
|
expectedSectors := types.Align(e.Length, d.Alignment)
|
|
|
|
if e.TypeGUID != "" && formatUUID(e.TypeGUID) != formatUUID(actualTypeGUID) {
|
|
t.Error("TypeGUID does not match!", e.TypeGUID, actualTypeGUID)
|
|
}
|
|
if e.GUID != "" && formatUUID(e.GUID) != formatUUID(actualGUID) {
|
|
t.Error("GUID does not match!", e.GUID, actualGUID)
|
|
}
|
|
if e.Label != actualLabel {
|
|
t.Error("Label does not match!", e.Label, actualLabel)
|
|
}
|
|
if strconv.Itoa(expectedSectors) != actualSectors {
|
|
t.Error(
|
|
"Sectors does not match!", expectedSectors, actualSectors)
|
|
}
|
|
}
|
|
|
|
if len(partitionSet) != 0 {
|
|
t.Error("Disk had extra partitions", partitionSet)
|
|
}
|
|
|
|
// TODO: inspect the disk without triggering partition rescans so we don't need to settle here
|
|
if _, err := runWithoutContext("udevadm", "settle"); err != nil {
|
|
t.Log(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func formatUUID(s string) string {
|
|
return strings.ToUpper(strings.ReplaceAll(s, "-", ""))
|
|
}
|
|
|
|
func validateFilesystems(t *testing.T, expected []*types.Partition) error {
|
|
for _, e := range expected {
|
|
if e.FilesystemType == "" &&
|
|
e.FilesystemUUID == "" &&
|
|
e.FilesystemLabel == "" {
|
|
continue
|
|
}
|
|
info, err := util.GetFilesystemInfo(e.Device, e.Ambivalent)
|
|
if err != nil {
|
|
return fmt.Errorf("couldn't get filesystem info: %v", err)
|
|
}
|
|
if e.FilesystemType != "" {
|
|
if info.Type != e.FilesystemType {
|
|
t.Errorf("FilesystemType does not match, expected:%q actual:%q",
|
|
e.FilesystemType, info.Type)
|
|
}
|
|
}
|
|
if e.FilesystemUUID != "" {
|
|
if formatUUID(info.UUID) != formatUUID(e.FilesystemUUID) {
|
|
t.Errorf("FilesystemUUID does not match, expected:%q actual:%q",
|
|
e.FilesystemUUID, info.UUID)
|
|
}
|
|
}
|
|
if e.FilesystemLabel != "" {
|
|
if info.Label != e.FilesystemLabel {
|
|
t.Errorf("FilesystemLabel does not match, expected:%q actual:%q",
|
|
e.FilesystemLabel, info.Label)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validatePartitionNodes(t *testing.T, ctx context.Context, partition *types.Partition) {
|
|
if len(partition.Files) == 0 &&
|
|
len(partition.Directories) == 0 &&
|
|
len(partition.Links) == 0 &&
|
|
len(partition.RemovedNodes) == 0 {
|
|
return
|
|
}
|
|
if err := mountPartition(ctx, partition); err != nil {
|
|
t.Errorf("failed to mount %s: %v", partition.Device, err)
|
|
}
|
|
defer func() {
|
|
if err := umountPartition(partition); err != nil {
|
|
// failing to unmount is not a validation failure
|
|
t.Fatalf("Failed to unmount %s: %v", partition.MountPath, err)
|
|
}
|
|
}()
|
|
for _, file := range partition.Files {
|
|
validateFile(t, partition, file)
|
|
}
|
|
for _, dir := range partition.Directories {
|
|
validateDirectory(t, partition, dir)
|
|
}
|
|
for _, link := range partition.Links {
|
|
validateLink(t, partition, link)
|
|
}
|
|
for _, node := range partition.RemovedNodes {
|
|
path := filepath.Join(partition.MountPath, node.Directory, node.Name)
|
|
if _, err := os.Lstat(path); !os.IsNotExist(err) {
|
|
t.Error("Node was expected to be removed and is present!", path)
|
|
}
|
|
}
|
|
}
|
|
|
|
func validateFilesDirectoriesAndLinks(t *testing.T, ctx context.Context, expected []*types.Partition) {
|
|
for _, partition := range expected {
|
|
if partition.TypeCode == "blank" || partition.Length == 0 || partition.FilesystemType == "" || partition.FilesystemType == "swap" {
|
|
continue
|
|
}
|
|
validatePartitionNodes(t, ctx, partition)
|
|
}
|
|
}
|
|
|
|
func validateFile(t *testing.T, partition *types.Partition, file types.File) {
|
|
path := filepath.Join(partition.MountPath, file.Directory, file.Name)
|
|
fileInfo := unix.Stat_t{}
|
|
if err := unix.Lstat(path, &fileInfo); err != nil {
|
|
t.Errorf("Error stat'ing file %s: %v", path, err)
|
|
return
|
|
}
|
|
if file.Contents != "" {
|
|
dat, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Error("Error when reading file", path)
|
|
return
|
|
}
|
|
|
|
actualContents := string(dat)
|
|
if file.Contents != actualContents {
|
|
t.Error("Contents of file", path, "do not match!",
|
|
file.Contents, actualContents)
|
|
}
|
|
}
|
|
|
|
validateMode(t, path, file.Mode)
|
|
validateNode(t, fileInfo, file.Node)
|
|
}
|
|
|
|
func validateDirectory(t *testing.T, partition *types.Partition, dir types.Directory) {
|
|
path := filepath.Join(partition.MountPath, dir.Directory, dir.Name)
|
|
dirInfo := unix.Stat_t{}
|
|
if err := unix.Lstat(path, &dirInfo); err != nil {
|
|
t.Errorf("Error stat'ing directory %s: %v", path, err)
|
|
return
|
|
}
|
|
if dirInfo.Mode&unix.S_IFDIR == 0 {
|
|
t.Errorf("Node at %s is not a directory!", path)
|
|
}
|
|
validateMode(t, path, dir.Mode)
|
|
validateNode(t, dirInfo, dir.Node)
|
|
}
|
|
|
|
func validateLink(t *testing.T, partition *types.Partition, link types.Link) {
|
|
linkPath := filepath.Join(partition.MountPath, link.Directory, link.Name)
|
|
linkInfo := unix.Stat_t{}
|
|
if err := unix.Lstat(linkPath, &linkInfo); err != nil {
|
|
t.Error("Error stat'ing link \"" + linkPath + "\": " + err.Error())
|
|
return
|
|
}
|
|
if link.Hard {
|
|
targetPath := filepath.Join(partition.MountPath, link.Target)
|
|
targetInfo := unix.Stat_t{}
|
|
if err := unix.Lstat(targetPath, &targetInfo); err != nil {
|
|
t.Error("Error stat'ing target \"" + targetPath + "\": " + err.Error())
|
|
return
|
|
}
|
|
if linkInfo.Ino != targetInfo.Ino {
|
|
t.Error("Hard link and target don't have same inode value: " + linkPath + " " + targetPath)
|
|
return
|
|
}
|
|
} else {
|
|
if linkInfo.Mode&unix.S_IFLNK == 0 {
|
|
t.Errorf("Node at symlink path is not a symlink (it's a %s): %s", os.FileMode(linkInfo.Mode).String(), linkPath)
|
|
return
|
|
}
|
|
targetPath, err := os.Readlink(linkPath)
|
|
if err != nil {
|
|
t.Error("Error reading symbolic link: " + err.Error())
|
|
return
|
|
}
|
|
if targetPath != link.Target {
|
|
t.Errorf("Actual and expected symbolic link targets don't match. Expected %q, got %q", link.Target, targetPath)
|
|
return
|
|
}
|
|
}
|
|
validateNode(t, linkInfo, link.Node)
|
|
}
|
|
|
|
func validateMode(t *testing.T, path string, mode int) {
|
|
if mode != 0 {
|
|
fileInfo, err := os.Lstat(path)
|
|
if err != nil {
|
|
t.Error("Error running stat on node", path, err)
|
|
return
|
|
}
|
|
|
|
found := fileInfo.Mode() & ^os.ModeType
|
|
if found != util.ToFileMode(mode) {
|
|
t.Error("Node Mode does not match", path, util.ToFileMode(mode), found)
|
|
}
|
|
}
|
|
}
|
|
|
|
func validateNode(t *testing.T, nodeInfo unix.Stat_t, node types.Node) {
|
|
if nodeInfo.Uid != uint32(node.User) {
|
|
t.Error("Node has the wrong owner", node.User, nodeInfo.Uid)
|
|
}
|
|
|
|
if nodeInfo.Gid != uint32(node.Group) {
|
|
t.Error("Node has the wrong group owner", node.Group, nodeInfo.Gid)
|
|
}
|
|
}
|