diff --git a/nmagent/client.go b/nmagent/client.go index ba0ab106dd..b06a12da88 100644 --- a/nmagent/client.go +++ b/nmagent/client.go @@ -77,6 +77,25 @@ func (c *Client) JoinNetwork(ctx context.Context, jnr JoinNetworkRequest) error return err // nolint:wrapcheck // wrapping this just introduces noise } +// DeleteNetwork deletes a customer network and it's associated subnets. +func (c *Client) DeleteNetwork(ctx context.Context, dnr DeleteNetworkRequest) error { + req, err := c.buildRequest(ctx, dnr) + if err != nil { + return errors.Wrap(err, "building request") + } + + resp, err := c.httpClient.Do(req) // nolint:govet // the shadow is intentional + if err != nil { + return errors.Wrap(err, "submitting request") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return die(resp.StatusCode, resp.Header, resp.Body) + } + return nil +} + // GetNetworkConfiguration retrieves the configuration of a customer's virtual // network. Only subnets which have been delegated will be returned. func (c *Client) GetNetworkConfiguration(ctx context.Context, gncr GetNetworkConfigRequest) (VirtualNetwork, error) { diff --git a/nmagent/client_test.go b/nmagent/client_test.go index 6be1abacbd..e197225731 100644 --- a/nmagent/client_test.go +++ b/nmagent/client_test.go @@ -78,7 +78,6 @@ func TestNMAgentClientJoinNetwork(t *testing.T) { defer cancel() // attempt to join network - // TODO(timraymond): need a more realistic network ID, I think err := client.JoinNetwork(ctx, nmagent.JoinNetworkRequest{test.id}) checkErr(t, err, test.shouldErr) @@ -123,6 +122,68 @@ func TestNMAgentClientJoinNetworkRetry(t *testing.T) { } } +func TestNMAgentClientDeleteNetwork(t *testing.T) { + deleteNetTests := []struct { + name string + id string + exp string + respStatus int + shouldErr bool + }{ + { + "happy path", + "00000000-0000-0000-0000-000000000000", + "/machine/plugins?comp=nmagent&type=NetworkManagement%2FjoinedVirtualNetworks%2F00000000-0000-0000-0000-000000000000%2Fapi-version%2F1%2Fmethod%2FDELETE", + http.StatusOK, + false, + }, + { + "empty network ID", + "", + "", + http.StatusOK, // this shouldn't be checked + true, + }, + { + "internal error", + "00000000-0000-0000-0000-000000000000", + "/machine/plugins?comp=nmagent&type=NetworkManagement%2FjoinedVirtualNetworks%2F00000000-0000-0000-0000-000000000000%2Fapi-version%2F1%2Fmethod%2FDELETE", + http.StatusInternalServerError, + true, + }, + } + + for _, test := range deleteNetTests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + // create a client + var got string + client := nmagent.NewTestClient(&TestTripper{ + RoundTripF: func(req *http.Request) (*http.Response, error) { + got = req.URL.RequestURI() + rr := httptest.NewRecorder() + _, _ = fmt.Fprintf(rr, `{"httpStatusCode":"%d"}`, test.respStatus) + rr.WriteHeader(http.StatusOK) + return rr.Result(), nil + }, + }) + + ctx, cancel := testContext(t) + defer cancel() + + // attempt to delete network + err := client.DeleteNetwork(ctx, nmagent.DeleteNetworkRequest{test.id}) + checkErr(t, err, test.shouldErr) + + if got != test.exp { + t.Error("received URL differs from expectation: got", got, "exp:", test.exp) + } + }) + } +} + func TestWSError(t *testing.T) { const wsError string = ` diff --git a/nmagent/requests.go b/nmagent/requests.go index b9d5dfb3c9..01182bfbb3 100644 --- a/nmagent/requests.go +++ b/nmagent/requests.go @@ -239,7 +239,7 @@ func (p *Policy) UnmarshalJSON(in []byte) error { var _ Request = JoinNetworkRequest{} type JoinNetworkRequest struct { - NetworkID string `validate:"presence" json:"-"` // the customer's VNet ID + NetworkID string `json:"-"` // the customer's VNet ID } // Path constructs a URL path for invoking a JoinNetworkRequest using the @@ -273,6 +273,45 @@ func (j JoinNetworkRequest) Validate() error { return err } +var _ Request = DeleteNetworkRequest{} + +// DeleteNetworkRequest represents all information necessary to request that +// NMAgent delete a particular network +type DeleteNetworkRequest struct { + NetworkID string `json:"-"` // the customer's VNet ID +} + +// Path constructs a URL path for invoking a DeleteNetworkRequest using the +// provided parameters +func (d DeleteNetworkRequest) Path() string { + const DeleteNetworkPath = "/NetworkManagement/joinedVirtualNetworks/%s/api-version/1/method/DELETE" + return fmt.Sprintf(DeleteNetworkPath, d.NetworkID) +} + +// Body returns nothing, because DeleteNetworkRequest has no request body +func (d DeleteNetworkRequest) Body() (io.Reader, error) { + return nil, nil +} + +// Method returns the HTTP request method to submit a DeleteNetworkRequest +func (d DeleteNetworkRequest) Method() string { + return http.MethodPost +} + +// Validate ensures that the provided parameters of the request are valid +func (d DeleteNetworkRequest) Validate() error { + err := internal.ValidationError{} + + if d.NetworkID == "" { + err.MissingFields = append(err.MissingFields, "NetworkID") + } + + if err.IsEmpty() { + return nil + } + return err +} + var _ Request = DeleteContainerRequest{} // DeleteContainerRequest represents all information necessary to request that