cns/client/client.go (822 lines of code) (raw):
package client
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/Azure/azure-container-networking/cns"
"github.com/Azure/azure-container-networking/cns/restserver"
"github.com/Azure/azure-container-networking/cns/types"
"github.com/pkg/errors"
)
const (
contentTypeJSON = "application/json"
defaultBaseURL = "http://localhost:10090"
// DefaultTimeout default timeout duration for CNS Client.
DefaultTimeout = 5 * time.Second
headerContentType = "Content-Type"
)
var clientPaths = []string{
cns.GetNetworkContainerByOrchestratorContext,
cns.GetAllNetworkContainers,
cns.CreateHostNCApipaEndpointPath,
cns.DeleteHostNCApipaEndpointPath,
cns.RequestIPConfig,
cns.RequestIPConfigs,
cns.ReleaseIPConfig,
cns.ReleaseIPConfigs,
cns.PathDebugIPAddresses,
cns.PathDebugPodContext,
cns.PathDebugRestData,
cns.UnpublishNetworkContainer,
cns.PublishNetworkContainer,
cns.CreateOrUpdateNetworkContainer,
cns.SetOrchestratorType,
cns.NumberOfCPUCores,
cns.NMAgentSupportedAPIs,
cns.DeleteNetworkContainer,
cns.NetworkContainersURLPath,
cns.GetHomeAz,
cns.EndpointAPI,
}
type do interface {
Do(*http.Request) (*http.Response, error)
}
// Client specifies a client to connect to Ipam Plugin.
type Client struct {
client do
routes map[string]url.URL
}
type ConnectionFailureErr struct {
cause error
}
func (e *ConnectionFailureErr) Error() string {
return e.cause.Error()
}
// New returns a new CNS client configured with the passed URL and timeout.
func New(baseURL string, requestTimeout time.Duration) (*Client, error) {
if baseURL == "" {
baseURL = defaultBaseURL
}
routes, err := buildRoutes(baseURL, clientPaths)
if err != nil {
return nil, err
}
return &Client{
client: &http.Client{
Timeout: requestTimeout,
},
routes: routes,
}, nil
}
func buildRoutes(baseURL string, paths []string) (map[string]url.URL, error) {
base, err := url.Parse(baseURL)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse base URL %s", baseURL)
}
routes := map[string]url.URL{}
for _, path := range paths {
u := *base
pathURI, err := url.Parse(path)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse path URI %s", path)
}
u.Path = pathURI.Path
routes[path] = u
}
return routes, nil
}
// GetAllNetworkContainers Request to get network container configs.
func (c *Client) GetAllNetworkContainers(ctx context.Context, orchestratorContext []byte) ([]cns.GetNetworkContainerResponse, error) {
payload := cns.GetNetworkContainerRequest{
OrchestratorContext: orchestratorContext,
}
var body bytes.Buffer
if err := json.NewEncoder(&body).Encode(payload); err != nil {
return nil, &CNSClientError{
Code: types.UnexpectedError,
Err: err,
}
}
u := c.routes[cns.GetAllNetworkContainers]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &body)
if err != nil {
return nil, errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "http request failed")
}
defer res.Body.Close()
// investigate 404 orchestratorContext which is invalid and make sure this is addressed
if res.StatusCode == http.StatusNotFound {
return nil, &CNSClientError{
Code: types.UnsupportedAPI,
Err: errors.Errorf("Unsupported API"),
}
}
if res.StatusCode != http.StatusOK {
return nil, &CNSClientError{
Code: types.UnexpectedError,
Err: errors.Errorf("http response %d", res.StatusCode),
}
}
var resp cns.GetAllNetworkContainersResponse
err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return nil, &CNSClientError{
Code: types.UnexpectedError,
Err: err,
}
}
if resp.Response.ReturnCode != 0 {
return nil, &CNSClientError{
Code: resp.Response.ReturnCode,
Err: errors.New(resp.Response.Message),
}
}
return resp.NetworkContainers, nil
}
// GetNetworkContainer Request to get network container config.
func (c *Client) GetNetworkContainer(ctx context.Context, orchestratorContext []byte) (*cns.GetNetworkContainerResponse, error) {
payload := cns.GetNetworkContainerRequest{
OrchestratorContext: orchestratorContext,
}
var body bytes.Buffer
if err := json.NewEncoder(&body).Encode(payload); err != nil {
return nil, &CNSClientError{
Code: types.UnexpectedError,
Err: err,
}
}
u := c.routes[cns.GetNetworkContainerByOrchestratorContext]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &body)
if err != nil {
return nil, errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "http request failed")
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, &CNSClientError{
Code: types.UnexpectedError,
Err: errors.Errorf("http response %d", res.StatusCode),
}
}
var resp cns.GetNetworkContainerResponse
err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return nil, &CNSClientError{
Code: types.UnexpectedError,
Err: err,
}
}
if resp.Response.ReturnCode != 0 {
return nil, &CNSClientError{
Code: resp.Response.ReturnCode,
Err: errors.New(resp.Response.Message),
}
}
return &resp, nil
}
// CreateHostNCApipaEndpoint creates an endpoint in APIPA network for host container connectivity.
func (c *Client) CreateHostNCApipaEndpoint(ctx context.Context, networkContainerID string) (string, error) {
payload := cns.CreateHostNCApipaEndpointRequest{
NetworkContainerID: networkContainerID,
}
var body bytes.Buffer
if err := json.NewEncoder(&body).Encode(payload); err != nil {
return "", errors.Wrap(err, "failed to encode CreateCNSHostNCApipaEndpointRequest")
}
u := c.routes[cns.CreateHostNCApipaEndpointPath]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &body)
if err != nil {
return "", errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
return "", errors.Wrap(err, "http request failed")
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return "", errors.Errorf("http response %d", res.StatusCode)
}
var resp cns.CreateHostNCApipaEndpointResponse
if err = json.NewDecoder(res.Body).Decode(&resp); err != nil {
return "", errors.Wrap(err, "failed to decode CreateHostNCApipaEndpointResponse")
}
if resp.Response.ReturnCode != 0 {
return "", errors.New(resp.Response.Message)
}
return resp.EndpointID, nil
}
// DeleteHostNCApipaEndpoint deletes the endpoint in APIPA network created for host container connectivity.
func (c *Client) DeleteHostNCApipaEndpoint(ctx context.Context, networkContainerID string) error {
payload := cns.DeleteHostNCApipaEndpointRequest{
NetworkContainerID: networkContainerID,
}
var body bytes.Buffer
if err := json.NewEncoder(&body).Encode(payload); err != nil {
return errors.Wrap(err, "failed to encode DeleteHostNCApipaEndpointRequest")
}
u := c.routes[cns.DeleteHostNCApipaEndpointPath]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &body)
if err != nil {
return errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
return errors.Wrap(err, "http request failed")
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return errors.Errorf("http response %d", res.StatusCode)
}
var resp cns.DeleteHostNCApipaEndpointResponse
err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return errors.Wrap(err, "failed to decode DeleteHostNCApipaEndpointResponse")
}
if resp.Response.ReturnCode != 0 {
return errors.New(resp.Response.Message)
}
return nil
}
// RequestIPAddress calls the requestIPAddress in CNS
func (c *Client) RequestIPAddress(ctx context.Context, ipconfig cns.IPConfigRequest) (*cns.IPConfigResponse, error) {
var err error
defer func() {
if err != nil {
if e := c.ReleaseIPAddress(ctx, ipconfig); e != nil {
err = errors.Wrap(e, err.Error())
}
}
}()
var body bytes.Buffer
err = json.NewEncoder(&body).Encode(ipconfig)
if err != nil {
return nil, errors.Wrap(err, "failed to encode IPConfigRequest")
}
u := c.routes[cns.RequestIPConfig]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &body)
if err != nil {
return nil, errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "http request failed")
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, errors.Errorf("http response %d", res.StatusCode)
}
var response cns.IPConfigResponse
err = json.NewDecoder(res.Body).Decode(&response)
if err != nil {
return nil, errors.Wrap(err, "failed to decode IPConfigResponse")
}
if response.Response.ReturnCode != 0 {
return nil, errors.New(response.Response.Message)
}
return &response, nil
}
// ReleaseIPAddress calls releaseIPAddress on CNS, ipaddress ex: (10.0.0.1)
func (c *Client) ReleaseIPAddress(ctx context.Context, ipconfig cns.IPConfigRequest) error {
var body bytes.Buffer
err := json.NewEncoder(&body).Encode(ipconfig)
if err != nil {
return errors.Wrap(err, "failed to encode IPConfigRequest")
}
u := c.routes[cns.ReleaseIPConfig]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &body)
if err != nil {
return errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
return &ConnectionFailureErr{
cause: err,
}
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return errors.Errorf("http response %d", res.StatusCode)
}
var resp cns.Response
err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return errors.Wrap(err, "failed to decode Response")
}
if resp.ReturnCode != 0 {
return errors.New(resp.Message)
}
return nil
}
// RequestIPs calls the RequestIPConfigs in CNS
func (c *Client) RequestIPs(ctx context.Context, ipconfig cns.IPConfigsRequest) (*cns.IPConfigsResponse, error) {
var err error
defer func() {
if err != nil {
if e := c.ReleaseIPs(ctx, ipconfig); e != nil {
err = errors.Wrap(e, err.Error())
}
}
}()
var body bytes.Buffer
err = json.NewEncoder(&body).Encode(ipconfig)
if err != nil {
return nil, errors.Wrap(err, "failed to encode IPConfigsRequest")
}
u := c.routes[cns.RequestIPConfigs]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &body)
if err != nil {
return nil, errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "http request failed")
}
defer res.Body.Close()
if res.StatusCode == http.StatusNotFound {
return nil, &CNSClientError{
Code: types.UnsupportedAPI,
Err: errors.Errorf("Unsupported API"),
}
}
if res.StatusCode != http.StatusOK {
return nil, errors.Errorf("http response %d", res.StatusCode)
}
var response cns.IPConfigsResponse
err = json.NewDecoder(res.Body).Decode(&response)
if err != nil {
return nil, errors.Wrap(err, "failed to decode IPConfigsResponse")
}
if response.Response.ReturnCode != 0 {
return nil, errors.New(response.Response.Message)
}
return &response, nil
}
// ReleaseIPs calls releaseIPs on which releases the IPs on the pod
func (c *Client) ReleaseIPs(ctx context.Context, ipconfig cns.IPConfigsRequest) error {
var body bytes.Buffer
err := json.NewEncoder(&body).Encode(ipconfig)
if err != nil {
return errors.Wrap(err, "failed to encode IPConfigsRequest")
}
u := c.routes[cns.ReleaseIPConfigs]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &body)
if err != nil {
return errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
return &ConnectionFailureErr{
cause: err,
}
}
defer res.Body.Close()
// if we get a 404 error
if res.StatusCode == http.StatusNotFound {
return &CNSClientError{
Code: types.UnsupportedAPI,
Err: errors.Errorf("Unsupported API"),
}
}
if res.StatusCode != http.StatusOK {
return errors.Errorf("http response %d", res.StatusCode)
}
var resp cns.Response
err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return errors.Wrap(err, "failed to decode Response")
}
if resp.ReturnCode != 0 {
return errors.New(resp.Message)
}
return nil
}
// GetIPAddressesMatchingStates takes a variadic number of string parameters, to get all IP Addresses matching a number of states
// usage GetIPAddressesWithStates(ctx, types.Available...)
func (c *Client) GetIPAddressesMatchingStates(ctx context.Context, stateFilter ...types.IPState) ([]cns.IPConfigurationStatus, error) {
if len(stateFilter) == 0 {
return nil, nil
}
payload := cns.GetIPAddressesRequest{
IPConfigStateFilter: stateFilter,
}
var body bytes.Buffer
if err := json.NewEncoder(&body).Encode(payload); err != nil {
return nil, errors.Wrap(err, "failed to encode GetIPAddressesRequest")
}
u := c.routes[cns.PathDebugIPAddresses]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), &body)
if err != nil {
return nil, errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "http request failed")
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, errors.Errorf("http response %d", res.StatusCode)
}
var resp cns.GetIPAddressStatusResponse
err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return nil, errors.Wrap(err, "failed to decode GetIPAddressStatusResponse")
}
if resp.Response.ReturnCode != 0 {
return nil, errors.New(resp.Response.Message)
}
return resp.IPConfigurationStatus, nil
}
// GetPodOrchestratorContext calls GetPodIpOrchestratorContext API on CNS
func (c *Client) GetPodOrchestratorContext(ctx context.Context) (map[string][]string, error) {
u := c.routes[cns.PathDebugPodContext]
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, errors.Wrap(err, "failed to build request")
}
res, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "http request failed")
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, errors.Errorf("http response %d", res.StatusCode)
}
var resp cns.GetPodContextResponse
if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
return nil, errors.Wrap(err, "failed to decode GetPodContextResponse")
}
if resp.Response.ReturnCode != 0 {
return nil, errors.New(resp.Response.Message)
}
return resp.PodContext, nil
}
// GetHTTPServiceData gets all public in-memory struct details for debugging purpose
func (c *Client) GetHTTPServiceData(ctx context.Context) (*restserver.GetHTTPServiceDataResponse, error) {
u := c.routes[cns.PathDebugRestData]
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, errors.Wrap(err, "failed to build request")
}
res, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "http request failed")
}
defer res.Body.Close()
b, err := io.ReadAll(res.Body)
s := string(b)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to read body %s", s))
}
if res.StatusCode != http.StatusOK {
return nil, errors.Errorf("http response %d", res.StatusCode)
}
var resp restserver.GetHTTPServiceDataResponse
err = json.NewDecoder(bytes.NewReader(b)).Decode(&resp)
if err != nil {
return nil, errors.Wrap(err, "failed to decode GetHTTPServiceDataResponse")
}
if resp.Response.ReturnCode != 0 {
return nil, errors.New(resp.Response.Message)
}
return &resp, nil
}
// NumOfCPUCores returns the number of CPU cores available on the host that
// CNS is running on.
func (c *Client) NumOfCPUCores(ctx context.Context) (*cns.NumOfCPUCoresResponse, error) {
// build the request
u := c.routes[cns.NumberOfCPUCores]
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody)
if err != nil {
return nil, errors.Wrap(err, "building http request")
}
// submit the request
resp, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "sending HTTP request")
}
defer resp.Body.Close()
// decode the response
var out cns.NumOfCPUCoresResponse
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return nil, errors.Wrap(err, "decoding response as JSON")
}
// if the return code is non-zero, something went wrong and it should be
// surfaced to the caller
if out.Response.ReturnCode != 0 {
return nil, &CNSClientError{
Code: out.Response.ReturnCode,
Err: errors.New(out.Response.Message),
}
}
return &out, nil
}
// DeleteNetworkContainer destroys the requested network container matching the
// provided ID.
func (c *Client) DeleteNetworkContainer(ctx context.Context, ncID string) error {
// the network container ID is required by the API, so ensure that we have
// one before we even make the request
if ncID == "" {
return errors.New("no network container ID provided")
}
// build the request
dncr := cns.DeleteNetworkContainerRequest{
NetworkContainerid: ncID,
}
body, err := json.Marshal(dncr)
if err != nil {
return errors.Wrap(err, "encoding request body")
}
u := c.routes[cns.DeleteNetworkContainer]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bytes.NewReader(body))
if err != nil {
return errors.Wrap(err, "building HTTP request")
}
// submit the request
resp, err := c.client.Do(req)
if err != nil {
return errors.Wrap(err, "sending HTTP request")
}
defer resp.Body.Close()
// decode the response
var out cns.DeleteNetworkContainerResponse
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return errors.Wrap(err, "decoding response as JSON")
}
// if a non-zero response code was received from CNS, it means something went
// wrong and it should be surfaced to the caller as an error
if out.Response.ReturnCode != 0 {
return errors.New(out.Response.Message)
}
// otherwise the response isn't terribly useful in a successful case, so it
// doesn't make sense to provide it to callers. The absence of an error is
// sufficient to communicate success.
return nil
}
// SetOrchestratorType sets the orchestrator type for a given node
func (c *Client) SetOrchestratorType(ctx context.Context, sotr cns.SetOrchestratorTypeRequest) error {
// validate that the request has all of the required fields before we waste a
// round trip
if sotr.OrchestratorType == "" {
return errors.New("request missing field OrchestratorType")
}
if sotr.DncPartitionKey == "" {
return errors.New("request missing field DncPartitionKey")
}
if sotr.NodeID == "" {
return errors.New("request missing field NodeID")
}
// build the HTTP request using the supplied request body
// submit the request
body, err := json.Marshal(sotr)
if err != nil {
return errors.Wrap(err, "encoding request body")
}
u := c.routes[cns.SetOrchestratorType]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bytes.NewReader(body))
if err != nil {
return errors.Wrap(err, "building HTTP request")
}
// send the request
resp, err := c.client.Do(req)
if err != nil {
return errors.Wrap(err, "sending HTTP request")
}
defer resp.Body.Close()
// decode the response
var out cns.Response
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return errors.Wrap(err, "decoding JSON response")
}
// if there was a non-zero response code, this is an error that
// should be communicated back to the caller...
if out.ReturnCode != 0 {
return errors.New(out.Message)
}
// ...otherwise it's a success and returning nil is sufficient to
// communicate that
return nil
}
// CreateNetworkContainer will create the provided network container, or update
// an existing one if one already exists.
func (c *Client) CreateNetworkContainer(ctx context.Context, cncr cns.CreateNetworkContainerRequest) error {
// CreateNetworkContainerRequest is a deep and complicated struct, so
// validating fields before we send it off is difficult and likely redundant
// since the backend will have similar checks. However, we can be pretty
// certain that if the NetworkContainerid is missing, it's likely an invalid
// request (since that parameter is mandatory).
if cncr.NetworkContainerid == "" {
return errors.New("empty request provided")
}
// build the request using the supplied struct and the client's internal
// routes
body, err := json.Marshal(cncr)
if err != nil {
return errors.Wrap(err, "encoding request as JSON")
}
u := c.routes[cns.CreateOrUpdateNetworkContainer]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bytes.NewReader(body))
if err != nil {
return errors.Wrap(err, "building HTTP request")
}
// send the request
resp, err := c.client.Do(req)
if err != nil {
return errors.Wrap(err, "sending HTTP request")
}
defer resp.Body.Close()
// decode the response
var out cns.Response
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return errors.Wrap(err, "decoding JSON response")
}
// if there was a non-zero response code, this is an error that
// should be communicated back to the caller...
if out.ReturnCode != 0 {
return errors.New(out.Message)
}
// ...otherwise the request was successful so
return nil
}
// PublishNetworkContainer publishes the provided network container via the
// NMAgent resident on the node where CNS is running. This effectively proxies
// the publication through CNS which can be useful for avoiding throttling
// issues from Wireserver.
func (c *Client) PublishNetworkContainer(ctx context.Context, pncr cns.PublishNetworkContainerRequest) error {
// Given that the PublishNetworkContainer endpoint is intended to publish
// network containers, it's reasonable to assume that the request is invalid
// if it's missing a NetworkContainerID. Check for its presence and
// pre-emptively fail if that ID is missing:
if pncr.NetworkContainerID == "" {
return errors.New("network container id missing from request")
}
// Now that the request is valid it can be packaged as an HTTP request:
body, err := json.Marshal(pncr)
if err != nil {
return errors.Wrap(err, "encoding request body as json")
}
u := c.routes[cns.PublishNetworkContainer]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bytes.NewReader(body))
if err != nil {
return errors.Wrap(err, "building HTTP request")
}
// send the HTTP request
resp, err := c.client.Do(req)
if err != nil {
return errors.Wrap(err, "sending HTTP request")
}
defer resp.Body.Close()
// decode the response to see if it was successful
var out cns.PublishNetworkContainerResponse
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return errors.Wrap(err, "decoding JSON response")
}
// if there was a non-zero response code, this is an error that
// should be communicated back to the caller...
if out.Response.ReturnCode != 0 {
return errors.New(out.Response.Message)
}
// ...otherwise the request was successful so
return nil
}
// UnpublishNC unpublishes the network container via the NMAgent running
// alongside the CNS service. This is useful to avoid throttling issues imposed
// by Wireserver.
func (c *Client) UnpublishNC(ctx context.Context, uncr cns.UnpublishNetworkContainerRequest) error {
// In order to unpublish a Network Container, we need its ID. If the ID is
// missing, we can assume that the request is invalid and immediately return
// an error
if uncr.NetworkContainerID == "" {
return errors.New("request missing network container id")
}
// Now that the request is valid it can be packaged as an HTTP request:
body, err := json.Marshal(uncr)
if err != nil {
return errors.Wrap(err, "encoding request body as json")
}
u := c.routes[cns.UnpublishNetworkContainer]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bytes.NewReader(body))
if err != nil {
return errors.Wrap(err, "building HTTP request")
}
// send the HTTP request
resp, err := c.client.Do(req)
if err != nil {
return errors.Wrap(err, "sending HTTP request")
}
defer resp.Body.Close()
// decode the response to see if it was successful
var out cns.UnpublishNetworkContainerResponse
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return errors.Wrap(err, "decoding JSON response")
}
// if there was a non-zero response code, this is an error that
// should be communicated back to the caller...
if out.Response.ReturnCode != 0 {
return errors.New(out.Response.Message)
}
// ...otherwise the request was successful so
return nil
}
// NMAgentSupportedAPIs returns the supported API names from NMAgent. This can
// be used, for example, to detect whether the node is capable for GRE
// allocations.
func (c *Client) NMAgentSupportedAPIs(ctx context.Context) (*cns.NmAgentSupportedApisResponse, error) {
// build the request
reqBody := &cns.NmAgentSupportedApisRequest{
// the IP used below is that of the Wireserver
GetNmAgentSupportedApisURL: "http://168.63.129.16/machine/plugins/?comp=nmagent&type=GetSupportedApis",
}
body, err := json.Marshal(reqBody)
if err != nil {
return nil, errors.Wrap(err, "encoding request body")
}
u := c.routes[cns.NMAgentSupportedAPIs]
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), bytes.NewReader(body))
if err != nil {
return nil, errors.Wrap(err, "building http request")
}
// submit the request
resp, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "sending http request")
}
defer resp.Body.Close()
if code := resp.StatusCode; code != http.StatusOK {
return nil, &FailedHTTPRequest{
Code: code,
}
}
// decode response
var out cns.NmAgentSupportedApisResponse
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return nil, errors.Wrap(err, "decoding response body")
}
// if there was a non-zero status code, that indicates an error and should be
// communicated as such
if out.Response.ReturnCode != 0 {
return nil, &CNSClientError{
Code: out.Response.ReturnCode,
Err: errors.New(out.Response.Message),
}
}
return &out, nil
}
func (c *Client) GetAllNCsFromCns(ctx context.Context) (cns.GetAllNetworkContainersResponse, error) {
// Build the request
urlPath := c.routes[cns.NetworkContainersURLPath]
req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlPath.String(), http.NoBody)
if err != nil {
return cns.GetAllNetworkContainersResponse{}, errors.Wrap(err, "building HTTP request")
}
// Submit the request
resp, err := c.client.Do(req)
if err != nil {
return cns.GetAllNetworkContainersResponse{}, errors.Wrap(err, "sending HTTP request")
}
defer resp.Body.Close()
// Decode the response
var response cns.GetAllNetworkContainersResponse
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil || response.Response.ReturnCode != types.Success {
return cns.GetAllNetworkContainersResponse{}, errors.Wrap(err, "decoding GetAllNetworkContainersResponse as JSON")
}
return response, nil
}
func (c *Client) PostAllNetworkContainers(ctx context.Context, createNcRequest cns.PostNetworkContainersRequest) error {
if createNcRequest.CreateNetworkContainerRequests == nil || len(createNcRequest.CreateNetworkContainerRequests) == 0 {
return errors.New("empty request provided")
}
// Build the request
var body bytes.Buffer
err := json.NewEncoder(&body).Encode(createNcRequest)
if err != nil {
return errors.Wrap(err, "building HTTP request")
}
urlPath := c.routes[cns.NetworkContainersURLPath]
req, err := http.NewRequestWithContext(ctx, http.MethodPost, urlPath.String(), &body)
if err != nil {
return errors.Wrap(err, "building HTTP request")
}
// Submit the request
resp, err := c.client.Do(req)
if err != nil {
return errors.Wrap(err, "sending HTTP request")
}
defer resp.Body.Close()
// Decode the response
var response cns.PostNetworkContainersResponse
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil || response.Response.ReturnCode != types.Success {
return errors.Wrap(err, "decoding PostNetworkContainersResponse as JSON")
}
return nil
}
// GetHomeAz gets home AZ of host
func (c *Client) GetHomeAz(ctx context.Context) (*cns.GetHomeAzResponse, error) {
// build the request
u := c.routes[cns.GetHomeAz]
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody)
if err != nil {
return nil, errors.Wrap(err, "building http request")
}
// submit the request
resp, err := c.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "sending HTTP request")
}
defer resp.Body.Close()
// decode the response
var getHomeAzResponse cns.GetHomeAzResponse
err = json.NewDecoder(resp.Body).Decode(&getHomeAzResponse)
if err != nil {
return nil, errors.Wrap(err, "decoding response as JSON")
}
// if the return code is non-zero, something went wrong and it should be
// surfaced to the caller
if getHomeAzResponse.Response.ReturnCode != 0 {
return nil, &CNSClientError{
Code: getHomeAzResponse.Response.ReturnCode,
Err: errors.New(getHomeAzResponse.Response.Message),
}
}
return &getHomeAzResponse, nil
}
// GetEndpoint calls the EndpointHandlerAPI in CNS to retrieve the state of a given EndpointID
func (c *Client) GetEndpoint(ctx context.Context, endpointID string) (*restserver.GetEndpointResponse, error) {
// build the request
u := c.routes[cns.EndpointAPI]
uString := u.String() + endpointID
var response restserver.GetEndpointResponse
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uString, http.NoBody)
if err != nil {
response.Response.ReturnCode = types.UnexpectedError
return &response, errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
response.Response.ReturnCode = types.ConnectionError
return &response, &ConnectionFailureErr{cause: err}
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
response.Response.ReturnCode = types.UnexpectedError
return &response, errors.Errorf("http response %d", res.StatusCode)
}
err = json.NewDecoder(res.Body).Decode(&response)
if err != nil {
response.Response.ReturnCode = types.UnexpectedError
return &response, errors.Wrap(err, "failed to decode GetEndpointResponse")
}
if response.Response.ReturnCode != 0 {
return &response, errors.New(response.Response.Message)
}
return &response, nil
}
// UpdateEndpoint calls the EndpointHandlerAPI in CNS
// to update the state of a given EndpointID with either HNSEndpointID or HostVethName
func (c *Client) UpdateEndpoint(ctx context.Context, endpointID string, ipInfo map[string]*restserver.IPInfo) (*cns.Response, error) {
// build the request
var body bytes.Buffer
if err := json.NewEncoder(&body).Encode(ipInfo); err != nil {
return nil, errors.Wrap(err, "failed to encode updateEndpoint")
}
u := c.routes[cns.EndpointAPI]
uString := u.String() + endpointID
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, uString, &body)
if err != nil {
return nil, errors.Wrap(err, "failed to build request")
}
req.Header.Set(headerContentType, contentTypeJSON)
res, err := c.client.Do(req)
if err != nil {
return nil, &ConnectionFailureErr{cause: err}
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, errors.Errorf("http response %d", res.StatusCode)
}
var response cns.Response
err = json.NewDecoder(res.Body).Decode(&response)
if err != nil {
return nil, errors.Wrap(err, "failed to decode CNS Response")
}
if response.ReturnCode != 0 {
return nil, errors.New(response.Message)
}
return &response, nil
}