Example #1
0
File: vm.go Project: npe9/minimega
// Get the VM info from all hosts optionally applying column/row filters.
// Returns a map with keys for the hostnames and values as the tabular data
// from the host.
func globalVmInfo() map[string]VMs {
	cmdStr := "vm info"

	res := map[string]VMs{}

	cmd := minicli.MustCompile(cmdStr)
	cmd.Record = false

	for resps := range runCommandGlobally(cmd) {
		for _, resp := range resps {
			if resp.Error != "" {
				log.Errorln(resp.Error)
				continue
			}

			switch data := resp.Data.(type) {
			case VMs:
				res[resp.Host] = data
			default:
				log.Error("unknown data field in vm info")
			}
		}
	}

	return res
}
Example #2
0
func webHosts(w http.ResponseWriter, r *http.Request) {
	table := htmlTable{
		Header:  []string{},
		Tabular: [][]interface{}{},
		ID:      "example",
		Class:   "hover",
	}

	cmd := minicli.MustCompile("host")
	cmd.Record = false

	for resps := range runCommandGlobally(cmd) {
		for _, resp := range resps {
			if resp.Error != "" {
				log.Errorln(resp.Error)
				continue
			}

			if len(table.Header) == 0 && len(resp.Header) > 0 {
				table.Header = append(table.Header, resp.Header...)
			}

			for _, row := range resp.Tabular {
				res := []interface{}{}
				for _, v := range row {
					res = append(res, v)
				}
				table.Tabular = append(table.Tabular, res)
			}
		}
	}

	webRenderTemplate(w, "hosts.html", table)
}
Example #3
0
// Get the VM info from all hosts optionally applying column/row filters.
// Returns a map with keys for the hostnames and values as the tabular data
// from the host.
func globalVmInfo(masks []string, filters []string) (map[string]VMs, map[string]minicli.Responses) {
	cmdStr := "vm info"
	for _, v := range filters {
		cmdStr = fmt.Sprintf(".filter %s %s", v, cmdStr)
	}
	if len(masks) > 0 {
		cmdStr = fmt.Sprintf(".columns %s %s", strings.Join(masks, ","), cmdStr)
	}

	res := map[string]VMs{}
	res2 := map[string]minicli.Responses{}

	for resps := range runCommandGlobally(minicli.MustCompile(cmdStr), false) {
		for _, resp := range resps {
			if resp.Error != "" {
				log.Errorln(resp.Error)
				continue
			}

			switch data := resp.Data.(type) {
			case VMs:
				res[resp.Host] = data
			default:
				log.Error("unknown data field in vm info")
			}

			res2[resp.Host] = append(res2[resp.Host], resp)
		}
	}

	return res, res2
}
Example #4
0
func webHosts(w http.ResponseWriter, r *http.Request) {
	hosts := [][]interface{}{}

	cmd := minicli.MustCompile("host")
	cmd.SetRecord(false)

	for resps := range runCommandGlobally(cmd) {
		for _, resp := range resps {
			if resp.Error != "" {
				log.Errorln(resp.Error)
				continue
			}

			for _, row := range resp.Tabular {
				res := []interface{}{}
				for _, v := range row {
					res = append(res, v)
				}
				hosts = append(hosts, res)
			}
		}
	}

	js, err := json.Marshal(hosts)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(js)
}
Example #5
0
// hostVMs is HostVMs without locking cmdLock.
func hostVMs(host string) VMs {
	// Compile info command and set it not to record
	cmd := minicli.MustCompile("vm info")
	cmd.SetRecord(false)
	cmd.SetSource(GetNamespaceName())

	cmds := makeCommandHosts([]string{host}, cmd)

	var vms VMs

	// LOCK: see func description.
	for resps := range runCommands(cmds...) {
		for _, resp := range resps {
			if resp.Error != "" {
				log.Errorln(resp.Error)
				continue
			}

			if vms2, ok := resp.Data.(VMs); ok {
				if vms != nil {
					// odd... should only be one vms per host and we're
					// querying a single host
					log.Warn("so many vms")
				}
				vms = vms2
			}
		}
	}

	return vms
}
Example #6
0
// webScreenshot serves routes like /screenshot/<host>/<id>.png. Optional size
// query parameter dictates the size of the screenshot.
func webScreenshot(w http.ResponseWriter, r *http.Request) {
	fields := strings.Split(r.URL.Path, "/")
	if len(fields) != 4 {
		http.NotFound(w, r)
		return
	}
	fields = fields[2:]

	size := r.URL.Query().Get("size")
	host := fields[0]
	id := strings.TrimSuffix(fields[1], ".png")

	cmdStr := fmt.Sprintf("vm screenshot %s file /dev/null %s", id, size)
	if host != hostname {
		cmdStr = fmt.Sprintf("mesh send %s .record false %s", host, cmdStr)
	}

	cmd := minicli.MustCompile(cmdStr)
	cmd.Record = false

	var screenshot []byte

	for resps := range runCommand(cmd) {
		for _, resp := range resps {
			if resp.Error != "" {
				if strings.HasPrefix(resp.Error, "vm not running:") {
					continue
				} else if strings.HasPrefix(resp.Error, "vm does not support screenshots") {
					continue
				}

				// Unknown error
				log.Errorln(resp.Error)
				http.Error(w, friendlyError, http.StatusInternalServerError)
				return
			}

			if resp.Data == nil {
				http.NotFound(w, r)
			}

			if screenshot == nil {
				screenshot = resp.Data.([]byte)
			} else {
				log.Error("received more than one response for vm screenshot")
			}
		}
	}

	if screenshot != nil {
		w.Write(screenshot)
	} else {
		http.NotFound(w, r)
	}
}
Example #7
0
// findRemoteVM attempts to find the VM ID of a VM by name or ID on a remote
// minimega node. It returns the ID of the VM on the remote host or an error,
// which may also be an error communicating with the remote node.
func findRemoteVM(host, vm string) (int, string, error) {
	log.Debug("findRemoteVM: %v %v", host, vm)

	// check for our own host
	if host == hostname || host == Localhost {
		log.Debugln("host is local node")
		vm := vms.findVm(vm)
		if vm != nil {
			log.Debug("got vm: %v %v %v", host, vm.ID, vm.Name)
			return vm.ID, vm.Name, nil
		}
	} else {
		log.Debugln("remote host")

		var cmdStr string
		v, err := strconv.Atoi(vm)
		if err == nil {
			cmdStr = fmt.Sprintf(".filter id=%v .columns name,id vm info", v)
		} else {
			cmdStr = fmt.Sprintf(".filter name=%v .columns name,id vm info", v)
		}

		cmd := minicli.MustCompile(cmdStr)

		remoteRespChan := make(chan minicli.Responses)
		go func() {
			meshageSend(cmd, host, remoteRespChan)
			close(remoteRespChan)
		}()

		for resps := range remoteRespChan {
			// Find a response that is not an error
			for _, resp := range resps {
				if resp.Error == "" && len(resp.Tabular) > 0 {
					// Found it!
					row := resp.Tabular[0] // should be name,id
					name := row[0]
					id, err := strconv.Atoi(row[1])
					if err != nil {
						log.Debug("malformed response: %#v", resp)
					} else {
						return id, name, nil
					}
				}
			}
		}
	}

	return 0, "", vmNotFound(vm)
}
Example #8
0
// lookupVLAN uses the allocatedVLANs and active namespace to turn a string
// into a VLAN. If the VLAN didn't already exist, broadcasts the update to the
// cluster.
func lookupVLAN(alias string) (int, error) {
	namespace := GetNamespaceName()

	vlan, err := allocatedVLANs.ParseVLAN(namespace, alias)
	if err != vlans.ErrUnallocated {
		// nil or other error
		return vlan, err
	}

	vlan, created, err := allocatedVLANs.Allocate(namespace, alias)
	if err != nil {
		return 0, err
	}

	if !created {
		return vlan, nil
	}

	// Broadcast out vlan alias to everyone so that we have a record of the
	// aliases, should this node crash.
	s := fmt.Sprintf("vlans add %q %v", alias, vlan)
	if namespace != "" && !strings.Contains(alias, vlans.AliasSep) {
		s = fmt.Sprintf("namespace %q %v", namespace, s)
	}
	cmd := minicli.MustCompile(s)
	cmd.SetRecord(false)
	cmd.SetSource(namespace)

	respChan, err := meshageSend(cmd, Wildcard)
	if err != nil {
		// don't propagate the error since this is supposed to be best-effort.
		log.Error("unable to broadcast alias update: %v", err)
		return vlan, nil
	}

	// read all the responses, looking for errors
	go func() {
		for resps := range respChan {
			for _, resp := range resps {
				if resp.Error != "" {
					log.Info("unable to send alias %v -> %v to %v: %v", alias, vlan, resp.Host, resp.Error)
				}
			}
		}

	}()

	return vlan, nil
}
Example #9
0
// makeCommandHosts creates commands to run the given command on a set of hosts
// handling the special case where the local node is included in the list.
// makeCommandHosts is namespace-aware -- it generates commands based on the
// currently active namespace.
func makeCommandHosts(hosts []string, cmd *minicli.Command) []*minicli.Command {
	// filter out the local host, if included
	var includeLocal bool
	var hosts2 []string

	for _, host := range hosts {
		if host == hostname {
			includeLocal = true
		} else {
			// Quote the hostname in case there are spaces
			hosts2 = append(hosts2, fmt.Sprintf("%q", host))
		}
	}

	var cmds = []*minicli.Command{}

	if includeLocal {
		// Create a deep copy of the command by recompiling it
		cmd2 := minicli.MustCompile(cmd.Original)
		cmd2.SetRecord(cmd.Record)
		cmd2.SetSource(cmd.Source)

		cmds = append(cmds, cmd2)
	}

	if len(hosts2) > 0 {
		ns := GetNamespace()

		targets := strings.Join(hosts2, ",")

		// Keep the original CLI input
		original := cmd.Original

		// Prefix with namespace, if one is set
		if ns != nil {
			original = fmt.Sprintf("namespace %q %v", ns.Name, original)
		}

		cmd2 := minicli.MustCompilef("mesh send %s %s", targets, original)
		cmd2.SetRecord(cmd.Record)
		cmd2.SetSource(cmd.Source)

		cmds = append(cmds, cmd2)
	}

	return cmds
}
Example #10
0
// globalVMs is GlobalVMs without locking cmdLock.
func globalVMs() VMs {
	// Compile info command and set it not to record
	cmd := minicli.MustCompile("vm info")
	cmd.SetRecord(false)
	cmd.SetSource(GetNamespaceName())

	// Figure out which hosts to query:
	//  * Hosts in the active namespace
	//  * Hosts connected via meshage plus ourselves
	var hosts []string
	if ns := GetNamespace(); ns != nil {
		hosts = ns.hostSlice()
	} else {
		hosts = meshageNode.BroadcastRecipients()
		hosts = append(hosts, hostname)
	}

	cmds := makeCommandHosts(hosts, cmd)

	// Collected VMs
	vms := VMs{}

	// LOCK: see func description.
	for resps := range runCommands(cmds...) {
		for _, resp := range resps {
			if resp.Error != "" {
				log.Errorln(resp.Error)
				continue
			}

			if vms2, ok := resp.Data.(VMs); ok {
				for _, vm := range vms2 {
					vms[len(vms)] = vm
				}
			} else {
				log.Error("unknown data field in vm info from %v", resp.Host)
			}
		}
	}

	return vms
}
Example #11
0
func main() {
	var err error

	flag.Usage = usage
	flag.Parse()

	logSetup()

	// see containerShim()
	if flag.NArg() > 1 && flag.Arg(0) == CONTAINER_MAGIC {
		containerShim()
	}

	cliSetup()

	if *f_cli {
		if err := minicli.Validate(); err != nil {
			log.Fatalln(err)
		}

		doc, err := minicli.Doc()
		if err != nil {
			log.Fatal("failed to generate docs: %v", err)
		}
		fmt.Println(doc)
		os.Exit(0)
	}

	// rebase f_iomBase if f_base changed but iomBase did not
	if *f_base != BASE_PATH && *f_iomBase == IOM_PATH {
		*f_iomBase = filepath.Join(*f_base, "files")
	}

	if *f_version {
		fmt.Println("minimega", version.Revision, version.Date)
		fmt.Println(version.Copyright)
		os.Exit(0)
	}

	hostname, err = os.Hostname()
	if err != nil {
		log.Fatalln(err)
	}

	if isReserved(hostname) {
		log.Warn("hostname `%s` is a reserved word -- abandon all hope, ye who enter here", hostname)
	}

	// special case, catch -e and execute a command on an already running
	// minimega instance
	if *f_e || *f_attach {
		// try to connect to the local minimega
		mm, err := miniclient.Dial(*f_base)
		if err != nil {
			log.Fatalln(err)
		}
		mm.Pager = minipager.DefaultPager

		if *f_e {
			a := flag.Args()
			log.Debugln("got args:", a)

			// TODO: Need to escape?
			cmd := minicli.MustCompile(strings.Join(a, " "))
			log.Infoln("got command:", cmd)

			mm.RunAndPrint(cmd, false)
		} else {
			mm.Attach()
		}

		return
	}

	// warn if we're not root
	user, err := user.Current()
	if err != nil {
		log.Fatalln(err)
	}
	if user.Uid != "0" {
		log.Warnln("not running as root")
	}

	// check for a running instance of minimega
	_, err = os.Stat(filepath.Join(*f_base, "minimega"))
	if err == nil {
		if !*f_force {
			log.Fatalln("minimega appears to already be running, override with -force")
		}
		log.Warn("minimega may already be running, proceed with caution")
		err = os.Remove(filepath.Join(*f_base, "minimega"))
		if err != nil {
			log.Fatalln(err)
		}
	}

	// set up signal handling
	sig := make(chan os.Signal, 1024)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
	go func() {
		first := true
		for s := range sig {
			if s == os.Interrupt && first {
				// do nothing
				continue
			}

			if *f_panic {
				panic("teardown")
			}
			if first {
				log.Info("caught signal, tearing down, ctrl-c again will force quit")
				go teardown()
				first = false
			} else {
				os.Exit(1)
			}
		}
	}()

	err = checkExternal()
	if err != nil {
		log.Warnln(err.Error())
	}

	// attempt to set up the base path
	err = os.MkdirAll(*f_base, os.FileMode(0770))
	if err != nil {
		log.Fatal("mkdir base path: %v", err)
	}

	pid := os.Getpid()
	writeOrDie(filepath.Join(*f_base, "minimega.pid"), strconv.Itoa(pid))

	go commandSocketStart()

	// create a node for meshage
	host, err := os.Hostname()
	if err != nil {
		log.Fatalln(err)
	}
	meshageInit(host, *f_context, *f_degree, *f_msaTimeout, *f_port)

	// start the cc service
	ccStart()

	// start tap reaper
	go periodicReapTaps()

	fmt.Println(banner)

	// fan out to the number of cpus on the system if GOMAXPROCS env variable is
	// not set.
	if os.Getenv("GOMAXPROCS") == "" {
		cpus := runtime.NumCPU()
		runtime.GOMAXPROCS(cpus)
	}

	if !*f_nostdin {
		cliLocal()
	} else {
		<-sig
		if *f_panic {
			panic("teardown")
		}
	}
	teardown()
}
Example #12
0
// webScreenshot serves routes like /screenshot/<host>/<id>.png. Optional size
// query parameter dictates the size of the screenshot.
func webScreenshot(w http.ResponseWriter, r *http.Request) {
	fields := strings.Split(r.URL.Path, "/")
	if len(fields) != 4 {
		http.NotFound(w, r)
		return
	}
	fields = fields[2:]

	size := r.URL.Query().Get("size")
	host := fields[0]
	id := strings.TrimSuffix(fields[1], ".png")
	do_encode := r.URL.Query().Get("base64") != ""

	cmdStr := fmt.Sprintf("vm screenshot %s file /dev/null %s", id, size)
	if host != hostname {
		cmdStr = fmt.Sprintf("mesh send %s %s", host, cmdStr)
	}

	cmd := minicli.MustCompile(cmdStr)
	cmd.SetRecord(false)

	var screenshot []byte

	for resps := range RunCommands(cmd) {
		for _, resp := range resps {
			if resp.Error != "" {
				if strings.HasPrefix(resp.Error, "vm not running:") {
					continue
				} else if resp.Error == "cannot take screenshot of container" {
					continue
				} else if strings.HasPrefix(resp.Error, "cannot take screenshot of container") {
					continue
				}

				// Unknown error
				log.Errorln(resp.Error)
				http.Error(w, friendlyError, http.StatusInternalServerError)
				return
			}

			if resp.Data == nil {
				http.NotFound(w, r)
			}

			if screenshot == nil {
				screenshot = resp.Data.([]byte)
			} else {
				log.Error("received more than one response for vm screenshot")
			}
		}
	}

	if screenshot != nil {
		if do_encode {
			base64string := "data:image/png;base64," + base64.StdEncoding.EncodeToString(screenshot)
			w.Write([]byte(base64string))
		} else {
			w.Write(screenshot)
		}
	} else {
		http.NotFound(w, r)
	}
}
Example #13
0
// dot returns a graphviz 'dotfile' string representing the experiment topology
// from the perspective of this node.
func cliDot(c *minicli.Command) *minicli.Response {
	resp := &minicli.Response{Host: hostname}

	// Create the file before running any commands incase there is an error
	fout, err := os.Create(c.StringArgs["filename"])
	if err != nil {
		resp.Error = err.Error()
		return resp
	}
	defer fout.Close()

	// TODO: Rewrite to use runCommandGlobally
	cmd := minicli.MustCompile(".columns host,name,id,ip,ip6,state,vlan vm info")

	writer := bufio.NewWriter(fout)

	fmt.Fprintln(writer, "graph minimega {")
	fmt.Fprintln(writer, `size=\"8,11\";`)
	fmt.Fprintln(writer, "overlap=false;")
	//fmt.Fprintf(fout, "Legend [shape=box, shape=plaintext, label=\"total=%d\"];\n", len(n.effectiveNetwork))

	var expVms []*dotVM

	// Get info from local hosts by invoking command directly
	for resp := range minicli.ProcessCommand(cmd, false) {
		expVms = append(expVms, dotProcessInfo(resp)...)
	}

	// Get info from remote hosts over meshage
	remoteRespChan := make(chan minicli.Responses)
	go func() {
		meshageBroadcast(cmd, remoteRespChan)
		close(remoteRespChan)
	}()

	for resp := range remoteRespChan {
		expVms = append(expVms, dotProcessInfo(resp)...)
	}

	vlans := make(map[string]bool)

	for _, v := range expVms {
		color := stateToColor[v.State]
		fmt.Fprintf(writer, "%s [style=filled, color=%s];\n", v.Text, color)

		for _, c := range v.Vlans {
			fmt.Fprintf(writer, "%s -- %s\n", v.Text, c)
			vlans[c] = true
		}
	}

	for k, _ := range vlans {
		fmt.Fprintf(writer, "%s;\n", k)
	}

	fmt.Fprint(writer, "}")
	err = writer.Flush()
	if err != nil {
		resp.Error = err.Error()
	}

	return resp
}
Example #14
0
func main() {
	var err error

	flag.Usage = usage
	flag.Parse()
	if !strings.HasSuffix(*f_base, "/") {
		*f_base += "/"
	}

	if *f_cli {
		doc, err := minicli.Doc()
		if err != nil {
			log.Fatal("failed to generate docs: %v", err)
		}
		fmt.Println(doc)
		os.Exit(0)
	}

	// rebase f_iomBase if f_base changed but iomBase did not
	if *f_base != BASE_PATH && *f_iomBase == IOM_PATH {
		*f_iomBase = *f_base + "files"
	}

	if !strings.HasSuffix(*f_iomBase, "/") {
		*f_iomBase += "/"
	}

	if *f_version {
		fmt.Println("minimega", version.Revision, version.Date)
		fmt.Println(version.Copyright)
		os.Exit(0)
	}

	logSetup()

	hostname, err = os.Hostname()
	if err != nil {
		log.Fatalln(err)
	}

	if isReserved(hostname) {
		log.Warn("hostname `%s` is a reserved word -- abandon all hope, ye who enter here", hostname)
	}

	vms = make(map[int]VM)

	// special case, catch -e and execute a command on an already running
	// minimega instance
	if *f_e || *f_attach {
		// try to connect to the local minimega
		mm, err := miniclient.Dial(*f_base)
		if err != nil {
			log.Fatalln(err)
		}

		if *f_e {
			a := flag.Args()
			log.Debugln("got args:", a)

			// TODO: Need to escape?
			cmd := minicli.MustCompile(strings.Join(a, " "))
			log.Infoln("got command:", cmd)

			mm.RunAndPrint(cmd, false)
		} else {
			mm.Attach()
		}

		return
	}

	// warn if we're not root
	user, err := user.Current()
	if err != nil {
		log.Fatalln(err)
	}
	if user.Uid != "0" {
		log.Warnln("not running as root")
	}

	// check for a running instance of minimega
	_, err = os.Stat(*f_base + "minimega")
	if err == nil {
		if !*f_force {
			log.Fatalln("minimega appears to already be running, override with -force")
		}
		log.Warn("minimega may already be running, proceed with caution")
		err = os.Remove(*f_base + "minimega")
		if err != nil {
			log.Fatalln(err)
		}
	}

	// set up signal handling
	sig := make(chan os.Signal, 1024)
	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
	go func() {
		first := true
		for {
			<-sig
			if *f_panic {
				panic("teardown")
			}
			if first {
				log.Info("caught signal, tearing down, ctrl-c again will force quit")
				go teardown()
				first = false
			} else {
				os.Exit(1)
			}
		}
	}()

	err = checkExternal()
	if err != nil {
		log.Warnln(err.Error())
	}

	// attempt to set up the base path
	err = os.MkdirAll(*f_base, os.FileMode(0770))
	if err != nil {
		log.Fatal("mkdir base path: %v", err)
	}
	pid := os.Getpid()
	err = ioutil.WriteFile(*f_base+"minimega.pid", []byte(fmt.Sprintf("%v", pid)), 0664)
	if err != nil {
		log.Error("write minimega pid: %v", err)
		teardown()
	}
	go commandSocketStart()

	// create a node for meshage
	host, err := os.Hostname()
	if err != nil {
		log.Fatalln(err)
	}
	meshageInit(host, *f_namespace, uint(*f_degree), *f_port)

	fmt.Println(banner)

	// fan out to the number of cpus on the system if GOMAXPROCS env variable is
	// not set.
	if os.Getenv("GOMAXPROCS") == "" {
		cpus := runtime.NumCPU()
		runtime.GOMAXPROCS(cpus)
	}

	if !*f_nostdin {
		cliLocal()
	} else {
		<-sig
		if *f_panic {
			panic("teardown")
		}
	}
	teardown()
}