From 5f4d1d734ac312ef7b7c7c69c3694800b48a4aa7 Mon Sep 17 00:00:00 2001 From: Ashvin Deodhar Date: Wed, 30 Oct 2019 15:11:11 -0700 Subject: [PATCH 01/10] initial code change to validate --- Makefile | 1 + cns/NetworkContainerContract.go | 31 +++++++++- cns/restserver/api.go | 2 + cns/restserver/restserver.go | 102 ++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2dce8cc90b..bbba1ba616 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ CNSFILES = \ $(wildcard cns/imdsclient/*.go) \ $(wildcard cns/ipamclient/*.go) \ $(wildcard cns/hnsclient/*.go) \ + $(wildcard cns/nmagentclient/*.go) \ $(wildcard cns/restserver/*.go) \ $(wildcard cns/routes/*.go) \ $(wildcard cns/service/*.go) \ diff --git a/cns/NetworkContainerContract.go b/cns/NetworkContainerContract.go index 55d82e7118..2a60a17a8b 100644 --- a/cns/NetworkContainerContract.go +++ b/cns/NetworkContainerContract.go @@ -1,6 +1,8 @@ package cns -import "encoding/json" +import ( + "encoding/json" +) // Container Network Service DNC Contract const ( @@ -8,6 +10,8 @@ const ( CreateOrUpdateNetworkContainer = "/network/createorupdatenetworkcontainer" DeleteNetworkContainer = "/network/deletenetworkcontainer" GetNetworkContainerStatus = "/network/getnetworkcontainerstatus" + PublishNetworkContainer = "/network/publishnetworkcontainer" + UnpublishNetworkContainer = "/network/unpublishnetworkcontainer" GetInterfaceForContainer = "/network/getinterfaceforcontainer" GetNetworkContainerByOrchestratorContext = "/network/getnetworkcontainerbyorchestratorcontext" AttachContainerToNetwork = "/network/attachcontainertonetwork" @@ -182,3 +186,28 @@ type NetworkInterface struct { Name string IPAddress string } + +// PublishNetworkContainerRequest specifies request to publish network container via NMAgent. +type PublishNetworkContainerRequest struct { + WireSererIP string + AssociatedInterfaceIP string + NetworkID string + NetworkContainerID string + AccessToken string + JoinNetworkURLFmt string + CreateNetworkContainerURLFmt string + JoinNetworkURL string + CreateNetworkContainerURL string + //CreateNetworkContainerRequestBody bytes.Buffer + CreateNetworkContainerRequestBody []byte +} + +// PublishNetworkContainerResponse specifies the response to publish network container request. +type PublishNetworkContainerResponse struct { + Response Response + //HttpStatusCode int + //HttpResponsePublish http.Response + PublishStatusCode int + PublishResponseBody []byte + PublishError error +} diff --git a/cns/restserver/api.go b/cns/restserver/api.go index f2a4fcbf2d..567e849688 100644 --- a/cns/restserver/api.go +++ b/cns/restserver/api.go @@ -24,6 +24,8 @@ const ( UnsupportedVerb = 21 UnsupportedNetworkContainerType = 22 InvalidRequest = 23 + NetworkPublishFailed = 24 + NetworkContainerPublishFailed = 25 UnexpectedError = 99 ) diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index f38c625d62..b31c31996f 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -6,6 +6,7 @@ package restserver import ( "encoding/json" "fmt" + "io/ioutil" "net" "net/http" "runtime" @@ -19,6 +20,7 @@ import ( "github.com/Azure/azure-container-networking/cns/imdsclient" "github.com/Azure/azure-container-networking/cns/ipamclient" "github.com/Azure/azure-container-networking/cns/networkcontainers" + "github.com/Azure/azure-container-networking/cns/nmagentclient" "github.com/Azure/azure-container-networking/cns/routes" acn "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/log" @@ -161,6 +163,8 @@ func (service *HTTPRestService) Start(config *common.ServiceConfig) error { listener.AddHandler(cns.NumberOfCPUCoresPath, service.getNumberOfCPUCores) listener.AddHandler(cns.CreateHostNCApipaEndpointPath, service.createHostNCApipaEndpoint) listener.AddHandler(cns.DeleteHostNCApipaEndpointPath, service.deleteHostNCApipaEndpoint) + listener.AddHandler(cns.PublishNetworkContainer, service.publishNetworkContainer) + //listener.AddHandler(cns.UnpublishNetworkContainer, service.unpublishNetworkContainer) // handlers for v0.2 listener.AddHandler(cns.V2Prefix+cns.SetEnvironmentPath, service.setEnvironment) @@ -1723,3 +1727,101 @@ func (service *HTTPRestService) deleteHostNCApipaEndpoint(w http.ResponseWriter, err = service.Listener.Encode(w, &response) log.Response(service.Name, response, response.Response.ReturnCode, ReturnCodeToString(response.Response.ReturnCode), err) } + +// TODO: move this to nmagent specific file +func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r *http.Request) { + log.Printf("[Azure-CNS] publishNetworkContainer") + + var ( + err error + req cns.PublishNetworkContainerRequest + returnCode int + returnMessage string + returnHttpStatusCode int + //joinRespStatusCode int + responsePublish *http.Response + ) + + err = service.Listener.Decode(w, r, &req) + log.Request(service.Name, &req, err) + if err != nil { + return + } + + switch r.Method { + case "POST": + // Publish Network + responsePublish, err = nmagentclient.PublishNetwork(req.NetworkID, req.JoinNetworkURLFmt, req.WireSererIP, req.JoinNetworkURL) + //TODO: check if responsePublish is nil + if responsePublish.StatusCode != http.StatusOK || err != nil { + returnMessage = fmt.Sprintf("Failed to publish Network: %s, HttpStatusCode: %d, Error: %v", + req.NetworkID, returnHttpStatusCode, err) + returnCode = NetworkPublishFailed + } else { + // Publish Network Container + responsePublish, err = nmagentclient.PublishNetworkContainer( + req.NetworkContainerID, + req.CreateNetworkContainerURLFmt, + req.WireSererIP, + req.AssociatedInterfaceIP, + req.AccessToken, + req.CreateNetworkContainerURL, + req.CreateNetworkContainerRequestBody) + // Below err handling is not needed + /* + if err != nil { + returnMessage = fmt.Sprintf("Failed to publish Network Container: %s, HttpStatusCode: %d, Error: %v", + req.NetworkContainerID, returnHttpStatusCode, err) + returnCode = NetworkContainerPublishFailed + } + */ + } + default: + returnMessage = "PublishNetworkContainer API expects a POST" + returnCode = UnsupportedVerb + } + + publishResponseBody, errParse := ioutil.ReadAll(responsePublish.Body) + if errParse != nil { + log.Printf("[Azure-CNS] tempdebug: failed to parse the body") + returnCode = UnexpectedError + returnMessage = "Failed to parse the publish body" + } + + response := cns.PublishNetworkContainerResponse{ + Response: cns.Response{ + ReturnCode: returnCode, + Message: returnMessage, + }, + //HttpStatusCode: returnHttpStatusCode, + // TODO: How to make sure responsePublish is not nil? + //HttpResponsePublish: *responsePublish, + PublishStatusCode: responsePublish.StatusCode, + PublishResponseBody: publishResponseBody, + PublishError: err, + } + + err = service.Listener.Encode(w, &response) + log.Response(service.Name, response, response.Response.ReturnCode, ReturnCodeToString(response.Response.ReturnCode), err) +} + +/* +//perform http request + resp, err := http.Post(url, "application/json; charset=utf-8", bytes.NewBuffer(requestData)) + defer resp.Body.Close() + utils.CheckErr(err) + + // read the response body to a variable + bodyBytes, _ := ioutil.ReadAll(resp.Body) + bodyString := string(bodyBytes) + //print raw response body for debugging purposes + fmt.Println("\n\n", bodyString, "\n\n") + + //reset the response body to the original unread state + resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + + + // Step 3 + oR := new(jsonResponse) + json.NewDecoder(resp.Body).Decode(oR) +*/ From 229d3753f4df2650737f710649317f89d6381abf Mon Sep 17 00:00:00 2001 From: Ashvin Deodhar Date: Wed, 30 Oct 2019 17:08:25 -0700 Subject: [PATCH 02/10] WIP-10-30-1 --- cns/NetworkContainerContract.go | 18 ++----- cns/restserver/restserver.go | 90 +++++++++++++-------------------- 2 files changed, 40 insertions(+), 68 deletions(-) diff --git a/cns/NetworkContainerContract.go b/cns/NetworkContainerContract.go index 2a60a17a8b..7f7dd8384f 100644 --- a/cns/NetworkContainerContract.go +++ b/cns/NetworkContainerContract.go @@ -189,24 +189,16 @@ type NetworkInterface struct { // PublishNetworkContainerRequest specifies request to publish network container via NMAgent. type PublishNetworkContainerRequest struct { - WireSererIP string - AssociatedInterfaceIP string - NetworkID string - NetworkContainerID string - AccessToken string - JoinNetworkURLFmt string - CreateNetworkContainerURLFmt string - JoinNetworkURL string - CreateNetworkContainerURL string - //CreateNetworkContainerRequestBody bytes.Buffer + NetworkID string + NetworkContainerID string + JoinNetworkURL string + CreateNetworkContainerURL string CreateNetworkContainerRequestBody []byte } // PublishNetworkContainerResponse specifies the response to publish network container request. type PublishNetworkContainerResponse struct { - Response Response - //HttpStatusCode int - //HttpResponsePublish http.Response + Response Response PublishStatusCode int PublishResponseBody []byte PublishError error diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index b31c31996f..228c89311e 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -1730,16 +1730,17 @@ func (service *HTTPRestService) deleteHostNCApipaEndpoint(w http.ResponseWriter, // TODO: move this to nmagent specific file func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r *http.Request) { - log.Printf("[Azure-CNS] publishNetworkContainer") + log.Printf("[Azure-CNS] PublishNetworkContainer") var ( - err error - req cns.PublishNetworkContainerRequest - returnCode int - returnMessage string - returnHttpStatusCode int - //joinRespStatusCode int - responsePublish *http.Response + err error + req cns.PublishNetworkContainerRequest + returnCode int + returnMessage string + responsePublish *http.Response + publishStatusCode int + publishResponseBody []byte + publishError error ) err = service.Listener.Decode(w, r, &req) @@ -1751,41 +1752,44 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r switch r.Method { case "POST": // Publish Network - responsePublish, err = nmagentclient.PublishNetwork(req.NetworkID, req.JoinNetworkURLFmt, req.WireSererIP, req.JoinNetworkURL) - //TODO: check if responsePublish is nil - if responsePublish.StatusCode != http.StatusOK || err != nil { + responsePublish, err = nmagentclient.PublishNetwork( + req.NetworkID, + req.JoinNetworkURL) + + if err != nil || responsePublish.StatusCode != http.StatusOK { returnMessage = fmt.Sprintf("Failed to publish Network: %s, HttpStatusCode: %d, Error: %v", - req.NetworkID, returnHttpStatusCode, err) + req.NetworkID, responsePublish.StatusCode, err) returnCode = NetworkPublishFailed + log.Errorf("[Azure-CNS] %s", returnMessage) } else { // Publish Network Container responsePublish, err = nmagentclient.PublishNetworkContainer( req.NetworkContainerID, - req.CreateNetworkContainerURLFmt, - req.WireSererIP, - req.AssociatedInterfaceIP, - req.AccessToken, req.CreateNetworkContainerURL, req.CreateNetworkContainerRequestBody) - // Below err handling is not needed - /* - if err != nil { - returnMessage = fmt.Sprintf("Failed to publish Network Container: %s, HttpStatusCode: %d, Error: %v", - req.NetworkContainerID, returnHttpStatusCode, err) - returnCode = NetworkContainerPublishFailed - } - */ + if err != nil || responsePublish.StatusCode != http.StatusOK { + returnMessage = fmt.Sprintf("Failed to publish Network Container: %s, HttpStatusCode: %d, Error: %v", + req.NetworkContainerID, responsePublish.StatusCode, err) + returnCode = NetworkContainerPublishFailed + log.Errorf("[Azure-CNS] %s", returnMessage) + } } default: returnMessage = "PublishNetworkContainer API expects a POST" returnCode = UnsupportedVerb } - publishResponseBody, errParse := ioutil.ReadAll(responsePublish.Body) - if errParse != nil { - log.Printf("[Azure-CNS] tempdebug: failed to parse the body") - returnCode = UnexpectedError - returnMessage = "Failed to parse the publish body" + publishError = err + if responsePublish != nil { + publishStatusCode = responsePublish.StatusCode + publishResponseBody, err = ioutil.ReadAll(responsePublish.Body) + if err != nil { + returnMessage = fmt.Sprintf("Failed to parse the publish body. Error: %v", err) + returnCode = UnexpectedError + log.Errorf("[Azure-CNS] %s", returnMessage) + } + + responsePublish.Body.Close() } response := cns.PublishNetworkContainerResponse{ @@ -1793,35 +1797,11 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r ReturnCode: returnCode, Message: returnMessage, }, - //HttpStatusCode: returnHttpStatusCode, - // TODO: How to make sure responsePublish is not nil? - //HttpResponsePublish: *responsePublish, - PublishStatusCode: responsePublish.StatusCode, + PublishStatusCode: publishStatusCode, PublishResponseBody: publishResponseBody, - PublishError: err, + PublishError: publishError, } err = service.Listener.Encode(w, &response) log.Response(service.Name, response, response.Response.ReturnCode, ReturnCodeToString(response.Response.ReturnCode), err) } - -/* -//perform http request - resp, err := http.Post(url, "application/json; charset=utf-8", bytes.NewBuffer(requestData)) - defer resp.Body.Close() - utils.CheckErr(err) - - // read the response body to a variable - bodyBytes, _ := ioutil.ReadAll(resp.Body) - bodyString := string(bodyBytes) - //print raw response body for debugging purposes - fmt.Println("\n\n", bodyString, "\n\n") - - //reset the response body to the original unread state - resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) - - - // Step 3 - oR := new(jsonResponse) - json.NewDecoder(resp.Body).Decode(oR) -*/ From 52d38f37d70db7767d87ce942ad6838b4fee4715 Mon Sep 17 00:00:00 2001 From: Ashvin Deodhar Date: Thu, 31 Oct 2019 06:16:12 -0700 Subject: [PATCH 03/10] WIP-10-31-1 --- cns/NetworkContainerContract.go | 2 +- cns/restserver/api.go | 2 +- cns/restserver/restserver.go | 82 +++++++++++++++++++++++---------- 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/cns/NetworkContainerContract.go b/cns/NetworkContainerContract.go index 7f7dd8384f..0a0519ebd1 100644 --- a/cns/NetworkContainerContract.go +++ b/cns/NetworkContainerContract.go @@ -199,7 +199,7 @@ type PublishNetworkContainerRequest struct { // PublishNetworkContainerResponse specifies the response to publish network container request. type PublishNetworkContainerResponse struct { Response Response + PublishError error PublishStatusCode int PublishResponseBody []byte - PublishError error } diff --git a/cns/restserver/api.go b/cns/restserver/api.go index 567e849688..ca701c2689 100644 --- a/cns/restserver/api.go +++ b/cns/restserver/api.go @@ -24,7 +24,7 @@ const ( UnsupportedVerb = 21 UnsupportedNetworkContainerType = 22 InvalidRequest = 23 - NetworkPublishFailed = 24 + NetworkJoinFailed = 24 NetworkContainerPublishFailed = 25 UnexpectedError = 99 ) diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index 228c89311e..0c4135b593 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -68,6 +68,7 @@ type httpRestServiceState struct { ContainerStatus map[string]containerstatus // NetworkContainerID is key. Networks map[string]*networkInfo TimeStamp time.Time + joinedNetworks map[string]struct{} } type networkInfo struct { @@ -1728,7 +1729,27 @@ func (service *HTTPRestService) deleteHostNCApipaEndpoint(w http.ResponseWriter, log.Response(service.Name, response, response.Response.ReturnCode, ReturnCodeToString(response.Response.ReturnCode), err) } -// TODO: move this to nmagent specific file +// Check if the network is joined +func (service *HTTPRestService) isNetworkJoined(networkID string) bool { + service.lock.Lock() + defer service.lock.Unlock() + if service.state.joinedNetworks == nil { + service.state.joinedNetworks = make(map[string]struct{}) + } + + _, exists := service.state.joinedNetworks[networkID] + + return exists +} + +// Set the network as joined +func (service *HTTPRestService) setNetworkStateJoined(networkID string) { + service.lock.Lock() + defer service.lock.Unlock() + service.state.joinedNetworks[networkID] = struct{}{} +} + +// Publish Network Container by calling nmagent func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r *http.Request) { log.Printf("[Azure-CNS] PublishNetworkContainer") @@ -1741,6 +1762,7 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r publishStatusCode int publishResponseBody []byte publishError error + isNetworkJoined bool ) err = service.Listener.Decode(w, r, &req) @@ -1751,28 +1773,48 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r switch r.Method { case "POST": - // Publish Network - responsePublish, err = nmagentclient.PublishNetwork( - req.NetworkID, - req.JoinNetworkURL) - - if err != nil || responsePublish.StatusCode != http.StatusOK { - returnMessage = fmt.Sprintf("Failed to publish Network: %s, HttpStatusCode: %d, Error: %v", - req.NetworkID, responsePublish.StatusCode, err) - returnCode = NetworkPublishFailed - log.Errorf("[Azure-CNS] %s", returnMessage) - } else { + // Join Network if not joined already + isNetworkJoined = service.isNetworkJoined(req.NetworkID) + if !isNetworkJoined { + responsePublish, err = nmagentclient.JoinNetwork( + req.NetworkID, + req.JoinNetworkURL) + + if err != nil || responsePublish.StatusCode != http.StatusOK { + //TODO: if err != nil - responsePublish will be nil and below will panic + returnMessage = fmt.Sprintf("Failed to join network: %s", req.NetworkID) + returnCode = NetworkJoinFailed + log.Errorf("[Azure-CNS] %s", returnMessage) + } else { + // Network joined successfully + service.setNetworkStateJoined(req.NetworkID) + isNetworkJoined = true + log.Printf("[Azure-CNS] setNetworkStateJoined") + } + } + + if isNetworkJoined { // Publish Network Container responsePublish, err = nmagentclient.PublishNetworkContainer( req.NetworkContainerID, req.CreateNetworkContainerURL, req.CreateNetworkContainerRequestBody) - if err != nil || responsePublish.StatusCode != http.StatusOK { - returnMessage = fmt.Sprintf("Failed to publish Network Container: %s, HttpStatusCode: %d, Error: %v", - req.NetworkContainerID, responsePublish.StatusCode, err) + if err != nil || responsePublish.StatusCode != http.StatusOK { //TODO: test this in goplayground + returnMessage = fmt.Sprintf("Failed to publish Network Container: %s", req.NetworkContainerID) returnCode = NetworkContainerPublishFailed log.Errorf("[Azure-CNS] %s", returnMessage) } + + if responsePublish != nil { + publishResponseBody, err = ioutil.ReadAll(responsePublish.Body) + if err != nil { + returnMessage = fmt.Sprintf("Failed to parse the publish body. Error: %v", err) + returnCode = UnexpectedError + log.Errorf("[Azure-CNS] %s", returnMessage) + } + + responsePublish.Body.Close() + } } default: returnMessage = "PublishNetworkContainer API expects a POST" @@ -1782,14 +1824,6 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r publishError = err if responsePublish != nil { publishStatusCode = responsePublish.StatusCode - publishResponseBody, err = ioutil.ReadAll(responsePublish.Body) - if err != nil { - returnMessage = fmt.Sprintf("Failed to parse the publish body. Error: %v", err) - returnCode = UnexpectedError - log.Errorf("[Azure-CNS] %s", returnMessage) - } - - responsePublish.Body.Close() } response := cns.PublishNetworkContainerResponse{ @@ -1797,9 +1831,9 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r ReturnCode: returnCode, Message: returnMessage, }, + PublishError: publishError, PublishStatusCode: publishStatusCode, PublishResponseBody: publishResponseBody, - PublishError: publishError, } err = service.Listener.Encode(w, &response) From e4786d2909fdd1531717a126a1368d486225d04d Mon Sep 17 00:00:00 2001 From: Ashvin Deodhar Date: Thu, 31 Oct 2019 09:13:31 -0700 Subject: [PATCH 04/10] WIP-10-31-2 --- cns/NetworkContainerContract.go | 16 +++++ cns/restserver/api.go | 1 + cns/restserver/restserver.go | 100 ++++++++++++++++++++++++++++++-- 3 files changed, 112 insertions(+), 5 deletions(-) diff --git a/cns/NetworkContainerContract.go b/cns/NetworkContainerContract.go index 0a0519ebd1..4cdd2a8c50 100644 --- a/cns/NetworkContainerContract.go +++ b/cns/NetworkContainerContract.go @@ -203,3 +203,19 @@ type PublishNetworkContainerResponse struct { PublishStatusCode int PublishResponseBody []byte } + +// UnpublishNetworkContainerRequest specifies request to unpublish network container via NMAgent. +type UnpublishNetworkContainerRequest struct { + NetworkID string + NetworkContainerID string + JoinNetworkURL string + DeleteNetworkContainerURL string +} + +// UnpublishNetworkContainerResponse specifies the response to unpublish network container request. +type UnpublishNetworkContainerResponse struct { + Response Response + UnpublishError error + UnpublishStatusCode int + UnpublishResponseBody []byte +} diff --git a/cns/restserver/api.go b/cns/restserver/api.go index ca701c2689..586dd1c2bc 100644 --- a/cns/restserver/api.go +++ b/cns/restserver/api.go @@ -26,6 +26,7 @@ const ( InvalidRequest = 23 NetworkJoinFailed = 24 NetworkContainerPublishFailed = 25 + NetworkContainerUnpublishFailed = 26 UnexpectedError = 99 ) diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index 0c4135b593..5293d8872c 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -165,7 +165,7 @@ func (service *HTTPRestService) Start(config *common.ServiceConfig) error { listener.AddHandler(cns.CreateHostNCApipaEndpointPath, service.createHostNCApipaEndpoint) listener.AddHandler(cns.DeleteHostNCApipaEndpointPath, service.deleteHostNCApipaEndpoint) listener.AddHandler(cns.PublishNetworkContainer, service.publishNetworkContainer) - //listener.AddHandler(cns.UnpublishNetworkContainer, service.unpublishNetworkContainer) + listener.AddHandler(cns.UnpublishNetworkContainer, service.unpublishNetworkContainer) // handlers for v0.2 listener.AddHandler(cns.V2Prefix+cns.SetEnvironmentPath, service.setEnvironment) @@ -1781,7 +1781,6 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r req.JoinNetworkURL) if err != nil || responsePublish.StatusCode != http.StatusOK { - //TODO: if err != nil - responsePublish will be nil and below will panic returnMessage = fmt.Sprintf("Failed to join network: %s", req.NetworkID) returnCode = NetworkJoinFailed log.Errorf("[Azure-CNS] %s", returnMessage) @@ -1806,9 +1805,10 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r } if responsePublish != nil { - publishResponseBody, err = ioutil.ReadAll(responsePublish.Body) - if err != nil { - returnMessage = fmt.Sprintf("Failed to parse the publish body. Error: %v", err) + var errParse error + publishResponseBody, errParse = ioutil.ReadAll(responsePublish.Body) + if errParse != nil { + returnMessage = fmt.Sprintf("Failed to parse the publish body. Error: %v", errParse) returnCode = UnexpectedError log.Errorf("[Azure-CNS] %s", returnMessage) } @@ -1839,3 +1839,93 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r err = service.Listener.Encode(w, &response) log.Response(service.Name, response, response.Response.ReturnCode, ReturnCodeToString(response.Response.ReturnCode), err) } + +// Unpublish Network Container by calling nmagent +func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter, r *http.Request) { + log.Printf("[Azure-CNS] UnpublishNetworkContainer") + + var ( + err error + req cns.UnpublishNetworkContainerRequest + returnCode int + returnMessage string + responseUnpublish *http.Response + unpublishStatusCode int + unpublishResponseBody []byte + unpublishError error + isNetworkJoined bool + ) + + err = service.Listener.Decode(w, r, &req) + log.Request(service.Name, &req, err) + if err != nil { + return + } + + switch r.Method { + case "POST": + // Join Network if not joined already + isNetworkJoined = service.isNetworkJoined(req.NetworkID) + if !isNetworkJoined { + responseUnpublish, err = nmagentclient.JoinNetwork( + req.NetworkID, + req.JoinNetworkURL) + + if err != nil || responseUnpublish.StatusCode != http.StatusOK { + returnMessage = fmt.Sprintf("Failed to join network: %s", req.NetworkID) + returnCode = NetworkJoinFailed + log.Errorf("[Azure-CNS] %s", returnMessage) + } else { + // Network joined successfully + service.setNetworkStateJoined(req.NetworkID) + isNetworkJoined = true + log.Printf("[Azure-CNS] setNetworkStateJoined") + } + } + + if isNetworkJoined { + // Unpublish Network Container + responseUnpublish, err = nmagentclient.UnpublishNetworkContainer( + req.NetworkContainerID, + req.DeleteNetworkContainerURL) + if err != nil || responseUnpublish.StatusCode != http.StatusOK { //TODO: test this in goplayground + returnMessage = fmt.Sprintf("Failed to unpublish Network Container: %s", req.NetworkContainerID) + returnCode = NetworkContainerUnpublishFailed + log.Errorf("[Azure-CNS] %s", returnMessage) + } + + if responseUnpublish != nil { + var errParse error + unpublishResponseBody, errParse = ioutil.ReadAll(responseUnpublish.Body) + if errParse != nil { + returnMessage = fmt.Sprintf("Failed to parse the unpublish body. Error: %v", errParse) + returnCode = UnexpectedError + log.Errorf("[Azure-CNS] %s", returnMessage) + } + + responseUnpublish.Body.Close() + } + } + default: + returnMessage = "UnpublishNetworkContainer API expects a POST" + returnCode = UnsupportedVerb + } + + unpublishError = err + if responseUnpublish != nil { + unpublishStatusCode = responseUnpublish.StatusCode + } + + response := cns.UnpublishNetworkContainerResponse{ + Response: cns.Response{ + ReturnCode: returnCode, + Message: returnMessage, + }, + UnpublishError: unpublishError, + UnpublishStatusCode: unpublishStatusCode, + UnpublishResponseBody: unpublishResponseBody, + } + + err = service.Listener.Encode(w, &response) + log.Response(service.Name, response, response.Response.ReturnCode, ReturnCodeToString(response.Response.ReturnCode), err) +} From 729e2117cca44dba59e3b06d428ff07ec5ccdf87 Mon Sep 17 00:00:00 2001 From: Ashvin Deodhar Date: Thu, 31 Oct 2019 15:03:07 -0700 Subject: [PATCH 05/10] WIP-10-31-3 --- cns/nmagentclient/nmagentclient.go | 84 ++++++++++++++++++++++++++++ cns/restserver/restserver.go | 88 ++++++++++++++++-------------- 2 files changed, 130 insertions(+), 42 deletions(-) create mode 100644 cns/nmagentclient/nmagentclient.go diff --git a/cns/nmagentclient/nmagentclient.go b/cns/nmagentclient/nmagentclient.go new file mode 100644 index 0000000000..b056ddd948 --- /dev/null +++ b/cns/nmagentclient/nmagentclient.go @@ -0,0 +1,84 @@ +package nmagentclient + +import ( + "bytes" + "encoding/json" + "net" + "net/http" + "time" + + "github.com/Azure/azure-container-networking/log" +) + +const ( + // Http connection timeout duration in milliseconds + connectionTimeoutDurationMs = 5000 + // Response header timeout duration in milliseconds + responseHeaderTimeoutDurationMs = 120000 +) + +// Creating http client object to be reused instead of creating one every time. +// This helps make use of the cached tcp connections. +// Clients are safe for concurrent use by multiple goroutines. +var httpClient = &http.Client{ + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: time.Duration(connectionTimeoutDurationMs) * time.Millisecond, + }).DialContext, + ResponseHeaderTimeout: time.Duration(responseHeaderTimeoutDurationMs) * time.Millisecond, + }, +} + +// JoinNetwork joins the given network +func JoinNetwork( + networkID string, + joinNetworkURL string) (*http.Response, error) { + log.Printf("[NMAgentClient] JoinNetwork: %s", networkID) + + // Empty body is required as wireserver cannot handle a post without the body. + var body bytes.Buffer + json.NewEncoder(&body).Encode("") + response, err := httpClient.Post(joinNetworkURL, "application/json", &body) + + if err == nil { + defer response.Body.Close() + } + + log.Printf("[NMAgentClient][Response] Join network: %s. Response: %+v. Error: %v", + networkID, response, err) + + return response, err +} + +// PublishNetworkContainer publishes given network container +func PublishNetworkContainer( + networkContainerID string, + createNetworkContainerURL string, + requestBodyData []byte) (*http.Response, error) { + log.Printf("[NMAgentClient] PublishNetworkContainer NC: %s", networkContainerID) + + requestBody := bytes.NewBuffer(requestBodyData) + response, err := httpClient.Post(createNetworkContainerURL, "application/json", requestBody) + + log.Printf("[NMAgentClient][Response] Publish NC: %s. Response: %+v. Error: %v", + networkContainerID, response, err) + + return response, err +} + +// UnpublishNetworkContainer unpublishes given network container +func UnpublishNetworkContainer( + networkContainerID string, + deleteNetworkContainerURL string) (*http.Response, error) { + log.Printf("[NMAgentClient] UnpublishNetworkContainer NC: %s", networkContainerID) + + // Empty body is required as wireserver cannot handle a post without the body. + var body bytes.Buffer + json.NewEncoder(&body).Encode("") + response, err := httpClient.Post(deleteNetworkContainerURL, "application/json", &body) + + log.Printf("[NMAgentClient][Response] Unpublish NC: %s. Response: %+v. Error: %v", + networkContainerID, response, err) + + return response, err +} diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index 5293d8872c..0536777ce4 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -1749,6 +1749,26 @@ func (service *HTTPRestService) setNetworkStateJoined(networkID string) { service.state.joinedNetworks[networkID] = struct{}{} } +// Join Network by calling nmagent +func (service *HTTPRestService) joinNetwork( + networkID string, + joinNetworkURL string) (*http.Response, error, error) { + var err error + unpublishResponse, unPublishErr := nmagentclient.JoinNetwork( + networkID, + joinNetworkURL) + + if unPublishErr == nil && unpublishResponse.StatusCode == http.StatusOK { + // Network joined successfully + service.setNetworkStateJoined(networkID) + log.Printf("[Azure-CNS] setNetworkStateJoined for network: %s", networkID) + } else { + err = fmt.Errorf("Failed to join network: %s", networkID) + } + + return unpublishResponse, unPublishErr, err +} + // Publish Network Container by calling nmagent func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r *http.Request) { log.Printf("[Azure-CNS] PublishNetworkContainer") @@ -1758,7 +1778,7 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r req cns.PublishNetworkContainerRequest returnCode int returnMessage string - responsePublish *http.Response + publishResponse *http.Response publishStatusCode int publishResponseBody []byte publishError error @@ -1776,44 +1796,37 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r // Join Network if not joined already isNetworkJoined = service.isNetworkJoined(req.NetworkID) if !isNetworkJoined { - responsePublish, err = nmagentclient.JoinNetwork( - req.NetworkID, - req.JoinNetworkURL) - - if err != nil || responsePublish.StatusCode != http.StatusOK { - returnMessage = fmt.Sprintf("Failed to join network: %s", req.NetworkID) - returnCode = NetworkJoinFailed - log.Errorf("[Azure-CNS] %s", returnMessage) - } else { - // Network joined successfully - service.setNetworkStateJoined(req.NetworkID) + publishResponse, publishError, err = service.joinNetwork(req.NetworkID, req.JoinNetworkURL) + if err == nil { isNetworkJoined = true - log.Printf("[Azure-CNS] setNetworkStateJoined") + } else { + returnMessage = err.Error() + returnCode = NetworkJoinFailed } } if isNetworkJoined { // Publish Network Container - responsePublish, err = nmagentclient.PublishNetworkContainer( + publishResponse, publishError = nmagentclient.PublishNetworkContainer( req.NetworkContainerID, req.CreateNetworkContainerURL, req.CreateNetworkContainerRequestBody) - if err != nil || responsePublish.StatusCode != http.StatusOK { //TODO: test this in goplayground + if publishError != nil || publishResponse.StatusCode != http.StatusOK { returnMessage = fmt.Sprintf("Failed to publish Network Container: %s", req.NetworkContainerID) returnCode = NetworkContainerPublishFailed log.Errorf("[Azure-CNS] %s", returnMessage) } - if responsePublish != nil { + if publishResponse != nil { var errParse error - publishResponseBody, errParse = ioutil.ReadAll(responsePublish.Body) + publishResponseBody, errParse = ioutil.ReadAll(publishResponse.Body) if errParse != nil { returnMessage = fmt.Sprintf("Failed to parse the publish body. Error: %v", errParse) returnCode = UnexpectedError log.Errorf("[Azure-CNS] %s", returnMessage) } - responsePublish.Body.Close() + publishResponse.Body.Close() } } default: @@ -1821,9 +1834,8 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r returnCode = UnsupportedVerb } - publishError = err - if responsePublish != nil { - publishStatusCode = responsePublish.StatusCode + if publishResponse != nil { + publishStatusCode = publishResponse.StatusCode } response := cns.PublishNetworkContainerResponse{ @@ -1849,7 +1861,7 @@ func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter, req cns.UnpublishNetworkContainerRequest returnCode int returnMessage string - responseUnpublish *http.Response + unpublishResponse *http.Response unpublishStatusCode int unpublishResponseBody []byte unpublishError error @@ -1867,43 +1879,36 @@ func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter, // Join Network if not joined already isNetworkJoined = service.isNetworkJoined(req.NetworkID) if !isNetworkJoined { - responseUnpublish, err = nmagentclient.JoinNetwork( - req.NetworkID, - req.JoinNetworkURL) - - if err != nil || responseUnpublish.StatusCode != http.StatusOK { - returnMessage = fmt.Sprintf("Failed to join network: %s", req.NetworkID) - returnCode = NetworkJoinFailed - log.Errorf("[Azure-CNS] %s", returnMessage) - } else { - // Network joined successfully - service.setNetworkStateJoined(req.NetworkID) + unpublishResponse, unpublishError, err = service.joinNetwork(req.NetworkID, req.JoinNetworkURL) + if err == nil { isNetworkJoined = true - log.Printf("[Azure-CNS] setNetworkStateJoined") + } else { + returnMessage = err.Error() + returnCode = NetworkJoinFailed } } if isNetworkJoined { // Unpublish Network Container - responseUnpublish, err = nmagentclient.UnpublishNetworkContainer( + unpublishResponse, unpublishError = nmagentclient.UnpublishNetworkContainer( req.NetworkContainerID, req.DeleteNetworkContainerURL) - if err != nil || responseUnpublish.StatusCode != http.StatusOK { //TODO: test this in goplayground + if unpublishError != nil || unpublishResponse.StatusCode != http.StatusOK { returnMessage = fmt.Sprintf("Failed to unpublish Network Container: %s", req.NetworkContainerID) returnCode = NetworkContainerUnpublishFailed log.Errorf("[Azure-CNS] %s", returnMessage) } - if responseUnpublish != nil { + if unpublishResponse != nil { var errParse error - unpublishResponseBody, errParse = ioutil.ReadAll(responseUnpublish.Body) + unpublishResponseBody, errParse = ioutil.ReadAll(unpublishResponse.Body) if errParse != nil { returnMessage = fmt.Sprintf("Failed to parse the unpublish body. Error: %v", errParse) returnCode = UnexpectedError log.Errorf("[Azure-CNS] %s", returnMessage) } - responseUnpublish.Body.Close() + unpublishResponse.Body.Close() } } default: @@ -1911,9 +1916,8 @@ func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter, returnCode = UnsupportedVerb } - unpublishError = err - if responseUnpublish != nil { - unpublishStatusCode = responseUnpublish.StatusCode + if unpublishResponse != nil { + unpublishStatusCode = unpublishResponse.StatusCode } response := cns.UnpublishNetworkContainerResponse{ From b951153ae0755c1993364fcebc19a4cfb2dbac72 Mon Sep 17 00:00:00 2001 From: Ashvin Deodhar Date: Thu, 31 Oct 2019 15:38:08 -0700 Subject: [PATCH 06/10] Fix the variable name --- cns/restserver/restserver.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index 0536777ce4..297b59e965 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -1754,11 +1754,11 @@ func (service *HTTPRestService) joinNetwork( networkID string, joinNetworkURL string) (*http.Response, error, error) { var err error - unpublishResponse, unPublishErr := nmagentclient.JoinNetwork( + unpublishResponse, unpublishErr := nmagentclient.JoinNetwork( networkID, joinNetworkURL) - if unPublishErr == nil && unpublishResponse.StatusCode == http.StatusOK { + if unpublishErr == nil && unpublishResponse.StatusCode == http.StatusOK { // Network joined successfully service.setNetworkStateJoined(networkID) log.Printf("[Azure-CNS] setNetworkStateJoined for network: %s", networkID) @@ -1766,7 +1766,7 @@ func (service *HTTPRestService) joinNetwork( err = fmt.Errorf("Failed to join network: %s", networkID) } - return unpublishResponse, unPublishErr, err + return unpublishResponse, unpublishErr, err } // Publish Network Container by calling nmagent From 8572fa239e891cbc35fb5ee3e734891aa68c60a1 Mon Sep 17 00:00:00 2001 From: Ashvin Deodhar Date: Mon, 4 Nov 2019 14:10:47 -0800 Subject: [PATCH 07/10] WIP-11-04-1 --- cns/NetworkContainerContract.go | 4 +-- cns/nmagentclient/nmagentclient.go | 2 +- cns/restserver/restserver.go | 42 ++++++++++++++++++------------ 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/cns/NetworkContainerContract.go b/cns/NetworkContainerContract.go index 4cdd2a8c50..a328f95a82 100644 --- a/cns/NetworkContainerContract.go +++ b/cns/NetworkContainerContract.go @@ -199,7 +199,7 @@ type PublishNetworkContainerRequest struct { // PublishNetworkContainerResponse specifies the response to publish network container request. type PublishNetworkContainerResponse struct { Response Response - PublishError error + PublishErrorStr string PublishStatusCode int PublishResponseBody []byte } @@ -215,7 +215,7 @@ type UnpublishNetworkContainerRequest struct { // UnpublishNetworkContainerResponse specifies the response to unpublish network container request. type UnpublishNetworkContainerResponse struct { Response Response - UnpublishError error + UnpublishErrorStr string UnpublishStatusCode int UnpublishResponseBody []byte } diff --git a/cns/nmagentclient/nmagentclient.go b/cns/nmagentclient/nmagentclient.go index b056ddd948..5fa1d66e56 100644 --- a/cns/nmagentclient/nmagentclient.go +++ b/cns/nmagentclient/nmagentclient.go @@ -40,7 +40,7 @@ func JoinNetwork( json.NewEncoder(&body).Encode("") response, err := httpClient.Post(joinNetworkURL, "application/json", &body) - if err == nil { + if err == nil && response.StatusCode == http.StatusOK { defer response.Body.Close() } diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index 297b59e965..a54d963161 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -1754,11 +1754,11 @@ func (service *HTTPRestService) joinNetwork( networkID string, joinNetworkURL string) (*http.Response, error, error) { var err error - unpublishResponse, unpublishErr := nmagentclient.JoinNetwork( + joinResponse, joinErr := nmagentclient.JoinNetwork( networkID, joinNetworkURL) - if unpublishErr == nil && unpublishResponse.StatusCode == http.StatusOK { + if joinErr == nil && joinResponse.StatusCode == http.StatusOK { // Network joined successfully service.setNetworkStateJoined(networkID) log.Printf("[Azure-CNS] setNetworkStateJoined for network: %s", networkID) @@ -1766,7 +1766,7 @@ func (service *HTTPRestService) joinNetwork( err = fmt.Errorf("Failed to join network: %s", networkID) } - return unpublishResponse, unpublishErr, err + return joinResponse, joinErr, err } // Publish Network Container by calling nmagent @@ -1782,6 +1782,7 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r publishStatusCode int publishResponseBody []byte publishError error + publishErrorStr string isNetworkJoined bool ) @@ -1816,26 +1817,28 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r returnCode = NetworkContainerPublishFailed log.Errorf("[Azure-CNS] %s", returnMessage) } - - if publishResponse != nil { - var errParse error - publishResponseBody, errParse = ioutil.ReadAll(publishResponse.Body) - if errParse != nil { - returnMessage = fmt.Sprintf("Failed to parse the publish body. Error: %v", errParse) - returnCode = UnexpectedError - log.Errorf("[Azure-CNS] %s", returnMessage) - } - - publishResponse.Body.Close() - } } default: returnMessage = "PublishNetworkContainer API expects a POST" returnCode = UnsupportedVerb } + if publishError != nil { + publishErrorStr = publishError.Error() + } + if publishResponse != nil { publishStatusCode = publishResponse.StatusCode + + var errParse error + publishResponseBody, errParse = ioutil.ReadAll(publishResponse.Body) + if errParse != nil { + returnMessage = fmt.Sprintf("Failed to parse the publish body. Error: %v", errParse) + returnCode = UnexpectedError + log.Errorf("[Azure-CNS] %s", returnMessage) + } + + publishResponse.Body.Close() } response := cns.PublishNetworkContainerResponse{ @@ -1843,7 +1846,7 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r ReturnCode: returnCode, Message: returnMessage, }, - PublishError: publishError, + PublishErrorStr: publishErrorStr, PublishStatusCode: publishStatusCode, PublishResponseBody: publishResponseBody, } @@ -1865,6 +1868,7 @@ func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter, unpublishStatusCode int unpublishResponseBody []byte unpublishError error + unpublishErrorStr string isNetworkJoined bool ) @@ -1916,6 +1920,10 @@ func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter, returnCode = UnsupportedVerb } + if unpublishError != nil { + unpublishErrorStr = unpublishError.Error() + } + if unpublishResponse != nil { unpublishStatusCode = unpublishResponse.StatusCode } @@ -1925,7 +1933,7 @@ func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter, ReturnCode: returnCode, Message: returnMessage, }, - UnpublishError: unpublishError, + UnpublishErrorStr: unpublishErrorStr, UnpublishStatusCode: unpublishStatusCode, UnpublishResponseBody: unpublishResponseBody, } From d1a5f3584100df4da8e95cd16676e024d5970a38 Mon Sep 17 00:00:00 2001 From: Ashvin Deodhar Date: Thu, 7 Nov 2019 09:39:16 -0800 Subject: [PATCH 08/10] Address review comments --- cns/nmagentclient/nmagentclient.go | 28 ++++------------------------ cns/restserver/restserver.go | 22 ++++++++++++++++++---- cns/service/main.go | 18 ++++++++++++++++++ common/config.go | 8 ++++++++ common/utils.go | 30 ++++++++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 28 deletions(-) diff --git a/cns/nmagentclient/nmagentclient.go b/cns/nmagentclient/nmagentclient.go index 5fa1d66e56..9dcd7aa6ff 100644 --- a/cns/nmagentclient/nmagentclient.go +++ b/cns/nmagentclient/nmagentclient.go @@ -3,32 +3,12 @@ package nmagentclient import ( "bytes" "encoding/json" - "net" "net/http" - "time" + "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/log" ) -const ( - // Http connection timeout duration in milliseconds - connectionTimeoutDurationMs = 5000 - // Response header timeout duration in milliseconds - responseHeaderTimeoutDurationMs = 120000 -) - -// Creating http client object to be reused instead of creating one every time. -// This helps make use of the cached tcp connections. -// Clients are safe for concurrent use by multiple goroutines. -var httpClient = &http.Client{ - Transport: &http.Transport{ - DialContext: (&net.Dialer{ - Timeout: time.Duration(connectionTimeoutDurationMs) * time.Millisecond, - }).DialContext, - ResponseHeaderTimeout: time.Duration(responseHeaderTimeoutDurationMs) * time.Millisecond, - }, -} - // JoinNetwork joins the given network func JoinNetwork( networkID string, @@ -38,7 +18,7 @@ func JoinNetwork( // Empty body is required as wireserver cannot handle a post without the body. var body bytes.Buffer json.NewEncoder(&body).Encode("") - response, err := httpClient.Post(joinNetworkURL, "application/json", &body) + response, err := common.GetHttpClient().Post(joinNetworkURL, "application/json", &body) if err == nil && response.StatusCode == http.StatusOK { defer response.Body.Close() @@ -58,7 +38,7 @@ func PublishNetworkContainer( log.Printf("[NMAgentClient] PublishNetworkContainer NC: %s", networkContainerID) requestBody := bytes.NewBuffer(requestBodyData) - response, err := httpClient.Post(createNetworkContainerURL, "application/json", requestBody) + response, err := common.GetHttpClient().Post(createNetworkContainerURL, "application/json", requestBody) log.Printf("[NMAgentClient][Response] Publish NC: %s. Response: %+v. Error: %v", networkContainerID, response, err) @@ -75,7 +55,7 @@ func UnpublishNetworkContainer( // Empty body is required as wireserver cannot handle a post without the body. var body bytes.Buffer json.NewEncoder(&body).Encode("") - response, err := httpClient.Post(deleteNetworkContainerURL, "application/json", &body) + response, err := common.GetHttpClient().Post(deleteNetworkContainerURL, "application/json", &body) log.Printf("[NMAgentClient][Response] Unpublish NC: %s. Response: %+v. Error: %v", networkContainerID, response, err) diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index a54d963161..578eed6780 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -28,12 +28,19 @@ import ( "github.com/Azure/azure-container-networking/store" ) +var ( + // Named Lock for accessing different states in httpRestServiceState + namedLock = acn.InitNamedLock() +) + const ( // Key against which CNS state is persisted. storeKey = "ContainerNetworkService" swiftAPIVersion = "1" attach = "Attach" detach = "Detach" + // Rest service state identifier for named lock + stateJoinedNetworks = "JoinedNetworks" ) // HTTPRestService represents http listener for CNS - Container Networking Service. @@ -190,6 +197,11 @@ func (service *HTTPRestService) Start(config *common.ServiceConfig) error { listener.AddHandler(cns.V2Prefix+cns.CreateHostNCApipaEndpointPath, service.createHostNCApipaEndpoint) listener.AddHandler(cns.V2Prefix+cns.DeleteHostNCApipaEndpointPath, service.deleteHostNCApipaEndpoint) + // Initialize HTTP client to be reused in CNS + connectionTimeout, _ := service.GetOption(acn.OptHttpConnectionTimeout).(int) + responseHeaderTimeout, _ := service.GetOption(acn.OptHttpResponseHeaderTimeout).(int) + acn.InitHttpClient(connectionTimeout, responseHeaderTimeout) + log.Printf("[Azure CNS] Listening.") return nil } @@ -1731,8 +1743,9 @@ func (service *HTTPRestService) deleteHostNCApipaEndpoint(w http.ResponseWriter, // Check if the network is joined func (service *HTTPRestService) isNetworkJoined(networkID string) bool { - service.lock.Lock() - defer service.lock.Unlock() + namedLock.LockAcquire(stateJoinedNetworks) + defer namedLock.LockRelease(stateJoinedNetworks) + if service.state.joinedNetworks == nil { service.state.joinedNetworks = make(map[string]struct{}) } @@ -1744,8 +1757,9 @@ func (service *HTTPRestService) isNetworkJoined(networkID string) bool { // Set the network as joined func (service *HTTPRestService) setNetworkStateJoined(networkID string) { - service.lock.Lock() - defer service.lock.Unlock() + namedLock.LockAcquire(stateJoinedNetworks) + defer namedLock.LockRelease(stateJoinedNetworks) + service.state.joinedNetworks[networkID] = struct{}{} } diff --git a/cns/service/main.go b/cns/service/main.go index a7b2af83e6..3609aba856 100644 --- a/cns/service/main.go +++ b/cns/service/main.go @@ -152,6 +152,20 @@ var args = acn.ArgumentList{ Type: "bool", DefaultValue: true, }, + { + Name: acn.OptHttpConnectionTimeout, + Shorthand: acn.OptHttpConnectionTimeoutAlias, + Description: "Set HTTP connection timeout in seconds to be used by http client in CNS", + Type: "int", + DefaultValue: "5", + }, + { + Name: acn.OptHttpResponseHeaderTimeout, + Shorthand: acn.OptHttpResponseHeaderTimeoutAlias, + Description: "Set HTTP response header timeout in seconds to be used by http client in CNS", + Type: "int", + DefaultValue: "120", + }, } // Prints description and version information. @@ -179,6 +193,8 @@ func main() { vers := acn.GetArg(acn.OptVersion).(bool) createDefaultExtNetworkType := acn.GetArg(acn.OptCreateDefaultExtNetworkType).(string) telemetryEnabled := acn.GetArg(acn.OptTelemetry).(bool) + httpConnectionTimeout := acn.GetArg(acn.OptHttpConnectionTimeout).(int) + httpResponseHeaderTimeout := acn.GetArg(acn.OptHttpResponseHeaderTimeout).(int) if vers { printVersion() @@ -240,6 +256,8 @@ func main() { httpRestService.SetOption(acn.OptNetPluginPath, cniPath) httpRestService.SetOption(acn.OptNetPluginConfigFile, cniConfigFile) httpRestService.SetOption(acn.OptCreateDefaultExtNetworkType, createDefaultExtNetworkType) + httpRestService.SetOption(acn.OptHttpConnectionTimeout, httpConnectionTimeout) + httpRestService.SetOption(acn.OptHttpResponseHeaderTimeout, httpResponseHeaderTimeout) // Create default ext network if commandline option is set if len(strings.TrimSpace(createDefaultExtNetworkType)) > 0 { diff --git a/common/config.go b/common/config.go index c1c4b23488..7a272dc73e 100644 --- a/common/config.go +++ b/common/config.go @@ -79,4 +79,12 @@ const ( // Disable Telemetry OptTelemetry = "telemetry" OptTelemetryAlias = "dt" + + // HTTP connection timeout + OptHttpConnectionTimeout = "http-connection-timeout" + OptHttpConnectionTimeoutAlias = "httpcontimeout" + + // HTTP response header timeout + OptHttpResponseHeaderTimeout = "http-response-header-timeout" + OptHttpResponseHeaderTimeoutAlias = "httprespheadertimeout" ) diff --git a/common/utils.go b/common/utils.go index 7c5b7b7b6b..8b08a37ba3 100644 --- a/common/utils.go +++ b/common/utils.go @@ -71,6 +71,36 @@ type metadataWrapper struct { Metadata Metadata `json:"compute"` } +var ( + // Creating http client object to be reused instead of creating one every time. + // This helps make use of the cached tcp connections. + // Clients are safe for concurrent use by multiple goroutines. + httpClient *http.Client +) + +// InitHttpClient initializes the httpClient object +func InitHttpClient( + connectionTimeoutSec int, + responseHeaderTimeoutSec int) *http.Client { + log.Printf("[Utils] Initializing HTTP client with connection timeout: %d, response header timeout: %d", + connectionTimeoutSec, responseHeaderTimeoutSec) + httpClient = &http.Client{ + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: time.Duration(connectionTimeoutSec) * time.Second, + }).DialContext, + ResponseHeaderTimeout: time.Duration(responseHeaderTimeoutSec) * time.Second, + }, + } + + return httpClient +} + +// GetHttpClient returns the singleton httpClient object +func GetHttpClient() *http.Client { + return httpClient +} + // LogNetworkInterfaces logs the host's network interfaces in the default namespace. func LogNetworkInterfaces() { interfaces, err := net.Interfaces() From dd6f2fb178f6f64654374e957dd641d6b748f8c2 Mon Sep 17 00:00:00 2001 From: Ashvin Deodhar Date: Mon, 11 Nov 2019 16:09:00 -0800 Subject: [PATCH 09/10] Add tests for new APIs --- cns/restserver/restserver_test.go | 100 ++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 4 deletions(-) diff --git a/cns/restserver/restserver_test.go b/cns/restserver/restserver_test.go index 98806a21d3..7c1713b929 100644 --- a/cns/restserver/restserver_test.go +++ b/cns/restserver/restserver_test.go @@ -12,6 +12,7 @@ import ( "net/http/httptest" "net/url" "os" + "strings" "testing" "github.com/Azure/azure-container-networking/cns" @@ -66,16 +67,23 @@ var ( } ) +const ( + nmagentEndpoint = "localhost:9000" +) + func getInterfaceInfo(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/xml") output, _ := xml.Marshal(hostQueryResponse) w.Write(output) } -func getContainerInfo(w http.ResponseWriter, r *http.Request) { +func nmagentHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(http.StatusOK) - w.Write([]byte(hostQueryForProgrammedVersionResponse)) + + if strings.Contains(r.RequestURI, "networkContainers") { + w.Write([]byte(hostQueryForProgrammedVersionResponse)) + } } // Wraps the test run with service setup and teardown. @@ -109,7 +117,7 @@ func TestMain(m *testing.M) { mux = service.(*HTTPRestService).Listener.GetMux() // Setup mock nmagent server - u, err := url.Parse("tcp://localhost:9000") + u, err := url.Parse("tcp://" + nmagentEndpoint) if err != nil { fmt.Println(err.Error()) } @@ -120,7 +128,7 @@ func TestMain(m *testing.M) { } nmAgentServer.AddHandler("/getInterface", getInterfaceInfo) - nmAgentServer.AddHandler("machine/plugins/?comp=nmagent&type=NetworkManagement/interfaces/{interface}/networkContainers/{networkContainer}/authenticationToken/{authToken}/api-version/{version}", getContainerInfo) + nmAgentServer.AddHandler("/", nmagentHandler) err = nmAgentServer.Start(make(chan error, 1)) if err != nil { @@ -133,6 +141,7 @@ func TestMain(m *testing.M) { // Cleanup. service.Stop() + nmAgentServer.Stop() os.Exit(exitCode) } @@ -749,3 +758,86 @@ func TestGetNumOfCPUCores(t *testing.T) { fmt.Printf("getNumberOfCPUCores Responded with %+v\n", numOfCoresResponse) } } + +func TestPublishNCViaCNS(t *testing.T) { + fmt.Println("Test: publishNetworkContainer") + + var ( + body bytes.Buffer + resp cns.PublishNetworkContainerResponse + ) + + networkID := "vnet1" + networkContainerID := "ethWebApp" + joinNetworkURL := "http://" + nmagentEndpoint + + "/machine/plugins/?comp=nmagent&type=NetworkManagement/joinedVirtualNetworks/" + networkID + "/api-version/1" + createNetworkContainerURL := "http://" + nmagentEndpoint + + "/machine/plugins/?comp=nmagent&type=NetworkManagement/interfaces/127.0.0.1/networkContainers/" + + networkContainerID + "/authenticationToken/dummyauthtoken/api-version/1" + + publishNCRequest := &cns.PublishNetworkContainerRequest{ + NetworkID: networkID, + NetworkContainerID: networkContainerID, + JoinNetworkURL: joinNetworkURL, + CreateNetworkContainerURL: createNetworkContainerURL, + CreateNetworkContainerRequestBody: make([]byte, 0), + } + + json.NewEncoder(&body).Encode(publishNCRequest) + req, err := http.NewRequest(http.MethodPost, cns.PublishNetworkContainer, &body) + if err != nil { + t.Fatal(err) + } + + w := httptest.NewRecorder() + mux.ServeHTTP(w, req) + + err = decodeResponse(w, &resp) + if err != nil || resp.Response.ReturnCode != 0 { + t.Errorf("PublishNetworkContainer failed with response %+v Err:%+v", resp, err) + t.Fatal(err) + } + + fmt.Printf("PublishNetworkContainer succeded with response %+v, raw:%+v\n", resp, w.Body) +} + +func TestUnpublishNCViaCNS(t *testing.T) { + fmt.Println("Test: unpublishNetworkContainer") + + var ( + body bytes.Buffer + resp cns.UnpublishNetworkContainerResponse + ) + + networkID := "vnet1" + networkContainerID := "ethWebApp" + joinNetworkURL := "http://" + nmagentEndpoint + + "/machine/plugins/?comp=nmagent&type=NetworkManagement/joinedVirtualNetworks/" + networkID + "/api-version/1" + deleteNetworkContainerURL := "http://" + nmagentEndpoint + + "/machine/plugins/?comp=nmagent&type=NetworkManagement/interfaces/127.0.0.1/networkContainers/" + + networkContainerID + "/authenticationToken/dummyauthtoken/api-version/1/method/DELETE" + + unpublishNCRequest := &cns.UnpublishNetworkContainerRequest{ + NetworkID: networkID, + NetworkContainerID: networkContainerID, + JoinNetworkURL: joinNetworkURL, + DeleteNetworkContainerURL: deleteNetworkContainerURL, + } + + json.NewEncoder(&body).Encode(unpublishNCRequest) + req, err := http.NewRequest(http.MethodPost, cns.UnpublishNetworkContainer, &body) + if err != nil { + t.Fatal(err) + } + + w := httptest.NewRecorder() + mux.ServeHTTP(w, req) + + err = decodeResponse(w, &resp) + if err != nil || resp.Response.ReturnCode != 0 { + t.Errorf("UnpublishNetworkContainer failed with response %+v Err:%+v", resp, err) + t.Fatal(err) + } + + fmt.Printf("UnpublishNetworkContainer succeded with response %+v, raw:%+v\n", resp, w.Body) +} From b151dd23d2f833ecfbc99c7b2874f2a8c51dc167 Mon Sep 17 00:00:00 2001 From: Ashvin Deodhar Date: Tue, 12 Nov 2019 11:28:48 -0800 Subject: [PATCH 10/10] Add dummy URLs --- cns/restserver/restserver_test.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/cns/restserver/restserver_test.go b/cns/restserver/restserver_test.go index 7c1713b929..3f10724869 100644 --- a/cns/restserver/restserver_test.go +++ b/cns/restserver/restserver_test.go @@ -769,11 +769,8 @@ func TestPublishNCViaCNS(t *testing.T) { networkID := "vnet1" networkContainerID := "ethWebApp" - joinNetworkURL := "http://" + nmagentEndpoint + - "/machine/plugins/?comp=nmagent&type=NetworkManagement/joinedVirtualNetworks/" + networkID + "/api-version/1" - createNetworkContainerURL := "http://" + nmagentEndpoint + - "/machine/plugins/?comp=nmagent&type=NetworkManagement/interfaces/127.0.0.1/networkContainers/" + - networkContainerID + "/authenticationToken/dummyauthtoken/api-version/1" + joinNetworkURL := "http://" + nmagentEndpoint + "/dummyVnetURL" + createNetworkContainerURL := "http://" + nmagentEndpoint + "/networkContainers/dummyNCURL" publishNCRequest := &cns.PublishNetworkContainerRequest{ NetworkID: networkID, @@ -811,11 +808,8 @@ func TestUnpublishNCViaCNS(t *testing.T) { networkID := "vnet1" networkContainerID := "ethWebApp" - joinNetworkURL := "http://" + nmagentEndpoint + - "/machine/plugins/?comp=nmagent&type=NetworkManagement/joinedVirtualNetworks/" + networkID + "/api-version/1" - deleteNetworkContainerURL := "http://" + nmagentEndpoint + - "/machine/plugins/?comp=nmagent&type=NetworkManagement/interfaces/127.0.0.1/networkContainers/" + - networkContainerID + "/authenticationToken/dummyauthtoken/api-version/1/method/DELETE" + joinNetworkURL := "http://" + nmagentEndpoint + "/dummyVnetURL" + deleteNetworkContainerURL := "http://" + nmagentEndpoint + "/networkContainers/dummyNCURL" unpublishNCRequest := &cns.UnpublishNetworkContainerRequest{ NetworkID: networkID,