mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-10-12 01:58:46 +02:00
fix(cli): carry the review fixes of the master port to v1.0
This commit is contained in:
1 parent
12ab2a0e71
commit
f52dfd0354
24 files changed
+993
-68
No files matched your search
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -289,10 +290,18 @@ func SendLogs(ctx context.Context, lr *LogsRequest, cdDev bool) error {
|
||||
if cdDev {
|
||||
apiUrl = logURLDev
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", apiUrl, lr.Data)
|
||||
body, size, getBody, cleanup, err := spoolLogBody(lr.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", apiUrl, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http.NewRequest: %w", err)
|
||||
}
|
||||
req.ContentLength = size
|
||||
req.GetBody = getBody
|
||||
q := req.URL.Query()
|
||||
q.Set("uid", lr.UID)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
@@ -418,6 +427,15 @@ func doWithFallback(client *http.Client, req *http.Request, apiIp string) (*http
|
||||
ipReq := req.Clone(req.Context())
|
||||
ipReq.Host = apiIp
|
||||
ipReq.URL.Host = apiIp
|
||||
// The first attempt consumed the body. A request that can give its body
|
||||
// again sends the whole body a second time.
|
||||
if req.GetBody != nil {
|
||||
body, bodyErr := req.GetBody()
|
||||
if bodyErr != nil {
|
||||
return nil, fmt.Errorf("request failed: %w; fallback to direct ip %s failed: %w", err, apiIp, bodyErr)
|
||||
}
|
||||
ipReq.Body = body
|
||||
}
|
||||
resp, fallbackErr := client.Do(ipReq)
|
||||
if fallbackErr != nil {
|
||||
return nil, fmt.Errorf("request failed: %w; fallback to direct ip %s failed: %w", err, apiIp, fallbackErr)
|
||||
@@ -425,6 +443,34 @@ func doWithFallback(client *http.Client, req *http.Request, apiIp string) (*http
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// spoolLogBody copies an upload to a temporary file, so the request can go
|
||||
// out a second time to the direct IP of the API. A log reader yields its
|
||||
// bytes once, and the first attempt consumes them. The caller runs cleanup
|
||||
// after the response arrived.
|
||||
func spoolLogBody(data io.Reader) (body *os.File, size int64, getBody func() (io.ReadCloser, error), cleanup func(), err error) {
|
||||
f, err := os.CreateTemp("", "ctrld-log-upload-*")
|
||||
if err != nil {
|
||||
return nil, 0, nil, nil, fmt.Errorf("creating the upload spool: %w", err)
|
||||
}
|
||||
cleanup = func() {
|
||||
_ = f.Close()
|
||||
_ = os.Remove(f.Name())
|
||||
}
|
||||
size, err = io.Copy(f, data)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return nil, 0, nil, nil, fmt.Errorf("writing the upload spool: %w", err)
|
||||
}
|
||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||
cleanup()
|
||||
return nil, 0, nil, nil, fmt.Errorf("rewinding the upload spool: %w", err)
|
||||
}
|
||||
// The transport closes the body of a failed attempt, so the second body
|
||||
// is a new handle on the same file.
|
||||
getBody = func() (io.ReadCloser, error) { return os.Open(f.Name()) }
|
||||
return f, size, getBody, cleanup, nil
|
||||
}
|
||||
|
||||
// apiServerIP returns the direct IP to connect to API server.
|
||||
func apiServerIP(cdDev bool) string {
|
||||
if cdDev {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package controld
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestDoWithFallbackResendsTheSpooledBody drives the two-attempt upload: the
|
||||
// first attempt fails on a closed port and the fallback to the direct IP must
|
||||
// carry the whole body, which the first attempt consumed.
|
||||
func TestDoWithFallbackResendsTheSpooledBody(t *testing.T) {
|
||||
payload := strings.Repeat("log line\n", 4096)
|
||||
var got string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
b, _ := io.ReadAll(r.Body)
|
||||
got = string(b)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
body, size, getBody, cleanup, err := spoolLogBody(strings.NewReader(payload))
|
||||
if err != nil {
|
||||
t.Fatalf("spoolLogBody: %v", err)
|
||||
}
|
||||
defer cleanup()
|
||||
if size != int64(len(payload)) {
|
||||
t.Fatalf("size = %d, want %d", size, len(payload))
|
||||
}
|
||||
// Port 1 refuses the connection, so the first attempt fails at once.
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "http://127.0.0.1:1/logs", body)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRequest: %v", err)
|
||||
}
|
||||
req.ContentLength = size
|
||||
req.GetBody = getBody
|
||||
|
||||
resp, err := doWithFallback(srv.Client(), req, strings.TrimPrefix(srv.URL, "http://"))
|
||||
if err != nil {
|
||||
t.Fatalf("doWithFallback: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if got != payload {
|
||||
t.Fatalf("the fallback carried %d bytes, want %d", len(got), len(payload))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user