mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-12-02 08:07:57 +00:00
226 lines
5.6 KiB
Go
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)
|
|
}
|