diff --git a/cns/NetworkContainerContract.go b/cns/NetworkContainerContract.go index 8b47eb14df..b544bd8dc7 100644 --- a/cns/NetworkContainerContract.go +++ b/cns/NetworkContainerContract.go @@ -17,11 +17,11 @@ const ( SetOrchestratorType = "/network/setorchestratortype" CreateOrUpdateNetworkContainer = "/network/createorupdatenetworkcontainer" DeleteNetworkContainer = "/network/deletenetworkcontainer" - GetNetworkContainerStatus = "/network/getnetworkcontainerstatus" PublishNetworkContainer = "/network/publishnetworkcontainer" UnpublishNetworkContainer = "/network/unpublishnetworkcontainer" GetInterfaceForContainer = "/network/getinterfaceforcontainer" GetNetworkContainerByOrchestratorContext = "/network/getnetworkcontainerbyorchestratorcontext" + NetworkContainersURLPath = "/network/networkcontainers" AttachContainerToNetwork = "/network/attachcontainertonetwork" DetachContainerFromNetwork = "/network/detachcontainerfromnetwork" RequestIPConfig = "/network/requestipconfig" @@ -340,12 +340,12 @@ type CreateNetworkContainerResponse struct { Response Response } -// GetNetworkContainerStatusRequest specifies the details about the request to retrieve status of a specifc network container. +// GetNetworkContainerStatusRequest specifies the details about the request to retrieve status of a specific network container. type GetNetworkContainerStatusRequest struct { NetworkContainerid string } -// GetNetworkContainerStatusResponse specifies response of retriving a network container status. +// GetNetworkContainerStatusResponse specifies response of retrieving a network container status. type GetNetworkContainerStatusResponse struct { NetworkContainerid string Version string @@ -353,13 +353,26 @@ type GetNetworkContainerStatusResponse struct { Response Response } -// GetNetworkContainerRequest specifies the details about the request to retrieve a specifc network container. +type GetAllNetworkContainersResponse struct { + NetworkContainers []GetNetworkContainerResponse + Response Response +} + +type PostNetworkContainersRequest struct { + CreateNetworkContainerRequests []CreateNetworkContainerRequest +} + +type PostNetworkContainersResponse struct { + Response Response +} + +// GetNetworkContainerRequest specifies the details about the request to retrieve a specific network container. type GetNetworkContainerRequest struct { NetworkContainerid string OrchestratorContext json.RawMessage } -// GetNetworkContainerResponse describes the response to retrieve a specifc network container. +// GetNetworkContainerResponse describes the response to retrieve a specific network container. type GetNetworkContainerResponse struct { NetworkContainerID string IPConfiguration IPConfiguration @@ -436,12 +449,12 @@ type IPAddressState struct { State string } -// DeleteNetworkContainerRequest specifies the details about the request to delete a specifc network container. +// DeleteNetworkContainerRequest specifies the details about the request to delete a specific network container. type DeleteNetworkContainerRequest struct { NetworkContainerid string } -// DeleteNetworkContainerResponse describes the response to delete a specifc network container. +// DeleteNetworkContainerResponse describes the response to delete a specific network container. type DeleteNetworkContainerResponse struct { Response Response } @@ -470,7 +483,7 @@ type DetachContainerFromNetworkResponse struct { Response Response } -// NetworkInterface specifies the information that can be used to unquely identify an interface. +// NetworkInterface specifies the information that can be used to uniquely identify an interface. type NetworkInterface struct { Name string IPAddress string diff --git a/cns/restserver/api.go b/cns/restserver/api.go index c2461d599f..6753285e5b 100644 --- a/cns/restserver/api.go +++ b/cns/restserver/api.go @@ -5,7 +5,6 @@ package restserver import ( "context" - "errors" "fmt" "io" "net" @@ -23,6 +22,7 @@ import ( "github.com/Azure/azure-container-networking/cns/wireserver" "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/platform" + "github.com/pkg/errors" ) var ( @@ -54,7 +54,7 @@ func (service *HTTPRestService) setEnvironment(w http.ResponseWriter, r *http.Re } switch r.Method { - case "POST": + case http.MethodPost: logger.Printf("[Azure CNS] POST received for SetEnvironment.") service.state.Location = req.Location service.state.NetworkType = req.NetworkType @@ -88,7 +88,7 @@ func (service *HTTPRestService) createNetwork(w http.ResponseWriter, r *http.Req returnCode = types.InvalidParameter } else { switch r.Method { - case "POST": + case http.MethodPost: dc := service.dockerClient rt := service.routingTable err = dc.NetworkExists(req.NetworkName) @@ -190,7 +190,7 @@ func (service *HTTPRestService) deleteNetwork(w http.ResponseWriter, r *http.Req } switch r.Method { - case "POST": + case http.MethodPost: dc := service.dockerClient err := dc.NetworkExists(req.NetworkName) @@ -249,7 +249,7 @@ func (service *HTTPRestService) createHnsNetwork(w http.ResponseWriter, r *http. returnCode = types.InvalidParameter } else { switch r.Method { - case "POST": + case http.MethodPost: if err := hnsclient.CreateHnsNetwork(req); err == nil { // Save the newly created HnsNetwork name. CNS deleteHnsNetwork API // will only allow deleting these networks. @@ -300,7 +300,7 @@ func (service *HTTPRestService) deleteHnsNetwork(w http.ResponseWriter, r *http. returnCode = types.InvalidParameter } else { switch r.Method { - case "POST": + case http.MethodPost: networkInfo, found := service.getNetworkInfo(req.NetworkName) if found && networkInfo.NetworkName == req.NetworkName { if err = hnsclient.DeleteHnsNetwork(req.NetworkName); err == nil { @@ -357,7 +357,7 @@ func (service *HTTPRestService) reserveIPAddress(w http.ResponseWriter, r *http. } switch r.Method { - case "POST": + case http.MethodPost: ic := service.ipamClient var ifInfo *wireserver.InterfaceInfo @@ -434,7 +434,7 @@ func (service *HTTPRestService) releaseIPAddress(w http.ResponseWriter, r *http. } switch r.Method { - case "POST": + case http.MethodPost: ic := service.ipamClient var ifInfo *wireserver.InterfaceInfo @@ -490,7 +490,7 @@ func (service *HTTPRestService) getHostLocalIP(w http.ResponseWriter, r *http.Re if service.state.Initialized { switch r.Method { - case "GET": + case http.MethodGet: switch service.state.NetworkType { case "Underlay": if service.wscli != nil { @@ -543,7 +543,7 @@ func (service *HTTPRestService) getIPAddressUtilization(w http.ResponseWriter, r var unhealthyAddrs []string switch r.Method { - case "GET": + case http.MethodGet: ic := service.ipamClient ifInfo, err := service.getPrimaryHostInterface(context.TODO()) @@ -601,11 +601,6 @@ func (service *HTTPRestService) getAvailableIPAddresses(w http.ResponseWriter, r logger.Printf("[Azure CNS] getAvailableIPAddresses") logger.Request(service.Name, "getAvailableIPAddresses", nil) - switch r.Method { - case "GET": - default: - } - resp := cns.Response{ReturnCode: 0} ipResp := &cns.GetIPAddressesResponse{Response: resp} err := service.Listener.Encode(w, &ipResp) @@ -618,11 +613,6 @@ func (service *HTTPRestService) getReservedIPAddresses(w http.ResponseWriter, r logger.Printf("[Azure CNS] getReservedIPAddresses") logger.Request(service.Name, "getReservedIPAddresses", nil) - switch r.Method { - case "GET": - default: - } - resp := cns.Response{ReturnCode: 0} ipResp := &cns.GetIPAddressesResponse{Response: resp} err := service.Listener.Encode(w, &ipResp) @@ -642,7 +632,7 @@ func (service *HTTPRestService) getUnhealthyIPAddresses(w http.ResponseWriter, r var unhealthyAddrs []string switch r.Method { - case "GET": + case http.MethodGet: ic := service.ipamClient ifInfo, err := service.getPrimaryHostInterface(context.TODO()) @@ -698,11 +688,6 @@ func (service *HTTPRestService) getAllIPAddresses(w http.ResponseWriter, r *http logger.Printf("[Azure CNS] getAllIPAddresses") logger.Request(service.Name, "getAllIPAddresses", nil) - switch r.Method { - case "GET": - default: - } - resp := cns.Response{ReturnCode: 0} ipResp := &cns.GetIPAddressesResponse{Response: resp} err := service.Listener.Encode(w, &ipResp) @@ -715,11 +700,6 @@ func (service *HTTPRestService) getHealthReport(w http.ResponseWriter, r *http.R logger.Printf("[Azure CNS] getHealthReport") logger.Request(service.Name, "getHealthReport", nil) - switch r.Method { - case "GET": - default: - } - resp := &cns.Response{ReturnCode: 0} err := service.Listener.Encode(w, &resp) @@ -798,7 +778,7 @@ func (service *HTTPRestService) createOrUpdateNetworkContainer(w http.ResponseWr var returnCode types.ResponseCode var returnMessage string switch r.Method { - case "POST": + case http.MethodPost: if req.NetworkContainerType == cns.WebApps { // try to get the saved nc state if it exists existing, ok := service.getNetworkContainerDetails(req.NetworkContainerid) @@ -899,6 +879,27 @@ func (service *HTTPRestService) getNetworkContainerByOrchestratorContext(w http. logger.Response(service.Name, getNetworkContainerResponse, returnCode, err) } +// getOrRefreshNetworkContainers is to check whether refresh association is needed. +// If received "GET": Return all NCs in CNS's state file to DNC in order to check if NC refresh is needed +// If received "POST": Store all the NCs (from the request body that client sent) into CNS's state file +func (service *HTTPRestService) getOrRefreshNetworkContainers(w http.ResponseWriter, r *http.Request) { + logger.Printf("[Azure CNS] getOrRefreshNetworkContainers") + + switch r.Method { + case http.MethodGet: + service.handleGetNetworkContainers(w) + return + case http.MethodPost: + service.handlePostNetworkContainers(w, r) + return + default: + w.WriteHeader(http.StatusMethodNotAllowed) + err := errors.New("[Azure CNS] getOrRefreshNetworkContainers did not receive a GET or POST") + logger.Response(service.Name, nil, types.InvalidParameter, err) + return + } +} + func (service *HTTPRestService) deleteNetworkContainer(w http.ResponseWriter, r *http.Request) { logger.Printf("[Azure CNS] deleteNetworkContainer") @@ -918,7 +919,7 @@ func (service *HTTPRestService) deleteNetworkContainer(w http.ResponseWriter, r } switch r.Method { - case "POST": + case http.MethodPost: var containerStatus containerstatus var ok bool @@ -1069,7 +1070,7 @@ func (service *HTTPRestService) getNumberOfCPUCores(w http.ResponseWriter, r *ht ) switch r.Method { - case "GET": + case http.MethodGet: num = runtime.NumCPU() default: errMsg = "[Azure-CNS] getNumberOfCPUCores API expects a GET." @@ -1170,7 +1171,7 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r } switch r.Method { - case "POST": + case http.MethodPost: // Join the network // Please refactor this // do not reuse the below variable between network join and publish @@ -1298,7 +1299,7 @@ func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter, } switch r.Method { - case "POST": + case http.MethodPost: // Join Network if not joined already isNetworkJoined = service.isNetworkJoined(req.NetworkID) if !isNetworkJoined { @@ -1384,7 +1385,7 @@ func (service *HTTPRestService) createHostNCApipaEndpoint(w http.ResponseWriter, } switch r.Method { - case "POST": + case http.MethodPost: networkContainerDetails, found := service.getNetworkContainerDetails(req.NetworkContainerID) if found { if !networkContainerDetails.CreateNetworkContainerRequest.AllowNCToHostCommunication && @@ -1442,7 +1443,7 @@ func (service *HTTPRestService) deleteHostNCApipaEndpoint(w http.ResponseWriter, } switch r.Method { - case "POST": + case http.MethodPost: if err = hnsclient.DeleteHostNCApipaEndpoint(req.NetworkContainerID); err != nil { returnMessage = fmt.Sprintf("Failed to delete endpoint for Network Container: %s "+ "due to error: %v", req.NetworkContainerID, err) diff --git a/cns/restserver/api_test.go b/cns/restserver/api_test.go index a9ecae7e2c..2bc6ec91dc 100644 --- a/cns/restserver/api_test.go +++ b/cns/restserver/api_test.go @@ -82,6 +82,25 @@ var ( }, }}, } + + nc1 = createOrUpdateNetworkContainerParams{ + ncID: "ethWebApp1", + ncIP: "11.0.0.5", + ncType: cns.AzureContainerInstance, + ncVersion: "0", + podName: "testpod", + podNamespace: "testpodnamespace", + } + nc2 = createOrUpdateNetworkContainerParams{ + ncID: "ethWebApp2", + ncIP: "11.0.0.5", + ncType: cns.AzureContainerInstance, + ncVersion: "0", + podName: "testpod", + podNamespace: "testpodnamespace", + } + + ncParams = []createOrUpdateNetworkContainerParams{nc1, nc2} ) const ( @@ -828,6 +847,148 @@ func TestCreateHostNCApipaEndpoint(t *testing.T) { fmt.Printf("createHostNCApipaEndpoint Responded with %+v\n", createHostNCApipaEndpointResponse) } +func TestGetNetworkContainers(t *testing.T) { + t.Run("Test Get Network Containers", func(t *testing.T) { + setEnv(t) + err := setOrchestratorType(t, cns.Kubernetes) + if err != nil { + t.Errorf("TestGetNetworkContainers failed with error:%+v", err) + t.Fatal(err) + } + + for i := 0; i < len(ncParams); i++ { + err = createOrUpdateNetworkContainerWithParams(t, ncParams[i]) + if err != nil { + t.Errorf("createOrUpdateNetworkContainerWithParams failed with error:%+v", err) + t.Fatal(err) + } + } + + err = getAllNetworkContainers(ncParams) + if err != nil { + t.Errorf("TestGetNetworkContainers failed with error:%+v", err) + t.Fatal(err) + } + + for i := 0; i < len(ncParams); i++ { + err = deleteNetworkContainerWithParams(t, ncParams[i]) + if err != nil { + t.Errorf("createOrUpdateNetworkContainerWithParams failed with error:%+v", err) + t.Fatal(err) + } + } + }) +} + +func getAllNetworkContainers(ncParams []createOrUpdateNetworkContainerParams) error { + req, err := http.NewRequest(http.MethodGet, cns.NetworkContainersURLPath, http.NoBody) + if err != nil { + return err + } + + w := httptest.NewRecorder() + mux.ServeHTTP(w, req) + + var resp cns.GetAllNetworkContainersResponse + err = decodeResponse(w, &resp) + if err != nil || resp.Response.ReturnCode != types.Success || len(resp.NetworkContainers) != len(ncParams) { + return fmt.Errorf("GetNetworkContainers failed with response %+v Err:%+v", resp, err) + } + + for i := 0; i < len(ncParams); i++ { + if resp.NetworkContainers[i].NetworkContainerID != (cns.SwiftPrefix + ncParams[i].ncID) { + return fmt.Errorf("GetNetworkContainers failed") + } + } + + fmt.Printf("GetNetworkContainers succeeded with response %+v, raw:%+v\n", resp, w.Body) + return nil +} + +func TestPostNetworkContainers(t *testing.T) { + t.Run("Test Post Network Containers", func(t *testing.T) { + setEnv(t) + err := setOrchestratorType(t, cns.Kubernetes) + if err != nil { + t.Errorf("TestPostNetworkContainers failed Err:%+v", err) + t.Fatal(err) + } + + err = postAllNetworkContainers(ncParams) + if err != nil { + t.Errorf("Failed to save all network containers due to error: %+v", err) + t.Fatal(err) + } + + err = getAllNetworkContainers(ncParams) + if err != nil { + t.Errorf("TestPostNetworkContainers failed Err:%+v", err) + t.Fatal(err) + } + + for i := 0; i < len(ncParams); i++ { + err = deleteNetworkContainerWithParams(t, ncParams[i]) + if err != nil { + t.Errorf("createOrUpdateNetworkContainerWithParams failed Err:%+v", err) + t.Fatal(err) + } + } + }) +} + +func postAllNetworkContainers(ncParams []createOrUpdateNetworkContainerParams) error { + var ipConfig cns.IPConfiguration + ipConfig.DNSServers = []string{"8.8.8.8", "8.8.4.4"} + ipConfig.GatewayIPAddress = "11.0.0.1" + podInfo := cns.KubernetesPodInfo{PodName: "testpod", PodNamespace: "testpodnamespace"} + ctx, err := json.Marshal(podInfo) + if err != nil { + return fmt.Errorf("postAllNetworkContainers failed with error:%+v", err) + } + createReq := make([]cns.CreateNetworkContainerRequest, len(ncParams)) + postReq := cns.PostNetworkContainersRequest{CreateNetworkContainerRequests: createReq} + + for i := 0; i < len(ncParams); i++ { + var ipSubnet cns.IPSubnet + ipSubnet.IPAddress = ncParams[i].ncIP + ipSubnet.PrefixLength = 24 + ipConfig.IPSubnet = ipSubnet + + postReq.CreateNetworkContainerRequests[i] = cns.CreateNetworkContainerRequest{ + Version: ncParams[i].ncVersion, + NetworkContainerType: ncParams[i].ncType, + NetworkContainerid: cns.SwiftPrefix + ncParams[i].ncID, + OrchestratorContext: ctx, + IPConfiguration: ipConfig, + PrimaryInterfaceIdentifier: "11.0.0.7", + } + } + + var body bytes.Buffer + err = json.NewEncoder(&body).Encode(postReq) + if err != nil { + return fmt.Errorf("postAllNetworkContainers failed with error:%+v", err) + } + + req, err := http.NewRequestWithContext(context.TODO(), http.MethodPost, cns.NetworkContainersURLPath, &body) + if err != nil { + return fmt.Errorf("postAllNetworkContainers failed with error:%+v", err) + } + + w := httptest.NewRecorder() + mux.ServeHTTP(w, req) + var resp cns.PostNetworkContainersResponse + err = decodeResponse(w, &resp) + fmt.Printf("Raw response: %+v", w.Body) + + if err != nil || resp.Response.ReturnCode != types.Success { + return fmt.Errorf("post Network Containers failed with response %+v Err:%+v", resp, err) + } + fmt.Printf("Post Network Containers succeeded with response %+v\n", resp) + + return nil +} + func setOrchestratorType(t *testing.T, orchestratorType string) error { var body bytes.Buffer diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index 5106583e66..f916340e21 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -223,6 +223,7 @@ func (service *HTTPRestService) Init(config *common.ServiceConfig) error { listener.AddHandler(cns.PathDebugIPAddresses, service.handleDebugIPAddresses) listener.AddHandler(cns.PathDebugPodContext, service.handleDebugPodContext) listener.AddHandler(cns.PathDebugRestData, service.handleDebugRestData) + listener.AddHandler(cns.NetworkContainersURLPath, service.getOrRefreshNetworkContainers) // handlers for v0.2 listener.AddHandler(cns.V2Prefix+cns.SetEnvironmentPath, service.setEnvironment) diff --git a/cns/restserver/util.go b/cns/restserver/util.go index 05acd5701e..c30235cd84 100644 --- a/cns/restserver/util.go +++ b/cns/restserver/util.go @@ -840,3 +840,90 @@ func (service *HTTPRestService) isNCWaitingForUpdate( } return } + +// handleGetNetworkContainers returns all NCs in CNS +func (service *HTTPRestService) handleGetNetworkContainers(w http.ResponseWriter) { + service.RLock() + networkContainers := make([]cns.GetNetworkContainerResponse, len(service.state.ContainerStatus)) + i := 0 + for ncID := range service.state.ContainerStatus { + ncDetails := service.state.ContainerStatus[ncID] + getNcResp := cns.GetNetworkContainerResponse{ + NetworkContainerID: ncDetails.CreateNetworkContainerRequest.NetworkContainerid, + IPConfiguration: ncDetails.CreateNetworkContainerRequest.IPConfiguration, + Routes: ncDetails.CreateNetworkContainerRequest.Routes, + CnetAddressSpace: ncDetails.CreateNetworkContainerRequest.CnetAddressSpace, + MultiTenancyInfo: ncDetails.CreateNetworkContainerRequest.MultiTenancyInfo, + PrimaryInterfaceIdentifier: ncDetails.CreateNetworkContainerRequest.PrimaryInterfaceIdentifier, + LocalIPConfiguration: ncDetails.CreateNetworkContainerRequest.LocalIPConfiguration, + AllowHostToNCCommunication: ncDetails.CreateNetworkContainerRequest.AllowHostToNCCommunication, + AllowNCToHostCommunication: ncDetails.CreateNetworkContainerRequest.AllowNCToHostCommunication, + } + networkContainers[i] = getNcResp + i++ + } + service.RUnlock() + + response := cns.GetAllNetworkContainersResponse{ + NetworkContainers: networkContainers, + Response: cns.Response{ + ReturnCode: types.Success, + }, + } + err := service.Listener.Encode(w, &response) + logger.Response(service.Name, response, response.Response.ReturnCode, err) +} + +// handlePostNetworkContainers stores all the NCs (from the request that client sent) into CNS's state file +func (service *HTTPRestService) handlePostNetworkContainers(w http.ResponseWriter, r *http.Request) { + var req cns.PostNetworkContainersRequest + err := service.Listener.Decode(w, r, &req) + logger.Request(service.Name, &req, err) + if err != nil { + response := cns.PostNetworkContainersResponse{ + Response: cns.Response{ + ReturnCode: types.InvalidRequest, + Message: fmt.Sprintf("[Azure CNS] Create Network Container failed with error: %s", err.Error()), + }, + } + err = service.Listener.Encode(w, &response) + logger.Response(service.Name, response, response.Response.ReturnCode, err) + return + } + + returnCode := types.Success + returnMessage := "" + for i := 0; i < len(req.CreateNetworkContainerRequests); i++ { + createNcReq := req.CreateNetworkContainerRequests[i] + ncDetails, found := service.getNetworkContainerDetails(createNcReq.NetworkContainerid) + // Create NC if it doesn't exist, or it exists and the requested version is different from the saved version + if !found || (found && ncDetails.VMVersion != createNcReq.Version) { + nc := service.networkContainer + if err = nc.Create(createNcReq); err != nil { + returnCode = types.UnexpectedError + returnMessage = fmt.Sprintf("[Azure CNS] Create Network Container failed with error: %s", err.Error()) + break + } + } + // Save NC Goal State details + saveNcReturnCode, saveNcReturnMessage := service.saveNetworkContainerGoalState(createNcReq) + + // If NC was created successfully, log NC snapshot. + if saveNcReturnCode == types.Success { + logNCSnapshot(createNcReq) + } else { + returnCode = saveNcReturnCode + returnMessage = saveNcReturnMessage + break + } + } + + response := cns.PostNetworkContainersResponse{ + Response: cns.Response{ + ReturnCode: returnCode, + Message: returnMessage, + }, + } + err = service.Listener.Encode(w, &response) + logger.Response(service.Name, response, response.Response.ReturnCode, err) +}