server.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package diagnostic
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "sync"
  8. "sync/atomic"
  9. stackdump "github.com/docker/docker/pkg/signal"
  10. "github.com/docker/libnetwork/common"
  11. "github.com/sirupsen/logrus"
  12. )
  13. // HTTPHandlerFunc TODO
  14. type HTTPHandlerFunc func(interface{}, http.ResponseWriter, *http.Request)
  15. type httpHandlerCustom struct {
  16. ctx interface{}
  17. F func(interface{}, http.ResponseWriter, *http.Request)
  18. }
  19. // ServeHTTP TODO
  20. func (h httpHandlerCustom) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  21. h.F(h.ctx, w, r)
  22. }
  23. var diagPaths2Func = map[string]HTTPHandlerFunc{
  24. "/": notImplemented,
  25. "/help": help,
  26. "/ready": ready,
  27. "/stackdump": stackTrace,
  28. }
  29. // Server when the debug is enabled exposes a
  30. // This data structure is protected by the Agent mutex so does not require and additional mutex here
  31. type Server struct {
  32. enable int32
  33. srv *http.Server
  34. port int
  35. mux *http.ServeMux
  36. registeredHanders map[string]bool
  37. sync.Mutex
  38. }
  39. // New creates a new diagnostic server
  40. func New() *Server {
  41. return &Server{
  42. registeredHanders: make(map[string]bool),
  43. }
  44. }
  45. // Init initialize the mux for the http handling and register the base hooks
  46. func (s *Server) Init() {
  47. s.mux = http.NewServeMux()
  48. // Register local handlers
  49. s.RegisterHandler(s, diagPaths2Func)
  50. }
  51. // RegisterHandler allows to register new handlers to the mux and to a specific path
  52. func (s *Server) RegisterHandler(ctx interface{}, hdlrs map[string]HTTPHandlerFunc) {
  53. s.Lock()
  54. defer s.Unlock()
  55. for path, fun := range hdlrs {
  56. if _, ok := s.registeredHanders[path]; ok {
  57. continue
  58. }
  59. s.mux.Handle(path, httpHandlerCustom{ctx, fun})
  60. s.registeredHanders[path] = true
  61. }
  62. }
  63. // ServeHTTP this is the method called bu the ListenAndServe, and is needed to allow us to
  64. // use our custom mux
  65. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  66. s.mux.ServeHTTP(w, r)
  67. }
  68. // EnableDiagnostic opens a TCP socket to debug the passed network DB
  69. func (s *Server) EnableDiagnostic(ip string, port int) {
  70. s.Lock()
  71. defer s.Unlock()
  72. s.port = port
  73. if s.enable == 1 {
  74. logrus.Info("The server is already up and running")
  75. return
  76. }
  77. logrus.Infof("Starting the diagnostic server listening on %d for commands", port)
  78. srv := &http.Server{Addr: fmt.Sprintf("%s:%d", ip, port), Handler: s}
  79. s.srv = srv
  80. s.enable = 1
  81. go func(n *Server) {
  82. // Ingore ErrServerClosed that is returned on the Shutdown call
  83. if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
  84. logrus.Errorf("ListenAndServe error: %s", err)
  85. atomic.SwapInt32(&n.enable, 0)
  86. }
  87. }(s)
  88. }
  89. // DisableDiagnostic stop the dubug and closes the tcp socket
  90. func (s *Server) DisableDiagnostic() {
  91. s.Lock()
  92. defer s.Unlock()
  93. s.srv.Shutdown(context.Background())
  94. s.srv = nil
  95. s.enable = 0
  96. logrus.Info("Disabling the diagnostic server")
  97. }
  98. // IsDiagnosticEnabled returns true when the debug is enabled
  99. func (s *Server) IsDiagnosticEnabled() bool {
  100. s.Lock()
  101. defer s.Unlock()
  102. return s.enable == 1
  103. }
  104. func notImplemented(ctx interface{}, w http.ResponseWriter, r *http.Request) {
  105. r.ParseForm()
  106. _, json := ParseHTTPFormOptions(r)
  107. rsp := WrongCommand("not implemented", fmt.Sprintf("URL path: %s no method implemented check /help\n", r.URL.Path))
  108. // audit logs
  109. log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
  110. log.Info("command not implemented done")
  111. HTTPReply(w, rsp, json)
  112. }
  113. func help(ctx interface{}, w http.ResponseWriter, r *http.Request) {
  114. r.ParseForm()
  115. _, json := ParseHTTPFormOptions(r)
  116. // audit logs
  117. log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
  118. log.Info("help done")
  119. n, ok := ctx.(*Server)
  120. var result string
  121. if ok {
  122. for path := range n.registeredHanders {
  123. result += fmt.Sprintf("%s\n", path)
  124. }
  125. HTTPReply(w, CommandSucceed(&StringCmd{Info: result}), json)
  126. }
  127. }
  128. func ready(ctx interface{}, w http.ResponseWriter, r *http.Request) {
  129. r.ParseForm()
  130. _, json := ParseHTTPFormOptions(r)
  131. // audit logs
  132. log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
  133. log.Info("ready done")
  134. HTTPReply(w, CommandSucceed(&StringCmd{Info: "OK"}), json)
  135. }
  136. func stackTrace(ctx interface{}, w http.ResponseWriter, r *http.Request) {
  137. r.ParseForm()
  138. _, json := ParseHTTPFormOptions(r)
  139. // audit logs
  140. log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
  141. log.Info("stack trace")
  142. path, err := stackdump.DumpStacks("/tmp/")
  143. if err != nil {
  144. log.WithError(err).Error("failed to write goroutines dump")
  145. HTTPReply(w, FailCommand(err), json)
  146. } else {
  147. log.Info("stack trace done")
  148. HTTPReply(w, CommandSucceed(&StringCmd{Info: fmt.Sprintf("goroutine stacks written to %s", path)}), json)
  149. }
  150. }
  151. // DebugHTTPForm helper to print the form url parameters
  152. func DebugHTTPForm(r *http.Request) {
  153. for k, v := range r.Form {
  154. logrus.Debugf("Form[%q] = %q\n", k, v)
  155. }
  156. }
  157. // JSONOutput contains details on JSON output printing
  158. type JSONOutput struct {
  159. enable bool
  160. prettyPrint bool
  161. }
  162. // ParseHTTPFormOptions easily parse the JSON printing options
  163. func ParseHTTPFormOptions(r *http.Request) (bool, *JSONOutput) {
  164. _, unsafe := r.Form["unsafe"]
  165. v, json := r.Form["json"]
  166. var pretty bool
  167. if len(v) > 0 {
  168. pretty = v[0] == "pretty"
  169. }
  170. return unsafe, &JSONOutput{enable: json, prettyPrint: pretty}
  171. }
  172. // HTTPReply helper function that takes care of sending the message out
  173. func HTTPReply(w http.ResponseWriter, r *HTTPResult, j *JSONOutput) (int, error) {
  174. var response []byte
  175. if j.enable {
  176. w.Header().Set("Content-Type", "application/json")
  177. var err error
  178. if j.prettyPrint {
  179. response, err = json.MarshalIndent(r, "", " ")
  180. if err != nil {
  181. response, _ = json.MarshalIndent(FailCommand(err), "", " ")
  182. }
  183. } else {
  184. response, err = json.Marshal(r)
  185. if err != nil {
  186. response, _ = json.Marshal(FailCommand(err))
  187. }
  188. }
  189. } else {
  190. response = []byte(r.String())
  191. }
  192. return fmt.Fprint(w, string(response))
  193. }