115 lines
3.1 KiB
Go
115 lines
3.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package agent
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
// apiServers is a wrapper around errgroup.Group for managing go routines for
|
|
// long running agent components (ex: http server, dns server). If any of the
|
|
// servers fail, the failed channel will be closed, which will cause the agent
|
|
// to be shutdown instead of running in a degraded state.
|
|
//
|
|
// This struct exists as a shim for using errgroup.Group without making major
|
|
// changes to Agent. In the future it may be removed and replaced with more
|
|
// direct usage of errgroup.Group.
|
|
type apiServers struct {
|
|
logger hclog.Logger
|
|
group *errgroup.Group
|
|
servers []apiServer
|
|
// failed channel is closed when the first server goroutines exit with a
|
|
// non-nil error.
|
|
failed <-chan struct{}
|
|
}
|
|
|
|
type apiServer struct {
|
|
// Protocol supported by this server. One of: dns, http, https
|
|
Protocol string
|
|
// Addr the server is listening on
|
|
Addr net.Addr
|
|
// Run will be called in a goroutine to run the server. When any Run exits
|
|
// with a non-nil error, the failed channel will be closed.
|
|
Run func() error
|
|
// Shutdown function used to stop the server
|
|
Shutdown func(context.Context) error
|
|
}
|
|
|
|
// NewAPIServers returns an empty apiServers that is ready to Start servers.
|
|
func NewAPIServers(logger hclog.Logger) *apiServers {
|
|
group, ctx := errgroup.WithContext(context.TODO())
|
|
return &apiServers{
|
|
logger: logger,
|
|
group: group,
|
|
failed: ctx.Done(),
|
|
}
|
|
}
|
|
|
|
func (s *apiServers) Start(srv apiServer) {
|
|
srv.logger(s.logger).Info("Starting server")
|
|
s.servers = append(s.servers, srv)
|
|
s.group.Go(srv.Run)
|
|
}
|
|
|
|
func (s apiServer) logger(base hclog.Logger) hclog.Logger {
|
|
return base.With(
|
|
"protocol", s.Protocol,
|
|
"address", s.Addr.String(),
|
|
"network", s.Addr.Network())
|
|
}
|
|
|
|
// Shutdown all the servers and log any errors as warning. Each server is given
|
|
// 1 second, or until ctx is cancelled, to shutdown gracefully.
|
|
func (s *apiServers) Shutdown(ctx context.Context) {
|
|
shutdownGroup := new(sync.WaitGroup)
|
|
|
|
for i := range s.servers {
|
|
server := s.servers[i]
|
|
shutdownGroup.Add(1)
|
|
|
|
go func() {
|
|
defer shutdownGroup.Done()
|
|
logger := server.logger(s.logger)
|
|
logger.Info("Stopping server")
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, time.Second)
|
|
defer cancel()
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
logger.Warn("Failed to stop server")
|
|
}
|
|
}()
|
|
}
|
|
s.servers = nil
|
|
shutdownGroup.Wait()
|
|
}
|
|
|
|
// WaitForShutdown waits until all server goroutines have exited. Shutdown
|
|
// must be called before WaitForShutdown, otherwise it will block forever.
|
|
func (s *apiServers) WaitForShutdown() error {
|
|
return s.group.Wait()
|
|
}
|
|
|
|
func newAPIServerHTTP(proto string, l net.Listener, httpServer *http.Server) apiServer {
|
|
return apiServer{
|
|
Protocol: proto,
|
|
Addr: l.Addr(),
|
|
Shutdown: httpServer.Shutdown,
|
|
Run: func() error {
|
|
err := httpServer.Serve(l)
|
|
if err == nil || err == http.ErrServerClosed {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("%s server %s failed: %w", proto, l.Addr(), err)
|
|
},
|
|
}
|
|
}
|