Release Argo Tunnel Client 2018.5.5

This commit is contained in:
cloudflare-warp-bot 2018-05-18 21:07:45 +00:00
parent 75d7561b5d
commit 7acd1b1fc8
6 changed files with 138 additions and 48 deletions

View File

@ -428,14 +428,11 @@ func startServer(c *cli.Context, shutdownC chan struct{}) error {
// update needs to be after DNS proxy is up to resolve equinox server address // update needs to be after DNS proxy is up to resolve equinox server address
if isAutoupdateEnabled(c) { if isAutoupdateEnabled(c) {
if initUpdate(&listeners) {
return nil
}
logger.Infof("Autoupdate frequency is set to %v", c.Duration("autoupdate-freq")) logger.Infof("Autoupdate frequency is set to %v", c.Duration("autoupdate-freq"))
wg.Add(1) wg.Add(1)
go func(){ go func(){
defer wg.Done() defer wg.Done()
autoupdate(c.Duration("autoupdate-freq"), &listeners, shutdownC) errC <- autoupdate(c.Duration("autoupdate-freq"), &listeners, shutdownC)
}() }()
} }
@ -451,12 +448,14 @@ func startServer(c *cli.Context, shutdownC chan struct{}) error {
errC <- metrics.ServeMetrics(metricsListener, shutdownC, logger) errC <- metrics.ServeMetrics(metricsListener, shutdownC, logger)
}() }()
go notifySystemd(connectedSignal)
if c.IsSet("pidFile") {
go writePidFile(connectedSignal, c.String("pidfile"))
}
// Serve DNS proxy stand-alone if no hostname or tag or app is going to run // Serve DNS proxy stand-alone if no hostname or tag or app is going to run
if dnsProxyStandAlone(c) { if dnsProxyStandAlone(c) {
if c.IsSet("pidfile") { close(connectedSignal)
go writePidFile(connectedSignal, c.String("pidfile"))
close(connectedSignal)
}
// no grace period, handle SIGINT/SIGTERM immediately // no grace period, handle SIGINT/SIGTERM immediately
return waitToShutdown(&wg, errC, shutdownC, graceShutdownSignal, 0) return waitToShutdown(&wg, errC, shutdownC, graceShutdownSignal, 0)
} }
@ -481,10 +480,6 @@ func startServer(c *cli.Context, shutdownC chan struct{}) error {
return err return err
} }
if c.IsSet("pidFile") {
go writePidFile(connectedSignal, c.String("pidfile"))
}
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
@ -521,12 +516,13 @@ func waitToShutdown(wg *sync.WaitGroup,
return err return err
} }
func writePidFile(waitForSignal chan struct{}, pidFile string) { func notifySystemd(waitForSignal chan struct{}) {
<-waitForSignal <-waitForSignal
daemon.SdNotify(false, "READY=1") daemon.SdNotify(false, "READY=1")
if pidFile == "" { }
return
} func writePidFile(waitForSignal chan struct{}, pidFile string) {
<-waitForSignal
file, err := os.Create(pidFile) file, err := os.Create(pidFile)
if err != nil { if err != nil {
logger.WithError(err).Errorf("Unable to write pid to %s", pidFile) logger.WithError(err).Errorf("Unable to write pid to %s", pidFile)

View File

@ -48,6 +48,7 @@ func waitForSignalWithGraceShutdown(errC chan error, shutdownC, graceShutdownSig
} }
close(shutdownC) close(shutdownC)
case <-shutdownC: case <-shutdownC:
close(graceShutdownSignal)
} }
return nil return nil

View File

@ -22,7 +22,7 @@ func testChannelClosed(t *testing.T, c chan struct{}) {
case <-c: case <-c:
return return
default: default:
t.Fatal("Channel should be readable") t.Fatal("Channel should be closed")
} }
} }
@ -35,6 +35,7 @@ func TestWaitForSignal(t *testing.T) {
errC <- serverErr errC <- serverErr
}() }()
// received error, shutdownC should be closed
err := waitForSignal(errC, shutdownC) err := waitForSignal(errC, shutdownC)
assert.Equal(t, serverErr, err) assert.Equal(t, serverErr, err)
testChannelClosed(t, shutdownC) testChannelClosed(t, shutdownC)
@ -72,14 +73,25 @@ func TestWaitForSignalWithGraceShutdown(t *testing.T) {
errC <- serverErr errC <- serverErr
}() }()
// received error, both shutdownC and graceshutdownC should be closed
err := waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick) err := waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
assert.Equal(t, serverErr, err) assert.Equal(t, serverErr, err)
testChannelClosed(t, shutdownC) testChannelClosed(t, shutdownC)
testChannelClosed(t, graceshutdownC) testChannelClosed(t, graceshutdownC)
// shutdownC closed, graceshutdownC should also be closed and no error
errC = make(chan error)
shutdownC = make(chan struct{})
graceshutdownC = make(chan struct{})
close(shutdownC)
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
assert.NoError(t, err)
testChannelClosed(t, shutdownC)
testChannelClosed(t, graceshutdownC)
// Test handling SIGTERM & SIGINT // Test handling SIGTERM & SIGINT
for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} { for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} {
//var wg sync.WaitGroup
errC := make(chan error) errC := make(chan error)
shutdownC = make(chan struct{}) shutdownC = make(chan struct{})
graceshutdownC = make(chan struct{}) graceshutdownC = make(chan struct{})

View File

@ -61,29 +61,26 @@ func update(_ *cli.Context) error {
return nil return nil
} }
func initUpdate(listeners *gracenet.Net) bool { func autoupdate(freq time.Duration, listeners *gracenet.Net, shutdownC chan struct{}) error {
if updateApplied() { tickC := time.Tick(freq)
os.Args = append(os.Args, "--is-autoupdated=true")
if _, err := listeners.StartProcess(); err != nil {
logger.WithError(err).Error("Unable to restart server automatically")
return false
}
return true
}
return false
}
func autoupdate(freq time.Duration, listeners *gracenet.Net, shutdownC chan struct{}) {
for { for {
if updateApplied() { if updateApplied() {
os.Args = append(os.Args, "--is-autoupdated=true") os.Args = append(os.Args, "--is-autoupdated=true")
if _, err := listeners.StartProcess(); err != nil { pid, err := listeners.StartProcess()
if err != nil {
logger.WithError(err).Error("Unable to restart server automatically") logger.WithError(err).Error("Unable to restart server automatically")
return err
} }
close(shutdownC) // stop old process after autoupdate. Otherwise we create a new process
return // after each update
logger.Infof("PID of the new process is %d", pid)
return nil
}
select {
case <-tickC:
case <-shutdownC:
return nil
} }
time.Sleep(freq)
} }
} }

