func main() { var host = flag.String("H", "", "host (or hosts) to act on") var role = flag.String("R", "", "role (or roles) to act on") var proxy = flag.String("P", "127.0.0.1", "agent to interact with") var all = flag.Bool("A", false, "act on all live nodes") var fserial = flag.Bool("s", false, "print output when commands finish") var fbinary = flag.Bool("b", false, "force binary mode output") var ftext = flag.Bool("l", false, "force line mode output") var glock = flag.String("G", "", "global lock to claim") var llock = flag.String("L", "", "local lock to claim") var dzrhost = flag.String("doozer", "localhost:8046", "host:port for doozer") var jobs = flag.Int("j", 0, "jobs to run in parallel") var tout = flag.Int("t", 20, "timeout (in seconds) for jobs") var fnowarn = flag.Bool("w", false, "suppress error text output") flag.Usage = clientUsage flag.Parse() timeout = *tout if timeout < 0 { log.Error("timeout must be 0 (no timeout) or positive") os.Exit(1) } nowarn = *fnowarn // The output selection modes. By default, we assume that if the output // is from a single machine, it's binary and we don't touch it. However, // if the user is running against multiple targets, we assume that the // output is line-based and we prefix the hostname returning each line. // // Line based defaults to interleaved. The serial flag can be used to // ask us to buffer each host's output and then dump it all at once when // that host is done. This is more useful for doing a batch job and having // easily read output later, but still running commands in parallel. // // Finally, the user can force binary mode on multi-host outputs, which // makes us not touch the output. serial = *fserial binary = *fbinary && !*ftext // Uses the nice golog package to handle logging arguments and flags // so we don't have to worry about it. log = logging.InitFromFlags() safedoozer.SetLogger(log) // Connect to our doozer host. We have to do this early because alias // validation requires doozer. dzr = safedoozer.Dial(*dzrhost) defer dzr.Close() // Do some simple argument validation. args := expandAlias(flag.Args()) if len(args) == 0 { flag.Usage() os.Exit(1) } if !isValidCommand(args[0], args[1:]) { os.Exit(1) } // Figure out localhost. var err error // If we use := below, we shadow the global, which is bad. hostname, err = os.Hostname() if err != nil { log.Error("failed getting hostname: %s", err) os.Exit(1) } hostname = strings.Split(hostname, ".")[0] if len(hostname) <= 0 { log.Error("hostname is empty!") os.Exit(1) } zmq_ctx, err = zmq.NewContext() if err != nil { log.Error("failed to init zmq: %s", err) os.Exit(1) } defer zmq_ctx.Close() // Connect to the proxy agent. This is the agent we will be having do all // of the work for us. This is probably localhost. psock = socketForIp(*proxy) if psock == nil { log.Error("unable to connect to proxy agent") os.Exit(1) } defer (*psock).Close() // Determine which nodes we will be addressing. hosts := make(map[string]bool) if *all { for _, host := range nodes() { hosts[host] = false } } else { if *host != "" { lhosts := strings.Split(*host, ",") for _, host := range lhosts { host = strings.TrimSpace(host) if len(host) > 0 { hosts[strings.TrimSpace(host)] = false } } } if *role != "" { roles := strings.Split(*role, ",") for _, role := range roles { lhosts := convertRoleToHosts(strings.TrimSpace(role)) for _, host := range lhosts { hosts[host] = false } } } if *host == "" && *role == "" { // Both empty, default to this machine. hosts[hostname] = false } } // If no hosts, bail out. if len(hosts) <= 0 { log.Error("no hosts or roles specified") os.Exit(1) } else if len(hosts) == 1 { // If we're targetting a single machine, we want to use binary mode by // default unless the user explicitly set text mode. binary = true && !*ftext } // If we have been told to get a global lock, let's try to get that now. if *glock != "" { resp := proxyCommand("global_lock", *glock) if resp != "locked" { log.Error("failed to get global lock %s: %s", *glock, resp) os.Exit(1) } defer func(lock string) { resp := proxyCommand("global_unlock", *glock) if resp != "unlocked" { log.Error("failed to release global lock %s: %s", *glock, resp) } }(*glock) } // Same, local. if *llock != "" { resp := proxyCommand("local_lock", *llock) if resp != "locked" { log.Error("failed to get local lock %s: %s", *llock, resp) os.Exit(1) } defer func(lock string) { resp := proxyCommand("local_unlock", *llock) if resp != "unlocked" { log.Error("failed to release local lock %s: %s", *llock, resp) } }(*llock) } // There are some commands which are client-only and don't run against a // given set of hosts. For these, just execute locally and then we're done. switch args[0] { case "roles": cmdRoles() case "hosts": cmdHosts() case "alias": cmdAlias(args[1:]) } // Queue up the jobs and then execute them. for host, _ := range hosts { queueJob(host, args) } runJobs(*jobs) // Returns when jobs are done. }
func main() { var myhost = flag.String("hostname", "", "this machine's hostname") var myport = flag.Int("port", 7330, "port number to listen on") var dzrhost = flag.String("doozer", "localhost:8046", "host:port for doozer") flag.Parse() // Uses the nice golog package to handle logging arguments and flags // so we don't have to worry about it. log = logging.InitFromFlags() safedoozer.SetLogger(log) log.Info("starting up - gid %s", gid) rand.Seed(int64(time.Now().Nanosecond())) // Not the best but ok? dzr = safedoozer.Dial(*dzrhost) defer dzr.Close() defer removeGlobalLocks() // see config.go for this initializeConfig() var err error // If we use := below, we shadow the global, which is bad. zmq_ctx, err = zmq.NewContext() if err != nil { log.Fatal("failed to init zmq: %s", err) } defer zmq_ctx.Close() myinfo, err := getSelfInfo() if err != nil { log.Fatal("Failed getting local information: %s", err) } if *myhost == "" { _, ok := myinfo["hostname"] if !ok { log.Fatal("getSelfInfo() did not return a hostname") } hostname = myinfo["hostname"] } else { log.Warn("user requested hostname override (command line argument)") hostname = *myhost } // Global, easy to access variable since we use it everywhere. This is safe // because it's read-only, too -- well, by definition. It isn't really. But // if it changes in maintainInfo then we exit. log.Info("client starting with hostname %s", hostname) // Now we have enough information to see if anybody else is claiming to be // this particular node. If so, we want to wait a bit and see if they // update again. If they are updating, then it is assumed they are running // okay, and we shouldn't start up again. lock := "/s/nlock/" + hostname rev := dzr.Stat(lock, nil) // If the lock is claimed, attempt to wait and see if the remote seems // to still be alive. if rev > 0 { log.Warn("node lock is claimed, waiting to see if it's lively") time.Sleep(3 * time.Second) nrev := dzr.Stat(lock, nil) if nrev > rev { log.Fatal("lock is lively, we can't continue!") } } // Safe to claim the node lock. Let's get it. log.Info("attempting to get the node lock") nrev := dzr.Set(lock, rev, fmt.Sprintf("%d", time.Now().Unix())) if nrev <= rev { log.Fatal("failed to obtain the lock") } log.Info("lock successfully obtained! we are lively.") go maintainLock(lock, nrev) // Now we want to reset the gid map, asserting that we are now the living // agent for this host. lock = "/s/gid/" + hostname rev = dzr.Stat(lock, nil) dzr.Set(lock, rev, gid) // There are certain miscellaneous tasks that need to get done somewhere, // so we use global locks to make sure that somebody is doing them. We // don't really care who. go manageGlobalFunc(5, "gf.expire-hosts", gfExpireHosts) go manageGlobalFunc(5, "gf.expire-glocks", gfExpireGlobalLocks) // Personal maintenance here. go maintainInfo(&myinfo) go maintainStanzas() runAgent(*myport) // Returns when dead. }