func makeResultSet(t *testing.T) resultset { temp, err := runtime.NewTemporaryStorage(os.TempDir()) if err != nil { t.Fatal(err) } context, _, err := runtime.NewTaskContext(temp.NewFilePath(), runtime.TaskInfo{}, nil) if err != nil { t.Fatal(err) } config := configType{ CreateUser: false, } e := engine{ config: &config, log: logrus.New().WithField("component", "test"), } return resultset{ ResultSetBase: engines.ResultSetBase{}, taskUser: user{}, context: context, success: true, engine: &e, } }
// NewTestEnvironment creates a new Environment suitable for use in tests. // // This function should only be used in testing func newTestEnvironment() *runtime.Environment { storage, err := runtime.NewTemporaryStorage(os.TempDir()) nilOrPanic(err, "Failed to create temporary storage at: ", os.TempDir()) folder, err := storage.NewFolder() nilOrPanic(err, "Failed to create temporary storage folder") // Set finalizer so that we always get the temporary folder removed. // This is should really only be used in tests, otherwise it would better to // call Remove() manually. rt.SetFinalizer(folder, func(f runtime.TemporaryFolder) { f.Remove() }) logger, err := runtime.CreateLogger(os.Getenv("LOGGING_LEVEL")) if err != nil { fmt.Fprintf(os.Stderr, "Error creating logger. %s", err) os.Exit(1) } return &runtime.Environment{ GarbageCollector: &gc.GarbageCollector{}, TemporaryStorage: folder, Log: logger, } }
func ensureEnvironment(t *testing.T) (*runtime.Environment, engines.Engine, plugins.Plugin) { tempPath := filepath.Join(os.TempDir(), slugid.Nice()) tempStorage, err := runtime.NewTemporaryStorage(tempPath) if err != nil { t.Fatal(err) } environment := &runtime.Environment{ TemporaryStorage: tempStorage, } engineProvider := engines.Engines()["mock"] engine, err := engineProvider.NewEngine(engines.EngineOptions{ Environment: environment, Log: logger.WithField("engine", "mock"), }) if err != nil { t.Fatal(err.Error()) } pluginOptions := plugins.PluginOptions{ Environment: environment, Engine: engine, Log: logger.WithField("component", "Plugin Manager"), } pm, err := plugins.Plugins()["success"].NewPlugin(pluginOptions) if err != nil { t.Fatalf("Error creating task manager. Could not create plugin manager. %s", err) } return environment, engine, pm }
func TestGuestToolsFailed(t *testing.T) { // Create temporary storage storage, err := runtime.NewTemporaryStorage(os.TempDir()) if err != nil { panic("Failed to create TemporaryStorage") } environment := &runtime.Environment{ TemporaryStorage: storage, } // Setup a new MetaService logTask := bytes.NewBuffer(nil) result := false resolved := false m := sync.Mutex{} s := metaservice.New([]string{"sh", "-c", "echo \"$TEST_TEXT\" && false"}, map[string]string{ "TEST_TEXT": "Hello world", }, logTask, func(r bool) { m.Lock() defer m.Unlock() if resolved { panic("It shouldn't be possible to resolve twice") } resolved = true result = r }, environment) // Create http server for testing ts := httptest.NewServer(s) defer ts.Close() u, err := url.Parse(ts.URL) if err != nil { panic("Expected a url we can parse") } // Create a logger logger, _ := runtime.CreateLogger("info") log := logger.WithField("component", "guest-tools-tests") // Create an run guest-tools g := new(u.Host, log) g.Run() // Check the state if !resolved { t.Error("Expected the metadata to have resolved the task") } if result != false { t.Error("Expected the metadata to get failed result") } if !strings.Contains(logTask.String(), "Hello world") { t.Error("Got unexpected taskLog: '", logTask.String(), "'") } }
func newTestSandbox(taskPayload *payloadType, env []string) (*sandbox, error) { temp, err := runtime.NewTemporaryStorage(os.TempDir()) if err != nil { return nil, err } context, _, err := runtime.NewTaskContext(temp.NewFilePath(), runtime.TaskInfo{}, nil) if err != nil { return nil, err } config := configType{ CreateUser: false, } e := engine{ EngineBase: engines.EngineBase{}, config: &config, log: logrus.New().WithField("component", "test"), } return newSandbox(context, taskPayload, env, &e), nil }
// New will create a worker given configuration matching the schema from // ConfigSchema(). The log parameter is optional and if nil is given a default // logrus logger will be used. func New(config interface{}, log *logrus.Logger) (*Worker, error) { // Validate and map configuration to c var c configType if err := schematypes.MustMap(ConfigSchema(), config, &c); err != nil { return nil, fmt.Errorf("Invalid configuration: %s", err) } // Create temporary folder err := os.RemoveAll(c.TemporaryFolder) if err != nil { return nil, fmt.Errorf("Failed to remove temporaryFolder: %s, error: %s", c.TemporaryFolder, err) } tempStorage, err := runtime.NewTemporaryStorage(c.TemporaryFolder) if err != nil { return nil, fmt.Errorf("Failed to create temporary folder, error: %s", err) } // Create logger if log == nil { log = logrus.New() } log.Level, _ = logrus.ParseLevel(c.LogLevel) // Setup WebHookServer localServer, err := webhookserver.NewLocalServer( net.ParseIP(c.ServerIP), c.ServerPort, c.NetworkInterface, c.ExposedPort, c.DNSDomain, c.DNSSecret, c.TLSCertificate, c.TLSKey, time.Duration(c.MaxLifeCycle)*time.Second, ) if err != nil { return nil, err } // Create environment gc := gc.New(c.TemporaryFolder, c.MinimumDiskSpace, c.MinimumMemory) env := &runtime.Environment{ GarbageCollector: gc, Log: log, TemporaryStorage: tempStorage, WebHookServer: localServer, } // Ensure that engine confiuguration was provided for the engine selected if _, ok := c.Engines[c.Engine]; !ok { return nil, fmt.Errorf("Invalid configuration: The key 'engines.%s' must "+ "be specified when engine '%s' is selected", c.Engine, c.Engine) } // Find engine provider (schema should ensure it exists) provider := engines.Engines()[c.Engine] engine, err := provider.NewEngine(engines.EngineOptions{ Environment: env, Log: env.Log.WithField("engine", c.Engine), Config: c.Engines[c.Engine], }) if err != nil { return nil, fmt.Errorf("Engine initialization failed, error: %s", err) } // Initialize plugin manager pm, err := plugins.NewPluginManager(plugins.PluginOptions{ Environment: env, Engine: engine, Log: env.Log.WithField("plugin", "plugin-manager"), Config: c.Plugins, }) if err != nil { return nil, fmt.Errorf("Plugin initialization failed, error: %s", err) } tm, err := newTaskManager( &c, engine, pm, env, env.Log.WithField("component", "task-manager"), gc, ) if err != nil { return nil, err } return &Worker{ log: env.Log.WithField("component", "worker"), tm: tm, sm: runtime.NewShutdownManager("local"), env: env, server: localServer, done: make(chan struct{}), }, nil }
func TestGuestToolsProcessingActions(t *testing.T) { // Create temporary storage storage, err := runtime.NewTemporaryStorage(os.TempDir()) if err != nil { panic("Failed to create TemporaryStorage") } environment := &runtime.Environment{ TemporaryStorage: storage, } logTask := bytes.NewBuffer(nil) meta := metaservice.New([]string{}, map[string]string{}, logTask, func(r bool) { panic("This test shouldn't get to this point!") }, environment) // Create http server for testing ts := httptest.NewServer(meta) defer ts.Close() defer meta.StopPollers() // Hack to stop pollers, otherwise server will block u, err := url.Parse(ts.URL) if err != nil { panic("Expected a url we can parse") } // Create a logger logger, _ := runtime.CreateLogger("info") log := logger.WithField("component", "guest-tools-tests") // Create an run guest-tools g := new(u.Host, log) // start processing actions go g.ProcessActions() defer g.StopProcessingActions() //////////////////// debug("### Test meta.GetArtifact") f, err := storage.NewFolder() if err != nil { panic("Failed to create temp folder") } defer f.Remove() testFile := filepath.Join(f.Path(), "hello.txt") err = ioutil.WriteFile(testFile, []byte("hello-world"), 0777) nilOrPanic(err, "Failed to create testFile: ", testFile) debug(" - request file: %s", testFile) r, err := meta.GetArtifact(testFile) nilOrPanic(err, "meta.GetArtifact failed, error: ", err) debug(" - reading testFile") data, err := ioutil.ReadAll(r) nilOrPanic(err, "Failed to read testFile") debug(" - read: '%s'", string(data)) assert(string(data) == "hello-world", "Wrong payload: ", string(data)) //////////////////// debug("### Test meta.GetArtifact (missing file)") r, err = meta.GetArtifact(filepath.Join(f.Path(), "missing-file.txt")) assert(r == nil, "Expected error wihtout a reader") assert(err == engines.ErrResourceNotFound, "Expected ErrResourceNotFound") //////////////////// debug("### Test meta.ListFolder") testFolder := filepath.Join(f.Path(), "test-folder") err = os.Mkdir(testFolder, 0777) nilOrPanic(err, "Failed to create test-folder/") testFile2 := filepath.Join(testFolder, "hello2.txt") err = ioutil.WriteFile(testFile2, []byte("hello-world-2"), 0777) nilOrPanic(err, "Failed to create testFile2: ", testFile2) debug(" - meta.ListFolder") files, err := meta.ListFolder(f.Path()) nilOrPanic(err, "ListFolder failed, err: ", err) assert(len(files) == 2, "Expected 2 files") assert(files[0] == testFile || files[1] == testFile, "Expected testFile") assert(files[0] == testFile2 || files[1] == testFile2, "Expected testFile2") //////////////////// debug("### Test meta.ListFolder (missing folder)") files, err = meta.ListFolder(filepath.Join(f.Path(), "no-such-folder")) assert(files == nil, "Expected files == nil, we hopefully have an error") assert(err == engines.ErrResourceNotFound, "Expected ErrResourceNotFound") //////////////////// debug("### Test meta.ListFolder (empty folder)") emptyFolder := filepath.Join(f.Path(), "empty-folder") err = os.Mkdir(emptyFolder, 0777) nilOrPanic(err, "Failed to create empty-folder/") files, err = meta.ListFolder(emptyFolder) assert(len(files) == 0, "Expected zero files") assert(err == nil, "Didn't expect any error") //////////////////// testShellHello(meta) testShellCat(meta) testShellCatStdErr(meta) }
func (cmd) Execute(arguments map[string]interface{}) bool { // Read arguments imageFile := arguments["<image>"].(string) command := arguments["<command>"].([]string) vnc := arguments["--vnc"].(bool) // Create temporary storage and environment storage, err := runtime.NewTemporaryStorage(os.TempDir()) if err != nil { panic("Failed to create TemporaryStorage") } environment := &runtime.Environment{ TemporaryStorage: storage, } // Create a temporary folder tempFolder := filepath.Join("/tmp", slugid.Nice()) if err = os.Mkdir(tempFolder, 0777); err != nil { log.Fatal("Failed to create temporary folder in /tmp, error: ", err) } // Create the necessary runtime setup gc := &gc.GarbageCollector{} logger, _ := runtime.CreateLogger("info") log := logger.WithField("component", "qemu-run") // Create image manager log.Info("Creating image manager") manager, err := image.NewManager(filepath.Join(tempFolder, "/images/"), gc, logger.WithField("component", "image-manager"), nil) if err != nil { log.Fatal("Failed to create image manager", err) } // Get an instance of the image log.Info("Creating instance of image") image, err := manager.Instance("image", func(target string) error { return cp.CopyFile(target, imageFile) }) if err != nil { log.Fatal("Failed to create instance of image, error: ", err) } // Setup a user-space network log.Info("Creating user-space network") net, err := network.NewUserNetwork(tempFolder) if err != nil { log.Fatal("Failed to create user-space network, error: ", err) } // Create virtual machine log.Info("Creating virtual machine") vm, err := vm.NewVirtualMachine( image.Machine().Options(), image, net, tempFolder, "", "", logger.WithField("component", "vm"), ) if err != nil { log.Fatal("Failed to create virtual-machine, error: ", err) } // Create meta-data service log.Info("Creating meta-data service") var shellServer *interactive.ShellServer var displayServer *interactive.DisplayServer ms := metaservice.New(command, make(map[string]string), os.Stdout, func(result bool) { fmt.Println("### Task Completed, result = ", result) shellServer.WaitAndClose() displayServer.Abort() vm.Kill() }, environment) // Setup http handler for network vm.SetHTTPHandler(ms) // Create ShellServer shellServer = interactive.NewShellServer( ms.ExecShell, log.WithField("component", "shell-server"), ) // Create displayServer displayServer = interactive.NewDisplayServer( &socketDisplayProvider{socket: vm.VNCSocket()}, log.WithField("component", "display-server"), ) interactiveHandler := http.NewServeMux() interactiveHandler.Handle("/shell/", shellServer) interactiveHandler.Handle("/display/", displayServer) interactiveServer := graceful.Server{ Timeout: 30 * time.Second, Server: &http.Server{ Addr: "localhost:8080", Handler: interactiveHandler, }, NoSignalHandling: true, } go interactiveServer.ListenAndServe() // Start the virtual machine log.Info("Start the virtual machine") vm.Start() // Start vncviewer done := make(chan struct{}) if vnc { go StartVNCViewer(vm.VNCSocket(), done) } // Wait for SIGINT/SIGKILL or vm.Done c := make(chan os.Signal, 2) signal.Notify(c, os.Interrupt, os.Kill) // This pattern leaks, acceptable here select { case <-c: signal.Stop(c) fmt.Println("### Terminating QEMU") vm.Kill() case <-vm.Done: fmt.Println("### QEMU terminated") } close(done) // Ensure that QEMU has terminated before we continue <-vm.Done interactiveServer.Stop(100 * time.Millisecond) // Clean up anything left in the garbage collector gc.CollectAll() return true }
func TestTaskManagerRunTask(t *testing.T) { resolved := false var serverURL string var handler = func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.Path, "/artifacts/public/logs/live_backing.log") { json.NewEncoder(w).Encode(&queue.S3ArtifactResponse{ PutURL: serverURL, }) return } if strings.Contains(r.URL.Path, "/artifacts/public/logs/live.log") { json.NewEncoder(w).Encode(&queue.RedirectArtifactResponse{}) return } if strings.Contains(r.URL.Path, "/task/abc/runs/1/completed") { resolved = true w.Header().Set("Content-Type", "application/json; charset=UTF-8") json.NewEncoder(w).Encode(&queue.TaskStatusResponse{}) } } s := httptest.NewServer(http.HandlerFunc(handler)) serverURL = s.URL defer s.Close() tempPath := filepath.Join(os.TempDir(), slugid.Nice()) tempStorage, err := runtime.NewTemporaryStorage(tempPath) if err != nil { t.Fatal(err) } localServer, err := webhookserver.NewLocalServer( []byte{127, 0, 0, 1}, 60000, "", 0, "example.com", "", "", "", 10*time.Minute, ) if err != nil { t.Error(err) } gc := &gc.GarbageCollector{} environment := &runtime.Environment{ GarbageCollector: gc, TemporaryStorage: tempStorage, WebHookServer: localServer, } engineProvider := engines.Engines()["mock"] engine, err := engineProvider.NewEngine(engines.EngineOptions{ Environment: environment, Log: logger.WithField("engine", "mock"), }) if err != nil { t.Fatal(err.Error()) } cfg := &configType{ QueueBaseURL: serverURL, } tm, err := newTaskManager(cfg, engine, MockPlugin{}, environment, logger.WithField("test", "TestTaskManagerRunTask"), gc) if err != nil { t.Fatal(err) } claim := &taskClaim{ taskID: "abc", runID: 1, taskClaim: &queue.TaskClaimResponse{ Credentials: struct { AccessToken string `json:"accessToken"` Certificate string `json:"certificate"` ClientID string `json:"clientId"` }{ AccessToken: "123", ClientID: "abc", Certificate: "", }, TakenUntil: tcclient.Time(time.Now().Add(time.Minute * 5)), }, definition: &queue.TaskDefinitionResponse{ Payload: []byte(`{"delay": 1,"function": "write-log","argument": "Hello World"}`), }, } tm.run(claim) assert.True(t, resolved, "Task was not resolved") }
func TestWorkerShutdown(t *testing.T) { var resCount int32 var serverURL string var handler = func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.Path, "/artifacts/public/logs/live_backing.log") { json.NewEncoder(w).Encode(&queue.S3ArtifactResponse{ PutURL: serverURL, }) return } if strings.Contains(r.URL.Path, "/artifacts/public/logs/live.log") { json.NewEncoder(w).Encode(&queue.RedirectArtifactResponse{}) return } if strings.Contains(r.URL.Path, "exception") { var exception queue.TaskExceptionRequest err := json.NewDecoder(r.Body).Decode(&exception) // Ignore errors for now if err != nil { return } assert.Equal(t, "worker-shutdown", exception.Reason) atomic.AddInt32(&resCount, 1) w.Header().Set("Content-Type", "application/json; charset=UTF-8") json.NewEncoder(w).Encode(&queue.TaskStatusResponse{}) } } s := httptest.NewServer(http.HandlerFunc(handler)) serverURL = s.URL defer s.Close() tempPath := filepath.Join(os.TempDir(), slugid.Nice()) tempStorage, err := runtime.NewTemporaryStorage(tempPath) if err != nil { t.Fatal(err) } localServer, err := webhookserver.NewLocalServer( []byte{127, 0, 0, 1}, 60000, "", 0, "example.com", "", "", "", 10*time.Minute, ) if err != nil { t.Error(err) } gc := &gc.GarbageCollector{} environment := &runtime.Environment{ GarbageCollector: gc, TemporaryStorage: tempStorage, WebHookServer: localServer, } engineProvider := engines.Engines()["mock"] engine, err := engineProvider.NewEngine(engines.EngineOptions{ Environment: environment, Log: logger.WithField("engine", "mock"), }) if err != nil { t.Fatal(err.Error()) } cfg := &configType{ QueueBaseURL: serverURL, } tm, err := newTaskManager(cfg, engine, MockPlugin{}, environment, logger.WithField("test", "TestRunTask"), gc) if err != nil { t.Fatal(err) } claims := []*taskClaim{ &taskClaim{ taskID: "abc", runID: 1, definition: &queue.TaskDefinitionResponse{ Payload: []byte(`{"delay": 5000,"function": "write-log","argument": "Hello World"}`), }, taskClaim: &queue.TaskClaimResponse{ Credentials: struct { AccessToken string `json:"accessToken"` Certificate string `json:"certificate"` ClientID string `json:"clientId"` }{ AccessToken: "123", ClientID: "abc", Certificate: "", }, TakenUntil: tcclient.Time(time.Now().Add(time.Minute * 5)), }, }, &taskClaim{ taskID: "def", runID: 0, definition: &queue.TaskDefinitionResponse{ Payload: []byte(`{"delay": 5000,"function": "write-log","argument": "Hello World"}`), }, taskClaim: &queue.TaskClaimResponse{ Credentials: struct { AccessToken string `json:"accessToken"` Certificate string `json:"certificate"` ClientID string `json:"clientId"` }{ AccessToken: "123", ClientID: "abc", Certificate: "", }, TakenUntil: tcclient.Time(time.Now().Add(time.Minute * 5)), }, }, } var wg sync.WaitGroup wg.Add(2) go func() { for _, c := range claims { go func(claim *taskClaim) { tm.run(claim) wg.Done() }(c) } }() time.Sleep(500 * time.Millisecond) assert.Equal(t, len(tm.RunningTasks()), 2) close(tm.done) tm.Stop() wg.Wait() assert.Equal(t, 0, len(tm.RunningTasks())) assert.Equal(t, int32(2), atomic.LoadInt32(&resCount)) }
func TestMetaServiceShell(t *testing.T) { // Create temporary storage storage, err := runtime.NewTemporaryStorage(os.TempDir()) if err != nil { panic("Failed to create TemporaryStorage") } // Setup a new MetaService log := bytes.NewBuffer(nil) resolved := false meta := New([]string{"bash", "-c", "whoami"}, make(map[string]string), log, func(r bool) { if resolved { panic("It shouldn't be possible to resolve twice") } resolved = true }, &runtime.Environment{ TemporaryStorage: storage, }) s := httptest.NewServer(meta) defer s.Close() debug("### Test shell running an echo service") go func() { // Start polling for an action req, err2 := http.NewRequest("GET", s.URL+"/engine/v1/poll", nil) nilOrPanic(err2) res, err2 := http.DefaultClient.Do(req) nilOrPanic(err2) assert(res.StatusCode == http.StatusOK) action := Action{} data, err2 := ioutil.ReadAll(res.Body) nilOrPanic(err2) err2 = json.Unmarshal(data, &action) nilOrPanic(err2, "Failed to decode JSON") // Check that the action is 'exec-shell' (as expected) assert(action.ID != "", "Expected action.ID != ''") assert(action.Type == "exec-shell", "Expected exec-shell action") assert(action.Path == "", "Didn't expect action.Path") dialer := websocket.Dialer{ HandshakeTimeout: 30 * time.Second, ReadBufferSize: 8 * 1024, WriteBufferSize: 8 * 1024, } ws, _, err2 := dialer.Dial("ws:"+s.URL[5:]+"/engine/v1/reply?id="+action.ID, nil) nilOrPanic(err2, "Failed to open websocket") debug("guest-tool: Read: 'hi' on stdin") t, m, err2 := ws.ReadMessage() nilOrPanic(err2, "ReadMessage failed") assert(t == websocket.BinaryMessage, "expected BinaryMessage") assert(bytes.Compare(m, []byte{ shellconsts.MessageTypeData, shellconsts.StreamStdin, 'h', 'i', }) == 0, "expected 'hi' on stdin") debug("guest-tool: Ack: 'hi' from stdin") err2 = ws.WriteMessage(websocket.BinaryMessage, []byte{ shellconsts.MessageTypeAck, shellconsts.StreamStdin, 0, 0, 0, 2, }) nilOrPanic(err2, "Failed to send ack") debug("guest-tool: Send: 'hello' on stdout") err2 = ws.WriteMessage(websocket.BinaryMessage, []byte{ shellconsts.MessageTypeData, shellconsts.StreamStdout, 'h', 'e', 'l', 'l', 'o', }) nilOrPanic(err2, "Failed to send 'hello'") debug("guest-tool: Read: ack for the 'hello'") t, m, err2 = ws.ReadMessage() nilOrPanic(err2, "Failed to ReadMessage") assert(t == websocket.BinaryMessage, "expected BinaryMessage") assert(bytes.Compare(m, []byte{ shellconsts.MessageTypeAck, shellconsts.StreamStdout, 0, 0, 0, 5, }) == 0, "expected ack for 5 on stdout") debug("guest-tool: Send: close on stdout") err2 = ws.WriteMessage(websocket.BinaryMessage, []byte{ shellconsts.MessageTypeData, shellconsts.StreamStdout, }) nilOrPanic(err2, "Failed to send close for stdout") debug("guest-tool: Read: close for stdin") t, m, err2 = ws.ReadMessage() nilOrPanic(err2, "Failed to ReadMessage") assert(t == websocket.BinaryMessage, "expected BinaryMessage") assert(bytes.Compare(m, []byte{ shellconsts.MessageTypeData, shellconsts.StreamStdin, }) == 0, "expected stdin to be closed") debug("guest-tool: Send: exit success") err2 = ws.WriteMessage(websocket.BinaryMessage, []byte{ shellconsts.MessageTypeExit, 0, }) nilOrPanic(err2, "Failed to send 'exit' success") }() // Exec shell through metaservice shell, err := meta.ExecShell(nil, false) assert(err == nil, "Unexpected error: ", err) debug("server: Writing 'hi' on stdin") _, err = shell.StdinPipe().Write([]byte("hi")) nilOrPanic(err, "Failed to write 'hi' on stdin") debug("server: Reading stdout (waiting for stdout to close)") b, err := ioutil.ReadAll(shell.StdoutPipe()) nilOrPanic(err, "Failed to readAll from stdout") assert(string(b) == "hello", "Failed to read 'hello'") debug("server: Closing stdin") err = shell.StdinPipe().Close() nilOrPanic(err) debug("server: Waiting for exit success") success, err := shell.Wait() assert(err == nil, "Unexpected error: ", err) assert(success, "Expected success") debug("server: Reading nothing stderr (just check that it's closed)") b, err = ioutil.ReadAll(shell.StderrPipe()) nilOrPanic(err, "Failed to readAll from stderr") assert(string(b) == "", "Failed to read ''") }
func TestMetaService(t *testing.T) { // Create temporary storage storage, err := runtime.NewTemporaryStorage(os.TempDir()) if err != nil { panic("Failed to create TemporaryStorage") } // Setup a new MetaService log := bytes.NewBuffer(nil) result := false resolved := false s := New([]string{"bash", "-c", "whoami"}, make(map[string]string), log, func(r bool) { if resolved { panic("It shouldn't be possible to resolve twice") } resolved = true result = r }, &runtime.Environment{ TemporaryStorage: storage, }) // Upload some log req, err := http.NewRequest("POST", "http://169.254.169.254/engine/v1/log", bytes.NewBufferString("Hello World")) nilOrPanic(err) w := httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK) // Check the log if log.String() != "Hello World" { panic("Expected 'Hello World' in the log") } // Check that we can report success req, err = http.NewRequest("PUT", "http://169.254.169.254/engine/v1/success", nil) nilOrPanic(err) w = httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK) // Check result assert(resolved) assert(result) // Check idempotency req, err = http.NewRequest("PUT", "http://169.254.169.254/engine/v1/success", nil) nilOrPanic(err) w = httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK) // Check that we can have a conflict req, err = http.NewRequest("PUT", "http://169.254.169.254/engine/v1/failed", nil) nilOrPanic(err) w = httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusConflict) debug("### Test polling and get-artifact") // Check that we can poll for an action, and reply with an artifact go func() { // Start polling for an action req, err2 := http.NewRequest("GET", "http://169.254.169.254/engine/v1/poll", nil) nilOrPanic(err2) w := httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK) action := Action{} err2 = json.Unmarshal(w.Body.Bytes(), &action) nilOrPanic(err2, "Failed to decode JSON") // Check that the action is 'get-artifact' (as expected) assert(action.ID != "", "Expected action.ID != ''") assert(action.Type == "get-artifact", "Expected get-artifact action") assert(action.Path == "/home/worker/test-file", "Expected action.Path") // Post back an artifact req, err2 = http.NewRequest("POST", "http://169.254.169.254/engine/v1/reply?id="+action.ID, bytes.NewBufferString("hello-world"), ) nilOrPanic(err2) w = httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK) }() // Get artifact through metaservice f, err := s.GetArtifact("/home/worker/test-file") nilOrPanic(err, "Failed to get artifact") defer f.Close() b, err := ioutil.ReadAll(f) nilOrPanic(err, "Error reading from file") assert(string(b) == "hello-world", "Expected hello-world artifact") debug("### Test polling and get-artifact for non-existing file") // Check that we can poll for an action, and reply with an error not-found go func() { // Start polling for an action req, err2 := http.NewRequest("GET", "http://169.254.169.254/engine/v1/poll", nil) nilOrPanic(err2) w := httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK) action := Action{} err2 = json.Unmarshal(w.Body.Bytes(), &action) nilOrPanic(err2, "Failed to decode JSON") // Check that the action is 'get-artifact' (as expected) assert(action.ID != "", "Expected action.ID != ''") assert(action.Type == "get-artifact", "Expected get-artifact action") assert(action.Path == "/home/worker/wrong-file", "Expected action.Path") // Post back an artifact req, err2 = http.NewRequest("POST", "http://169.254.169.254/engine/v1/reply?id="+action.ID, nil, ) nilOrPanic(err2) req.Header.Set("X-Taskcluster-Worker-Error", "file-not-found") w = httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK) }() // Get error for artifact through metaservice f, err = s.GetArtifact("/home/worker/wrong-file") assert(f == nil, "Didn't expect to get a file") assert(err == engines.ErrResourceNotFound, "Expected ErrResourceNotFound") debug("### Test polling and list-folder") // Check that we can poll for an action, and reply to a list-folder go func() { // Start polling for an action req, err2 := http.NewRequest("GET", "http://169.254.169.254/engine/v1/poll", nil) nilOrPanic(err2) w := httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK) action := Action{} err2 = json.Unmarshal(w.Body.Bytes(), &action) nilOrPanic(err2, "Failed to decode JSON") // Check that the action is 'list-folder' (as expected) assert(action.ID != "", "Expected action.ID != ''") assert(action.Type == "list-folder", "Expected list-folder action") assert(action.Path == "/home/worker/", "Expected action.Path") // Post back an reply payload, _ := json.Marshal(Files{ Files: []string{"/home/worker/test-file"}, NotFound: false, }) req, err2 = http.NewRequest("POST", "http://169.254.169.254/engine/v1/reply?id="+action.ID, bytes.NewBuffer(payload), ) nilOrPanic(err2) req.Header.Set("Content-Type", "application/json") w = httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK, "Unexpected status: ", w.Code) }() // List folder through metaservice files, err := s.ListFolder("/home/worker/") nilOrPanic(err, "Failed to list-folder") assert(len(files) == 1, "Expected one file") assert(files[0] == "/home/worker/test-file", "Got the wrong file") debug("### Test polling and list-folder (not-found)") // Check that we can poll for an action, and reply to a list-folder, not found go func() { // Start polling for an action req, err2 := http.NewRequest("GET", "http://169.254.169.254/engine/v1/poll", nil) nilOrPanic(err2) w := httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK) action := Action{} err2 = json.Unmarshal(w.Body.Bytes(), &action) nilOrPanic(err2, "Failed to decode JSON") // Check that the action is 'list-folder' (as expected) assert(action.ID != "", "Expected action.ID != ''") assert(action.Type == "list-folder", "Expected list-folder action") assert(action.Path == "/home/worker/missing/", "Expected action.Path") // Post back an reply payload, _ := json.Marshal(Files{ Files: nil, NotFound: true, }) req, err2 = http.NewRequest("POST", "http://169.254.169.254/engine/v1/reply?id="+action.ID, bytes.NewBuffer(payload), ) nilOrPanic(err2) req.Header.Set("Content-Type", "application/json") w = httptest.NewRecorder() s.ServeHTTP(w, req) assert(w.Code == http.StatusOK, "Unexpected status: ", w.Code) }() // List folder through metaservice files, err = s.ListFolder("/home/worker/missing/") assert(err == engines.ErrResourceNotFound, "Expected ErrResourceNotFound") assert(len(files) == 0, "Expected zero files") }
func TestGuestToolsLiveLog(t *testing.T) { nowReady := sync.WaitGroup{} nowReady.Add(1) ps := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { debug("Waiting for ready-now to be readable in log") nowReady.Wait() debug("replying: request-ok") w.WriteHeader(http.StatusOK) w.Write([]byte("request-ok")) })) // Create temporary storage storage, err := runtime.NewTemporaryStorage(os.TempDir()) if err != nil { panic("Failed to create TemporaryStorage") } environment := &runtime.Environment{ TemporaryStorage: storage, } // Setup a new MetaService reader, writer := io.Pipe() result := false resolved := false m := sync.Mutex{} s := metaservice.New([]string{"sh", "-c", "echo \"$TEST_TEXT\" && curl -s " + ps.URL}, map[string]string{ "TEST_TEXT": "ready-now", }, writer, func(r bool) { m.Lock() defer m.Unlock() if resolved { panic("It shouldn't be possible to resolve twice") } resolved = true result = r }, environment) // Create http server for testing ts := httptest.NewServer(s) defer ts.Close() u, err := url.Parse(ts.URL) if err != nil { panic("Expected a url we can parse") } // Create a logger logger, _ := runtime.CreateLogger("info") log := logger.WithField("component", "guest-tools-tests") // Wait for logTask := bytes.NewBuffer(nil) logDone := sync.WaitGroup{} logDone.Add(1) go func() { b := make([]byte, 1) for !strings.Contains(logTask.String(), "ready-now") { n, err := reader.Read(b) logTask.Write(b[:n]) if err != nil { panic("Unexpected error") } } nowReady.Done() io.Copy(logTask, reader) logDone.Done() }() // Create an run guest-tools g := new(u.Host, log) g.Run() writer.Close() logDone.Wait() // Check the state if !resolved { t.Error("Expected the metadata to have resolved the task") } if result != true { t.Error("Expected the metadata to get successful result") } if !strings.Contains(logTask.String(), "request-ok") { t.Error("Got unexpected taskLog: '", logTask.String(), "'") } }