Example #1
0
// clients
func cliCCClients(c *minicli.Command, resp *minicli.Response) error {
	resp.Header = []string{
		"UUID", "hostname", "arch", "OS",
		"IP", "MAC",
	}
	resp.Tabular = [][]string{}

	clients := ccNode.GetActiveClients()

	var uuids []string
	for k, _ := range clients {
		uuids = append(uuids, k)
	}
	sort.Strings(uuids)

	for _, i := range uuids {
		v := clients[i]
		row := []string{
			v.UUID,
			v.Hostname,
			v.Arch,
			v.OS,
			fmt.Sprintf("%v", v.IPs),
			fmt.Sprintf("%v", v.MACs),
		}

		resp.Tabular = append(resp.Tabular, row)
	}

	return nil
}
Example #2
0
func cliVmMigrate(c *minicli.Command, resp *minicli.Response) error {
	if _, ok := c.StringArgs["vm"]; !ok { // report current migrations
		resp.Header = []string{"id", "name", "status", "%% complete"}

		for _, vm := range vms.FindKvmVMs() {
			status, complete, err := vm.QueryMigrate()
			if err != nil {
				return err
			}
			if status == "" {
				continue
			}

			resp.Tabular = append(resp.Tabular, []string{
				fmt.Sprintf("%v", vm.GetID()),
				vm.GetName(),
				status,
				fmt.Sprintf("%.2f", complete)})
		}

		return nil
	}

	vm, err := vms.FindKvmVM(c.StringArgs["vm"])
	if err != nil {
		return err
	}

	return vm.Migrate(c.StringArgs["filename"])
}
Example #3
0
func cliHost(c *minicli.Command, resp *minicli.Response) error {
	// If they selected one of the fields to display
	for k := range c.BoolArgs {
		val, err := hostInfoFns[k]()
		if err != nil {
			return err
		}

		resp.Response = val
		return nil
	}

	// Must want all fields
	resp.Header = hostInfoKeys

	row := []string{}
	for _, k := range resp.Header {
		val, err := hostInfoFns[k]()
		if err != nil {
			return err
		}

		row = append(row, val)
	}
	resp.Tabular = [][]string{row}

	return nil
}
Example #4
0
func cliCC(c *minicli.Command, resp *minicli.Response) error {
	// Ensure that cc is running before proceeding
	if ccNode == nil {
		return errors.New("cc service not running")
	}

	if len(c.BoolArgs) > 0 {
		// Invoke a particular handler
		for k, fn := range ccCliSubHandlers {
			if c.BoolArgs[k] {
				log.Debug("cc handler %v", k)
				return fn(c, resp)
			}
		}

		return errors.New("unreachable")
	}

	// Getting status
	clients := ccNode.GetActiveClients()

	resp.Header = []string{"number of clients"}
	resp.Tabular = [][]string{
		[]string{
			fmt.Sprintf("%v", len(clients)),
		},
	}

	return nil
}
Example #5
0
func cliCapture(c *minicli.Command, resp *minicli.Response) error {
	if c.BoolArgs["netflow"] {
		// Capture to netflow
		return cliCaptureNetflow(c, resp)
	} else if c.BoolArgs["pcap"] {
		// Capture to pcap
		return cliCapturePcap(c, resp)
	}

	// Print capture info
	resp.Header = []string{
		"ID",
		"Type",
		"Bridge",
		"VM/interface",
		"Path",
		"Mode",
		"Compress",
	}

	resp.Tabular = [][]string{}
	for _, v := range captureEntries {
		row := []string{
			strconv.Itoa(v.ID),
			v.Type,
			v.Bridge,
			fmt.Sprintf("%v/%v", v.VM, v.Interface),
			v.Path,
			v.Mode,
			strconv.FormatBool(v.Compress),
		}
		resp.Tabular = append(resp.Tabular, row)
	}

	return nil

	// TODO: How does this fit in?
	//
	// get netflow stats for each bridge
	//var nfstats string
	//b := enumerateBridges()
	//for _, v := range b {
	//	nf, err := getNetflowFromBridge(v)
	//	if err != nil {
	//		if !strings.Contains(err.Error(), "has no netflow object") {
	//			return cliResponse{
	//				Error: err.Error(),
	//			}
	//		}
	//		continue
	//	}
	//	nfstats += fmt.Sprintf("Bridge %v:\n", v)
	//	nfstats += fmt.Sprintf("minimega listening on port: %v\n", nf.GetPort())
	//	nfstats += nf.GetStats()
	//}

	//out := o.String() + "\n" + nfstats
}
Example #6
0
func cliShell(c *minicli.Command, resp *minicli.Response, background bool) error {
	var sOut bytes.Buffer
	var sErr bytes.Buffer

	p, err := exec.LookPath(c.ListArgs["command"][0])
	if err != nil {
		return err
	}

	args := []string{p}
	if len(c.ListArgs["command"]) > 1 {
		args = append(args, c.ListArgs["command"][1:]...)
	}

	cmd := &exec.Cmd{
		Path:   p,
		Args:   args,
		Env:    nil,
		Dir:    "",
		Stdout: &sOut,
		Stderr: &sErr,
	}
	log.Info("starting: %v", args)
	if err := cmd.Start(); err != nil {
		return err
	}

	if background {
		go func() {
			if err := cmd.Wait(); err != nil {
				log.Error(err.Error())
				return
			}

			log.Info("command %v exited", args)
			if out := sOut.String(); out != "" {
				log.Info(out)
			}
			if err := sErr.String(); err != "" {
				log.Info(err)
			}
		}()

		return nil
	}

	if err = cmd.Wait(); err != nil {
		return err
	}

	resp.Response = sOut.String()
	resp.Error = sErr.String()

	return nil
}
Example #7
0
// process
func cliCCProcess(c *minicli.Command, resp *minicli.Response) error {
	if c.BoolArgs["kill"] {
		return cliCCProcessKill(c, resp)
	} else if c.BoolArgs["killall"] {
		return cliCCProcessKillAll(c, resp)
	}

	// list processes
	v := c.StringArgs["vm"]

	var activeVms []string

	if v == Wildcard {
		clients := ccNode.GetActiveClients()
		for _, client := range clients {
			activeVms = append(activeVms, client.UUID)
		}
	} else {
		// get the vm uuid
		vm := vms.FindVM(v)
		if vm == nil {
			return vmNotFound(v)
		}
		log.Debug("got vm: %v %v", vm.GetID(), vm.GetName())
		activeVms = []string{vm.GetUUID()}
	}

	resp.Header = []string{"name", "uuid", "pid", "command"}
	for _, uuid := range activeVms {
		vm := vms.FindVM(uuid)
		if vm == nil {
			return vmNotFound(v)
		}

		processes, err := ccNode.GetProcesses(uuid)
		if err != nil {
			return err
		}

		for _, p := range processes {
			resp.Tabular = append(resp.Tabular, []string{
				vm.GetName(),
				vm.GetUUID(),
				fmt.Sprintf("%v", p.PID),
				strings.Join(p.Command, " "),
			})
		}
	}

	return nil
}
Example #8
0
func cliVmConfig(c *minicli.Command, resp *minicli.Response) error {
	if c.BoolArgs["save"] {
		// Save the current config
		savedInfo[c.StringArgs["name"]] = vmConfig.Copy()

		return nil
	} else if c.BoolArgs["restore"] {
		if name, ok := c.StringArgs["name"]; ok {
			// Try to restore an existing config
			if _, ok := savedInfo[name]; !ok {
				return fmt.Errorf("config %v does not exist", name)
			}

			vmConfig = savedInfo[name].Copy()

			return nil
		} else if len(savedInfo) == 0 {
			return errors.New("no vm configs saved")
		}

		// List the save configs
		for k := range savedInfo {
			resp.Response += fmt.Sprintln(k)
		}

		return nil
	} else if c.BoolArgs["clone"] {
		// Clone the config of an existing vm
		vm := vms.FindVM(c.StringArgs["vm"])
		if vm == nil {
			return vmNotFound(c.StringArgs["vm"])
		}

		switch vm := vm.(type) {
		case *KvmVM:
			vmConfig.BaseConfig = vm.BaseConfig.Copy()
			vmConfig.KVMConfig = vm.KVMConfig.Copy()
		case *ContainerVM:
			vmConfig.BaseConfig = vm.BaseConfig.Copy()
			vmConfig.ContainerConfig = vm.ContainerConfig.Copy()
		}

		return nil
	}

	// Print the config
	resp.Response = vmConfig.String()
	return nil
}
Example #9
0
func hostTapList(resp *minicli.Response) {
	resp.Header = []string{"bridge", "tap", "vlan"}
	resp.Tabular = [][]string{}

	// find all the host taps first
	for k, b := range bridges {
		for name, tap := range b.Taps {
			if tap.host {
				resp.Tabular = append(resp.Tabular, []string{
					k, name, strconv.Itoa(tap.lan),
				})
			}
		}
	}
}
Example #10
0
func cliBridge(c *minicli.Command, resp *minicli.Response) error {
	iface := c.StringArgs["interface"]
	remoteIP := c.StringArgs["remote"]

	// Get the specifed bridge. If we're listing the bridges, we'll get the
	// default bridge which should be fine.
	br, err := getBridge(c.StringArgs["bridge"])
	if err != nil {
		return err
	}

	if c.BoolArgs["trunk"] {
		return br.AddTrunk(iface)
	} else if c.BoolArgs["notrunk"] {
		return br.RemoveTrunk(iface)
	} else if c.BoolArgs["tunnel"] {
		t := bridge.TunnelVXLAN
		if c.BoolArgs["gre"] {
			t = bridge.TunnelGRE
		}

		return br.AddTunnel(t, remoteIP)
	} else if c.BoolArgs["notunnel"] {
		return br.RemoveTunnel(iface)
	}

	// Must want to list bridges
	resp.Header = []string{"Bridge", "Existed before minimega", "Active VLANs", "Trunk ports", "Tunnels"}
	resp.Tabular = [][]string{}

	for _, info := range bridges.Info() {
		vlans := []string{}
		for k, _ := range info.VLANs {
			vlans = append(vlans, printVLAN(k))
		}
		sort.Strings(vlans)

		row := []string{
			info.Name,
			strconv.FormatBool(info.PreExist),
			fmt.Sprintf("%v", vlans),
			fmt.Sprintf("%v", info.Trunks),
			fmt.Sprintf("%v", info.Tunnels)}
		resp.Tabular = append(resp.Tabular, row)
	}

	return nil
}
Example #11
0
func cliVmConfigField(c *minicli.Command, resp *minicli.Response, field string) error {
	// If there are no args it means that we want to display the current value
	nArgs := len(c.StringArgs) + len(c.ListArgs) + len(c.BoolArgs)

	var ok bool
	var fns VMConfigFns
	var config interface{}

	// Find the right config functions, baseConfigFns has highest priority
	if fns, ok = baseConfigFns[field]; ok {
		config = &vmConfig.BaseConfig
	} else if fns, ok = kvmConfigFns[field]; ok {
		config = &vmConfig.KVMConfig
	} else if fns, ok = containerConfigFns[field]; ok {
		config = &vmConfig.ContainerConfig
	} else {
		return fmt.Errorf("unknown config field: `%s`", field)
	}

	if nArgs == 0 {
		resp.Response = fns.Print(config)
		return nil
	}

	return fns.Update(config, c)
}
Example #12
0
func cliLogFile(c *minicli.Command, resp *minicli.Response) error {
	if len(c.StringArgs) == 0 {
		// Print true or false depending on whether file is enabled
		if logFile != nil {
			resp.Response = logFile.Name()
		}

		return nil
	}

	// Enable logging to file if it's not already enabled
	level, _ := log.LevelInt(*f_loglevel)

	if logFile != nil {
		if err := stopFileLogger(); err != nil {
			return err
		}
	}

	err := os.MkdirAll(filepath.Dir(c.StringArgs["file"]), 0755)
	if err != nil {
		return err
	}

	flags := os.O_WRONLY | os.O_APPEND | os.O_CREATE
	logFile, err = os.OpenFile(c.StringArgs["file"], flags, 0660)
	if err != nil {
		return err
	}

	log.AddLogger("file", logFile, level, false)
	return nil
}
Example #13
0
func cliVmConfigTag(c *minicli.Command, resp *minicli.Response) error {
	k := c.StringArgs["key"]

	if v, ok := c.StringArgs["value"]; ok {
		// Setting a new value
		vmConfig.Tags[k] = v
	} else if k != "" {
		// Printing a single tag
		resp.Response = vmConfig.Tags[k]
	} else {
		// Printing all configured tags
		resp.Response = vmConfig.TagsString()
	}

	return nil
}
Example #14
0
func hostTapList(resp *minicli.Response) {
	resp.Header = []string{"bridge", "tap", "vlan", "option"}
	resp.Tabular = [][]string{}

	// find all the host taps first
	for k, v := range bridges {
		for lan, t := range v.lans {
			for tap, ti := range t.Taps {
				if ti.host {
					resp.Tabular = append(resp.Tabular, []string{
						k, tap, strconv.Itoa(lan), ti.hostOption,
					})
				}
			}
		}
	}
}
Example #15
0
func cliMeshageStatus(c *minicli.Command, resp *minicli.Response) error {
	mesh := meshageNode.Mesh()
	degree := meshageNode.GetDegree()
	nodes := len(mesh)

	resp.Header = []string{"mesh size", "degree", "peers", "context", "port"}
	resp.Tabular = [][]string{
		[]string{
			strconv.Itoa(nodes),
			strconv.FormatUint(uint64(degree), 10),
			strconv.Itoa(len(mesh[hostname])),
			*f_context,
			strconv.Itoa(*f_port),
		},
	}

	return nil
}
Example #16
0
// prefix
func cliCCPrefix(c *minicli.Command, resp *minicli.Response) error {
	if prefix, ok := c.StringArgs["prefix"]; ok {
		ccPrefix = prefix
		return nil
	}

	resp.Response = ccPrefix
	return nil
}
Example #17
0
func cliHelp(c *minicli.Command, resp *minicli.Response) error {
	input := ""
	if args, ok := c.ListArgs["command"]; ok {
		input = strings.Join(args, " ")
	}

	resp.Response = minicli.Help(input)
	return nil
}
Example #18
0
// routines for interfacing bridge mechanisms with the cli
func cliHostTap(c *minicli.Command, resp *minicli.Response) error {
	if c.BoolArgs["create"] {
		b := c.StringArgs["bridge"]

		tap, err := hostTapCreate(b, c.StringArgs["tap"], c.StringArgs["vlan"])
		if err != nil {
			return err
		}

		if c.BoolArgs["dhcp"] {
			log.Debug("obtaining dhcp on tap %v", tap)

			var out string
			out, err = processWrapper("dhcp", tap)
			if err != nil {
				err = fmt.Errorf("dhcp error %v: `%v`", err, out)
			}
		} else if c.StringArgs["ip"] != "" {
			ip := c.StringArgs["ip"]

			log.Debug("setting ip on tap %v: %v", tap, ip)

			var out string
			out, err = processWrapper("ip", "addr", "add", "dev", tap, ip)
			if err != nil {
				err = fmt.Errorf("ip error %v: `%v`", err, out)
			}
		}

		if err != nil {
			// One of the above cases failed, try to clean up the tap
			if err := hostTapDelete(tap); err != nil {
				// Welp, we're boned
				log.Error("zombie tap -- %v %v", tap, err)
			}

			return err
		}
		// Success!
		if ns := GetNamespace(); ns != nil {
			// TODO: probably need lock...
			ns.Taps[tap] = true
		}

		resp.Response = tap

		return nil
	} else if c.BoolArgs["delete"] {
		return hostTapDelete(c.StringArgs["id"])
	}

	// Must be the list command
	hostTapList(resp)

	return nil
}
Example #19
0
func cliVLANs(c *minicli.Command, resp *minicli.Response) error {
	// Look for matching subhandler
	if len(c.BoolArgs) > 0 {
		for k, fn := range vlansCLISubHandlers {
			if c.BoolArgs[k] {
				log.Debug("vlan handler %v", k)
				return fn(c, resp)
			}
		}
	}

	namespace := GetNamespaceName()

	// No match, must want to just print
	resp.Header = []string{"namespace", "alias", "vlan"}
	resp.Tabular = allocatedVLANs.Tabular(namespace)

	return nil
}
Example #20
0
// List all active recordings and playbacks
func cliVNCList(c *minicli.Command, resp *minicli.Response) error {
	resp.Header = []string{"name", "type", "time", "filename"}
	resp.Tabular = [][]string{}

	vncRecordingLock.RLock()
	for _, v := range vncKBRecording {
		resp.Tabular = append(resp.Tabular, []string{
			v.VM.Name, "record kb",
			time.Since(v.start).String(),
			v.file.Name(),
		})
	}
	vncRecordingLock.RUnlock()

	vncRecordingLock.RLock()
	for _, v := range vncFBRecording {
		resp.Tabular = append(resp.Tabular, []string{
			v.VM.Name, "record fb",
			time.Since(v.start).String(),
			v.file.Name(),
		})
	}
	vncRecordingLock.RUnlock()

	vncPlayingLock.RLock()
	for _, v := range vncPlaying {
		var r string
		if v.state == Pause {
			r = "PAUSED"
		} else {
			r = v.timeRemaining() + " remaining"
		}

		resp.Tabular = append(resp.Tabular, []string{
			v.VM.Name, "playback kb",
			r,
			v.file.Name(),
		})
	}
	vncPlayingLock.RUnlock()
	return nil
}
Example #21
0
func cliMeshageList(c *minicli.Command, resp *minicli.Response) error {
	mesh := meshageNode.Mesh()

	var keys []string
	for k, _ := range mesh {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, key := range keys {
		v := mesh[key]
		resp.Response += fmt.Sprintf("%s\n", key)
		sort.Strings(v)
		for _, x := range v {
			resp.Response += fmt.Sprintf(" |--%s\n", x)
		}
	}

	return nil
}
Example #22
0
func cliDnsmasq(c *minicli.Command, resp *minicli.Response) error {
	if c.StringArgs["id"] == Wildcard {
		// Must be "kill all"
		return dnsmasqKillAll()
	} else if c.StringArgs["id"] != "" {
		// Must be "kill <id>"
		id, err := strconv.Atoi(c.StringArgs["id"])
		if err != nil {
			return err
		}

		return dnsmasqKill(id)
	} else if c.StringArgs["listen"] != "" || c.StringArgs["config"] != "" {
		// Must be "start"
		// We don't need to differentiate between the two start commands
		// because dnsmasqStart expects the zero string value when values
		// are not specified.
		return dnsmasqStart(
			c.StringArgs["listen"],
			c.StringArgs["low"],
			c.StringArgs["high"],
			c.StringArgs["config"])
	}

	// Must be "list"
	resp.Header = []string{"ID", "Listening Address", "Min", "Max", "Path", "PID"}
	resp.Tabular = [][]string{}
	for id, c := range dnsmasqServers {
		pid := dnsmasqPID(id)
		resp.Tabular = append(resp.Tabular, []string{
			strconv.Itoa(id),
			c.Addr,
			c.MinRange,
			c.MaxRange,
			c.Path,
			strconv.Itoa(pid)})
	}

	return nil
}
Example #23
0
func dnsmasqDNSInfo(c *minicli.Command, resp *minicli.Response) {
	// print info
	resp.Header = []string{"ID", "IP", "Hostname"}
	resp.Tabular = [][]string{}
	if c.StringArgs["ID"] == Wildcard {
		for id, v := range dnsmasqServers {
			for ip, host := range v.Hostnames {
				resp.Tabular = append(resp.Tabular, []string{strconv.Itoa(id), ip, host})
			}
		}
	} else {
		id, err := strconv.Atoi(c.StringArgs["ID"])
		if err != nil {
			resp.Error = "Invalid dnsmasq ID"
			return
		}
		if _, ok := dnsmasqServers[id]; ok {
			for ip, host := range dnsmasqServers[id].Hostnames {
				resp.Tabular = append(resp.Tabular, []string{strconv.Itoa(id), ip, host})
			}
		} else {
			resp.Error = "Invalid dnsmasq ID"
		}
	}
}
Example #24
0
func dnsmasqHostInfo(c *minicli.Command, resp *minicli.Response) {
	// print info about the mapping
	resp.Header = []string{"ID", "MAC", "IP"}
	resp.Tabular = [][]string{}
	if c.StringArgs["ID"] == Wildcard {
		for id, v := range dnsmasqServers {
			for mac, ip := range v.DHCPhosts {
				resp.Tabular = append(resp.Tabular, []string{strconv.Itoa(id), mac, ip})
			}
		}
	} else {
		id, err := strconv.Atoi(c.StringArgs["ID"])
		if err != nil {
			resp.Error = "Invalid dnsmasq ID"
			return
		}
		if _, ok := dnsmasqServers[id]; ok {
			for mac, ip := range dnsmasqServers[id].DHCPhosts {
				resp.Tabular = append(resp.Tabular, []string{strconv.Itoa(id), mac, ip})
			}
		} else {
			resp.Error = "Invalid dnsmasq ID"
		}
	}
}
Example #25
0
func cliVmTag(c *minicli.Command, resp *minicli.Response) error {
	target := c.StringArgs["target"]

	key := c.StringArgs["key"]
	if key == "" {
		// If they didn't specify a key then they probably want all the tags
		// for a given VM
		key = Wildcard
	}

	value, write := c.StringArgs["value"]
	if write {
		if key == Wildcard {
			return errors.New("cannot assign to wildcard")
		}

		vms.SetTag(target, key, value)

		return nil
	}

	if key == Wildcard {
		resp.Header = []string{"ID", "Tag", "Value"}
	} else {
		resp.Header = []string{"ID", "Value"}
	}

	for _, tag := range vms.GetTags(target, key) {
		row := []string{strconv.Itoa(tag.ID)}

		if key == Wildcard {
			row = append(row, tag.Key)
		}

		row = append(row, tag.Value)
		resp.Tabular = append(resp.Tabular, row)
	}

	return nil
}
Example #26
0
// hostTapList populates resp with information about all the host taps.
func hostTapList(resp *minicli.Response) {
	resp.Header = []string{"bridge", "tap", "vlan"}
	resp.Tabular = [][]string{}

	// no namespace active => add an extra column
	ns := GetNamespace()
	if ns == nil {
		resp.Header = append(resp.Header, "namespace")
	}

	// find all the host taps first
	for _, tap := range bridges.HostTaps() {
		// skip taps that don't belong to the active namespace
		if ns != nil && !ns.HasTap(tap.Name) {
			continue
		}

		row := []string{
			tap.Bridge, tap.Name, printVLAN(tap.VLAN),
		}

		// no namespace active => find namespace tap belongs to so that we can
		// populate that column
		if ns == nil {
			v := ""
			for _, n := range ListNamespaces() {
				if ns := GetOrCreateNamespace(n); ns.HasTap(tap.Name) {
					v = ns.Name
					break
				}
			}

			row = append(row, v)
		}

		resp.Tabular = append(resp.Tabular, row)
	}
}
Example #27
0
// cli commands for meshage control
func cliMeshageDegree(c *minicli.Command, resp *minicli.Response) error {
	if c.StringArgs["degree"] != "" {
		degree, err := strconv.ParseUint(c.StringArgs["degree"], 0, 10)
		if err != nil {
			return err
		}

		meshageNode.SetDegree(uint(degree))
		return nil
	}

	resp.Response = fmt.Sprintf("%d", meshageNode.GetDegree())
	return nil
}
Example #28
0
func cliVmQmp(c *minicli.Command, resp *minicli.Response) error {
	vm, err := vms.FindKvmVM(c.StringArgs["vm"])
	if err != nil {
		return err
	}

	out, err := vm.QMPRaw(c.StringArgs["qmp"])
	if err != nil {
		return err
	}

	resp.Response = out
	return nil
}
Example #29
0
// command
func cliCCCommand(c *minicli.Command, resp *minicli.Response) error {
	resp.Header = []string{
		"ID", "prefix", "command", "responses", "background",
		"send files", "receive files", "filter",
	}
	resp.Tabular = [][]string{}

	var commandIDs []int
	commands := ccNode.GetCommands()
	for k, v := range commands {
		// only show commands for the active namespace
		if !ccMatchNamespace(v) {
			continue
		}

		commandIDs = append(commandIDs, k)
	}
	sort.Ints(commandIDs)

	for _, i := range commandIDs {
		v := commands[i]
		row := []string{
			strconv.Itoa(v.ID),
			ccPrefixMap[i],
			fmt.Sprintf("%v", v.Command),
			strconv.Itoa(len(v.CheckedIn)),
			strconv.FormatBool(v.Background),
			fmt.Sprintf("%v", v.FilesSend),
			fmt.Sprintf("%v", v.FilesRecv),
			filterString(v.Filter),
		}

		resp.Tabular = append(resp.Tabular, row)
	}

	return nil
}
Example #30
0
// Info populates resp with info about the VMs running in the active namespace.
func (vms VMs) Info(masks []string, resp *minicli.Response) {
	vmLock.Lock()
	defer vmLock.Unlock()

	resp.Header = masks
	res := VMs{} // for res.Data

	for _, vm := range vms {
		if !inNamespace(vm) {
			continue
		}

		// Update dynamic fields before querying info
		vm.UpdateNetworks()

		// Copy the VM and use the copy from here on. This ensures that the
		// Tabular info matches the Data field.
		vm := vm.Copy()

		res[vm.GetID()] = vm

		row := []string{}

		for _, mask := range masks {
			if v, err := vm.Info(mask); err != nil {
				// Field most likely not set for VM type
				row = append(row, "N/A")
			} else {
				row = append(row, v)
			}
		}

		resp.Tabular = append(resp.Tabular, row)
	}

	resp.Data = res
}