From af70b455a5642ef1ad8d2c36af60aaf8147f63a3 Mon Sep 17 00:00:00 2001 From: Dominic Evans Date: Fri, 11 Aug 2023 11:43:19 +0100 Subject: [PATCH] feat(fvt): implement toxiproxy client Signed-off-by: Dominic Evans --- functional_test.go | 2 +- internal/toxiproxy/client.go | 87 +++++++++++++++++++++++++++--- internal/toxiproxy/proxy.go | 102 ++++++++++++++++++++++++++++++++--- internal/toxiproxy/toxic.go | 8 ++- 4 files changed, 184 insertions(+), 15 deletions(-) diff --git a/functional_test.go b/functional_test.go index 6a0d560a6d..31d64ba44a 100644 --- a/functional_test.go +++ b/functional_test.go @@ -456,7 +456,7 @@ func resetProxies(t testing.TB) { } func SaveProxy(t *testing.T, px string) { - if err := FunctionalTestEnv.Proxies[px].Save(); err != nil { + if _, err := FunctionalTestEnv.Proxies[px].Save(); err != nil { t.Fatal(err) } } diff --git a/internal/toxiproxy/client.go b/internal/toxiproxy/client.go index ec4c016d0d..e916bcbcac 100644 --- a/internal/toxiproxy/client.go +++ b/internal/toxiproxy/client.go @@ -1,19 +1,94 @@ package toxiproxy -type Client struct{} +import ( + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "time" +) + +type Client struct { + httpClient *http.Client + endpoint string +} func NewClient(endpoint string) *Client { - return &Client{} + return &Client{ + httpClient: &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + }, + }, + endpoint: endpoint, + } } -func (c *Client) Proxy(name string) (*Proxy, error) { - return &Proxy{}, nil +func (c *Client) CreateProxy( + name string, + listenAddr string, + targetAddr string, +) (*Proxy, error) { + proxy := &Proxy{ + Name: name, + ListenAddr: listenAddr, + TargetAddr: targetAddr, + Enabled: true, + client: c, + } + return proxy.Save() } -func (c *Client) CreateProxy(name string, listenerAddr string, targetAddr string) (*Proxy, error) { - return &Proxy{}, nil +func (c *Client) Proxy(name string) (*Proxy, error) { + req, err := http.NewRequest("GET", c.endpoint+"/proxies/"+name, nil) + if err != nil { + return nil, fmt.Errorf("failed to make proxy request: %w", err) + } + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to http get proxy: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("error getting proxy %s: %s %s", name, resp.Status, body) + } + + var p Proxy + if err := json.NewDecoder(resp.Body).Decode(&p); err != nil { + return nil, fmt.Errorf("error decoding json for proxy %s: %w", name, err) + } + p.client = c + + return &p, nil } func (c *Client) ResetState() error { + req, err := http.NewRequest("POST", c.endpoint+"/reset", http.NoBody) + if err != nil { + return fmt.Errorf("failed to make reset request: %w", err) + } + resp, err := c.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to http post reset: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 204 { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("error resetting proxies: %s %s", resp.Status, body) + } + return nil } diff --git a/internal/toxiproxy/proxy.go b/internal/toxiproxy/proxy.go index 9b73990087..4fafa2b13d 100644 --- a/internal/toxiproxy/proxy.go +++ b/internal/toxiproxy/proxy.go @@ -1,23 +1,111 @@ package toxiproxy +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + type Proxy struct { - Enabled bool + client *Client + Name string `json:"name"` + ListenAddr string `json:"listen"` + TargetAddr string `json:"upstream"` + Enabled bool `json:"enabled"` } type Attributes map[string]int -func (p *Proxy) AddToxic(name string, toxicType string, stream string, toxicity float32, attributes Attributes) (*Toxic, error) { - return &Toxic{}, nil +func (p *Proxy) AddToxic( + name string, + toxicType string, + stream string, + toxicity float32, + attributes Attributes, +) (*Toxic, error) { + toxic := &Toxic{ + Name: name, + Type: toxicType, + Stream: stream, + Toxicity: toxicity, + Attributes: attributes, + } + var b bytes.Buffer + if err := json.NewEncoder(&b).Encode(&toxic); err != nil { + return nil, fmt.Errorf("failed to json encode toxic: %w", err) + } + body := bytes.NewReader(b.Bytes()) + + c := p.client + req, err := http.NewRequest("POST", c.endpoint+"/proxies/"+p.Name+"/toxics", body) + if err != nil { + return nil, fmt.Errorf("failed to make post toxic request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to http post toxic: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("error creating toxic %s: %s %s", name, resp.Status, body) + } + + return toxic, nil } func (p *Proxy) Enable() error { - return nil + p.Enabled = true + _, err := p.Save() + return err } func (p *Proxy) Disable() error { - return nil + p.Enabled = false + _, err := p.Save() + return err } -func (p *Proxy) Save() error { - return nil +func (p *Proxy) Save() (*Proxy, error) { + var b bytes.Buffer + if err := json.NewEncoder(&b).Encode(&p); err != nil { + return nil, fmt.Errorf("failed to json encode proxy: %w", err) + } + body := bytes.NewReader(b.Bytes()) + + c := p.client + req, err := http.NewRequest("POST", c.endpoint+"/proxies/"+p.Name, body) + if err != nil { + return nil, fmt.Errorf("failed to make post proxy request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to http post proxy: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == 404 { + req, err = http.NewRequest("POST", c.endpoint+"/proxies", body) + if err != nil { + return nil, fmt.Errorf("failed to make post proxy request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + resp, err = c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to http post proxy: %w", err) + } + defer resp.Body.Close() + } + + if resp.StatusCode != 200 && resp.StatusCode != 201 { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("error saving proxy: %s %s", resp.Status, body) + } + + return p, nil } diff --git a/internal/toxiproxy/toxic.go b/internal/toxiproxy/toxic.go index 5b29b001ad..1461e9ae3f 100644 --- a/internal/toxiproxy/toxic.go +++ b/internal/toxiproxy/toxic.go @@ -1,3 +1,9 @@ package toxiproxy -type Toxic struct{} +type Toxic struct { + Name string `json:"name"` + Type string `json:"type"` + Stream string `json:"stream,omitempty"` + Toxicity float32 `json:"toxicity"` + Attributes Attributes `json:"attributes"` +}