rename to overseer, allow manual restarts with USR2, fetcher now optional, added fetcher init method

master
Jaime Pillora 2016-02-09 14:46:45 +11:00
parent 887f12bfaf
commit 6df2e197eb
14 changed files with 281 additions and 140 deletions

View File

@ -1,15 +1,22 @@
# go-upgrade
# overseer
[![GoDoc](https://godoc.org/github.com/jpillora/go-upgrade?status.svg)](https://godoc.org/github.com/jpillora/go-upgrade)
[![GoDoc](https://godoc.org/github.com/jpillora/overseer?status.svg)](https://godoc.org/github.com/jpillora/overseer)
Daemonizable self-upgrading binaries in Go (golang).
The main goal of this project is to facilitate the creation of self-upgrading binaries which play nice with standard process managers. The secondary goal is user simplicity. :warning: This is beta software.
### Features
* Works with process managers
* Simple
* Graceful, zero-down time restarts
* Allows self-upgrading binaries
### Install
```
go get github.com/jpillora/go-upgrade
go get github.com/jpillora/overseer
```
### Quick Usage
@ -23,33 +30,32 @@ import (
"net/http"
"time"
"github.com/jpillora/go-upgrade"
"github.com/jpillora/go-upgrade/fetcher"
"github.com/jpillora/overseer"
"github.com/jpillora/overseer/fetcher"
)
//convert your 'main()' into a 'prog(state)'
//'prog()' is run in a child process
func prog(state upgrade.State) {
log.Printf("app (%s) listening...", state.ID)
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "app (%s) says hello\n", state.ID)
}))
http.Serve(state.Listener, nil)
}
//then create another 'main()' which runs the upgrades
//'main()' is run in the initial process
//convert your main() into a 'prog(state)' and then
//create another main() to run the main process
func main() {
upgrade.Run(upgrade.Config{
overseer.Run(overseer.Config{
Program: prog,
Address: ":3000",
Fetcher: &fetcher.HTTP{
URL: "http://localhost:4000/binaries/myapp",
Interval: 1 * time.Second,
},
// Log: false, //display log of go-upgrade actions
// Log: false, //display log of overseer actions
})
}
//prog(state) runs in a child process
func prog(state overseer.State) {
log.Printf("app (%s) listening...", state.ID)
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "app (%s) says hello\n", state.ID)
}))
http.Serve(state.Listener, nil)
}
```
```sh
@ -84,36 +90,32 @@ app#3 (286848c2aefcd3f7321a65b5e4efae987fb17911) exiting...
### Documentation
* [Core `upgrade` package](https://godoc.org/github.com/jpillora/go-upgrade)
* [Common `fetcher.Interface`](https://godoc.org/github.com/jpillora/go-upgrade/fetcher#Interface)
* [Basic `fetcher.HTTP` fetcher type](https://godoc.org/github.com/jpillora/go-upgrade/fetcher#HTTP)
* [Core `upgrade` package](https://godoc.org/github.com/jpillora/overseer)
* [Common `fetcher.Interface`](https://godoc.org/github.com/jpillora/overseer/fetcher#Interface)
* [Basic `fetcher.HTTP` fetcher type](https://godoc.org/github.com/jpillora/overseer/fetcher#HTTP)
### Architecture overview
* `go-upgrade` uses the main process to check for and install upgrades and a child process to run `Program`
* `overseer` uses the main process to check for and install upgrades and a child process to run `Program`
* All child process pipes are connected back to the main process
* All signals received on the main process are forwarded through to the child process
* The provided `fetcher.Interface` will be used to `Fetch()` the latest build of the binary
* The `fetcher.HTTP` accepts a `URL`, it polls this URL with HEAD requests and until it detects a change. On change, we `GET` the `URL` and stream it back out to `go-upgrade`.
* Once a binary is received, it is run with a simple echo token to confirm it is a `go-upgrade` binary.
* Except for scheduled upgrades, the child process exiting will cause the main process to exit with the same code. So, **`go-upgrade` is not a process manager**.
* The `fetcher.HTTP` accepts a `URL`, it polls this URL with HEAD requests and until it detects a change. On change, we `GET` the `URL` and stream it back out to `overseer`.
* Once a binary is received, it is run with a simple echo token to confirm it is a `overseer` binary.
* Except for scheduled upgrades, the child process exiting will cause the main process to exit with the same code. So, **`overseer` is not a process manager**.
### Docker
* Create the simple image:
* Compile your `overseer`able `app` to a `/path/on/docker/host/myapp/app`
* Then run it with
```Dockerfile
FROM alpine:latest
RUN mkdir /apphome
VOLUME /apphome
CMD ["/apphome/app"]
```
#run app inside alpine linux (5MB linux distro)
docker run -d -v /path/on/docker/host/myapp/:/home/ -w /home/ alpine -w /home/app
```
* Mount `-v` your binary directory to into the container as `/apphome`
* Compile your application into binary directory`/app`
* The application will update itself, which will be kept on disk incase of crashes etc
Alternatively to mounting, `ADD app /apphome/app` and let it fetch the latest version each time it runs.
* For testing, swap out `-d` (daemonize) for `--rm -it` (remove on exit, input, terminal)
* `app` can use the current working directory as storage
### Alternatives
@ -125,7 +127,7 @@ Alternatively to mounting, `ADD app /apphome/app` and let it fetch the latest ve
* Github fetcher (given a repo)
* S3 fetcher (given a bucket and credentials)
* etcd fetcher (given a cluster, watch key)
* `go-upgrade` CLI tool ([TODO](cmd/go-upgrade/TODO.md))
* `overseer` CLI tool ([TODO](cmd/overseer/TODO.md))
* `upgrade` package
* Execute and verify calculated delta updates with https://github.com/kr/binarydist
* [Omaha](https://coreos.com/docs/coreupdate/custom-apps/coreupdate-protocol/) client support

View File

@ -4,8 +4,8 @@ import (
"strconv"
"time"
"github.com/jpillora/go-upgrade"
"github.com/jpillora/go-upgrade/fetcher"
"github.com/jpillora/overseer"
"github.com/jpillora/overseer/fetcher"
"github.com/jpillora/opts"
)
@ -19,9 +19,9 @@ func main() {
Log: true,
}
opts.Parse(&c)
upgrade.Run(upgrade.Config{
overseer.Run(overseer.Config{
Log: c.Log,
Program: func(state upgrade.State) {
Program: func(state overseer.State) {
//noop
select {}
},

View File

@ -1,7 +1,7 @@
placeholder for the goupgrade binary tool
### Placeholder for the `overseer` binary tool
* Calculate delta updates with https://github.com/kr/binarydist ([courgette](http://dev.chromium.org/developers/design-documents/software-updates-courgette) would be nice)
* Signed binaries and updates *(use HTTPS where in the meantime)*
* Create signing ECDSA private and private key, store locally
* Build binaries and include public key with `-ldflags "-X github.com/jpillora/go-upgrade/fetcher.PublicKey=A" -o myapp`
* Build binaries and include public key with `-ldflags "-X github.com/jpillora/overseer/fetcher.PublicKey=A" -o myapp`
* Only accept future updates with binaries signed by the matching private key

View File

@ -1,11 +1,11 @@
#!/bin/bash
#NOTE: DONT CTRL+C OR CLEANUP WONT OCCUR
# ENSURE PORTS 3000,4000 ARE UNUSED
# ENSURE PORTS 5001,5002 ARE UNUSED
#http file server
go get github.com/jpillora/serve
serve --port 4000 --quiet . &
serve --port 5002 --quiet . &
SERVEPID=$!
#initial build
@ -17,33 +17,33 @@ echo "RUNNING APP"
APPPID=$!
sleep 1
curl localhost:3000
curl localhost:5001
sleep 1
curl localhost:3000
curl localhost:5001
sleep 1
#request during an update
curl localhost:3000?d=5s &
curl localhost:5001?d=5s &
go build -ldflags '-X main.BUILD_ID=2' -o myappnew
echo "BUILT APP (2)"
sleep 2
curl localhost:3000
curl localhost:5001
sleep 1
curl localhost:3000
curl localhost:5001
sleep 1
#request during an update
curl localhost:3000?d=5s &
curl localhost:5001?d=5s &
go build -ldflags '-X main.BUILD_ID=3' -o myappnew
echo "BUILT APP (3)"
sleep 2
curl localhost:3000
curl localhost:5001
sleep 1
curl localhost:3000
curl localhost:5001
sleep 1
curl localhost:3000
curl localhost:5001
#end demo - cleanup
kill $SERVEPID

View File

@ -5,8 +5,7 @@ import (
"net/http"
"time"
"github.com/jpillora/go-upgrade"
"github.com/jpillora/go-upgrade/fetcher"
"github.com/jpillora/overseer"
)
//see example.sh for the use-case
@ -15,7 +14,7 @@ var BUILD_ID = "0"
//convert your 'main()' into a 'prog(state)'
//'prog()' is run in a child process
func prog(state upgrade.State) {
func prog(state overseer.State) {
fmt.Printf("app#%s (%s) listening...\n", BUILD_ID, state.ID)
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
d, _ := time.ParseDuration(r.URL.Query().Get("d"))
@ -29,13 +28,13 @@ func prog(state upgrade.State) {
//then create another 'main' which runs the upgrades
//'main()' is run in the initial process
func main() {
upgrade.Run(upgrade.Config{
Log: false, //display log of go-upgrade actions
overseer.Run(overseer.Config{
Log: true, //display log of overseer actions
Program: prog,
Address: ":3000",
Fetcher: &fetcher.HTTP{
URL: "http://localhost:4000/myappnew",
Interval: 1 * time.Second,
},
Address: ":5001",
// Fetcher: &fetcher.HTTP{
// URL: "http://localhost:5002/myappnew",
// Interval: 1 * time.Second,
// },
})
}

View File

@ -3,6 +3,11 @@ package fetcher
import "io"
type Interface interface {
//Init should perform validation on fields. For
//example, ensure the appropriate URLs or keys
//are defined or ensure there is connectivity
//to the appropriate web service.
Init() error
//Fetch should check if there is an updated
//binary to fetch, and then stream it back the
//form of an io.Reader. If io.Reader is nil,
@ -21,6 +26,10 @@ type fetcher struct {
fn func() (io.Reader, error)
}
func (f fetcher) Init() error {
return nil //skip
}
func (f fetcher) Fetch() (io.Reader, error) {
return f.fn()
}

View File

@ -9,7 +9,7 @@ import (
//HTTPFetcher uses HEAD requests to poll the status of a given
//file. If it detects this file has been updated, it will fetch
//and stream out to the binary writer.
//and return its io.Reader stream.
type HTTP struct {
//URL to poll for new binaries
URL string
@ -23,20 +23,22 @@ type HTTP struct {
//if any of these change, the binary has been updated
var defaultHTTPCheckHeaders = []string{"ETag", "If-Modified-Since", "Last-Modified", "Content-Length"}
func (h *HTTP) Fetch() (io.Reader, error) {
func (h *HTTP) Init() error {
//apply defaults
if h.URL == "" {
return nil, fmt.Errorf("fetcher.HTTP requires a URL")
return fmt.Errorf("URL required")
}
h.lasts = map[string]string{}
if h.Interval == 0 {
h.Interval = 5 * time.Minute
}
if h.CheckHeaders == nil {
h.CheckHeaders = defaultHTTPCheckHeaders
}
if h.lasts == nil {
h.lasts = map[string]string{}
}
return nil
}
func (h *HTTP) Fetch() (io.Reader, error) {
//delay fetches after first
if h.delay {
time.Sleep(h.Interval)

81
fetcher/fetcher_s3.go Normal file
View File

@ -0,0 +1,81 @@
package fetcher
import (
"errors"
"fmt"
"io"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
//S3 uses authenticated HEAD requests to poll the status of a given
//object. If it detects this file has been updated, it will perform
//an object GET and return its io.Reader stream.
type S3 struct {
Access string
Secret string
Region string
Bucket string
Key string
//interal state
Interval time.Duration
client *s3.S3
delay bool
lastETag string
}
func (s *S3) Init() error {
if s.Bucket == "" {
return errors.New("S3 bucket not set")
} else if s.Key == "" {
return errors.New("S3 key not set")
}
if s.Access == "" {
s.client = s3.New(session.New())
} else {
if s.Region == "" {
s.Region = "ap-southeast-2"
}
s.client = s3.New(session.New(&aws.Config{
Credentials: credentials.NewStaticCredentials(s.Access, s.Secret, ""),
Region: &s.Region,
}))
}
//TODO include this? maybe given access to bucket after init
// resp, err := s.client.HeadBucketRequest(&s3.HeadBucketInput{Bucket: &s.Bucket})
// if err != nil {}
//apply defaults
if s.Interval == 0 {
s.Interval = 5 * time.Minute
}
return nil
}
func (s *S3) Fetch() (io.Reader, error) {
//delay fetches after first
if s.delay {
time.Sleep(s.Interval)
}
s.delay = true
//status check using HEAD
head, err := s.client.HeadObject(&s3.HeadObjectInput{Bucket: &s.Bucket, Key: &s.Key})
if err != nil {
return nil, fmt.Errorf("HEAD request failed (%s)", err)
}
if s.lastETag == *head.ETag {
return nil, nil //skip, file match
}
s.lastETag = *head.ETag
//binary fetch using GET
get, err := s.client.GetObject(&s3.GetObjectInput{Bucket: &s.Bucket, Key: &s.Key})
if err != nil {
return nil, fmt.Errorf("GET request failed (%s)", err)
}
//success!
return get.Body, nil
}

View File

@ -1,4 +1,4 @@
package upgrade
package overseer
import (
"net"

View File

@ -1,5 +1,5 @@
// Daemonizable self-upgrading binaries in Go (golang).
package upgrade
package overseer
import (
"fmt"
@ -8,7 +8,7 @@ import (
"runtime"
"time"
"github.com/jpillora/go-upgrade/fetcher"
"github.com/jpillora/overseer/fetcher"
)
const (
@ -19,7 +19,7 @@ const (
)
type Config struct {
//Optional allows go-upgrade to fallback to running
//Optional allows overseer to fallback to running
//running the program in the main process.
Optional bool
//Program's main function
@ -28,12 +28,11 @@ type Config struct {
Address string
//Program's zero-downtime socket listening addresses (set this or Address)
Addresses []string
//Signal program will accept to initiate graceful
//application termination. Defaults to SIGTERM.
Signal os.Signal
//TerminateTimeout controls how long go-upgrade should
//RestartSignal will manually trigger a graceful restart. Defaults to SIGUSR2.
RestartSignal os.Signal
//TerminateTimeout controls how long overseer should
//wait for the program to terminate itself. After this
//timeout, go-upgrade will issue a SIGKILL.
//timeout, overseer will issue a SIGKILL.
TerminateTimeout time.Duration
//MinFetchInterval defines the smallest duration between Fetch()s.
//This helps to prevent unwieldy fetch.Interfaces from hogging
@ -42,16 +41,16 @@ type Config struct {
//PreUpgrade runs after a binary has been retreived, user defined checks
//can be run here and returning an error will cancel the upgrade.
PreUpgrade func(tempBinaryPath string) error
//NoRestart disables automatic restarts after each upgrade.
NoRestart bool
//Log enables [go-upgrade] logs to be sent to stdout.
//Log enables [overseer] logs to be sent to stdout.
Log bool
//NoRestartAfterFetch disables automatic restarts after each upgrade.
NoRestartAfterFetch bool
//Fetcher will be used to fetch binaries.
Fetcher fetcher.Interface
}
func fatalf(f string, args ...interface{}) {
log.Fatalf("[go-upgrade] "+f, args...)
log.Fatalf("[overseer] "+f, args...)
}
func Run(c Config) {
@ -62,16 +61,18 @@ func Run(c Config) {
}
//validate
if c.Program == nil {
fatalf("upgrade.Config.Program required")
fatalf("overseer.Config.Program required")
}
if c.Address != "" {
if len(c.Addresses) > 0 {
fatalf("upgrade.Config.Address and Addresses cant both be set")
fatalf("overseer.Config.Address and Addresses cant both be set")
}
c.Addresses = []string{c.Address}
} else if len(c.Addresses) > 0 {
c.Address = c.Addresses[0]
}
if c.Signal == nil {
c.Signal = SIGTERM
if c.RestartSignal == nil {
c.RestartSignal = SIGUSR2
}
if c.TerminateTimeout == 0 {
c.TerminateTimeout = 30 * time.Second
@ -79,9 +80,6 @@ func Run(c Config) {
if c.MinFetchInterval == 0 {
c.MinFetchInterval = 1 * time.Second
}
if c.Fetcher == nil {
fatalf("upgrade.Config.Fetcher required")
}
//os not supported
if !supported {
if !c.Optional {

View File

@ -1,4 +1,4 @@
package upgrade
package overseer
import (
"bytes"
@ -21,9 +21,9 @@ import (
"github.com/kardianos/osext"
)
var tmpBinPath = filepath.Join(os.TempDir(), "goupgrade")
var tmpBinPath = filepath.Join(os.TempDir(), "overseer")
//a go-upgrade master process
//a overseer master process
type master struct {
Config
slaveCmd *exec.Cmd
@ -38,7 +38,6 @@ type master struct {
awaitingUSR1 bool
descriptorsReleased chan bool
signalledAt time.Time
signals chan os.Signal
}
func (mp *master) run() {
@ -47,14 +46,23 @@ func (mp *master) run() {
fatalf("%s", err)
}
//run program directly
mp.logf("%s, disabling go-upgrade.", err)
mp.logf("%s, disabling overseer.", err)
mp.Program(DisabledState)
return
}
if mp.Config.Fetcher != nil {
if err := mp.Config.Fetcher.Init(); err != nil {
mp.logf("fetcher init failed (%s)", err)
mp.Config.Fetcher = nil
}
}
mp.setupSignalling()
mp.retreiveFileDescriptors()
mp.fetch()
go mp.fetchLoop()
if mp.Config.Fetcher != nil {
//TODO is required? fatalf("overseer.Config.Fetcher required")
mp.fetch()
go mp.fetchLoop()
}
mp.forkLoop()
}
@ -98,23 +106,27 @@ func (mp *master) setupSignalling() {
mp.restarted = make(chan bool)
mp.descriptorsReleased = make(chan bool)
//read all master process signals
mp.signals = make(chan os.Signal)
signal.Notify(mp.signals)
signals := make(chan os.Signal)
signal.Notify(signals)
go func() {
for s := range mp.signals {
for s := range signals {
mp.handleSignal(s)
}
}()
}
func (mp *master) handleSignal(s os.Signal) {
if s.String() == "child exited" {
if s == mp.RestartSignal {
//user initiated manual restart
mp.triggerRestart()
} else if s.String() == "child exited" {
// will occur on every restart
} else
//**during a restart** a SIGUSR1 signals
//to the master process that, the file
//descriptors have been released
if mp.awaitingUSR1 && s == SIGUSR1 {
mp.logf("signaled, sockets ready")
mp.awaitingUSR1 = false
mp.descriptorsReleased <- true
} else
@ -122,10 +134,7 @@ func (mp *master) handleSignal(s os.Signal) {
//all signals through
if mp.slaveCmd != nil && mp.slaveCmd.Process != nil {
mp.logf("proxy signal (%s)", s)
if err := mp.slaveCmd.Process.Signal(s); err != nil {
mp.logf("proxy signal failed (%s)", err)
os.Exit(1)
}
mp.sendSignal(s)
} else
//otherwise if not running, kill on CTRL+c
if s == os.Interrupt {
@ -136,6 +145,13 @@ func (mp *master) handleSignal(s os.Signal) {
}
}
func (mp *master) sendSignal(s os.Signal) {
if err := mp.slaveCmd.Process.Signal(s); err != nil {
mp.logf("signal failed (%s), assuming slave process died", err)
os.Exit(1)
}
}
func (mp *master) retreiveFileDescriptors() {
mp.slaveExtraFiles = make([]*os.File, len(mp.Config.Addresses))
for i, addr := range mp.Config.Addresses {
@ -229,7 +245,7 @@ func (mp *master) fetch() {
return
}
}
//go-upgrade sanity check, dont replace our good binary with a text file
//overseer sanity check, dont replace our good binary with a text file
buff := make([]byte, 8)
rand.Read(buff)
tokenIn := hex.EncodeToString(buff)
@ -252,25 +268,37 @@ func (mp *master) fetch() {
mp.logf("upgraded binary (%x -> %x)", mp.binHash[:12], newHash[:12])
mp.binHash = newHash
//binary successfully replaced
if !mp.Config.NoRestart && mp.slaveCmd != nil {
//if running, perform graceful restart
mp.restarting = true
mp.awaitingUSR1 = true
mp.signalledAt = time.Now()
mp.signals <- mp.Config.Signal //ask nicely to terminate
select {
case <-mp.restarted:
//success
case <-time.After(mp.TerminateTimeout):
//times up process, we did ask nicely!
mp.logf("graceful timeout, forcing exit")
mp.signals <- os.Kill
}
if !mp.Config.NoRestartAfterFetch {
mp.triggerRestart()
}
//and keep fetching...
return
}
func (mp *master) triggerRestart() {
if mp.restarting {
mp.logf("already graceful restarting")
return //skip
} else if mp.slaveCmd == nil || mp.restarting {
mp.logf("no slave process")
return //skip
}
mp.logf("graceful restart triggered")
mp.restarting = true
mp.awaitingUSR1 = true
mp.signalledAt = time.Now()
mp.sendSignal(mp.Config.RestartSignal) //ask nicely to terminate
select {
case <-mp.restarted:
//success
mp.logf("restart success")
case <-time.After(mp.TerminateTimeout):
//times up process, we did ask nicely!
mp.logf("graceful timeout, forcing exit")
mp.sendSignal(os.Kill)
}
}
//not a real fork
func (mp *master) forkLoop() {
//loop, restart command
@ -341,6 +369,6 @@ func (mp *master) fork() {
func (mp *master) logf(f string, args ...interface{}) {
if mp.Log {
log.Printf("[go-upgrade master] "+f, args...)
log.Printf("[overseer master] "+f, args...)
}
}

View File

@ -1,4 +1,4 @@
package upgrade
package overseer
import (
"log"
@ -12,15 +12,15 @@ import (
var (
//DisabledState is a placeholder state for when
//go-upgrade is disabled and the program function
//overseer is disabled and the program function
//is run manually.
DisabledState = State{Enabled: false}
)
type State struct {
//whether go-upgrade is running enabled. When enabled,
//whether overseer is running enabled. When enabled,
//this program will be running in a child process and
//go-upgrade will perform rolling upgrades.
//overseer will perform rolling upgrades.
Enabled bool
//ID is a SHA-1 hash of the current running binary
ID string
@ -32,9 +32,16 @@ type State struct {
//process. These are all passed into this program in the
//same order they are specified in Config.Addresses.
Listeners []net.Listener
//Program's first listening address
Address string
//Program's listening addresses
Addresses []string
//GracefulShutdown will be filled when its time to perform
//a graceful shutdown.
GracefulShutdown chan bool
}
//a go-upgrade slave process
//a overseer slave process
type slave struct {
Config
@ -48,6 +55,9 @@ func (sp *slave) run() {
sp.state.Enabled = true
sp.state.ID = os.Getenv(envBinID)
sp.state.StartedAt = time.Now()
sp.state.Address = sp.Config.Address
sp.state.Addresses = sp.Config.Addresses
sp.state.GracefulShutdown = make(chan bool, 1)
sp.watchParent()
sp.initFileDescriptors()
sp.watchSignal()
@ -99,24 +109,34 @@ func (sp *slave) initFileDescriptors() {
func (sp *slave) watchSignal() {
signals := make(chan os.Signal)
signal.Notify(signals, sp.Config.Signal)
signal.Notify(signals, sp.Config.RestartSignal)
go func() {
<-signals
signal.Stop(signals)
sp.logf("graceful shutdown requested")
//master wants to restart,
//perform graceful shutdown:
for _, l := range sp.listeners {
l.release(sp.Config.TerminateTimeout)
sp.state.GracefulShutdown <- true
//release any sockets and notify master
if len(sp.listeners) > 0 {
//perform graceful shutdown
for _, l := range sp.listeners {
l.release(sp.Config.TerminateTimeout)
}
//signal release of held sockets
sp.masterProc.Signal(SIGUSR1)
//listeners should be waiting on connections to close...
}
//signal released fds
sp.masterProc.Signal(SIGUSR1)
//listeners should be waiting on connections to close...
sp.logf("graceful shutdown")
//start death-timer
go func() {
time.Sleep(sp.Config.TerminateTimeout)
sp.logf("timeout. forceful shutdown")
os.Exit(1)
}()
}()
}
func (sp *slave) logf(f string, args ...interface{}) {
if sp.Log {
log.Printf("[go-upgrade slave] "+f, args...)
log.Printf("[overseer slave] "+f, args...)
}
}

View File

@ -1,6 +1,6 @@
// +build linux darwin
package upgrade
package overseer
//this file attempts to contain all posix
//specific stuff, that needs to be implemented
@ -15,11 +15,13 @@ const supported = true
var (
SIGUSR1 = syscall.SIGUSR1
SIGUSR2 = syscall.SIGUSR2
SIGTERM = syscall.SIGTERM
)
func move(dst, src string) error {
// HACK: we're shelling out to mv because linux
//throws errors when we use Rename/Create.
//HACK: we're shelling out to mv because linux
//throws errors when we use Rename/Create a
//running binary.
return exec.Command("mv", src, dst).Run()
}

View File

@ -1,5 +1,5 @@
// +build !linux,!darwin
package upgrade
package overseer
const supported = false