// Copyright © 2018 Intel Corporation // // SPDX-License-Identifier: GPL-3.0-only package model import ( "fmt" "io/ioutil" "os" "path/filepath" "strings" "testing" "github.com/clearlinux/clr-installer/args" "github.com/clearlinux/clr-installer/user" "github.com/clearlinux/clr-installer/utils" ) func init() { utils.SetLocale("en_US.UTF-8") } var ( testsDir string ) func init() { testsDir = os.Getenv("TESTS_DIR") testAlias = append(testAlias, "/dev/sda", "/dev/sdb") } func TestLoadFile(t *testing.T) { tests := []struct { file string valid bool }{ {"basic-invalid-descriptor.yaml", false}, {"basic-valid-descriptor.yaml", true}, {"invalid-no-keyboard.yaml", false}, {"invalid-no-language.yaml", false}, {"malformed-descriptor.yaml", false}, {"no-bootable-descriptor.yaml", false}, {"no-root-partition-descriptor.yaml", false}, {"no-telemetry.yaml", false}, {"invalid-no-kernel.yaml", false}, {"block-device-image.yaml", true}, {"block-devices-alias.yaml", true}, {"mixed-block-device.yaml", true}, {"real-example.yaml", true}, {"user-sshkeys.yaml", true}, {"valid-minimal.yaml", true}, {"valid-network.yaml", true}, {"valid-with-pre-post-hooks.yaml", true}, {"valid-with-version.yaml", true}, {"azure-config.json", true}, {"azure-docker-config.json", true}, {"azure-machine-learning-config.json", true}, {"ciao-networking-config.json", true}, {"cloud-config.json", true}, {"cloud-docker-config.json", true}, {"gce-config.json", true}, {"hyperv-config.json", true}, {"hyperv-mini-config.json", true}, {"hyperv-test-config.json", true}, {"kvm-config.json", true}, {"legacy-kvm-config.json", true}, {"live-config.json", true}, {"live-docker-config.json", true}, {"provision-config.json", true}, {"vmware-config.json", true}, {"mbr.json", true}, {"min-good.json", false}, {"release-image-config.json", true}, {"full-good.json", true}, {"installer-config.json", true}, {"installer-config-vm.json", true}, {"ister.json", true}, {"valid-ister-full-virtual.json", true}, {"valid-ister-full-physical.json", true}, {"invalid-ister-basic-descriptor.json", false}, {"invalid-ister-no-kernel.json", false}, {"invalid-ister-malformed-descriptor.json", false}, {"invalid-ister-dt.json", false}, {"invalid-ister-missing-pl.json", false}, {"invalid-ister-duplicate-pl.json", false}, {"invalid-ister-disk-ft.json", false}, {"invalid-ister-partition-ft.json", false}, {"invalid-ister-disk-pmp.json", false}, {"invalid-ister-partition-pmp.json", false}, } for _, curr := range tests { path := filepath.Join(testsDir, curr.file) var err error if filepath.Ext(curr.file) == ".json" { path, err = JSONtoYAMLConfig(path) if curr.valid && err != nil { t.Fatalf("%s is a valid test and shouldn't return an error: %v", curr.file, err) } } model, err := LoadFile(path, args.Args{}) if curr.valid && err != nil { t.Fatalf("%s is a valid tests and shouldn't return an error: %v", curr.file, err) } err = model.Validate() if curr.valid && err != nil { t.Fatalf("%s is a valid tests and shouldn't return an error: %v", curr.file, err) } } } func TestIsTestAlias(t *testing.T) { testAlias = []string{} if isTestAlias("/dev/sda") { t.Fatalf("Should have returned false for invalid alias") } testAlias = append(testAlias, "/dev/sda") if !isTestAlias("/dev/sda") { t.Fatalf("Should have returned true for valid alias") } testAlias = append(testAlias, "/dev/sdb") if !isTestAlias("/dev/sdb") { t.Fatalf("Should have returned true for valid alias") } } func TestBlockDevicesAlias(t *testing.T) { path := filepath.Join(testsDir, "block-devices-alias.yaml") model, err := LoadFile(path, args.Args{}) if err != nil { t.Fatalf("Failed to load yaml file: %s", err) } tm := model.TargetMedias[0] if tm.Name != "sda" { t.Fatalf("Failed to expand Name variable, value: %s, expected: sda", tm.Name) } if tm.GetDeviceFile() != "/dev/sda" { t.Fatalf("Invalid device name value: %s, expected: /dev/sda", tm.GetDeviceFile()) } for i, bd := range tm.Children { expected := fmt.Sprintf("sda%d", i+1) expectedFile := filepath.Join("/dev/", expected) if bd.Name != expected { t.Fatalf("Failed to expand Name variable, value: %s, expected: %s", bd.Name, expected) } if bd.GetDeviceFile() != expectedFile { t.Fatalf("Invalid device name value: %s, expected: %s", bd.GetDeviceFile(), expectedFile) } } } func TestBlockDevicesAliasOverwrite(t *testing.T) { path := filepath.Join(testsDir, "block-devices-alias.yaml") options := args.Args{BlockDevices: []string{"target:/dev/sdb"}} model, err := LoadFile(path, options) if err != nil { t.Fatalf("Failed to load yaml file: %s", err) } tm := model.TargetMedias[0] if tm.Name != "sdb" { t.Fatalf("Failed to expand Name variable, value: %s, expected: sdb", tm.Name) } if tm.GetDeviceFile() != "/dev/sdb" { t.Fatalf("Invalid device name value: %s, expected: /dev/sdb", tm.GetDeviceFile()) } for i, bd := range tm.Children { expected := fmt.Sprintf("sdb%d", i+1) expectedFile := filepath.Join("/dev/", expected) if bd.Name != expected { t.Fatalf("Failed to expand Name variable, value: %s, expected: %s", bd.Name, expected) } if bd.GetDeviceFile() != expectedFile { t.Fatalf("Invalid device name value: %s, expected: %s", bd.GetDeviceFile(), expectedFile) } } } func TestInvalidBlockDeviceArgument(t *testing.T) { path := filepath.Join(testsDir, "block-devices-alias.yaml") options := args.Args{BlockDevices: []string{"invalid"}} model, err := LoadFile(path, options) if err != nil { t.Fatalf("Failed to load yaml file: %s", err) } if len(model.StorageAlias) != 1 { t.Fatalf("The model should contain only 2 storage aliases") } for _, curr := range model.StorageAlias { if curr.Name == "invalid" { t.Fatalf("The \"invalid\" block-device argument shouldn't be added to the model") } } } func TestEnableTelemetry(t *testing.T) { si := &SystemInstall{} if si.IsTelemetryEnabled() == true { t.Fatal("Default value for telemetry should be false") } // should always succeed si.EnableTelemetry(true) if si.Telemetry == nil { t.Fatal("SystemInstall.EnableTelemetry() should allocate Telemetry object") } if si.IsTelemetryEnabled() == false { t.Fatal("Wrong Telemetry value set or returned") } } func TestUnreadable(t *testing.T) { file, err := ioutil.TempFile("", "test-") if err != nil { t.Fatal("Could not create a temp file") } defer func() { if err = file.Close(); err != nil { t.Fatal(err) } _ = os.Remove(file.Name()) }() if file.Chmod(0111) != nil { t.Fatal("Failed to change tmp file mod") } if utils.IsRoot() { t.Log("Not running as 'root', not checking read permission") } else { _, err = LoadFile(file.Name(), args.Args{}) if err == nil { t.Fatal("Should have failed to read") } } if os.Remove(file.Name()) != nil { t.Fatal("Failed to cleanup test file") } } func TestBundle(t *testing.T) { si := &SystemInstall{} if si.ContainsBundle("test-bundle") { t.Fatal("Should return false since test-bundle wasn't added to si") } si.AddBundle("test-bundle") si.AddBundle("test-bundle-2") if !si.ContainsBundle("test-bundle") { t.Fatal("Should return true since test-bundle was added to si") } si.RemoveBundle("test-bundle") if si.ContainsBundle("test-bundle") { t.Fatal("Should return false since test-bundle was removed from si") } si.RemoveBundle("test-bundle-2") // duplicated si.AddBundle("test-bundle") si.AddBundle("test-bundle") if len(si.Bundles) > 1 { t.Fatal("We should have handled the duplication") } } func TestUserBundle(t *testing.T) { si := &SystemInstall{} if si.ContainsUserBundle("test-ubundle") { t.Fatal("Should return false since test-ubundle wasn't added to si") } si.AddUserBundle("test-ubundle") si.AddUserBundle("test-ubundle-2") if !si.ContainsUserBundle("test-ubundle") { t.Fatal("Should return true since test-ubundle was added to si") } si.RemoveUserBundle("test-ubundle") if si.ContainsUserBundle("test-ubundle") { t.Fatal("Should return false since test-ubundle was removed from si") } si.RemoveUserBundle("test-ubundle-2") // duplicated si.AddUserBundle("test-ubundle") si.AddUserBundle("test-ubundle") if len(si.UserBundles) > 1 { t.Fatal("We should have handled the duplication") } } func TestAddTargetMedia(t *testing.T) { path := filepath.Join(testsDir, "basic-valid-descriptor.yaml") loaded, err := LoadFile(path, args.Args{}) if err != nil { t.Fatal("Failed to load a valid descriptor") } nm := &SystemInstall{} nm.AddTargetMedia(loaded.TargetMedias[0]) if len(nm.TargetMedias) != 1 { t.Fatal("Failed to add target media to model") } // the AddTargetMedia() interface must prevent duplication cl := len(nm.TargetMedias) nm.AddTargetMedia(loaded.TargetMedias[0]) if len(nm.TargetMedias) != cl { t.Fatal("AddTargetMedia() must prevent duplication") } // AddTargetMedia() should always add non equal medias clone := loaded.TargetMedias[0].Clone() clone.Name = clone.Name + "-cloned" // Check for encryption passphrase needed; should not if nm.EncryptionRequiresPassphrase() { t.Fatal("nm.EncryptionRequiresPassphrase() should NOT be true") } nm.AddTargetMedia(clone) if len(nm.TargetMedias) == cl { t.Fatal("AddTargetMedia() failed to add a cloned and modified target media") } } func TestAddNetworkInterface(t *testing.T) { path := filepath.Join(testsDir, "valid-network.yaml") loaded, err := LoadFile(path, args.Args{}) if err != nil { t.Fatal("Failed to load a valid descriptor") } nm := &SystemInstall{} nm.AddNetworkInterface(loaded.NetworkInterfaces[0]) if len(nm.NetworkInterfaces) != 1 { t.Fatal("Failed to add network interface to model") } } func TestUser(t *testing.T) { users := []*user.User{ {Login: "login1", Password: "pwd1", Admin: false}, {Login: "login2", Password: "pwd2", Admin: false}, {Login: "login3", Password: "pwd3", Admin: false}, {Login: "login4", Password: "pwd4", Admin: false}, } si := &SystemInstall{} for i, curr := range users { si.AddUser(curr) if len(si.Users) != i+1 { t.Fatal("User wasn't added") } } cl := len(si.Users) // don't add same user twice si.AddUser(users[0]) if len(si.Users) != cl { t.Fatal("The AddUser() interface should prevent user duplication") } si.RemoveAllUsers() if len(si.Users) != 0 { t.Fatal("User list should be empty") } } func TestWriteFile(t *testing.T) { path := filepath.Join(testsDir, "basic-valid-descriptor.yaml") loaded, err := LoadFile(path, args.Args{}) if err != nil { t.Fatal("Failed to load a valid descriptor") } tmpFile, err := ioutil.TempFile("", "test-") if err != nil { t.Fatal("Could not create a temp file") } defer func() { _ = os.Remove(tmpFile.Name()) }() path = tmpFile.Name() if err = tmpFile.Close(); err != nil { t.Fatal(err) } if err := loaded.WriteFile(path); err != nil { t.Fatal("Failed to write descriptor, should be valid") } // test writing to an invalid file if err := loaded.WriteFile("/invalid-dir/invalid.yaml"); err == nil { t.Fatal("Should have failed writing to an invalid file") } } func TestAddExtraKernelArguments(t *testing.T) { args := []string{"arg1", "arg2", "arg3"} si := &SystemInstall{} si.AddExtraKernelArguments(args) if si.KernelArguments == nil { t.Fatal("AddExtraKernelArguments() should had created a KernelArguments object") } if len(si.KernelArguments.Add) != len(args) { t.Fatal("AddExtraKernelArguments() didn't add all requested arguments") } for _, curr := range args { if !utils.StringSliceContains(si.KernelArguments.Add, curr) { t.Fatal("AddExtraKernelArguments() didn't add all the requested arguments") } } l := len(si.KernelArguments.Add) // testing duplication checks si.AddExtraKernelArguments(args) if l < len(si.KernelArguments.Add) { t.Fatal("The duplication check has failed") } } func TestRemoveKernelArguments(t *testing.T) { args := []string{"arg1", "arg2", "arg3"} si := &SystemInstall{} si.RemoveKernelArguments(args) if si.KernelArguments == nil { t.Fatal("RemoveKernelArguments() should had created a KernelArguments object") } if len(si.KernelArguments.Remove) != len(args) { t.Fatal("RemoveKernelArguments() didn't add all requested arguments") } for _, curr := range args { if !utils.StringSliceContains(si.KernelArguments.Remove, curr) { t.Fatal("RemoveKernelArguments() didn't add all the requested arguments") } } l := len(si.KernelArguments.Remove) // testing duplication check si.RemoveKernelArguments(args) if l < len(si.KernelArguments.Remove) { t.Fatal("The duplication check has failed") } } func TestAddEncryptedTargetMedia(t *testing.T) { path := filepath.Join(testsDir, "encrypt-valid-descriptor.yaml") loaded, err := LoadFile(path, args.Args{}) if err != nil { t.Fatal("Failed to load a valid descriptor") } nm := &SystemInstall{} nm.AddTargetMedia(loaded.TargetMedias[0]) if len(nm.TargetMedias) != 1 { t.Fatal("Failed to add target media to model") } cl := len(nm.TargetMedias) // AddTargetMedia() should always add non equal medias clone := loaded.TargetMedias[0].Clone() clone.Name = clone.Name + "-cloned" // Check for encryption passphrase needed; should not if !nm.EncryptionRequiresPassphrase() { t.Fatal("nm.EncryptionRequiresPassphrase() must always be true") } nm.AddTargetMedia(clone) if len(nm.TargetMedias) == cl { t.Fatal("AddTargetMedia() failed to add a cloned and modified target media") } } func TestBackupFile(t *testing.T) { var err error path := filepath.Join(testsDir, "valid-ister-full-physical.json") cf := strings.TrimSuffix(path, filepath.Ext(path)) + ".yaml" info, err := os.Stat(cf) if os.IsNotExist(err) { t.Fatalf("%s should already exist and shouldn't return an error: %v", cf, err) } mt := info.ModTime() suffix := fmt.Sprintf("-%d-%02d-%02d-%02d%02d%02d", mt.Year(), mt.Month(), mt.Day(), mt.Hour(), mt.Minute(), mt.Second()) bf := strings.TrimSuffix(cf, filepath.Ext(cf)) + suffix + ".yaml" path, err = JSONtoYAMLConfig(path) if err != nil { t.Fatalf("%s is a valid test and shouldn't return an error: %v", path, err) } info, err = os.Stat(cf) if os.IsNotExist(err) { t.Fatalf("%s should still exist and shouldn't return an error: %v", cf, err) } info, err = os.Stat(bf) if os.IsNotExist(err) { t.Fatalf("%s should exist and shouldn't return an error: %v", cf, err) } }