// Wrapper for minicli.ProcessCommand for commands that use meshage. // Specifically, for `mesh send all ...`, runs the subcommand locally and // across meshage, combining the results from the two channels into a single // channel. This is useful if you want to get the output of a command from all // nodes in the cluster without having to run a command locally and over // meshage. func runCommandGlobally(cmd *minicli.Command) chan minicli.Responses { // Keep the original CLI input original := cmd.Original record := cmd.Record cmd, err := minicli.Compilef("mesh send %s .record %t %s", Wildcard, record, original) if err != nil { log.Fatal("cannot run `%v` globally -- %v", original, err) } cmd.Record = record cmdLock.Lock() var wg sync.WaitGroup out := make(chan minicli.Responses) cmd, err = cliPreprocessor(cmd) if err != nil { log.Errorln(err) out <- minicli.Responses{ &minicli.Response{ Host: hostname, Error: err.Error(), }, } close(out) return out } // Run the command (should be `mesh send all ...` and the subcommand which // should run locally). ins := []chan minicli.Responses{ minicli.ProcessCommand(cmd), minicli.ProcessCommand(cmd.Subcommand), } // De-mux ins into out for _, in := range ins { wg.Add(1) go func(in chan minicli.Responses) { defer wg.Done() for v := range in { out <- v } }(in) } // Wait until everything has been read before closing the chan and // releasing the lock. go func() { defer cmdLock.Unlock() defer close(out) wg.Wait() }() return out }
// runCommands is RunCommands without locking cmdLock. func runCommands(cmd ...*minicli.Command) <-chan minicli.Responses { out := make(chan minicli.Responses) // Preprocess all the commands so that if there's an error, we haven't // already started to run some of the commands. for i := range cmd { if err := cliPreprocessor(cmd[i]); err != nil { log.Errorln(err) // Send the error from a separate goroutine because nothing will // receive from this channel until we return. Otherwise, we will // cause a deadlock. go func() { out <- errResp(err) close(out) }() return out } } // Completed preprocessing run commands serially and forward all the // responses to out go func() { defer close(out) for _, c := range cmd { forward(minicli.ProcessCommand(c), out) } }() return out }
// Wrapper for minicli.ProcessCommand. Ensures that the command execution lock // is acquired before running the command. func runCommand(cmd *minicli.Command) chan minicli.Responses { cmdLock.Lock() // Forward the responses and unlock when all are passed through localChan := make(chan minicli.Responses) go func() { defer cmdLock.Unlock() cmd, err := cliPreprocessor(cmd) if err != nil { log.Errorln(err) localChan <- minicli.Responses{ &minicli.Response{ Host: hostname, Error: err.Error(), }, } close(localChan) return } for resp := range minicli.ProcessCommand(cmd) { localChan <- resp } close(localChan) }() return localChan }
func cliRead(c *minicli.Command, respChan chan minicli.Responses) { resp := &minicli.Response{Host: hostname} file, err := os.Open(c.StringArgs["file"]) if err != nil { resp.Error = err.Error() respChan <- minicli.Responses{resp} return } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { var cmd *minicli.Command command := scanner.Text() log.Debug("read command: %v", command) // commands don't have their newlines removed cmd, err = minicli.CompileCommand(command) if err != nil { break } // No command was returned, must have been a blank line or a comment // line. Either way, don't try to run a nil command. if cmd == nil { continue } // HAX: Make sure we don't have a recursive read command if hasCommand(cmd, "read") { err = errors.New("cannot run nested `read` commands") break } for resp := range minicli.ProcessCommand(cmd, true) { respChan <- resp // Stop processing at the first error if there is one response. // TODO: What should we do if the command was mesh send and there // is a mixture of success and failure? if len(resp) == 1 && resp[0].Error != "" { break } } } if err != nil { resp.Error = err.Error() respChan <- minicli.Responses{resp} } if err := scanner.Err(); err != nil { resp.Error = err.Error() respChan <- minicli.Responses{resp} } }
func main() { flag.Parse() logSetup() // register CLI handlers for i := range cliHandlers { err := minicli.Register(&cliHandlers[i]) if err != nil { log.Fatal("invalid handler, `%v` -- %v", cliHandlers[i].HelpShort, err) } } var err error hostname, err = os.Hostname() if err != nil { log.Fatal("unable to get hostname: %v", hostname) } rond, err = ron.NewServer(*f_port, *f_path) if err != nil { log.Fatal("unable to create server: %v", err) } for { line, err := goreadline.Readline("rond$ ", true) if err != nil { return } command := string(line) log.Debug("got from stdin: `%s`", line) cmd, err := minicli.Compile(command) if err != nil { log.Error("%v", err) continue } // No command was returned, must have been a blank line or a comment // line. Either way, don't try to run a nil command. if cmd == nil { continue } for resp := range minicli.ProcessCommand(cmd) { minipager.DefaultPager.Page(resp.String()) errs := resp.Error() if errs != "" { fmt.Fprintln(os.Stderr, errs) } } } }
// Wrapper for minicli.ProcessCommand for commands that use meshage. // Specifically, for `mesh send all ...`, runs the subcommand locally and // across meshage, combining the results from the two channels into a single // channel. This is useful if you want to get the output of a command from all // nodes in the cluster without having to run a command locally and over // meshage. func runCommandGlobally(cmd *minicli.Command, record bool) chan minicli.Responses { cmdStr := fmt.Sprintf("mesh send %s %s", Wildcard, cmd.Original) cmd, err := minicli.CompileCommand(cmdStr) if err != nil { log.Fatal("cannot run `%v` globally -- %v", cmd.Original, err) } cmdLock.Lock() defer cmdLock.Unlock() var wg sync.WaitGroup out := make(chan minicli.Responses) // Run the command (should be `mesh send all ...` and the subcommand which // should run locally). ins := []chan minicli.Responses{ minicli.ProcessCommand(cmd, record), minicli.ProcessCommand(cmd.Subcommand, record), } // De-mux ins into out for _, in := range ins { wg.Add(1) go func(in chan minicli.Responses) { defer wg.Done() for v := range in { out <- v } }(in) } // Wait until everything has been read before closing out go func() { wg.Wait() close(out) }() return out }
// Wrapper for minicli.ProcessCommand. Ensures that the command execution lock // is acquired before running the command. func runCommand(cmd *minicli.Command) chan minicli.Responses { cmdLock.Lock() // Forward the responses and unlock when all are passed through localChan := make(chan minicli.Responses) go func() { defer cmdLock.Unlock() for resp := range minicli.ProcessCommand(cmd) { localChan <- resp } close(localChan) }() return localChan }
// Wrapper for minicli.ProcessCommand. Ensures that the command execution lock // is acquired before running the command. func runCommand(cmd *minicli.Command, record bool) chan minicli.Responses { cmdLock.Lock() defer cmdLock.Unlock() return minicli.ProcessCommand(cmd, record) }
// 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 }