diff --git a/cns/NetworkContainerContract.go b/cns/NetworkContainerContract.go index 5c6dc44c9d..847249b731 100644 --- a/cns/NetworkContainerContract.go +++ b/cns/NetworkContainerContract.go @@ -23,6 +23,7 @@ const ( RequestIPConfig = "/network/requestipconfig" ReleaseIPConfig = "/network/releaseipconfig" GetIPAddresses = "/debug/getipaddresses" + GetPodIPOrchestratorContext = "/debug/getpodcontext" ) // NetworkContainer Prefixes @@ -244,8 +245,14 @@ type GetIPAddressStateResponse struct { // GetIPAddressStatusResponse is used in CNS IPAM mode as a response to get IP address, state and Pod info type GetIPAddressStatusResponse struct { - IPConfigurationStatus[] IPConfigurationStatus - Response Response + IPConfigurationStatus []IPConfigurationStatus + Response Response +} + +//GetPodContextResponse is used in CNS Client debug mode to get mapping of Orchestrator Context to Pod IP UUID +type GetPodContextResponse struct { + PodContext map[string]string + Response Response } // IPAddressState Only used in the GetIPConfig API to return IP's that match a filter diff --git a/cns/cnsclient/cli.go b/cns/cnsclient/cli.go index 72bec087c5..a704d7fbef 100644 --- a/cns/cnsclient/cli.go +++ b/cns/cnsclient/cli.go @@ -15,6 +15,7 @@ const ( getAllocatedArg = "Allocated" getAllArg = "All" getPendingReleaseArg = "PendingRelease" + getPodCmdArg = "getPodContexts" releaseArg = "release" @@ -28,6 +29,7 @@ const ( var ( availableCmds = []string{ getCmdArg, + getPodCmdArg, } getFlags = []string{ @@ -49,6 +51,8 @@ func HandleCNSClientCommands(cmd, arg string) error { switch { case strings.EqualFold(getCmdArg, cmd): return getCmd(cnsClient, arg) + case strings.EqualFold(getPodCmdArg, cmd): + return getPodCmd(cnsClient) default: return fmt.Errorf("No debug cmd supplied, options are: %v", getCmdArg) } @@ -96,3 +100,22 @@ func printIPAddresses(addrSlice []cns.IPConfigurationStatus) { cns.IPConfigurationStatus.String(addr) } } + +func getPodCmd(client *CNSClient) error { + + resp, err := client.GetPodOrchestratorContext() + if err != nil { + return err + } + + printPodContext(resp) + return nil +} + +func printPodContext(podContext map[string]string) { + var i = 1 + for orchContext, podID := range podContext { + fmt.Println(i, " ", orchContext, " : ", podID) + i++ + } +} diff --git a/cns/cnsclient/cnsclient.go b/cns/cnsclient/cnsclient.go index 8d75cbac4a..8e55bcf6cb 100644 --- a/cns/cnsclient/cnsclient.go +++ b/cns/cnsclient/cnsclient.go @@ -371,3 +371,42 @@ func (cnsClient *CNSClient) GetIPAddressesMatchingStates(StateFilter ...string) return resp.IPConfigurationStatus, err } + +//GetPodOrchestratorContext calls GetPodIpOrchestratorContext API on CNS +func (cnsClient *CNSClient) GetPodOrchestratorContext() (map[string]string, error) { + var ( + resp cns.GetPodContextResponse + err error + res *http.Response + ) + + url := cnsClient.connectionURL + cns.GetPodIPOrchestratorContext + log.Printf("GetPodIPOrchestratorContext url %v", url) + + res, err = http.Get(url) + if err != nil { + log.Errorf("[Azure CNSClient] GetPodIPOrchestratorContext HTTP Get returned error %v", err.Error()) + return resp.PodContext, err + } + + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + errMsg := fmt.Sprintf("[Azure CNSClient] GetPodIPOrchestratorContext invalid http status code: %v", res.StatusCode) + log.Errorf(errMsg) + return resp.PodContext, fmt.Errorf(errMsg) + } + + err = json.NewDecoder(res.Body).Decode(&resp) + if err != nil { + log.Errorf("[Azure CNSClient] Error received while parsing GetPodContext response resp:%v err:%v", res.Body, err.Error()) + return resp.PodContext, err + } + + if resp.Response.ReturnCode != 0 { + log.Errorf("[Azure CNSClient] GetPodContext received error response :%v", resp.Response.Message) + return resp.PodContext, fmt.Errorf(resp.Response.Message) + } + + return resp.PodContext, err +} diff --git a/cns/cnsclient/cnsclient_test.go b/cns/cnsclient/cnsclient_test.go index dd09dcb03a..2790a60c6e 100644 --- a/cns/cnsclient/cnsclient_test.go +++ b/cns/cnsclient/cnsclient_test.go @@ -226,24 +226,60 @@ func TestCNSClientRequestAndRelease(t *testing.T) { if reflect.DeepEqual(desired, resultIPnet) != true { t.Fatalf("Desired result not matching actual result, expected: %+v, actual: %+v", desired, resultIPnet) } + //checking for allocated IP address and pod context printing before ReleaseIPAddress is called + ipaddresses, err := cnsClient.GetIPAddressesMatchingStates(cns.Allocated) + if err != nil { + t.Fatalf("Get allocated IP addresses failed %+v", err) + } + + if len(ipaddresses) != 1 { + t.Fatalf("Number of available IP addresses expected to be 1, actual %+v", ipaddresses) + } + + if ipaddresses[0].IPAddress != desiredIpAddress && ipaddresses[0].State != cns.Allocated { + t.Fatalf("Available IP address does not match expected, address state: %+v", ipaddresses) + } + + t.Log(ipaddresses) // release requested IP address, expect success err = cnsClient.ReleaseIPAddress(orchestratorContext) if err != nil { t.Fatalf("Expected to not fail when releasing IP reservation found with context: %+v", err) } +} - ipaddresses, err := cnsClient.GetIPAddressesMatchingStates(cns.Available) +func TestCNSClientPodContextApi(t *testing.T) { + podName := "testpodname" + podNamespace := "testpodnamespace" + desiredIpAddress := "10.0.0.5" + + secondaryIps := make([]string, 0) + secondaryIps = append(secondaryIps, desiredIpAddress) + cnsClient, _ := InitCnsClient("") + + addTestStateToRestServer(t, secondaryIps) + + podInfo := cns.KubernetesPodInfo{PodName: podName, PodNamespace: podNamespace} + orchestratorContext, err := json.Marshal(podInfo) if err != nil { - t.Fatalf("Get allocated IP addresses failed %+v", err) + t.Fatal(err) } - if len(ipaddresses) != 1 { - t.Fatalf("Number of available IP addresses expected to be 1, actual %+v", ipaddresses) + // request IP address + _, err = cnsClient.RequestIPAddress(orchestratorContext) + if err != nil { + t.Fatalf("get IP from CNS failed with %+v", err) } - if ipaddresses[0].IPAddress != desiredIpAddress && ipaddresses[0].State != cns.Available { - t.Fatalf("Available IP address does not match expected, address state: %+v", ipaddresses) + //test for pod ip by orch context map + podcontext, err := cnsClient.GetPodOrchestratorContext() + if err != nil { + t.Errorf("Get pod ip by orchestrator context failed: %+v", err) } - fmt.Println(ipaddresses) + if len(podcontext) < 1 { + t.Errorf("Expected atleast 1 entry in map for podcontext: %+v", podcontext) + } + + t.Log(podcontext) } diff --git a/cns/restserver/ipam.go b/cns/restserver/ipam.go index 156c072b4d..afe93444ef 100644 --- a/cns/restserver/ipam.go +++ b/cns/restserver/ipam.go @@ -100,33 +100,33 @@ func (service *HTTPRestService) MarkIPAsPendingRelease(totalIpsToRelease int) (m defer service.Unlock() for uuid, existingIpConfig := range service.PodIPConfigState { - if existingIpConfig.State == cns.PendingProgramming { - updatedIpConfig, err := service.updateIPConfigState(uuid, cns.PendingRelease, existingIpConfig.OrchestratorContext) - if err != nil { - return nil, err - } + if existingIpConfig.State == cns.PendingProgramming { + updatedIpConfig, err := service.updateIPConfigState(uuid, cns.PendingRelease, existingIpConfig.OrchestratorContext) + if err != nil { + return nil, err + } - pendingReleasedIps[uuid] = updatedIpConfig + pendingReleasedIps[uuid] = updatedIpConfig if len(pendingReleasedIps) == totalIpsToRelease { return pendingReleasedIps, nil } - } + } } - - // if not all expected IPs are set to PendingRelease, then check the Available IPs + + // if not all expected IPs are set to PendingRelease, then check the Available IPs for uuid, existingIpConfig := range service.PodIPConfigState { - if existingIpConfig.State == cns.Available { - updatedIpConfig, err := service.updateIPConfigState(uuid, cns.PendingRelease, existingIpConfig.OrchestratorContext) - if err != nil { - return nil, err - } - - pendingReleasedIps[uuid] = updatedIpConfig - + if existingIpConfig.State == cns.Available { + updatedIpConfig, err := service.updateIPConfigState(uuid, cns.PendingRelease, existingIpConfig.OrchestratorContext) + if err != nil { + return nil, err + } + + pendingReleasedIps[uuid] = updatedIpConfig + if len(pendingReleasedIps) == totalIpsToRelease { return pendingReleasedIps, nil - } - } + } + } } logger.Printf("[MarkIPAsPendingRelease] Set total ips to PendingRelease %d, expected %d", len(pendingReleasedIps), totalIpsToRelease) @@ -140,9 +140,9 @@ func (service *HTTPRestService) updateIPConfigState(ipId string, updatedState st ipConfig.OrchestratorContext = orchestratorContext service.PodIPConfigState[ipId] = ipConfig return ipConfig, nil - } - - return cns.IPConfigurationStatus{}, fmt.Errorf("[updateIPConfigState] Failed to update state %s for the IPConfig. ID %s not found PodIPConfigState", updatedState, ipId) + } + + return cns.IPConfigurationStatus{}, fmt.Errorf("[updateIPConfigState] Failed to update state %s for the IPConfig. ID %s not found PodIPConfigState", updatedState, ipId) } // MarkIpsAsAvailableUntransacted will update pending programming IPs to available if NMAgent side's programmed nc version keep up with nc version. @@ -181,6 +181,37 @@ func (service *HTTPRestService) GetPodIPConfigState() map[string]cns.IPConfigura return service.PodIPConfigState } +func (service *HTTPRestService) getPodIPIDByOrchestratorContexthandler(w http.ResponseWriter, r *http.Request) { + var ( + resp cns.GetPodContextResponse + statusCode int + returnMessage string + err error + ) + + statusCode = UnexpectedError + + defer func() { + if err != nil { + resp.Response.ReturnCode = statusCode + resp.Response.Message = returnMessage + } + + err = service.Listener.Encode(w, &resp) + logger.Response(service.Name, resp, resp.Response.ReturnCode, ReturnCodeToString(resp.Response.ReturnCode), err) + }() + + resp.PodContext = service.GetPodIPIDByOrchestratorContext() + + return +} + +func (service *HTTPRestService) GetPodIPIDByOrchestratorContext() map[string]string { + service.RLock() + defer service.RUnlock() + return service.PodIPIDByOrchestratorContext +} + // GetPendingProgramIPConfigs returns list of IPs which are in pending program status func (service *HTTPRestService) GetPendingProgramIPConfigs() []cns.IPConfigurationStatus { service.RLock() @@ -348,7 +379,7 @@ func (service *HTTPRestService) MarkExistingIPsAsPending(pendingIPIDs []string) return fmt.Errorf("Failed to mark IP [%v] as pending, currently allocated", id) } - logger.Printf("[MarkExistingIPsAsPending]: Marking IP [%+v] to PendingRelease", ipconfig) + logger.Printf("[MarkExistingIPsAsPending]: Marking IP [%+v] to PendingRelease", ipconfig) ipconfig.State = cns.PendingRelease service.PodIPConfigState[id] = ipconfig } else { diff --git a/cns/restserver/restserver.go b/cns/restserver/restserver.go index a3f1b77e0d..ab70e82e95 100644 --- a/cns/restserver/restserver.go +++ b/cns/restserver/restserver.go @@ -174,6 +174,7 @@ func (service *HTTPRestService) Init(config *common.ServiceConfig) error { listener.AddHandler(cns.ReleaseIPConfig, service.releaseIPConfigHandler) listener.AddHandler(cns.NmAgentSupportedApisPath, service.nmAgentSupportedApisHandler) listener.AddHandler(cns.GetIPAddresses, service.getIPAddressesHandler) + listener.AddHandler(cns.GetPodIPOrchestratorContext, service.getPodIPIDByOrchestratorContexthandler) // handlers for v0.2 listener.AddHandler(cns.V2Prefix+cns.SetEnvironmentPath, service.setEnvironment)