func (u *Universe) VMs() []*VM {
	u.mu.Lock()
	defer u.mu.Unlock()
	ret := make([]*VM, 0, len(u.vms))
	for _, vm := range u.vms {
		ret = append(ret, vm)
	}
	return ret
}
VM returns a list of all VMs in the universe.
package virtuakube
import (
	"context"
	"crypto/rand"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"time"
	"go.universe.tf/virtuakube/internal/config"
)
var universeTools = []string{
	"vde_switch",
	"qemu-system-x86_64",
	"qemu-img",
}
func checkTools(tools []string) error {
	missing := []string{}
	for _, tool := range tools {
		_, err := exec.LookPath(tool)
		if err != nil {
			if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
				missing = append(missing, tool)
				continue
			}
			return err
		}
	}
	if len(missing) > 0 {
		return fmt.Errorf("required tools missing: %s", strings.Join(missing, ", "))
	}
	return nil
}
type UniverseConfig struct {
	CommandLog io.Writer
	VMGraphics bool
	Interactive bool
	NoAcceleration bool
}
type Universe struct {
	dir string
	tmpdir string
	closedCh chan bool
	runtimecfg *UniverseConfig
	mu sync.Mutex
	cfg *config.Universe
	nextPort int
	nextNet  int
	activeSnapshot string
	startTime time.Time
	networks map[string]*Network
	images   map[string]string  
	vms      map[string]*VM
	clusters map[string]*Cluster
	closed   bool
	closeErr error
}
func Create(dir string, runtimecfg *UniverseConfig) (*Universe, error) {
	cfg := &config.Universe{
		Snapshots: map[string]*config.Snapshot{
			"": {
				Name:     "",
				ID:       randomSnapshotID(),
				NextPort: 50000,
				Clock:    time.Now(),
			},
		},
	}
	dir, err := filepath.Abs(dir)
	if err != nil {
		return nil, err
	}
	if err := os.Mkdir(dir, 0700); err != nil {
		return nil, err
	}
	cfgPath := filepath.Join(dir, "config.json")
	if err := config.Write(cfgPath, cfg); err != nil {
		return nil, err
	}
	return Open(dir, "", runtimecfg)
}
func Open(dir string, snapshot string, runtimecfg *UniverseConfig) (*Universe, error) {
	if err := checkTools(universeTools); err != nil {
		return nil, err
	}
	dir, err := filepath.Abs(dir)
	if err != nil {
		return nil, err
	}
	if runtimecfg == nil {
		runtimecfg = &UniverseConfig{}
	}
	cfgPath := filepath.Join(dir, "config.json")
	cfg, err := config.Read(cfgPath)
	if err != nil {
		return nil, fmt.Errorf("reading universe config: %v", err)
	}
	snap := cfg.Snapshots[snapshot]
	if snap == nil {
		return nil, fmt.Errorf("no snapshot %q in universe", snapshot)
	}
	tmpdir, err := ioutil.TempDir(dir, "tmp")
	if err != nil {
		return nil, fmt.Errorf("creating temporary directory: %v", err)
	}
	ret := &Universe{
		dir:            dir,
		tmpdir:         tmpdir,
		closedCh:       make(chan bool),
		cfg:            cfg,
		runtimecfg:     runtimecfg,
		nextPort:       snap.NextPort,
		nextNet:        snap.NextNet,
		activeSnapshot: snapshot,
		startTime:      time.Now(),
		networks:       map[string]*Network{},
		images:         map[string]string{},
		vms:            map[string]*VM{},
		clusters:       map[string]*Cluster{},
	}
	for _, img := range snap.Images {
		ret.images[img.Name] = img.File
	}
	for _, net := range snap.Networks {
		if err := ret.resumeNetwork(net); err != nil {
			return nil, err
		}
	}
	vms := snap.VMs
	res := make(chan error, len(vms))
	for _, vmcfg := range vms {
		go func(vmcfg *config.VM) {
			_, err := ret.resumeVM(vmcfg)
			if err != nil {
				res <- err
				return
			}
			res <- nil
		}(vmcfg)
	}
	for range vms {
		if err := <-res; err != nil {
			return nil, err
		}
	}
	for _, vm := range ret.vms {
		if err := vm.boot(); err != nil {
			return nil, err
		}
	}
	for _, clusterCfg := range snap.Clusters {
		if err := ret.resumeCluster(clusterCfg); err != nil {
			return nil, err
		}
	}
	return ret, nil
}
func (u *Universe) Close() error {
	u.mu.Lock()
	defer u.mu.Unlock()
	if u.closed {
		return u.closeErr
	}
	u.closeWithLock()
	close(u.closedCh)
	return u.closeErr
}
func (u *Universe) closeWithLock() {
	u.closed = true
	for _, vm := range u.vms {
		if err := vm.Close(); err != nil {
			u.closeErr = err
		}
	}
	for _, net := range u.networks {
		if err := net.Close(); err != nil {
			u.closeErr = err
		}
	}
	snap := u.cfg.Snapshots[u.activeSnapshot]
	for name, path := range u.images {
		if snap.Images[name] == nil {
			if err := os.Remove(filepath.Join(u.dir, path)); err != nil {
				u.closeErr = err
			}
		}
	}
	for name, vm := range u.vms {
		if snap.VMs[name] == nil {
			if err := os.Remove(filepath.Join(u.dir, vm.cfg.DiskFile)); err != nil {
				u.closeErr = err
			}
		}
	}
	if err := os.RemoveAll(u.tmpdir); err != nil {
		u.closeErr = err
	}
}
func (u *Universe) Destroy() error {
	u.mu.Lock()
	defer u.mu.Unlock()
	if u.closed {
		return u.closeErr
	}
	u.closeWithLock()
	if err := os.RemoveAll(u.dir); err != nil {
		u.closeErr = err
	}
	close(u.closedCh)
	return u.closeErr
}
func (u *Universe) Save(snapshotName string) error {
	u.mu.Lock()
	defer u.mu.Unlock()
	if u.closed {
		return u.closeErr
	}
	snap := &config.Snapshot{
		Name:     snapshotName,
		NextPort: u.nextPort,
		NextNet:  u.nextNet,
		Clock:    u.cfg.Snapshots[u.activeSnapshot].Clock.Add(time.Since(u.startTime)),
		Networks: map[string]*config.Network{},
		Images:   map[string]*config.Image{},
		VMs:      map[string]*config.VM{},
		Clusters: map[string]*config.Cluster{},
	}
	if oldSnap := u.cfg.Snapshots[snapshotName]; oldSnap != nil {
		snap.ID = oldSnap.ID
	} else {
		snap.ID = randomSnapshotID()
	}
	errs := make(chan error, len(u.vms))
	for name, vm := range u.vms {
		go func(name string, vm *VM) {
			if err := vm.freeze(snap.ID); err != nil {
				errs <- fmt.Errorf("freezing %q: %v", name, err)
				return
			}
			errs <- nil
		}(name, vm)
	}
	for range u.vms {
		if err := <-errs; err != nil {
			u.closeErr = err
			return u.closeErr
		}
	}
	for _, network := range u.networks {
		snap.Networks[network.cfg.Name] = network.cfg
	}
	for name, disk := range u.images {
		snap.Images[name] = &config.Image{
			Name: name,
			File: disk,
		}
	}
	for _, vm := range u.vms {
		snap.VMs[vm.cfg.Name] = vm.cfg
	}
	for _, cluster := range u.clusters {
		snap.Clusters[cluster.cfg.Name] = cluster.cfg
	}
	u.networks = nil
	u.images = nil
	u.vms = nil
	u.clusters = nil
	u.closeWithLock()
	u.cfg.Snapshots[snapshotName] = snap
	bs, err := json.MarshalIndent(u.cfg, "", "  ")
	if err != nil {
		u.closeErr = err
		return u.closeErr
	}
	if err := ioutil.WriteFile(filepath.Join(u.dir, "config.json"), bs, 0600); err != nil {
		u.closeErr = err
		return u.closeErr
	}
	close(u.closedCh)
	return nil
}
func (u *Universe) Wait(ctx context.Context) error {
	select {
	case <-u.closedCh:
		return nil
	case <-ctx.Done():
		return errors.New("timeout")
	}
}
func (u *Universe) Snapshots() []string {
	u.mu.Lock()
	defer u.mu.Unlock()
	var ret []string
	for name := range u.cfg.Snapshots {
		ret = append(ret, name)
	}
	return ret
}
func (u *Universe) image(name string) string {
	u.mu.Lock()
	defer u.mu.Unlock()
	return u.images[name]
}
func (u *Universe) Command(command string, args ...string) *exec.Cmd {
	cmd := exec.Command(command, args...)
	if u.runtimecfg.CommandLog != nil {
		cmd.Stdout = u.runtimecfg.CommandLog
		cmd.Stderr = u.runtimecfg.CommandLog
	}
	return cmd
}
func (u *Universe) VM(name string) *VM {
	u.mu.Lock()
	defer u.mu.Unlock()
	return u.vms[name]
}