View File

@ -8,9 +8,12 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"time"
"unsafe"
cli "gopkg.in/urfave/cli.v2" cli "gopkg.in/urfave/cli.v2"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog" "golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr" "golang.org/x/sys/windows/svc/mgr"
@ -19,6 +22,13 @@ import (
const ( const (
windowsServiceName = "Cloudflared" windowsServiceName = "Cloudflared"
windowsServiceDescription = "Argo Tunnel agent" windowsServiceDescription = "Argo Tunnel agent"
recoverActionDelay = time.Second * 20
failureCountResetPeriod = time.Hour * 24
// not defined in golang.org/x/sys/windows package
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681988(v=vs.85).aspx
serviceConfigFailureActionsFlag = 4
) )
func runApp(app *cli.App, shutdownC chan struct{}) { func runApp(app *cli.App, shutdownC chan struct{}) {
@ -58,7 +68,8 @@ func runApp(app *cli.App, shutdownC chan struct{}) {
elog.Info(1, fmt.Sprintf("%s service starting", windowsServiceName)) elog.Info(1, fmt.Sprintf("%s service starting", windowsServiceName))
// Run executes service name by calling windowsService which is a Handler // Run executes service name by calling windowsService which is a Handler
// interface that implements Execute method // interface that implements Execute method.
// It will set service status to stop after Execute returns
err = svc.Run(windowsServiceName, &windowsService{app: app, elog: elog, shutdownC: shutdownC}) err = svc.Run(windowsServiceName, &windowsService{app: app, elog: elog, shutdownC: shutdownC})
if err != nil { if err != nil {
elog.Error(1, fmt.Sprintf("%s service failed: %v", windowsServiceName, err)) elog.Error(1, fmt.Sprintf("%s service failed: %v", windowsServiceName, err))
@ -74,34 +85,45 @@ type windowsService struct {
} }
// called by the package code at the start of the service // called by the package code at the start of the service
func (s *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { func (s *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, statusChan chan<- svc.Status) (ssec bool, errno uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
changes <- svc.Status{State: svc.StartPending} statusChan <- svc.Status{State: svc.StartPending}
go s.app.Run(args) errC := make(chan error)
go func() {
errC <- s.app.Run(args)
}()
statusChan <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
loop:
for { for {
select { select {
case c := <-r: case c := <-r:
switch c.Cmd { switch c.Cmd {
case svc.Interrogate: case svc.Interrogate:
s.elog.Info(1, fmt.Sprintf("control request 1 #%d", c)) s.elog.Info(1, fmt.Sprintf("control request 1 #%d", c))
changes <- c.CurrentStatus statusChan <- c.CurrentStatus
case svc.Stop: case svc.Stop:
s.elog.Info(1, "received stop control request") s.elog.Info(1, "received stop control request")
break loop close(s.shutdownC)
statusChan <- svc.Status{State: svc.StopPending}
case svc.Shutdown: case svc.Shutdown:
s.elog.Info(1, "received shutdown control request") s.elog.Info(1, "received shutdown control request")
break loop close(s.shutdownC)
statusChan <- svc.Status{State: svc.StopPending}
default: default:
s.elog.Error(1, fmt.Sprintf("unexpected control request #%d", c)) s.elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
} }
case err := <-errC:
ssec = true
if err != nil {
s.elog.Error(1, fmt.Sprintf("cloudflared terminated with error %v", err))
errno = 1
} else {
s.elog.Info(1, "cloudflared terminated without error")
errno = 0
}
return
} }
} }
close(s.shutdownC)
changes <- svc.Status{State: svc.StopPending}
return
} }
func installWindowsService(c *cli.Context) error { func installWindowsService(c *cli.Context) error {
@ -130,12 +152,18 @@ func installWindowsService(c *cli.Context) error {
return err return err
} }
defer s.Close() defer s.Close()
logger.Infof("Argo Tunnel agent service is installed")
err = eventlog.InstallAsEventCreate(windowsServiceName, eventlog.Error|eventlog.Warning|eventlog.Info) err = eventlog.InstallAsEventCreate(windowsServiceName, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil { if err != nil {
s.Delete() s.Delete()
logger.WithError(err).Errorf("Cannot install event logger") logger.WithError(err).Errorf("Cannot install event logger")
return fmt.Errorf("SetupEventLogSource() failed: %s", err) return fmt.Errorf("SetupEventLogSource() failed: %s", err)
} }
err = configRecoveryOption(s.Handle)
if err != nil {
logger.WithError(err).Errorf("Cannot set service recovery actions")
logger.Infof("See %s to manually configure service recovery actions", serviceUrl)
}
return nil return nil
} }
@ -158,6 +186,7 @@ func uninstallWindowsService(c *cli.Context) error {
logger.Errorf("Cannot delete service %s", windowsServiceName) logger.Errorf("Cannot delete service %s", windowsServiceName)
return err return err
} }
logger.Infof("Argo Tunnel agent service is uninstalled")
err = eventlog.Remove(windowsServiceName) err = eventlog.Remove(windowsServiceName)
if err != nil { if err != nil {
logger.Errorf("Cannot remove event logger") logger.Errorf("Cannot remove event logger")
@ -165,3 +194,58 @@ func uninstallWindowsService(c *cli.Context) error {
} }
return nil return nil
} }
// defined in https://msdn.microsoft.com/en-us/library/windows/desktop/ms685126(v=vs.85).aspx
type scAction int
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms685126(v=vs.85).aspx
const (
scActionNone scAction = iota
scActionRestart
scActionReboot
scActionRunCommand
)
// defined in https://msdn.microsoft.com/en-us/library/windows/desktop/ms685939(v=vs.85).aspx
type serviceFailureActions struct {
// time to wait to reset the failure count to zero if there are no failures in seconds
resetPeriod uint32
rebootMsg *uint16
command *uint16
// If failure count is greater than actionCount, the service controller repeats
// the last action in actions
actionCount uint32
actions uintptr
}
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms685937(v=vs.85).aspx
// Not supported in Windows Server 2003 and Windows XP
type serviceFailureActionsFlag struct {
// enableActionsForStopsWithErr is of type BOOL, which is declared as
// typedef int BOOL in C
enableActionsForStopsWithErr int
}
type recoveryAction struct {
recoveryType uint32
// The time to wait before performing the specified action, in milliseconds
delay uint32
}
// until https://github.com/golang/go/issues/23239 is release, we will need to
// configure through ChangeServiceConfig2
func configRecoveryOption(handle windows.Handle) error {
actions := []recoveryAction{
{recoveryType: uint32(scActionRestart), delay: uint32(recoverActionDelay / time.Millisecond)},
}
serviceRecoveryActions := serviceFailureActions{
resetPeriod: uint32(failureCountResetPeriod / time.Second),
actionCount: uint32(len(actions)),
actions: uintptr(unsafe.Pointer(&actions[0])),
}
if err := windows.ChangeServiceConfig2(handle, windows.SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&serviceRecoveryActions))); err != nil {
return err
}
serviceFailureActionsFlag := serviceFailureActionsFlag{enableActionsForStopsWithErr: 1}
return windows.ChangeServiceConfig2(handle, serviceConfigFailureActionsFlag, (*byte)(unsafe.Pointer(&serviceFailureActionsFlag)))
}

View File

@ -36,7 +36,7 @@ func (p MetricsPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dn
status, err := plugin.NextOrFailure(p.Name(), p.Next, ctx, rw, r) status, err := plugin.NextOrFailure(p.Name(), p.Next, ctx, rw, r)
// Update built-in metrics // Update built-in metrics
vars.Report(state, ".", rcode.ToString(rw.Rcode), rw.Len, rw.Start) vars.Report(ctx, state, ".", rcode.ToString(rw.Rcode), rw.Len, rw.Start)
return status, err return status, err
} }