Пример #1
0
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{}{}
}
Пример #2
0
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{}{}
}
Пример #3
0
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(&params.Interval, "interval", "i", 30, "Interval in seconds to refresh lock")
	flag.Uint64VarP(&params.TTL, "ttl", "t", 0, "TTL for key in seconds, leave 0 for (2 * interval)")
	flag.StringVarP(&params.Key, "key", "k", "/lock", "Key to use as lock")
	flag.BoolVarP(&params.Blocking, "block", "b", false, "Block if we failed to acquire the lock")
	flag.StringVarP(&params.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(&params)
	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")
		}
	}
}
Пример #4
0
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, &params); 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")
	}
}