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 TestImageManager(t *testing.T) { fmt.Println(" - Setup environment needed to test") gc := &gc.GarbageCollector{} log := logrus.StandardLogger() sentry, _ := raven.New("") imageFolder := filepath.Join("/tmp", slugid.Nice()) fmt.Println(" - Create manager") manager, err := NewManager(imageFolder, gc, log.WithField("subsystem", "image-manager"), sentry) nilOrPanic(err, "Failed to create image manager") fmt.Println(" - Test parallel download") // Check that download can return and error, and we won't download twice // if we call before returning... downloadError := errors.New("test error") var err1 error wg := sync.WaitGroup{} wg.Add(2) go func() { _, err1 = manager.Instance("url:test-image-1", func(target string) error { time.Sleep(100 * time.Millisecond) // Sleep giving the second call time return downloadError }) wg.Done() }() time.Sleep(50 * time.Millisecond) // Sleep giving the second call time instance, err2 := manager.Instance("url:test-image-1", func(target string) error { panic("We shouldn't get here, as the previous download haven't returned") }) wg.Done() wg.Wait() assert(err1 == err2, "Expected the same errors: ", err1, err2) assert(downloadError == err1, "Expected the downloadError: ", err1) assert(instance == nil, "Expected instance to nil, when we have an error") fmt.Println(" - Test instantiation of image") instance, err = manager.Instance("url:test-image-1", func(target string) error { return copyFile(testImageFile, target) }) nilOrPanic(err, "Failed to loadImage") assert(instance != nil, "Expected an instance") fmt.Println(" - Get the diskImage path so we can check it gets deleted") diskImage := instance.DiskFile() fmt.Println(" - Inspect file for sanity check: ", diskImage) info := inspectImageFile(diskImage, imageQCOW2Format) assert(info != nil, "Expected a qcow2 file") assert(info.Format == formatQCOW2) assert(!info.DirtyFlag) assert(info.BackingFile != "", "Missing backing file in qcow2") fmt.Println(" - Check that backing file exists") backingFile := filepath.Join(filepath.Dir(diskImage), info.BackingFile) _, err = os.Lstat(backingFile) nilOrPanic(err, "backingFile missing") fmt.Println(" - Garbage collect and test that image is still there") nilOrPanic(gc.CollectAll(), "gc.CollectAll() failed") _, err = os.Lstat(backingFile) nilOrPanic(err, "backingFile missing after GC") info = inspectImageFile(diskImage, imageQCOW2Format) assert(info != nil, "diskImage for instance deleted after GC") fmt.Println(" - Make a new instance") instance2, err := manager.Instance("url:test-image-1", func(target string) error { panic("We shouldn't get here, as it is currently in the cache") }) nilOrPanic(err, "Failed to create new instance") diskImage2 := instance2.DiskFile() assert(diskImage2 != diskImage, "Expected a new disk image") info = inspectImageFile(diskImage2, imageQCOW2Format) assert(info != nil, "diskImage2 missing initially") fmt.Println(" - Release the first instance") instance.Release() _, err = os.Lstat(diskImage) assert(os.IsNotExist(err), "first instance diskImage shouldn't exist!") info = inspectImageFile(diskImage2, imageQCOW2Format) assert(info != nil, "diskImage2 missing after first instance release") fmt.Println(" - Garbage collect and test that image is still there") nilOrPanic(gc.CollectAll(), "gc.CollectAll() failed") _, err = os.Lstat(backingFile) nilOrPanic(err, "backingFile missing after second GC") _, err = os.Lstat(diskImage) assert(os.IsNotExist(err), "first instance diskImage shouldn't exist!") info = inspectImageFile(diskImage2, imageQCOW2Format) assert(info != nil, "diskImage2 missing after first instance release") fmt.Println(" - Release the second instance") instance2.Release() _, err = os.Lstat(diskImage2) assert(os.IsNotExist(err), "second instance diskImage shouldn't exist!") _, err = os.Lstat(backingFile) nilOrPanic(err, "backingFile missing after release, this shouldn't be...") fmt.Println(" - Garbage collect everything") // this should dispose the image nilOrPanic(gc.CollectAll(), "gc.CollectAll() failed") _, err = os.Lstat(backingFile) assert(os.IsNotExist(err), "Expected backingFile to be deleted after GC, file: ", backingFile) fmt.Println(" - Check that we can indeed reload the image") _, err = manager.Instance("url:test-image-1", func(target string) error { return downloadError }) assert(err == downloadError, "Expected a downloadError", err) }