Files
linuxkit/alpine/packages/diagnostics/http.go
Nathan LeClaire 0ecaed9d0b Fix system container bug
Signed-off-by: Nathan LeClaire <nathan.leclaire@gmail.com>
2017-02-08 17:02:14 -08:00

226 lines
5.6 KiB
Go

package main
import (
"archive/tar"
"bytes"
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httputil"
"os"
"strconv"
"strings"
"time"
)
var (
errDockerRespNotOK = errors.New("Docker API call did not return OK")
)
const (
healthcheckTimeout = 5 * time.Second
dockerSock = "/var/run/docker.sock"
dockerPingOK = "LGTM"
httpMagicPort = ":44554" // chosen arbitrarily due to IANA availability -- might change
bucket = "editionsdiagnostics"
sessionIDField = "session"
)
var (
cloudCaptures = []Capturer{}
)
func init() {
for _, c := range commonCmdCaptures {
cloudCaptures = append(cloudCaptures, c)
}
cloudCaptures = append(cloudCaptures, SystemContainerCapturer{})
}
// HTTPDiagnosticListener sets a health check and optional diagnostic endpoint
// for cloud editions.
type HTTPDiagnosticListener struct{}
func dockerHTTPGet(ctx context.Context, url string) (*http.Response, error) {
client := &http.Client{
Transport: &UnixSocketRoundTripper{},
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
resp, err := client.Do(req)
if err != nil {
return resp, err
}
if resp.StatusCode != http.StatusOK {
return resp, errDockerRespNotOK
}
return resp, err
}
// UnixSocketRoundTripper provides a way to make HTTP request to Docker socket
// directly.
type UnixSocketRoundTripper struct{}
// RoundTrip dials the Docker UNIX socket to make a HTTP request.
func (u UnixSocketRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
dial, err := net.Dial("unix", dockerSock)
if err != nil {
return nil, err
}
conn := httputil.NewClientConn(dial, nil)
return conn.Do(req)
}
// Listen starts the HTTPDiagnosticListener and sets up handlers for its endpoints
func (h HTTPDiagnosticListener) Listen() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(context.Background(), healthcheckTimeout)
defer cancel()
errCh := make(chan error)
go func() {
_, err := dockerHTTPGet(ctx, "/_ping")
errCh <- err
}()
select {
case err := <-errCh:
if err != nil {
http.Error(w, "Docker daemon ping error", http.StatusInternalServerError)
return
}
case <-ctx.Done():
http.Error(w, "Docker daemon ping timed out", http.StatusServiceUnavailable)
return
}
if _, err := w.Write([]byte(dockerPingOK)); err != nil {
log.Println("Error writing HTTP success response:", err)
return
}
})
http.HandleFunc("/diagnose", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Invalid request type. Should be POST with form value 'session' set", http.StatusBadRequest)
return
}
diagnosticsSessionID := r.FormValue(sessionIDField)
if diagnosticsSessionID == "" {
http.Error(w, "No 'session' field specified for diagnostics run", http.StatusBadRequest)
return
}
hostname, err := os.Hostname()
if err != nil {
http.Error(w, "Error getting hostname:"+err.Error(), http.StatusInternalServerError)
return
}
// To keep URL cleaner
hostname = strings.Replace(hostname, ".", "-", -1)
if _, err := w.Write([]byte("OK hostname=" + hostname + " session=" + diagnosticsSessionID + "\n")); err != nil {
http.Error(w, "Error writing: "+err.Error(), http.StatusInternalServerError)
return
}
// Do the actual capture and uplaod to S3 in the background.
// No need to make caller sit and wait for the result. They
// probably have a lot of other things going on, like other
// servers to request diagnostics for.
//
// TODO(nathanleclaire): Potentially, endpoint to check the
// result of this capture and upload process as well.
go func() {
dir, err := ioutil.TempDir("", "diagnostics")
if err != nil {
log.Println("Error creating temp dir on diagnostic request:: ", err)
return
}
file, err := ioutil.TempFile(dir, "diagnostics")
if err != nil {
log.Println("Error creating temp file on diagnostic request:", err)
return
}
tarWriter := tar.NewWriter(file)
Capture(tarWriter, cloudCaptures)
if err := tarWriter.Close(); err != nil {
log.Println("Error closing archive writer: ", err)
return
}
if err := file.Close(); err != nil {
log.Println("Error closing file: ", err)
return
}
readFile, err := os.Open(file.Name())
if err != nil {
log.Println("Error opening report file to upload: ", err)
return
}
defer readFile.Close()
buf := &bytes.Buffer{}
contentLength, err := io.Copy(buf, readFile)
if err != nil {
log.Println("Error copying to buffer: ", err)
return
}
reportURI := fmt.Sprintf("https://%s.s3.amazonaws.com/%s-%s.tar", bucket, diagnosticsSessionID, hostname)
uploadReq, err := http.NewRequest("PUT", reportURI, buf)
if err != nil {
log.Println("Error getting bucket request: ", err)
return
}
uploadReq.Header.Set("x-amz-acl", "bucket-owner-full-control")
uploadReq.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
uploadReq.Header.Set("Content-Length", strconv.Itoa(int(contentLength)))
client := &http.Client{}
if uploadResp, err := client.Do(uploadReq); err != nil {
log.Println("Error writing: ", err)
body, err := ioutil.ReadAll(uploadResp.Body)
if err != nil {
log.Println("Error reading response body: ", err)
}
log.Println(string(body))
return
}
log.Println("No error sending S3 request")
log.Println("Diagnostics request finished")
}()
})
// Start HTTP server to indicate general Docker health.
// TODO: no magic port?
http.ListenAndServe(httpMagicPort, nil)
}