Skip to content

Commit 4e0faf0

Browse files
authored
Port DeleteNetworkContainer and SetOrchestratorType from DNC (Azure#1454)
* Add DeleteNetworkContainer method to CNS Client DNC needs to delete network containers using CNS, and it currently does so through raw HTTP requests. This instead makes this an official operation within the CNS client so that it can be used there instead. * Remove return value from DeleteNetworkContainer The return value from DeleteNetworkContainer was basically unused in DeleteNetworkContainer, since it only contains a CNSResponse type which can only ever be a successful response. Given this, it's sufficient to just return no error as a signifier of success. * Add SetOrchestratorType endpoint to cns client DNC uses SetOrchestratorType currently by making straight HTTP requests to the CNS backend. Since we should have one client only, this moves these endpoints into the CNS client so that it can be consumed in DNC. * Fix two linter issues In one instance the linter had a false positive on an error that doesn't really need to be wrapped. The other was a good suggestion since it helps readers of the test understand what is going on. * Add CreateNetworkContainer endpoint to client Turns out DNC uses a few more endpoints than it would seem. This adds a CreateNetworkContainer method to encapsulate the /network/createorupdatenetworkcontainer endpoint. * Add a PublishNetworkContainer to CNS client Publishing network containers via CNS is something that DNC does directly through an HTTP client. Given how common this is, it makes sense to adopt this into the CNS client so that DNC can use the client instead. * Fix placeholder error message This was just used in testing and was forgotten. * Add context to DeleteNetworkContainer Everything involving the network should take a context parameter. * Fix PublishNetworkContainer return type The PublishNetworkContainer endpoint returns a wrapping type around the cns.Response. This updates the tests and the endpoint to reflect that. * Add UnpublishNC to CNS Client Unpublishing NCs is accomplished by DNC currently by directly making HTTP calls. This adds that functionality to the client so the client can be used instead.
1 parent b931932 commit 4e0faf0

File tree

2 files changed

+831
-0
lines changed

2 files changed

+831
-0
lines changed

cns/client/client.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,3 +411,247 @@ func (c *Client) GetHTTPServiceData(ctx context.Context) (*restserver.GetHTTPSer
411411

412412
return &resp, nil
413413
}
414+
415+
// DeleteNetworkContainer destroys the requested network container matching the
416+
// provided ID.
417+
func (c *Client) DeleteNetworkContainer(ctx context.Context, ncID string) error {
418+
// the network container ID is required by the API, so ensure that we have
419+
// one before we even make the request
420+
if ncID == "" {
421+
return errors.New("no network container ID provided")
422+
}
423+
424+
// build the request
425+
dncr := cns.DeleteNetworkContainerRequest{
426+
NetworkContainerid: ncID,
427+
}
428+
body, err := json.Marshal(dncr)
429+
if err != nil {
430+
return errors.Wrap(err, "encoding request body")
431+
}
432+
u := c.routes[cns.DeleteNetworkContainer]
433+
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(body))
434+
if err != nil {
435+
return errors.Wrap(err, "building HTTP request")
436+
}
437+
438+
// submit the request
439+
resp, err := c.client.Do(req)
440+
if err != nil {
441+
return errors.Wrap(err, "sending HTTP request")
442+
}
443+
defer resp.Body.Close()
444+
445+
// decode the response
446+
var out cns.DeleteNetworkContainerResponse
447+
err = json.NewDecoder(resp.Body).Decode(&out)
448+
if err != nil {
449+
return errors.Wrap(err, "decoding response as JSON")
450+
}
451+
452+
// if a non-zero response code was received from CNS, it means something went
453+
// wrong and it should be surfaced to the caller as an error
454+
if out.Response.ReturnCode != 0 {
455+
return errors.New(out.Response.Message)
456+
}
457+
458+
// otherwise the response isn't terribly useful in a successful case, so it
459+
// doesn't make sense to provide it to callers. The absence of an error is
460+
// sufficient to communicate success.
461+
return nil
462+
}
463+
464+
// SetOrchestratorType sets the orchestrator type for a given node
465+
func (c *Client) SetOrchestratorType(ctx context.Context, sotr cns.SetOrchestratorTypeRequest) error {
466+
// validate that the request has all of the required fields before we waste a
467+
// round trip
468+
if sotr.OrchestratorType == "" {
469+
return errors.New("request missing field OrchestratorType")
470+
}
471+
472+
if sotr.DncPartitionKey == "" {
473+
return errors.New("request missing field DncPartitionKey")
474+
}
475+
476+
if sotr.NodeID == "" {
477+
return errors.New("request missing field NodeID")
478+
}
479+
480+
// build the HTTP request using the supplied request body
481+
// submit the request
482+
body, err := json.Marshal(sotr)
483+
if err != nil {
484+
return errors.Wrap(err, "encoding request body")
485+
}
486+
u := c.routes[cns.SetOrchestratorType]
487+
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(body))
488+
if err != nil {
489+
return errors.Wrap(err, "building HTTP request")
490+
}
491+
492+
// send the request
493+
resp, err := c.client.Do(req)
494+
if err != nil {
495+
return errors.Wrap(err, "sending HTTP request")
496+
}
497+
defer resp.Body.Close()
498+
499+
// decode the response
500+
var out cns.Response
501+
err = json.NewDecoder(resp.Body).Decode(&out)
502+
if err != nil {
503+
return errors.Wrap(err, "decoding JSON response")
504+
}
505+
506+
// if there was a non-zero response code, this is an error that
507+
// should be communicated back to the caller...
508+
if out.ReturnCode != 0 {
509+
return errors.New(out.Message)
510+
}
511+
512+
// ...otherwise it's a success and returning nil is sufficient to
513+
// communicate that
514+
return nil
515+
}
516+
517+
// CreateNetworkContainer will create the provided network container, or update
518+
// an existing one if one already exists.
519+
func (c *Client) CreateNetworkContainer(ctx context.Context, cncr cns.CreateNetworkContainerRequest) error {
520+
// CreateNetworkContainerRequest is a deep and complicated struct, so
521+
// validating fields before we send it off is difficult and likely redundant
522+
// since the backend will have similar checks. However, we can be pretty
523+
// certain that if the NetworkContainerid is missing, it's likely an invalid
524+
// request (since that parameter is mandatory).
525+
if cncr.NetworkContainerid == "" {
526+
return errors.New("empty request provided")
527+
}
528+
529+
// build the request using the supplied struct and the client's internal
530+
// routes
531+
body, err := json.Marshal(cncr)
532+
if err != nil {
533+
return errors.Wrap(err, "encoding request as JSON")
534+
}
535+
u := c.routes[cns.CreateOrUpdateNetworkContainer]
536+
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(body))
537+
if err != nil {
538+
return errors.Wrap(err, "building HTTP request")
539+
}
540+
541+
// send the request
542+
resp, err := c.client.Do(req)
543+
if err != nil {
544+
return errors.Wrap(err, "sending HTTP request")
545+
}
546+
defer resp.Body.Close()
547+
548+
// decode the response
549+
var out cns.Response
550+
err = json.NewDecoder(resp.Body).Decode(&out)
551+
if err != nil {
552+
return errors.Wrap(err, "decoding JSON response")
553+
}
554+
555+
// if there was a non-zero response code, this is an error that
556+
// should be communicated back to the caller...
557+
if out.ReturnCode != 0 {
558+
return errors.New(out.Message)
559+
}
560+
561+
// ...otherwise the request was successful so
562+
return nil
563+
}
564+
565+
// PublishNetworkContainer publishes the provided network container via the
566+
// NMAgent resident on the node where CNS is running. This effectively proxies
567+
// the publication through CNS which can be useful for avoiding throttling
568+
// issues from Wireserver.
569+
func (c *Client) PublishNetworkContainer(ctx context.Context, pncr cns.PublishNetworkContainerRequest) error {
570+
// Given that the PublishNetworkContainer endpoint is intended to publish
571+
// network containers, it's reasonable to assume that the request is invalid
572+
// if it's missing a NetworkContainerID. Check for its presence and
573+
// pre-emptively fail if that ID is missing:
574+
if pncr.NetworkContainerID == "" {
575+
return errors.New("network container id missing from request")
576+
}
577+
578+
// Now that the request is valid it can be packaged as an HTTP request:
579+
body, err := json.Marshal(pncr)
580+
if err != nil {
581+
return errors.Wrap(err, "encoding request body as json")
582+
}
583+
u := c.routes[cns.PublishNetworkContainer]
584+
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(body))
585+
if err != nil {
586+
return errors.Wrap(err, "building HTTP request")
587+
}
588+
589+
// send the HTTP request
590+
resp, err := c.client.Do(req)
591+
if err != nil {
592+
return errors.Wrap(err, "sending HTTP request")
593+
}
594+
defer resp.Body.Close()
595+
596+
// decode the response to see if it was successful
597+
var out cns.PublishNetworkContainerResponse
598+
err = json.NewDecoder(resp.Body).Decode(&out)
599+
if err != nil {
600+
return errors.Wrap(err, "decoding JSON response")
601+
}
602+
603+
// if there was a non-zero response code, this is an error that
604+
// should be communicated back to the caller...
605+
if out.Response.ReturnCode != 0 {
606+
return errors.New(out.Response.Message)
607+
}
608+
609+
// ...otherwise the request was successful so
610+
return nil
611+
}
612+
613+
// UnpublishNC unpublishes the network container via the NMAgent running
614+
// alongside the CNS service. This is useful to avoid throttling issues imposed
615+
// by Wireserver.
616+
func (c *Client) UnpublishNC(ctx context.Context, uncr cns.UnpublishNetworkContainerRequest) error {
617+
// In order to unpublish a Network Container, we need its ID. If the ID is
618+
// missing, we can assume that the request is invalid and immediately return
619+
// an error
620+
if uncr.NetworkContainerID == "" {
621+
return errors.New("request missing network container id")
622+
}
623+
624+
// Now that the request is valid it can be packaged as an HTTP request:
625+
body, err := json.Marshal(uncr)
626+
if err != nil {
627+
return errors.Wrap(err, "encoding request body as json")
628+
}
629+
u := c.routes[cns.UnpublishNetworkContainer]
630+
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(body))
631+
if err != nil {
632+
return errors.Wrap(err, "building HTTP request")
633+
}
634+
635+
// send the HTTP request
636+
resp, err := c.client.Do(req)
637+
if err != nil {
638+
return errors.Wrap(err, "sending HTTP request")
639+
}
640+
defer resp.Body.Close()
641+
642+
// decode the response to see if it was successful
643+
var out cns.UnpublishNetworkContainerResponse
644+
err = json.NewDecoder(resp.Body).Decode(&out)
645+
if err != nil {
646+
return errors.Wrap(err, "decoding JSON response")
647+
}
648+
649+
// if there was a non-zero response code, this is an error that
650+
// should be communicated back to the caller...
651+
if out.Response.ReturnCode != 0 {
652+
return errors.New(out.Response.Message)
653+
}
654+
655+
// ...otherwise the request was successful so
656+
return nil
657+
}

0 commit comments

Comments
 (0)