func runService(dc *deferer.Deferer, serviceDone chan struct{}, id int, ttl uint64, target, cmd, base, arg string) { d := deferer.NewDeferer(dc) defer d.Run() conn, err := dbus.New() if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "dbus.New", }, "error creating new dbus connection") } f, err := os.Create("/run/systemd/system/" + target) if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "os.Create", }, "error creating service file") } d.Defer(func() { _ = f.Close() }) arg = base64.StdEncoding.EncodeToString([]byte(arg)) dotService := fmt.Sprintf(service, base, cmd, arg, ttl) _, err = f.WriteString(dotService) if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "f.WriteString", }, "error writing service file") } if err := f.Sync(); err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "f.Sync", }, "error syncing service file") } log.WithFields(log.Fields{ "locker": fmt.Sprintf("locker-%s-%d", base, id), "locked": fmt.Sprintf("locked-%s-%d", base, id), }).Info("created service names") done := make(chan string) _, err = conn.StartUnit(target, "fail", done) if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "conn.StartUnit", "name": target, }, "error starting service") } status := <-done if status != "done" { d.FatalWithFields(log.Fields{ "status": status, "func": "StartUnit", "name": target, }, "StartUnit returned a bad status") } serviceDone <- struct{}{} }
func runService(dc *deferer.Deferer, serviceDone chan struct{}, id int, name string, args []string) { d := deferer.NewDeferer(dc) defer d.Run() conn, err := dbus.New() if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "dbus.New", }, "error creating new dbus connection") } f, err := os.Create("/run/systemd/system/" + name) if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "os.Create", }, "error creating service file") } d.Defer(func() { _ = f.Close() }) base := filepath.Base(args[0]) // For args with spaces, quote 'em for i, v := range args { if strings.Contains(v, " ") { args[i] = "'" + v + "'" } } dotService := fmt.Sprintf(service, base, strings.Join(args, " ")) _, err = f.WriteString(dotService) if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "f.WriteString", "name": name, }, "error writing service file") } if err := f.Sync(); err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "f.Sync", "name": name, }, "error syncing service file") } done := make(chan string) _, err = conn.StartUnit(name, "fail", done) if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "conn.StartUnit", "name": name, }, "error starting service") } status := <-done if status != "done" { d.FatalWithFields(log.Fields{ "status": status, "func": "StartUnit", }, "StartUnit returned a bad status") } serviceDone <- struct{}{} }
func main() { d := deferer.NewDeferer(nil) defer d.Run() rand.Seed(time.Now().UnixNano()) id := rand.Int() if ID := os.Getenv("ID"); ID != "" { fmt.Sscanf(ID, "%d", &id) } params := params{ID: id} flag.Uint64VarP(¶ms.Interval, "interval", "i", 30, "Interval in seconds to refresh lock") flag.Uint64VarP(¶ms.TTL, "ttl", "t", 0, "TTL for key in seconds, leave 0 for (2 * interval)") flag.StringVarP(¶ms.Key, "key", "k", "/lock", "Key to use as lock") flag.BoolVarP(¶ms.Blocking, "block", "b", false, "Block if we failed to acquire the lock") flag.StringVarP(¶ms.Addr, "etcd", "e", defaultAddr, "address of etcd machine") flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s: [options] -- command args\n", os.Args[0]) flag.PrintDefaults() fmt.Fprintf(os.Stderr, "\ncommand will be run with args via fork/exec not a shell\n") } flag.Parse() if params.TTL == 0 { params.TTL = params.Interval * 2 } params.Args = flag.Args() if len(params.Args) < 1 { d.Fatal("command is required") } cmd := resolveCommand(params.Args[0]) if cmd == "" { d.Fatal("") } params.Args[0] = cmd hostname, err := os.Hostname() if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "os.Hostname", }, "failed to get hostname") } c := etcd.NewClient([]string{params.Addr}) l, err := lock.Acquire(c, params.Key, hostname, params.TTL, params.Blocking) if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "lock.Acquire", "lock": params.Key, "ttl": params.TTL, "blocking": params.Blocking, }, "failed to get lock") } d.Defer(func() { if err := l.Release(); err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "l.Release", }, "failed to release lock") } }) params.Lock = l args, err := json.Marshal(¶ms) if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "json.Marshal", }, "failed to serialize params") } serviceDone := make(chan struct{}) base := filepath.Base(params.Args[0]) target := fmt.Sprintf("locker-%s-%d.service", base, id) locker := resolveCommand("locker") if locker == "" { d.Fatal("") } go runService(d, serviceDone, params.ID, params.TTL, target, locker, base, string(args)) sigs := make(chan os.Signal) signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) select { case <-serviceDone: log.WithField("service_state", "done").Info("service is done") case s := <-sigs: log.WithField("signal", s).Info("signal received") if err := killService(target, int32(s.(syscall.Signal))); err != nil { log.WithField("error", err).Info("failed to kill service") } } }
func main() { d := deferer.NewDeferer(nil) defer d.Run() params := params{} arg, err := base64.StdEncoding.DecodeString(os.Args[1]) if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "base64.DecodeString", "arg": os.Args[1], }, "error decoding arg string") } if err := json.Unmarshal(arg, ¶ms); err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "json.Unmarshal", "json": arg, }, "error unmarshaling json") } l := params.Lock if err := l.Refresh(); err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "lock.Refresh", }, "failed to refresh lock") } d.Defer(func() { if err := l.Release(); err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "lock.Release", }, "failed to release lock") } }) locker := refresh(l, params.Interval) sdttl, err := sd.WatchdogEnabled() if err != nil { d.FatalWithFields(log.Fields{ "error": err, "func": "sd.WatchdogEnabled", }, "failed to check watchdog configuration") } if uint64(sdttl.Seconds()) != params.TTL { d.FatalWithFields(log.Fields{ "serviceTTL": sdttl, "paramTTL": params.TTL, }, "params and systemd ttls do not match") } tickler := tickle(params.Interval) serviceDone := make(chan struct{}) base := filepath.Base(params.Args[0]) target := fmt.Sprintf("locked-%s-%d.service", base, params.ID) go runService(d, serviceDone, params.ID, target, params.Args) sigs := make(chan os.Signal) signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) var killErr error select { case <-locker: // TODO: should we never expect this? killErr = killService(target, int32(syscall.SIGINT)) case <-serviceDone: close(locker) case s := <-sigs: log.WithField("signal", s).Info("signal received") killErr = killService(target, int32(s.(syscall.Signal))) close(locker) case <-tickler: // watchdog tickler stopped, uh oh we are going down pretty soon killErr = killService(target, int32(syscall.SIGINT)) close(locker) } if killErr != nil { log.WithField("error", killErr).Fatal("failed to kill service") } }