2017-12-06 14:21:51 -05:00
|
|
|
package diagnostic
|
2017-12-01 13:13:01 -05:00
|
|
|
|
|
|
|
import (
|
2017-12-05 12:19:17 -05:00
|
|
|
"context"
|
2017-12-01 13:13:01 -05:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"sync"
|
2017-12-05 12:19:17 -05:00
|
|
|
"sync/atomic"
|
2017-12-01 13:13:01 -05:00
|
|
|
|
2021-04-05 20:24:47 -04:00
|
|
|
"github.com/docker/docker/libnetwork/internal/caller"
|
2021-05-27 20:15:56 -04:00
|
|
|
stackdump "github.com/docker/docker/pkg/signal"
|
2017-12-01 13:13:01 -05:00
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
// HTTPHandlerFunc TODO
|
|
|
|
type HTTPHandlerFunc func(interface{}, http.ResponseWriter, *http.Request)
|
|
|
|
|
|
|
|
type httpHandlerCustom struct {
|
|
|
|
ctx interface{}
|
|
|
|
F func(interface{}, http.ResponseWriter, *http.Request)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServeHTTP TODO
|
|
|
|
func (h httpHandlerCustom) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
h.F(h.ctx, w, r)
|
|
|
|
}
|
|
|
|
|
|
|
|
var diagPaths2Func = map[string]HTTPHandlerFunc{
|
|
|
|
"/": notImplemented,
|
|
|
|
"/help": help,
|
|
|
|
"/ready": ready,
|
|
|
|
"/stackdump": stackTrace,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Server when the debug is enabled exposes a
|
|
|
|
// This data structure is protected by the Agent mutex so does not require and additional mutex here
|
|
|
|
type Server struct {
|
2017-12-05 12:19:17 -05:00
|
|
|
enable int32
|
|
|
|
srv *http.Server
|
2017-12-01 13:13:01 -05:00
|
|
|
port int
|
|
|
|
mux *http.ServeMux
|
|
|
|
registeredHanders map[string]bool
|
|
|
|
sync.Mutex
|
|
|
|
}
|
|
|
|
|
2017-12-06 14:21:51 -05:00
|
|
|
// New creates a new diagnostic server
|
2017-12-01 13:13:01 -05:00
|
|
|
func New() *Server {
|
|
|
|
return &Server{
|
|
|
|
registeredHanders: make(map[string]bool),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init initialize the mux for the http handling and register the base hooks
|
2017-12-05 16:45:08 -05:00
|
|
|
func (s *Server) Init() {
|
|
|
|
s.mux = http.NewServeMux()
|
2017-12-01 13:13:01 -05:00
|
|
|
|
|
|
|
// Register local handlers
|
2017-12-05 16:45:08 -05:00
|
|
|
s.RegisterHandler(s, diagPaths2Func)
|
2017-12-01 13:13:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterHandler allows to register new handlers to the mux and to a specific path
|
2017-12-05 16:45:08 -05:00
|
|
|
func (s *Server) RegisterHandler(ctx interface{}, hdlrs map[string]HTTPHandlerFunc) {
|
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
2017-12-01 13:13:01 -05:00
|
|
|
for path, fun := range hdlrs {
|
2017-12-05 16:45:08 -05:00
|
|
|
if _, ok := s.registeredHanders[path]; ok {
|
2017-12-01 13:13:01 -05:00
|
|
|
continue
|
|
|
|
}
|
2017-12-05 16:45:08 -05:00
|
|
|
s.mux.Handle(path, httpHandlerCustom{ctx, fun})
|
|
|
|
s.registeredHanders[path] = true
|
2017-12-01 13:13:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-05 16:45:08 -05:00
|
|
|
// ServeHTTP this is the method called bu the ListenAndServe, and is needed to allow us to
|
|
|
|
// use our custom mux
|
|
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
s.mux.ServeHTTP(w, r)
|
2017-12-05 12:19:17 -05:00
|
|
|
}
|
|
|
|
|
2017-12-06 14:21:51 -05:00
|
|
|
// EnableDiagnostic opens a TCP socket to debug the passed network DB
|
|
|
|
func (s *Server) EnableDiagnostic(ip string, port int) {
|
2017-12-05 16:45:08 -05:00
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
2017-12-01 13:13:01 -05:00
|
|
|
|
2017-12-05 16:45:08 -05:00
|
|
|
s.port = port
|
2017-12-01 13:13:01 -05:00
|
|
|
|
2017-12-05 16:45:08 -05:00
|
|
|
if s.enable == 1 {
|
2017-12-01 13:13:01 -05:00
|
|
|
logrus.Info("The server is already up and running")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-12-06 14:21:51 -05:00
|
|
|
logrus.Infof("Starting the diagnostic server listening on %d for commands", port)
|
2017-12-12 11:23:49 -05:00
|
|
|
srv := &http.Server{Addr: fmt.Sprintf("%s:%d", ip, port), Handler: s}
|
2017-12-05 16:45:08 -05:00
|
|
|
s.srv = srv
|
|
|
|
s.enable = 1
|
2017-12-05 12:19:17 -05:00
|
|
|
go func(n *Server) {
|
2018-09-06 23:43:42 -04:00
|
|
|
// Ignore ErrServerClosed that is returned on the Shutdown call
|
2017-12-05 12:19:17 -05:00
|
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
|
|
logrus.Errorf("ListenAndServe error: %s", err)
|
|
|
|
atomic.SwapInt32(&n.enable, 0)
|
|
|
|
}
|
2017-12-05 16:45:08 -05:00
|
|
|
}(s)
|
2017-12-01 13:13:01 -05:00
|
|
|
}
|
|
|
|
|
2017-12-06 14:21:51 -05:00
|
|
|
// DisableDiagnostic stop the dubug and closes the tcp socket
|
|
|
|
func (s *Server) DisableDiagnostic() {
|
2017-12-05 16:45:08 -05:00
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
2017-12-05 12:19:17 -05:00
|
|
|
|
2021-05-27 20:15:56 -04:00
|
|
|
s.srv.Shutdown(context.Background()) // nolint:errcheck
|
2017-12-05 16:45:08 -05:00
|
|
|
s.srv = nil
|
|
|
|
s.enable = 0
|
2017-12-06 14:21:51 -05:00
|
|
|
logrus.Info("Disabling the diagnostic server")
|
2017-12-01 13:13:01 -05:00
|
|
|
}
|
|
|
|
|
2017-12-06 14:21:51 -05:00
|
|
|
// IsDiagnosticEnabled returns true when the debug is enabled
|
|
|
|
func (s *Server) IsDiagnosticEnabled() bool {
|
2017-12-05 16:45:08 -05:00
|
|
|
s.Lock()
|
|
|
|
defer s.Unlock()
|
|
|
|
return s.enable == 1
|
2017-12-01 13:13:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func notImplemented(ctx interface{}, w http.ResponseWriter, r *http.Request) {
|
2021-05-27 20:15:56 -04:00
|
|
|
r.ParseForm() // nolint:errcheck
|
2017-12-01 13:13:01 -05:00
|
|
|
_, json := ParseHTTPFormOptions(r)
|
|
|
|
rsp := WrongCommand("not implemented", fmt.Sprintf("URL path: %s no method implemented check /help\n", r.URL.Path))
|
|
|
|
|
|
|
|
// audit logs
|
2018-07-16 20:34:20 -04:00
|
|
|
log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
|
2017-12-01 13:13:01 -05:00
|
|
|
log.Info("command not implemented done")
|
|
|
|
|
2021-05-27 20:15:56 -04:00
|
|
|
HTTPReply(w, rsp, json) // nolint:errcheck
|
2017-12-01 13:13:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func help(ctx interface{}, w http.ResponseWriter, r *http.Request) {
|
2021-05-27 20:15:56 -04:00
|
|
|
r.ParseForm() // nolint:errcheck
|
2017-12-01 13:13:01 -05:00
|
|
|
_, json := ParseHTTPFormOptions(r)
|
|
|
|
|
|
|
|
// audit logs
|
2018-07-16 20:34:20 -04:00
|
|
|
log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
|
2017-12-01 13:13:01 -05:00
|
|
|
log.Info("help done")
|
|
|
|
|
|
|
|
n, ok := ctx.(*Server)
|
|
|
|
var result string
|
|
|
|
if ok {
|
|
|
|
for path := range n.registeredHanders {
|
|
|
|
result += fmt.Sprintf("%s\n", path)
|
|
|
|
}
|
2021-05-27 20:15:56 -04:00
|
|
|
HTTPReply(w, CommandSucceed(&StringCmd{Info: result}), json) // nolint:errcheck
|
2017-12-01 13:13:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func ready(ctx interface{}, w http.ResponseWriter, r *http.Request) {
|
2021-05-27 20:15:56 -04:00
|
|
|
r.ParseForm() // nolint:errcheck
|
2017-12-01 13:13:01 -05:00
|
|
|
_, json := ParseHTTPFormOptions(r)
|
|
|
|
|
|
|
|
// audit logs
|
2018-07-16 20:34:20 -04:00
|
|
|
log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
|
2017-12-01 13:13:01 -05:00
|
|
|
log.Info("ready done")
|
2021-05-27 20:15:56 -04:00
|
|
|
HTTPReply(w, CommandSucceed(&StringCmd{Info: "OK"}), json) // nolint:errcheck
|
2017-12-01 13:13:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func stackTrace(ctx interface{}, w http.ResponseWriter, r *http.Request) {
|
2021-05-27 20:15:56 -04:00
|
|
|
r.ParseForm() // nolint:errcheck
|
2017-12-01 13:13:01 -05:00
|
|
|
_, json := ParseHTTPFormOptions(r)
|
|
|
|
|
|
|
|
// audit logs
|
2018-07-16 20:34:20 -04:00
|
|
|
log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
|
2017-12-01 13:13:01 -05:00
|
|
|
log.Info("stack trace")
|
|
|
|
|
|
|
|
path, err := stackdump.DumpStacks("/tmp/")
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("failed to write goroutines dump")
|
2021-05-27 20:15:56 -04:00
|
|
|
HTTPReply(w, FailCommand(err), json) // nolint:errcheck
|
2017-12-01 13:13:01 -05:00
|
|
|
} else {
|
|
|
|
log.Info("stack trace done")
|
2021-05-27 20:15:56 -04:00
|
|
|
HTTPReply(w, CommandSucceed(&StringCmd{Info: fmt.Sprintf("goroutine stacks written to %s", path)}), json) // nolint:errcheck
|
2017-12-01 13:13:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// DebugHTTPForm helper to print the form url parameters
|
|
|
|
func DebugHTTPForm(r *http.Request) {
|
|
|
|
for k, v := range r.Form {
|
|
|
|
logrus.Debugf("Form[%q] = %q\n", k, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// JSONOutput contains details on JSON output printing
|
|
|
|
type JSONOutput struct {
|
|
|
|
enable bool
|
|
|
|
prettyPrint bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseHTTPFormOptions easily parse the JSON printing options
|
|
|
|
func ParseHTTPFormOptions(r *http.Request) (bool, *JSONOutput) {
|
|
|
|
_, unsafe := r.Form["unsafe"]
|
|
|
|
v, json := r.Form["json"]
|
|
|
|
var pretty bool
|
|
|
|
if len(v) > 0 {
|
|
|
|
pretty = v[0] == "pretty"
|
|
|
|
}
|
|
|
|
return unsafe, &JSONOutput{enable: json, prettyPrint: pretty}
|
|
|
|
}
|
|
|
|
|
|
|
|
// HTTPReply helper function that takes care of sending the message out
|
|
|
|
func HTTPReply(w http.ResponseWriter, r *HTTPResult, j *JSONOutput) (int, error) {
|
|
|
|
var response []byte
|
|
|
|
if j.enable {
|
2017-12-05 12:19:17 -05:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2017-12-01 13:13:01 -05:00
|
|
|
var err error
|
|
|
|
if j.prettyPrint {
|
|
|
|
response, err = json.MarshalIndent(r, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
response, _ = json.MarshalIndent(FailCommand(err), "", " ")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
response, err = json.Marshal(r)
|
|
|
|
if err != nil {
|
|
|
|
response, _ = json.Marshal(FailCommand(err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
response = []byte(r.String())
|
|
|
|
}
|
|
|
|
return fmt.Fprint(w, string(response))
|
|
|
|
}
|