feat: shim in metric for ipconfigstatus state transition durations (#1080)

Signed-off-by: Evan Baker <rbtr@users.noreply.github.com>
This commit is contained in:
Evan Baker 2021-12-06 16:32:51 -08:00 коммит произвёл GitHub
Родитель 5a477a9f89
Коммит 9fff334a19
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 222 добавлений и 140 удалений

Просмотреть файл

@ -149,6 +149,8 @@ type PodInfo interface {
Namespace() string
// OrchestratorContext is a JSON KubernetesPodInfo
OrchestratorContext() (json.RawMessage, error)
// Equals implements a functional equals for PodInfos
Equals(PodInfo) bool
}
type KubernetesPodInfo struct {
@ -166,6 +168,16 @@ type podInfo struct {
Version podInfoScheme
}
func (p *podInfo) Equals(o PodInfo) bool {
if (p == nil) != (o == nil) {
return false
}
if p == nil {
return true
}
return p.Key() == o.Key()
}
func (p *podInfo) InfraContainerID() string {
return p.PodInfraContainerID
}

Просмотреть файл

@ -7,10 +7,12 @@ import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/Azure/azure-container-networking/cns/common"
"github.com/Azure/azure-container-networking/cns/types"
"github.com/Azure/azure-container-networking/crd/nodenetworkconfig/api/v1alpha"
"github.com/pkg/errors"
)
// Container Network Service remote API Contract
@ -51,16 +53,70 @@ type HTTPService interface {
// This is used for KubernetesCRD orchestrator Type where NC has multiple ips.
// This struct captures the state for SecondaryIPs associated to a given NC
type IPConfigurationStatus struct {
NCID string
ID string // uuid
IPAddress string
State types.IPState
PodInfo PodInfo
ID string // uuid
IPAddress string
LastStateTransition time.Time
NCID string
PodInfo PodInfo
state types.IPState
stateMiddlewareFuncs []stateMiddlewareFunc
}
func (i IPConfigurationStatus) String() string {
// Equals compares a subset of the IPConfigurationStatus fields since a direct
// DeepEquals or otherwise complete comparison of two IPConfigurationStatus objects
// compares internal state details that don't impact their functional equality.
//nolint:gocritic // it's safer to pass this by value
func (i *IPConfigurationStatus) Equals(o IPConfigurationStatus) bool {
if i.PodInfo != nil && o.PodInfo != nil {
if !i.PodInfo.Equals(o.PodInfo) {
return false
}
}
return i.ID == o.ID &&
i.IPAddress == o.IPAddress &&
i.NCID == o.NCID &&
i.state == o.state
}
func (i *IPConfigurationStatus) GetState() types.IPState {
return i.state
}
type stateMiddlewareFunc func(*IPConfigurationStatus, types.IPState)
func (i *IPConfigurationStatus) SetState(s types.IPState) {
for _, f := range i.stateMiddlewareFuncs {
f(i, s)
}
i.LastStateTransition = time.Now()
i.state = s
}
func (i *IPConfigurationStatus) WithStateMiddleware(fs ...stateMiddlewareFunc) {
i.stateMiddlewareFuncs = append(i.stateMiddlewareFuncs, fs...)
}
func (i *IPConfigurationStatus) String() string {
return fmt.Sprintf("IPConfigurationStatus: Id: [%s], NcId: [%s], IpAddress: [%s], State: [%s], PodInfo: [%v]",
i.ID, i.NCID, i.IPAddress, i.State, i.PodInfo)
i.ID, i.NCID, i.IPAddress, i.state, i.PodInfo)
}
// MarshalJSON is a custom marshaller for IPConfigurationStatus that
// is capable of marshalling the private fields in the struct. The default
// marshaller can't see private fields by default, so we alias the type through
// a struct that has public fields for the original struct's private fields,
// embed the original struct in an anonymous struct as the alias type, and then
// let the default marshaller do its magic.
//nolint:gocritic // ignore hugeParam it's a value receiver on purpose
func (i IPConfigurationStatus) MarshalJSON() ([]byte, error) {
type alias IPConfigurationStatus
return json.Marshal(&struct { //nolint:wrapcheck // MarshalJSON is not called by us
State types.IPState `json:"state"`
*alias
}{
State: i.state,
alias: (*alias)(&i),
})
}
// UnmarshalJSON is a custom unmarshaller for IPConfigurationStatus that
@ -70,32 +126,32 @@ func (i IPConfigurationStatus) String() string {
func (i *IPConfigurationStatus) UnmarshalJSON(b []byte) error {
m := map[string]json.RawMessage{}
if err := json.Unmarshal(b, &m); err != nil {
return err
return errors.Wrap(err, "failed to unmarshal to RawMessage")
}
if s, ok := m["NCID"]; ok {
if err := json.Unmarshal(s, &(i.NCID)); err != nil {
return err
return errors.Wrap(err, "failed to unmarshal key NCID to string")
}
}
if s, ok := m["ID"]; ok {
if err := json.Unmarshal(s, &(i.ID)); err != nil {
return err
return errors.Wrap(err, "failed to unmarshal key ID to string")
}
}
if s, ok := m["IPAddress"]; ok {
if err := json.Unmarshal(s, &(i.IPAddress)); err != nil {
return err
return errors.Wrap(err, "failed to unmarshal key IPAddress to string")
}
}
if s, ok := m["State"]; ok {
if err := json.Unmarshal(s, &(i.State)); err != nil {
return err
if s, ok := m["state"]; ok {
if err := json.Unmarshal(s, &(i.state)); err != nil {
return errors.Wrap(err, "failed to unmarshal key state to IPConfigState")
}
}
if s, ok := m["PodInfo"]; ok {
pi, err := UnmarshalPodInfo(s)
if err != nil {
return err
return errors.Wrap(err, "failed to unmarshal key PodInfo to PodInfo")
}
i.PodInfo = pi
}

Просмотреть файл

@ -4,6 +4,8 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
@ -388,12 +390,17 @@ func (c *Client) GetHTTPServiceData(ctx context.Context) (*restserver.GetHTTPSer
return nil, errors.Wrap(err, "http request failed")
}
defer res.Body.Close()
b, err := io.ReadAll(res.Body)
s := string(b)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to read body %s", s))
}
if res.StatusCode != http.StatusOK {
return nil, errors.Errorf("http response %d", res.StatusCode)
}
var resp restserver.GetHTTPServiceDataResponse
err = json.NewDecoder(res.Body).Decode(&resp)
err = json.NewDecoder(bytes.NewReader(b)).Decode(&resp)
if err != nil {
return nil, errors.Wrap(err, "failed to decode GetHTTPServiceDataResponse")
}

Просмотреть файл

@ -255,7 +255,7 @@ func TestCNSClientRequestAndRelease(t *testing.T) {
secondaryIps := make([]string, 0)
secondaryIps = append(secondaryIps, desiredIpAddress)
cnsClient, _ := New("", 2*time.Second)
cnsClient, _ := New("", 2*time.Hour)
addTestStateToRestServer(t, secondaryIps)
@ -289,7 +289,7 @@ func TestCNSClientRequestAndRelease(t *testing.T) {
assert.Len(t, ipaddresses, 1, "Number of available IP addresses expected to be 1")
assert.Equal(t, desiredIpAddress, ipaddresses[0].IPAddress, "Available IP address does not match expected, address state")
assert.Equal(t, types.Assigned, ipaddresses[0].State, "Available IP address does not match expected, address state")
assert.Equal(t, types.Assigned, ipaddresses[0].GetState(), "Available IP address does not match expected, address state")
t.Log(ipaddresses)
@ -334,7 +334,7 @@ func TestCNSClientDebugAPI(t *testing.T) {
desiredIpAddress := "10.0.0.5"
secondaryIps := []string{desiredIpAddress}
cnsClient, _ := New("", 2*time.Second)
cnsClient, _ := New("", 2*time.Hour)
addTestStateToRestServer(t, secondaryIps)
@ -356,7 +356,7 @@ func TestCNSClientDebugAPI(t *testing.T) {
podConfig := inmemory.HTTPRestServiceData.PodIPConfigState
for _, v := range podConfig {
assert.Equal(t, "10.0.0.5", v.IPAddress, "Not the expected set values for testing IPConfigurationStatus, %+v", podConfig)
assert.Equal(t, types.Assigned, v.State, "Not the expected set values for testing IPConfigurationStatus, %+v", podConfig)
assert.Equal(t, types.Assigned, v.GetState(), "Not the expected set values for testing IPConfigurationStatus, %+v", podConfig)
assert.Equal(t, "testNcId1", v.NCID, "Not the expected set values for testing IPConfigurationStatus, %+v", podConfig)
}
assert.GreaterOrEqual(t, len(inmemory.HTTPRestServiceData.PodIPConfigState), 1, "PodIpConfigState with at least 1 entry expected")

Просмотреть файл

@ -68,7 +68,7 @@ func (ipm *IPStateManager) AddIPConfigs(ipconfigs []cns.IPConfigurationStatus) {
ipm.Lock()
defer ipm.Unlock()
for _, ipconfig := range ipconfigs {
switch ipconfig.State {
switch ipconfig.GetState() {
case types.PendingProgramming:
ipm.PendingProgramIPConfigState[ipconfig.ID] = ipconfig
case types.Available:
@ -98,7 +98,7 @@ func (ipm *IPStateManager) ReserveIPConfig() (cns.IPConfigurationStatus, error)
return cns.IPConfigurationStatus{}, err
}
ipc := ipm.AvailableIPConfigState[id]
ipc.State = types.Assigned
ipc.SetState(types.Assigned)
ipm.AssignedIPConfigState[id] = ipc
delete(ipm.AvailableIPConfigState, id)
return ipm.AssignedIPConfigState[id], nil
@ -108,7 +108,7 @@ func (ipm *IPStateManager) ReleaseIPConfig(ipconfigID string) (cns.IPConfigurati
ipm.Lock()
defer ipm.Unlock()
ipc := ipm.AssignedIPConfigState[ipconfigID]
ipc.State = types.Available
ipc.SetState(types.Available)
ipm.AvailableIPConfigState[ipconfigID] = ipc
ipm.AvailableIPIDStack.Push(ipconfigID)
delete(ipm.AssignedIPConfigState, ipconfigID)
@ -127,7 +127,7 @@ func (ipm *IPStateManager) MarkIPAsPendingRelease(numberOfIPsToMark int) (map[st
// if there was an error, and not all ip's have been freed, restore state
if err != nil && len(pendingReleaseIPs) != numberOfIPsToMark {
for uuid, ipState := range pendingReleaseIPs {
ipState.State = types.Available
ipState.SetState(types.Available)
ipm.AvailableIPIDStack.Push(pendingReleaseIPs[uuid].ID)
ipm.AvailableIPConfigState[pendingReleaseIPs[uuid].ID] = ipState
delete(ipm.PendingReleaseIPConfigState, pendingReleaseIPs[uuid].ID)
@ -143,7 +143,7 @@ func (ipm *IPStateManager) MarkIPAsPendingRelease(numberOfIPsToMark int) (map[st
// add all pending release to a slice
ipConfig := ipm.AvailableIPConfigState[id]
ipConfig.State = types.PendingRelease
ipConfig.SetState(types.PendingRelease)
pendingReleaseIPs[id] = ipConfig
delete(ipm.AvailableIPConfigState, id)

Просмотреть файл

@ -56,8 +56,8 @@ func (rc *RequestControllerFake) CarveIPConfigsAndAddToStatusAndCNS(numberOfIPCo
ipconfigCNS := cns.IPConfigurationStatus{
ID: ipconfigCRD.Name,
IPAddress: ipconfigCRD.IP,
State: types.Available,
}
ipconfigCNS.SetState(types.Available)
cnsIPConfigs = append(cnsIPConfigs, ipconfigCNS)
incrementIP(rc.ip)

Просмотреть файл

@ -29,7 +29,7 @@ var filters = map[types.IPState]IPConfigStatePredicate{
// the passed State string and returns true when equal.
func ipConfigStatePredicate(test types.IPState) IPConfigStatePredicate {
return func(ipconfig cns.IPConfigurationStatus) bool {
return ipconfig.State == test
return ipconfig.GetState() == test
}
}

Просмотреть файл

@ -16,34 +16,33 @@ var testStatuses = []struct {
{
State: types.Assigned,
Status: cns.IPConfigurationStatus{
ID: "assigned",
State: types.Assigned,
ID: "assigned",
},
},
{
State: types.Available,
Status: cns.IPConfigurationStatus{
ID: "available",
State: types.Available,
ID: "available",
},
},
{
State: types.PendingProgramming,
Status: cns.IPConfigurationStatus{
ID: "pending-programming",
State: types.PendingProgramming,
ID: "pending-programming",
},
},
{
State: types.PendingRelease,
Status: cns.IPConfigurationStatus{
ID: "pending-release",
State: types.PendingRelease,
ID: "pending-release",
},
},
}
func TestMatchesAnyIPConfigState(t *testing.T) {
for i := range testStatuses {
testStatuses[i].Status.SetState(testStatuses[i].State)
}
for i := range testStatuses {
status := testStatuses[i].Status
failStatus := testStatuses[(i+1)%len(testStatuses)].Status
@ -54,6 +53,9 @@ func TestMatchesAnyIPConfigState(t *testing.T) {
}
func TestMatchAnyIPConfigState(t *testing.T) {
for i := range testStatuses {
testStatuses[i].Status.SetState(testStatuses[i].State)
}
m := map[string]cns.IPConfigurationStatus{}
for i := range testStatuses {
key := strconv.Itoa(i)

Просмотреть файл

@ -126,7 +126,7 @@ func buildIPPoolState(ips map[string]cns.IPConfigurationStatus, spec v1alpha.Nod
requested: spec.RequestedIPCount,
}
for _, v := range ips {
switch v.State {
switch v.GetState() {
case types.Assigned:
state.assigned++
case types.Available:

Просмотреть файл

@ -154,8 +154,8 @@ func TestPendingIPsGotUpdatedWhenSyncHostNCVersion(t *testing.T) {
}
for i := range receivedSecondaryIPConfigs {
podIPConfigState := svc.PodIPConfigState[i]
if podIPConfigState.State != types.PendingProgramming {
t.Errorf("Unexpected State %s, expeted State is %s, received %s, IP address is %s", podIPConfigState.State, types.PendingProgramming, podIPConfigState.State, podIPConfigState.IPAddress)
if podIPConfigState.GetState() != types.PendingProgramming {
t.Errorf("Unexpected State %s, expected State is %s, IP address is %s", podIPConfigState.GetState(), types.PendingProgramming, podIPConfigState.IPAddress)
}
}
svc.SyncHostNCVersion(context.Background(), cns.CRD)
@ -167,8 +167,8 @@ func TestPendingIPsGotUpdatedWhenSyncHostNCVersion(t *testing.T) {
}
for i := range receivedSecondaryIPConfigs {
podIPConfigState := svc.PodIPConfigState[i]
if podIPConfigState.State != types.Available {
t.Errorf("Unexpected State %s, expeted State is %s, received %s, IP address is %s", podIPConfigState.State, types.Available, podIPConfigState.State, podIPConfigState.IPAddress)
if podIPConfigState.GetState() != types.Available {
t.Errorf("Unexpected State %s, expeted State is %s, IP address is %s", podIPConfigState.GetState(), types.Available, podIPConfigState.IPAddress)
}
}
}
@ -483,15 +483,15 @@ func validateNetworkRequest(t *testing.T, req cns.CreateNetworkContainerRequest)
// Validate IP state
if ipStatus.PodInfo != nil {
if _, exists := svc.PodIPIDByPodInterfaceKey[ipStatus.PodInfo.Key()]; exists {
if ipStatus.State != types.Assigned {
if ipStatus.GetState() != types.Assigned {
t.Fatalf("IPId: %s State is not Assigned, ipStatus: %+v", ipid, ipStatus)
}
} else {
t.Fatalf("Failed to find podContext for assigned ip: %+v, podinfo :%+v", ipStatus, ipStatus.PodInfo)
}
} else if ipStatus.State != expectedIPStatus {
} else if ipStatus.GetState() != expectedIPStatus {
// Todo: Validate for pendingRelease as well
t.Fatalf("IPId: %s State is not as expected, ipStatus is : %+v, expected status is %+v", ipid, ipStatus.State, expectedIPStatus)
t.Fatalf("IPId: %s State is not as expected, ipStatus is : %+v, expected status is %+v", ipid, ipStatus.GetState(), expectedIPStatus)
}
alreadyValidated[ipid] = ipStatus.IPAddress
@ -552,7 +552,7 @@ func validateNCStateAfterReconcile(t *testing.T, ncRequest *cns.CreateNetworkCon
ipId := svc.PodIPIDByPodInterfaceKey[podInfo.Key()]
ipConfigstate := svc.PodIPConfigState[ipId]
if ipConfigstate.State != types.Assigned {
if ipConfigstate.GetState() != types.Assigned {
t.Fatalf("IpAddress %s is not marked as assigned to Pod: %+v, ipState: %+v", ipaddress, podInfo, ipConfigstate)
}
@ -582,7 +582,7 @@ func validateNCStateAfterReconcile(t *testing.T, ncRequest *cns.CreateNetworkCon
// Validate IP state
if secIpConfigState, found := svc.PodIPConfigState[secIpId]; found {
if secIpConfigState.State != types.Available {
if secIpConfigState.GetState() != types.Available {
t.Fatalf("IPId: %s State is not Available, ipStatus: %+v", secIpId, secIpConfigState)
}
} else {

Просмотреть файл

@ -112,7 +112,7 @@ func (service *HTTPRestService) MarkIPAsPendingRelease(totalIpsToRelease int) (m
defer service.Unlock()
for uuid, existingIpConfig := range service.PodIPConfigState {
if existingIpConfig.State == types.PendingProgramming {
if existingIpConfig.GetState() == types.PendingProgramming {
updatedIPConfig, err := service.updateIPConfigState(uuid, types.PendingRelease, existingIpConfig.PodInfo)
if err != nil {
return nil, err
@ -127,7 +127,7 @@ func (service *HTTPRestService) MarkIPAsPendingRelease(totalIpsToRelease int) (m
// if not all expected IPs are set to PendingRelease, then check the Available IPs
for uuid, existingIpConfig := range service.PodIPConfigState {
if existingIpConfig.State == types.Available {
if existingIpConfig.GetState() == types.Available {
updatedIPConfig, err := service.updateIPConfigState(uuid, types.PendingRelease, existingIpConfig.PodInfo)
if err != nil {
return nil, err
@ -148,7 +148,7 @@ func (service *HTTPRestService) MarkIPAsPendingRelease(totalIpsToRelease int) (m
func (service *HTTPRestService) updateIPConfigState(ipID string, updatedState types.IPState, podInfo cns.PodInfo) (cns.IPConfigurationStatus, error) {
if ipConfig, found := service.PodIPConfigState[ipID]; found {
logger.Printf("[updateIPConfigState] Changing IpId [%s] state to [%s], podInfo [%+v]. Current config [%+v]", ipID, updatedState, podInfo, ipConfig)
ipConfig.State = updatedState
ipConfig.SetState(updatedState)
ipConfig.PodInfo = podInfo
service.PodIPConfigState[ipID] = ipConfig
return ipConfig, nil
@ -175,7 +175,7 @@ func (service *HTTPRestService) MarkIpsAsAvailableUntransacted(ncID string, newH
for uuid, secondaryIPConfigs := range ncInfo.CreateNetworkContainerRequest.SecondaryIPConfigs {
if ipConfigStatus, exist := service.PodIPConfigState[uuid]; !exist {
logger.Errorf("IP %s with uuid as %s exist in service state Secondary IP list but can't find in PodIPConfigState", ipConfigStatus.IPAddress, uuid)
} else if ipConfigStatus.State == types.PendingProgramming && secondaryIPConfigs.NCVersion <= newHostNCVersion {
} else if ipConfigStatus.GetState() == types.PendingProgramming && secondaryIPConfigs.NCVersion <= newHostNCVersion {
_, err := service.updateIPConfigState(uuid, types.Available, nil)
if err != nil {
logger.Errorf("Error updating IPConfig [%+v] state to Available, err: %+v", ipConfigStatus, err)
@ -338,12 +338,12 @@ func (service *HTTPRestService) MarkExistingIPsAsPending(pendingIPIDs []string)
for _, id := range pendingIPIDs {
if ipconfig, exists := service.PodIPConfigState[id]; exists {
if ipconfig.State == types.Assigned {
if ipconfig.GetState() == types.Assigned {
return errors.Errorf("Failed to mark IP [%v] as pending, currently assigned", id)
}
logger.Printf("[MarkExistingIPsAsPending]: Marking IP [%+v] to PendingRelease", ipconfig)
ipconfig.State = types.PendingRelease
ipconfig.SetState(types.PendingRelease)
service.PodIPConfigState[id] = ipconfig
} else {
logger.Errorf("Inconsistent state, ipconfig with ID [%v] marked as pending release, but does not exist in state", id)
@ -382,7 +382,7 @@ func (service *HTTPRestService) AssignDesiredIPConfig(podInfo cns.PodInfo, desir
for _, ipConfig := range service.PodIPConfigState {
if ipConfig.IPAddress == desiredIPAddress {
switch ipConfig.State { //nolint:exhaustive // ignoring PendingRelease case intentionally
switch ipConfig.GetState() { //nolint:exhaustive // ignoring PendingRelease case intentionally
case types.Assigned:
// This IP has already been assigned, if it is assigned to same pod, then return the same
// IPconfiguration
@ -412,7 +412,7 @@ func (service *HTTPRestService) AssignAnyAvailableIPConfig(podInfo cns.PodInfo)
defer service.Unlock()
for _, ipState := range service.PodIPConfigState {
if ipState.State == types.Available {
if ipState.GetState() == types.Available {
if err := service.assignIPConfig(ipState, podInfo); err != nil {
return cns.PodIpInfo{}, err
}

Просмотреть файл

@ -4,7 +4,6 @@
package restserver
import (
"reflect"
"strconv"
"testing"
@ -13,6 +12,8 @@ import (
"github.com/Azure/azure-container-networking/cns/fakes"
"github.com/Azure/azure-container-networking/cns/types"
"github.com/Azure/azure-container-networking/crd/nodenetworkconfig/api/v1alpha"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
var (
@ -53,77 +54,49 @@ func newSecondaryIPConfig(ipAddress string, ncVersion int) cns.SecondaryIPConfig
func NewPodState(ipaddress string, prefixLength uint8, id, ncid string, state types.IPState, ncVersion int) cns.IPConfigurationStatus {
ipconfig := newSecondaryIPConfig(ipaddress, ncVersion)
return cns.IPConfigurationStatus{
status := &cns.IPConfigurationStatus{
IPAddress: ipconfig.IPAddress,
ID: id,
NCID: ncid,
State: state,
}
status.SetState(state)
return *status
}
func requestIpAddressAndGetState(t *testing.T, req cns.IPConfigRequest) (cns.IPConfigurationStatus, error) {
var (
ipState cns.IPConfigurationStatus
PodIpInfo cns.PodIpInfo
err error
)
PodIpInfo, err = requestIPConfigHelper(svc, req)
func requestIPAddressAndGetState(t *testing.T, req cns.IPConfigRequest) (cns.IPConfigurationStatus, error) {
PodIPInfo, err := requestIPConfigHelper(svc, req)
if err != nil {
return ipState, err
return cns.IPConfigurationStatus{}, err
}
if reflect.DeepEqual(PodIpInfo.NetworkContainerPrimaryIPConfig.IPSubnet.IPAddress, primaryIp) != true {
t.Fatalf("PrimarIP is not added as expected ipConfig %+v, expected primaryIP: %+v", PodIpInfo.NetworkContainerPrimaryIPConfig, primaryIp)
}
if PodIpInfo.NetworkContainerPrimaryIPConfig.IPSubnet.PrefixLength != subnetPrfixLength {
t.Fatalf("Primary IP Prefix length is not added as expected ipConfig %+v, expected: %+v", PodIpInfo.NetworkContainerPrimaryIPConfig, subnetPrfixLength)
}
// validate DnsServer and Gateway Ip as the same configured for Primary IP
if reflect.DeepEqual(PodIpInfo.NetworkContainerPrimaryIPConfig.DNSServers, dnsservers) != true {
t.Fatalf("DnsServer is not added as expected ipConfig %+v, expected dnsServers: %+v", PodIpInfo.NetworkContainerPrimaryIPConfig, dnsservers)
}
if reflect.DeepEqual(PodIpInfo.NetworkContainerPrimaryIPConfig.GatewayIPAddress, gatewayIp) != true {
t.Fatalf("Gateway is not added as expected ipConfig %+v, expected GatewayIp: %+v", PodIpInfo.NetworkContainerPrimaryIPConfig, gatewayIp)
}
if PodIpInfo.PodIPConfig.PrefixLength != subnetPrfixLength {
t.Fatalf("Pod IP Prefix length is not added as expected ipConfig %+v, expected: %+v", PodIpInfo.PodIPConfig, subnetPrfixLength)
}
if reflect.DeepEqual(PodIpInfo.HostPrimaryIPInfo.PrimaryIP, fakes.HostPrimaryIP) != true {
t.Fatalf("Host PrimaryIP is not added as expected ipConfig %+v, expected primaryIP: %+v", PodIpInfo.HostPrimaryIPInfo, fakes.HostPrimaryIP)
}
if reflect.DeepEqual(PodIpInfo.HostPrimaryIPInfo.Subnet, fakes.HostSubnet) != true {
t.Fatalf("Host Subnet is not added as expected ipConfig %+v, expected Host subnet: %+v", PodIpInfo.HostPrimaryIPInfo, fakes.HostSubnet)
}
assert.Equal(t, primaryIp, PodIPInfo.NetworkContainerPrimaryIPConfig.IPSubnet.IPAddress)
assert.Equal(t, subnetPrfixLength, int(PodIPInfo.NetworkContainerPrimaryIPConfig.IPSubnet.PrefixLength))
assert.Equal(t, dnsservers, PodIPInfo.NetworkContainerPrimaryIPConfig.DNSServers)
assert.Equal(t, gatewayIp, PodIPInfo.NetworkContainerPrimaryIPConfig.GatewayIPAddress)
assert.Equal(t, subnetPrfixLength, int(PodIPInfo.PodIPConfig.PrefixLength))
assert.Equal(t, fakes.HostPrimaryIP, PodIPInfo.HostPrimaryIPInfo.PrimaryIP)
assert.Equal(t, fakes.HostSubnet, PodIPInfo.HostPrimaryIPInfo.Subnet)
// retrieve podinfo from orchestrator context
podInfo, err := cns.UnmarshalPodInfo(req.OrchestratorContext)
if err != nil {
return ipState, err
return cns.IPConfigurationStatus{}, errors.Wrap(err, "failed to unmarshal pod info")
}
ipId := svc.PodIPIDByPodInterfaceKey[podInfo.Key()]
ipState = svc.PodIPConfigState[ipId]
return ipState, err
ipID := svc.PodIPIDByPodInterfaceKey[podInfo.Key()]
return svc.PodIPConfigState[ipID], nil
}
func NewPodStateWithOrchestratorContext(ipaddress, id, ncid string, state types.IPState, prefixLength uint8, ncVersion int, podInfo cns.PodInfo) (cns.IPConfigurationStatus, error) {
ipconfig := newSecondaryIPConfig(ipaddress, ncVersion)
return cns.IPConfigurationStatus{
status := &cns.IPConfigurationStatus{
IPAddress: ipconfig.IPAddress,
ID: id,
NCID: ncid,
State: state,
PodInfo: podInfo,
}, nil
}
status.SetState(state)
return *status, nil
}
// Test function to populate the IPConfigState
@ -144,7 +117,7 @@ func UpdatePodIpConfigState(t *testing.T, svc *HTTPRestService, ipconfigs map[st
// update ipconfigs to expected state
for ipId, ipconfig := range ipconfigs {
if ipconfig.State == types.Assigned {
if ipconfig.GetState() == types.Assigned {
svc.PodIPIDByPodInterfaceKey[ipconfig.PodInfo.Key()] = ipId
svc.PodIPConfigState[ipId] = ipconfig
}
@ -169,7 +142,7 @@ func TestIPAMGetAvailableIPConfig(t *testing.T) {
b, _ := testPod1Info.OrchestratorContext()
req.OrchestratorContext = b
actualstate, err := requestIpAddressAndGetState(t, req)
actualstate, err := requestIPAddressAndGetState(t, req)
if err != nil {
t.Fatal("Expected IP retrieval to be nil")
}
@ -177,9 +150,11 @@ func TestIPAMGetAvailableIPConfig(t *testing.T) {
desiredState := NewPodState(testIP1, 24, testPod1GUID, testNCID, types.Assigned, 0)
desiredState.PodInfo = testPod1Info
if reflect.DeepEqual(desiredState, actualstate) != true {
t.Fatalf("Desired state not matching actual state, expected: %+v, actual: %+v", desiredState, actualstate)
}
assert.Equal(t, desiredState.GetState(), actualstate.GetState())
assert.Equal(t, desiredState.ID, actualstate.ID)
assert.Equal(t, desiredState.IPAddress, actualstate.IPAddress)
assert.Equal(t, desiredState.NCID, actualstate.NCID)
assert.Equal(t, desiredState.PodInfo, actualstate.PodInfo)
}
// First IP is already assigned to a pod, want second IP
@ -207,16 +182,18 @@ func TestIPAMGetNextAvailableIPConfig(t *testing.T) {
b, _ := testPod2Info.OrchestratorContext()
req.OrchestratorContext = b
actualstate, err := requestIpAddressAndGetState(t, req)
actualstate, err := requestIPAddressAndGetState(t, req)
if err != nil {
t.Fatalf("Expected IP retrieval to be nil: %+v", err)
}
// want second available Pod IP State as first has been assigned
desiredState, _ := NewPodStateWithOrchestratorContext(testIP2, testPod2GUID, testNCID, types.Assigned, 24, 0, testPod2Info)
if reflect.DeepEqual(desiredState, actualstate) != true {
t.Fatalf("Desired state not matching actual state, expected: %+v, actual: %+v", desiredState, actualstate)
}
assert.Equal(t, desiredState.GetState(), actualstate.GetState())
assert.Equal(t, desiredState.ID, actualstate.ID)
assert.Equal(t, desiredState.IPAddress, actualstate.IPAddress)
assert.Equal(t, desiredState.NCID, actualstate.NCID)
assert.Equal(t, desiredState.PodInfo, actualstate.PodInfo)
}
func TestIPAMGetAlreadyAssignedIPConfigForSamePod(t *testing.T) {
@ -239,16 +216,18 @@ func TestIPAMGetAlreadyAssignedIPConfigForSamePod(t *testing.T) {
b, _ := testPod1Info.OrchestratorContext()
req.OrchestratorContext = b
actualstate, err := requestIpAddressAndGetState(t, req)
actualstate, err := requestIPAddressAndGetState(t, req)
if err != nil {
t.Fatalf("Expected not error: %+v", err)
}
desiredState, _ := NewPodStateWithOrchestratorContext(testIP1, testPod1GUID, testNCID, types.Assigned, 24, 0, testPod1Info)
if reflect.DeepEqual(desiredState, actualstate) != true {
t.Fatalf("Desired state not matching actual state, expected: %+v, actual: %+v", desiredState, actualstate)
}
assert.Equal(t, desiredState.GetState(), actualstate.GetState())
assert.Equal(t, desiredState.ID, actualstate.ID)
assert.Equal(t, desiredState.IPAddress, actualstate.IPAddress)
assert.Equal(t, desiredState.NCID, actualstate.NCID)
assert.Equal(t, desiredState.PodInfo, actualstate.PodInfo)
}
func TestIPAMAttemptToRequestIPNotFoundInPool(t *testing.T) {
@ -273,7 +252,7 @@ func TestIPAMAttemptToRequestIPNotFoundInPool(t *testing.T) {
req.OrchestratorContext = b
req.DesiredIPAddress = testIP2
_, err = requestIpAddressAndGetState(t, req)
_, err = requestIPAddressAndGetState(t, req)
if err == nil {
t.Fatalf("Expected to fail as IP not found in pool")
}
@ -301,7 +280,7 @@ func TestIPAMGetDesiredIPConfigWithSpecfiedIP(t *testing.T) {
req.OrchestratorContext = b
req.DesiredIPAddress = testIP1
actualstate, err := requestIpAddressAndGetState(t, req)
actualstate, err := requestIPAddressAndGetState(t, req)
if err != nil {
t.Fatalf("Expected IP retrieval to be nil: %+v", err)
}
@ -309,9 +288,11 @@ func TestIPAMGetDesiredIPConfigWithSpecfiedIP(t *testing.T) {
desiredState := NewPodState(testIP1, 24, testPod1GUID, testNCID, types.Assigned, 0)
desiredState.PodInfo = testPod1Info
if reflect.DeepEqual(desiredState, actualstate) != true {
t.Fatalf("Desired state not matching actual state, expected: %+v, actual: %+v", desiredState, actualstate)
}
assert.Equal(t, desiredState.GetState(), actualstate.GetState())
assert.Equal(t, desiredState.ID, actualstate.ID)
assert.Equal(t, desiredState.IPAddress, actualstate.IPAddress)
assert.Equal(t, desiredState.NCID, actualstate.NCID)
assert.Equal(t, desiredState.PodInfo, actualstate.PodInfo)
}
func TestIPAMFailToGetDesiredIPConfigWithAlreadyAssignedSpecfiedIP(t *testing.T) {
@ -336,7 +317,7 @@ func TestIPAMFailToGetDesiredIPConfigWithAlreadyAssignedSpecfiedIP(t *testing.T)
req.OrchestratorContext = b
req.DesiredIPAddress = testIP1
_, err = requestIpAddressAndGetState(t, req)
_, err = requestIPAddressAndGetState(t, req)
if err == nil {
t.Fatalf("Expected failure requesting already assigned IP: %+v", err)
}
@ -363,7 +344,7 @@ func TestIPAMFailToGetIPWhenAllIPsAreAssigned(t *testing.T) {
b, _ := testPod3Info.OrchestratorContext()
req.OrchestratorContext = b
_, err = requestIpAddressAndGetState(t, req)
_, err = requestIPAddressAndGetState(t, req)
if err == nil {
t.Fatalf("Expected failure requesting IP when there are no more IPs: %+v", err)
}
@ -398,7 +379,7 @@ func TestIPAMRequestThenReleaseThenRequestAgain(t *testing.T) {
req.OrchestratorContext = b
req.DesiredIPAddress = desiredIpAddress
_, err = requestIpAddressAndGetState(t, req)
_, err = requestIPAddressAndGetState(t, req)
if err == nil {
t.Fatal("Expected failure requesting IP when there are no more IPs")
}
@ -418,7 +399,7 @@ func TestIPAMRequestThenReleaseThenRequestAgain(t *testing.T) {
req.OrchestratorContext = b
req.DesiredIPAddress = desiredIpAddress
actualstate, err := requestIpAddressAndGetState(t, req)
actualstate, err := requestIPAddressAndGetState(t, req)
if err != nil {
t.Fatalf("Expected IP retrieval to be nil: %+v", err)
}
@ -428,9 +409,11 @@ func TestIPAMRequestThenReleaseThenRequestAgain(t *testing.T) {
desiredState.IPAddress = desiredIpAddress
desiredState.PodInfo = testPod2Info
if reflect.DeepEqual(desiredState, actualstate) != true {
t.Fatalf("Desired state not matching actual state, expected: %+v, actual: %+v", state1, actualstate)
}
assert.Equal(t, desiredState.GetState(), actualstate.GetState())
assert.Equal(t, desiredState.ID, actualstate.ID)
assert.Equal(t, desiredState.IPAddress, actualstate.IPAddress)
assert.Equal(t, desiredState.NCID, actualstate.NCID)
assert.Equal(t, desiredState.PodInfo, actualstate.PodInfo)
}
func TestIPAMReleaseIPIdempotency(t *testing.T) {
@ -513,7 +496,7 @@ func TestAvailableIPConfigs(t *testing.T) {
req.OrchestratorContext = b
req.DesiredIPAddress = state1.IPAddress
_, err := requestIpAddressAndGetState(t, req)
_, err := requestIPAddressAndGetState(t, req)
if err != nil {
t.Fatal("Expected IP retrieval to be nil")
}
@ -534,18 +517,18 @@ func validateIpState(t *testing.T, actualIps []cns.IPConfigurationStatus, expect
t.Fatalf("Actual and expected count doesnt match, expected %d, actual %d", len(actualIps), len(expectedList))
}
for _, actualIp := range actualIps {
var expectedIp cns.IPConfigurationStatus
for _, actualIP := range actualIps { //nolint:gocritic // ignore copy
var expectedIP cns.IPConfigurationStatus
var found bool
for _, expectedIp = range expectedList {
if reflect.DeepEqual(actualIp, expectedIp) == true {
for _, expectedIP = range expectedList { //nolint:gocritic // ignore copy
if expectedIP.Equals(actualIP) {
found = true
break
}
}
if !found {
t.Fatalf("Actual and expected list doesnt match actual: %+v, expected: %+v", actualIp, expectedIp)
t.Fatalf("Actual and expected list doesnt match actual: %+v, expected: %+v", actualIP, expectedIP)
}
}
}
@ -635,10 +618,10 @@ func TestIPAMMarkIPAsPendingWithPendingProgrammingIPs(t *testing.T) {
}
// Check returning released IPs are from pod 1 and 3
if _, exists := ips[testPod1GUID]; !exists {
t.Fatalf("Expected ID not marked as pending: %+v, ips is %s", err, ips)
t.Fatalf("Expected ID not marked as pending: %+v, ips is %v", err, ips)
}
if _, exists := ips[testPod3GUID]; !exists {
t.Fatalf("Expected ID not marked as pending: %+v, ips is %s", err, ips)
t.Fatalf("Expected ID not marked as pending: %+v, ips is %v", err, ips)
}
pendingRelease := svc.GetPendingReleaseIPConfigs()
@ -670,10 +653,10 @@ func TestIPAMMarkIPAsPendingWithPendingProgrammingIPs(t *testing.T) {
}
// Make sure newly released IPs are from pod 2 and pod 4
if _, exists := ips[testPod2GUID]; !exists {
t.Fatalf("Expected ID not marked as pending: %+v, ips is %s", err, ips)
t.Fatalf("Expected ID not marked as pending: %+v, ips is %v", err, ips)
}
if _, exists := ips[testPod4GUID]; !exists {
t.Fatalf("Expected ID not marked as pending: %+v, ips is %s", err, ips)
t.Fatalf("Expected ID not marked as pending: %+v, ips is %v", err, ips)
}
// Get all pending release IPs and check total number is 4

Просмотреть файл

@ -4,6 +4,8 @@ import (
"net/http"
"time"
"github.com/Azure/azure-container-networking/cns"
"github.com/Azure/azure-container-networking/cns/types"
"github.com/prometheus/client_golang/prometheus"
"sigs.k8s.io/controller-runtime/pkg/metrics"
)
@ -31,10 +33,21 @@ var ipAssignmentLatency = prometheus.NewHistogram(
},
)
var ipConfigStatusStateTransitionTime = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "ipconfigstatus_state_transition",
Help: "Time spent by the IP Configuration Status in each state transition",
//nolint:gomnd // default bucket consts
Buckets: prometheus.ExponentialBuckets(0.001, 2, 15), // 1 ms to ~16 seconds
},
[]string{"previous_state", "next_state"},
)
func init() {
metrics.Registry.MustRegister(
httpRequestLatency,
ipAssignmentLatency,
ipConfigStatusStateTransitionTime,
)
}
@ -47,3 +60,11 @@ func newHandlerFuncWithHistogram(handler http.HandlerFunc, histogram *prometheus
handler(w, req)
}
}
func stateTransitionMiddleware(i *cns.IPConfigurationStatus, s types.IPState) {
// if no state transition has been recorded yet, don't collect any metric
if i.LastStateTransition.IsZero() {
return
}
ipConfigStatusStateTransitionTime.WithLabelValues(string(i.GetState()), string(s)).Observe(time.Since(i.LastStateTransition).Seconds())
}

Просмотреть файл

@ -225,7 +225,7 @@ func (service *HTTPRestService) updateIPConfigsStateUntransacted(
ipConfigStatus, exists := service.PodIPConfigState[ipID]
if exists {
// pod ip exists, validate if state is not assigned, else fail
if ipConfigStatus.State == types.Assigned {
if ipConfigStatus.GetState() == types.Assigned {
errMsg := fmt.Sprintf("Failed to delete an Assigned IP %v", ipConfigStatus)
return types.InconsistentIPConfigState, errMsg
}
@ -288,9 +288,10 @@ func (service *HTTPRestService) addIPConfigStateUntransacted(ncID string, hostVe
NCID: ncID,
ID: ipID,
IPAddress: ipconfig.IPAddress,
State: newIPCNSStatus,
PodInfo: nil,
}
ipconfigStatus.WithStateMiddleware(stateTransitionMiddleware)
ipconfigStatus.SetState(newIPCNSStatus)
logger.Printf("[Azure-Cns] Add IP %s as %s", ipconfig.IPAddress, newIPCNSStatus)
service.PodIPConfigState[ipID] = ipconfigStatus
@ -321,7 +322,7 @@ func (service *HTTPRestService) removeToBeDeletedIPStateUntransacted(
ipConfigStatus, exists := service.PodIPConfigState[ipID]
if exists {
// pod ip exists, validate if state is not assigned, else fail
if ipConfigStatus.State == types.Assigned {
if ipConfigStatus.GetState() == types.Assigned {
errMsg := fmt.Sprintf("Failed to delete an Assigned IP %v", ipConfigStatus)
return types.InconsistentIPConfigState, errMsg
}