exp.upspin.io@v0.0.0-20230625230448-5076e5b595ec/cmd/upspin-warden/main.go (about)

     1  // Copyright 2017 The Upspin Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Command upspin-warden runs Upspin client daemons, such as upspinfs and
     6  // cacheserver, and exports information about them to external programs.
     7  package main
     8  
     9  import (
    10  	"bytes"
    11  	"flag"
    12  	"fmt"
    13  	"io"
    14  	"net/http"
    15  	"os"
    16  	"os/exec"
    17  	"sort"
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  
    22  	"upspin.io/flags"
    23  	"upspin.io/log"
    24  )
    25  
    26  func main() {
    27  	cmd := flag.String("cmd", "cacheserver,upspinfs,upspin-sharebot", "comma-separated list of `commands` to run")
    28  	flags.Parse(nil, "log", "config", "http")
    29  	w := NewWarden(strings.Split(*cmd, ","))
    30  	log.Fatal(http.ListenAndServe(flags.HTTPAddr, w))
    31  }
    32  
    33  // restartInterval specifies the time between daemon restarts.
    34  const restartInterval = 10 * time.Second
    35  
    36  // Warden implements the upspin-warden daemon.
    37  type Warden struct {
    38  	log   rollingLog
    39  	procs map[string]*Process
    40  }
    41  
    42  // NewWarden creates a Warden that runs the given commands.
    43  // It implements a http.Handler that exports server state and logs.
    44  // It redirects global Upspin log output to its internal rolling log.
    45  func NewWarden(cmds []string) *Warden {
    46  	w := &Warden{procs: map[string]*Process{}}
    47  	for _, c := range cmds {
    48  		w.procs[c] = &Process{name: c}
    49  	}
    50  	log.SetOutput(io.MultiWriter(os.Stderr, &w.log))
    51  	for _, p := range w.procs {
    52  		go p.Run()
    53  	}
    54  	return w
    55  }
    56  
    57  // ServeHTTP implements http.Handler.
    58  func (w *Warden) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
    59  	switch name := r.URL.Path[1:]; name {
    60  	case "": // Root.
    61  		// Show truncated warden logs.
    62  		fmt.Fprintln(rw, "warden:")
    63  		fprintLastNLines(rw, w.log.Log(), 10, "\t")
    64  		// Show processes, their states, and truncated logs.
    65  		var names []string
    66  		for n := range w.procs {
    67  			names = append(names, n)
    68  		}
    69  		sort.Strings(names)
    70  		for _, n := range names {
    71  			p := w.procs[n]
    72  			fmt.Fprintf(rw, "\n%s: %s\n", n, p.State())
    73  			fprintLastNLines(rw, p.log.Log(), 10, "\t")
    74  		}
    75  	case "warden":
    76  		// Show complete warden log.
    77  		rw.Write(w.log.Log())
    78  	default:
    79  		// Show log for the given process.
    80  		p, ok := w.procs[name]
    81  		if !ok {
    82  			http.NotFound(rw, r)
    83  			return
    84  		}
    85  		rw.Write(p.log.Log())
    86  	}
    87  }
    88  
    89  // fprintLastNLines writes the last n lines of buf to w,
    90  // adding prefix to the start of each line.
    91  func fprintLastNLines(w io.Writer, buf []byte, n int, prefix string) {
    92  	lines := make([][]byte, 0, n)
    93  	for i := 0; i <= n; i++ {
    94  		j := bytes.LastIndexByte(buf, '\n')
    95  		if j <= 0 {
    96  			if len(buf) > 0 {
    97  				lines = append(lines, buf)
    98  			}
    99  			break
   100  		}
   101  		lines = append(lines, buf[j+1:])
   102  		buf = buf[:j]
   103  	}
   104  	for i := len(lines) - 1; i >= 0; i-- {
   105  		fmt.Fprintf(w, "%s%s\n", prefix, lines[i])
   106  	}
   107  }
   108  
   109  // ProcessState describes the state of a Process.
   110  type ProcessState int
   111  
   112  //go:generate stringer -type ProcessState
   113  
   114  const (
   115  	NotStarted ProcessState = iota
   116  	Starting
   117  	Running
   118  	Error
   119  )
   120  
   121  // Process manages the execution of a daemon process and captures its logs.
   122  type Process struct {
   123  	name string
   124  	log  rollingLog
   125  
   126  	mu    sync.Mutex
   127  	state ProcessState
   128  }
   129  
   130  // State reports the state of the process.
   131  func (p *Process) State() ProcessState {
   132  	p.mu.Lock()
   133  	defer p.mu.Unlock()
   134  	return p.state
   135  }
   136  
   137  // Run executes the process in a loop, restarting it after restartInterval
   138  // since its last start.
   139  func (p *Process) Run() {
   140  	for {
   141  		started := time.Now()
   142  		err := p.exec()
   143  		log.Error.Printf("%v: %v", p.name, err)
   144  		if d := time.Since(started); d < restartInterval {
   145  			i := restartInterval - d
   146  			log.Debug.Printf("%v: waiting %v before restarting", p.name, i)
   147  			time.Sleep(i)
   148  		}
   149  	}
   150  }
   151  
   152  // Exec starts the process and waits for it to return,
   153  // updating the process's state field as necessary.
   154  func (p *Process) exec() error {
   155  	cmd := exec.Command(p.name,
   156  		"-log="+flags.Log.String(),
   157  		"-config="+flags.Config)
   158  	cmd.Stdout = &p.log
   159  	cmd.Stderr = &p.log
   160  	p.setState(Starting)
   161  	if err := cmd.Start(); err != nil {
   162  		return err
   163  	}
   164  	p.setState(Running)
   165  	err := cmd.Wait()
   166  	p.setState(Error)
   167  	return err
   168  }
   169  
   170  func (p *Process) setState(s ProcessState) {
   171  	p.mu.Lock()
   172  	p.state = s
   173  	p.mu.Unlock()
   174  	log.Debug.Printf("%s: %s", p.name, s)
   175  }