// Copyright (c) 2019, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE.md file distributed with the sources of this project regarding your // rights to use or distribute this software. package instance import ( "bytes" "io/ioutil" "os" "path/filepath" "strconv" "testing" "github.com/pkg/errors" "github.com/sylabs/singularity/e2e/internal/e2e" ) type ctx struct { env e2e.TestEnv privileged bool } // Test that no instances are running. func (c *ctx) testNoInstances(t *testing.T) { c.expectedNumberOfInstances(t, 0) } // Test that a basic echo server instance can be started, communicated with, // and stopped. func (c *ctx) testBasicEchoServer(t *testing.T) { const instanceName = "echo1" args := []string{c.env.ImagePath, instanceName, strconv.Itoa(instanceStartPort)} // Start the instance. c.env.RunSingularity( t, e2e.WithCommand("instance start"), e2e.WithPrivileges(c.privileged), e2e.WithArgs(args...), e2e.PostRun(func(t *testing.T) { if t.Failed() { return } // Try to contact the instance. echo(t, instanceStartPort) c.stopInstance(t, instanceName) }), e2e.ExpectExit(0), ) } // Test creating many instances, but don't stop them. func (c *ctx) testCreateManyInstances(t *testing.T) { const n = 10 // Start n instances. for i := 0; i < n; i++ { port := instanceStartPort + i instanceName := "echo" + strconv.Itoa(i+1) c.env.RunSingularity( t, e2e.WithCommand("instance start"), e2e.WithPrivileges(c.privileged), e2e.WithArgs(c.env.ImagePath, instanceName, strconv.Itoa(port)), e2e.PostRun(func(t *testing.T) { echo(t, port) }), e2e.ExpectExit(0), ) } // Verify all instances started. c.expectedNumberOfInstances(t, n) } // Test stopping all running instances. func (c *ctx) testStopAll(t *testing.T) { c.stopInstance(t, "", "--all") } // Test basic options like mounting a custom home directory, changing the // hostname, etc. func (c *ctx) testBasicOptions(t *testing.T) { const fileName = "hello" const instanceName = "testbasic" const testHostname = "echoserver99" fileContents := []byte("world") // Create a temporary directory to serve as a home directory. dir, err := ioutil.TempDir(c.env.TestDir, "TestInstance") if err != nil { t.Fatalf("Failed to create temporary directory: %v", err) } defer os.RemoveAll(dir) // Create and populate a temporary file. tempFile := filepath.Join(dir, fileName) err = ioutil.WriteFile(tempFile, fileContents, 0644) err = errors.Wrapf(err, "creating temporary test file %s", tempFile) if err != nil { t.Fatalf("Failed to create file: %+v", err) } // Start an instance with the temporary directory as the home directory. c.env.RunSingularity( t, e2e.WithCommand("instance start"), e2e.WithPrivileges(c.privileged), e2e.WithArgs( "-H", dir+":/home/temp", "--hostname", testHostname, "-e", c.env.ImagePath, instanceName, strconv.Itoa(instanceStartPort), ), e2e.PostRun(func(t *testing.T) { if t.Failed() { return } // Verify we can see the file's contents from within the container. stdout, _, success := c.execInstance(t, instanceName, "cat", "/home/temp/"+fileName) if success && !bytes.Equal(fileContents, []byte(stdout)) { t.Errorf("File contents were %s, but expected %s", stdout, string(fileContents)) } // Verify that the hostname has been set correctly. stdout, _, success = c.execInstance(t, instanceName, "hostname") if success && !bytes.Equal([]byte(testHostname+"\n"), []byte(stdout)) { t.Errorf("Hostname is %s, but expected %s", stdout, testHostname) } // Stop the instance. c.stopInstance(t, instanceName) }), e2e.ExpectExit(0), ) } // Test that contain works. func (c *ctx) testContain(t *testing.T) { const instanceName = "testcontain" const fileName = "thegreattestfile" // Create a temporary directory to serve as a contain directory. dir, err := ioutil.TempDir("", "TestInstance") if err != nil { t.Fatalf("Failed to create temporary directory: %v", err) } defer os.RemoveAll(dir) // Start the instance. c.env.RunSingularity( t, e2e.WithCommand("instance start"), e2e.WithPrivileges(c.privileged), e2e.WithArgs( "-c", "-W", dir, c.env.ImagePath, instanceName, strconv.Itoa(instanceStartPort), ), e2e.PostRun(func(t *testing.T) { if t.Failed() { return } // Touch a file within /tmp. _, _, success := c.execInstance(t, instanceName, "touch", "/tmp/"+fileName) if success { // Verify that the touched file exists outside the container. if _, err = os.Stat(filepath.Join(dir, "tmp", fileName)); os.IsNotExist(err) { t.Errorf("The temp file doesn't exist.") } } // Stop the container. c.stopInstance(t, instanceName) }), e2e.ExpectExit(0), ) } // Test by running directly from URI func (c *ctx) testInstanceFromURI(t *testing.T) { instances := []struct { name string uri string }{ { name: "test_from_docker", uri: "docker://busybox", }, { name: "test_from_library", uri: "library://busybox", }, // TODO(mem): reenable this; disabled while shub is down // { // name: "test_from_shub", // uri: "shub://singularityhub/busybox", // }, } for _, i := range instances { args := []string{i.uri, i.name} c.env.RunSingularity( t, e2e.WithCommand("instance start"), e2e.WithPrivileges(c.privileged), e2e.WithArgs(args...), e2e.PostRun(func(t *testing.T) { if t.Failed() { return } c.execInstance(t, i.name, "id") c.stopInstance(t, i.name) }), e2e.ExpectExit(0), ) } } // RunE2ETests is the bootstrap to run all instance tests. func RunE2ETests(env e2e.TestEnv) func(*testing.T) { c := &ctx{ env: env, privileged: false, } return func(t *testing.T) { e2e.EnsureImage(t, c.env) // Define and loop through tests. tests := []struct { name string function func(*testing.T) }{ {"InitialNoInstances", c.testNoInstances}, {"BasicEchoServer", c.testBasicEchoServer}, {"BasicOptions", c.testBasicOptions}, {"Contain", c.testContain}, {"InstanceFromURI", c.testInstanceFromURI}, {"CreateManyInstances", c.testCreateManyInstances}, {"StopAll", c.testStopAll}, {"FinalNoInstances", c.testNoInstances}, } // run unprivileged for _, tt := range tests { t.Run(tt.name, tt.function) } // run privileged c.privileged = true for _, tt := range tests { t.Run("WithPriv"+tt.name, tt.function) } } }