Release Argo Tunnel Client 2018.5.5
This commit is contained in:
parent
75d7561b5d
commit
7acd1b1fc8
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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{})
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)))
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue