Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Modifying stateless CNI state to account for swift 2.0 changes. #2523

Merged
merged 5 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cns/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,4 +363,5 @@ type GetHomeAzResponse struct {
type EndpointRequest struct {
HnsEndpointID string `json:"hnsEndpointID"`
HostVethName string `json:"hostVethName"`
InterfaceName string `json:"InterfaceName"`
}
8 changes: 2 additions & 6 deletions cns/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1059,15 +1059,11 @@ func (c *Client) GetEndpoint(ctx context.Context, endpointID string) (*restserve

// UpdateEndpoint calls the EndpointHandlerAPI in CNS
// to update the state of a given EndpointID with either HNSEndpointID or HostVethName
func (c *Client) UpdateEndpoint(ctx context.Context, endpointID, hnsID, vethName string) (*cns.Response, error) {
func (c *Client) UpdateEndpoint(ctx context.Context, endpointID string, ipInfo map[string]*restserver.IPInfo) (*cns.Response, error) {
// build the request
updateEndpoint := cns.EndpointRequest{
HnsEndpointID: hnsID,
HostVethName: vethName,
}
var body bytes.Buffer

if err := json.NewEncoder(&body).Encode(updateEndpoint); err != nil {
if err := json.NewEncoder(&body).Encode(ipInfo); err != nil {
return nil, errors.Wrap(err, "failed to encode updateEndpoint")
}

Expand Down
34 changes: 24 additions & 10 deletions cns/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2710,15 +2710,17 @@ func TestUpdateEndpoint(t *testing.T) {
containerID string
hnsID string
vethName string
ifName string
response *RequestCapture
expReq *cns.EndpointRequest
expReq map[string]*restserver.IPInfo
shouldErr bool
}{
{
"empty",
"",
"",
"",
"",
&RequestCapture{
Next: &mockdo{},
},
Expand All @@ -2730,13 +2732,17 @@ func TestUpdateEndpoint(t *testing.T) {
"foo",
"bar",
"",
"eth0",
&RequestCapture{
Next: &mockdo{
httpStatusCodeToReturn: http.StatusOK,
},
},
&cns.EndpointRequest{
HnsEndpointID: "bar",
map[string]*restserver.IPInfo{
"eth0": {
HnsEndpointID: "bar",
NICType: cns.InfraNIC,
},
},
false,
},
Expand All @@ -2745,13 +2751,17 @@ func TestUpdateEndpoint(t *testing.T) {
"foo",
"",
"bar",
"eth0",
&RequestCapture{
Next: &mockdo{
httpStatusCodeToReturn: http.StatusOK,
},
},
&cns.EndpointRequest{
HostVethName: "bar",
map[string]*restserver.IPInfo{
"eth0": {
HostVethName: "bar",
NICType: cns.InfraNIC,
},
},
false,
},
Expand All @@ -2760,13 +2770,17 @@ func TestUpdateEndpoint(t *testing.T) {
"foo",
"",
"bar",
"eth0",
&RequestCapture{
Next: &mockdo{
httpStatusCodeToReturn: http.StatusBadRequest,
},
},
&cns.EndpointRequest{
HostVethName: "bar",
map[string]*restserver.IPInfo{
"eth0": {
HostVethName: "bar",
NICType: cns.InfraNIC,
},
},
true,
},
Expand All @@ -2784,7 +2798,7 @@ func TestUpdateEndpoint(t *testing.T) {
}

// execute the method under test
res, err := client.UpdateEndpoint(context.TODO(), test.containerID, test.hnsID, test.vethName)
res, err := client.UpdateEndpoint(context.TODO(), test.containerID, test.expReq)
if err != nil && !test.shouldErr {
t.Fatal("unexpected error: err: ", err, res.Message)
}
Expand All @@ -2801,7 +2815,7 @@ func TestUpdateEndpoint(t *testing.T) {
// if a request was expected to be sent, decode it and ensure that it
// matches expectations
if test.expReq != nil {
var gotReq cns.EndpointRequest
var gotReq map[string]*restserver.IPInfo
err = json.NewDecoder(test.response.Request.Body).Decode(&gotReq)
if err != nil {
t.Fatal("error decoding the received request: err:", err)
Expand All @@ -2810,7 +2824,7 @@ func TestUpdateEndpoint(t *testing.T) {
// a nil expReq is semantically meaningful (i.e. "no request"), but in
// order for cmp to work properly, the outer types should be identical.
// Thus we have to dereference it explicitly:
expReq := *test.expReq
expReq := test.expReq

// ensure that the received request is what was expected
if !cmp.Equal(gotReq, expReq) {
Expand Down
46 changes: 32 additions & 14 deletions cns/restserver/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -1083,7 +1083,7 @@
func (service *HTTPRestService) UpdateEndpointHandler(w http.ResponseWriter, r *http.Request) {
logger.Printf("[updateEndpoint] updateEndpoint for %s", r.URL.Path)

var req cns.EndpointRequest
var req map[string]*IPInfo
err := service.Listener.Decode(w, r, &req)
endpointID := strings.TrimPrefix(r.URL.Path, cns.EndpointPath)
logger.Request(service.Name, &req, err)
Expand All @@ -1098,11 +1098,10 @@
logger.Response(service.Name, response, response.ReturnCode, err)
return
}
if req.HostVethName == "" && req.HnsEndpointID == "" {
logger.Warnf("[updateEndpoint] No HnsEndpointID or HostVethName has been provided")
if err = verifyUpdateEndpointStateRequest(req); err != nil {
response := cns.Response{
ReturnCode: types.InvalidRequest,
Message: "[updateEndpoint] No HnsEndpointID or HostVethName has been provided",
Message: err.Error(),
}
w.Header().Set(cnsReturnCode, response.ReturnCode.String())
err = service.Listener.Encode(w, &response)
Expand Down Expand Up @@ -1131,27 +1130,46 @@
}

// UpdateEndpointHelper updates the state of the given endpointId with HNSId or VethName
func (service *HTTPRestService) UpdateEndpointHelper(endpointID string, req cns.EndpointRequest) error {
func (service *HTTPRestService) UpdateEndpointHelper(endpointID string, req map[string]*IPInfo) error {
if service.EndpointStateStore == nil {
return ErrStoreEmpty
}
logger.Printf("[updateEndpoint] Updating endpoint state for infra container %s", endpointID)
if endpointInfo, ok := service.EndpointState[endpointID]; ok {
logger.Printf("[updateEndpoint] Found existing endpoint state for infra container %s", endpointID)
if req.HnsEndpointID != "" {
service.EndpointState[endpointID].HnsEndpointID = req.HnsEndpointID
logger.Printf("[updateEndpoint] update the endpoint %s with HNSID %s", endpointID, req.HnsEndpointID)
}
if req.HostVethName != "" {
service.EndpointState[endpointID].HostVethName = req.HostVethName
logger.Printf("[updateEndpoint] update the endpoint %s with vethName %s", endpointID, req.HostVethName)
for ifName, InterfaceInfo := range req {
logger.Printf("[updateEndpoint] Found existing endpoint state for infra container %s", endpointID)
if InterfaceInfo.HnsEndpointID != "" {
service.EndpointState[endpointID].IfnameToIPMap[ifName].HnsEndpointID = InterfaceInfo.HnsEndpointID
logger.Printf("[updateEndpoint] update the endpoint %s with HNSID %s", endpointID, InterfaceInfo.HnsEndpointID)
}
if InterfaceInfo.HostVethName != "" {
service.EndpointState[endpointID].IfnameToIPMap[ifName].HostVethName = InterfaceInfo.HostVethName
logger.Printf("[updateEndpoint] update the endpoint %s with vethName %s", endpointID, InterfaceInfo.HostVethName)
}
if InterfaceInfo.NICType != "" {
service.EndpointState[endpointID].IfnameToIPMap[ifName].NICType = InterfaceInfo.NICType
logger.Printf("[updateEndpoint] update the endpoint %s with NICType %s", endpointID, InterfaceInfo.NICType)
}
}

err := service.EndpointStateStore.Write(EndpointStoreKey, service.EndpointState)
if err != nil {
return fmt.Errorf("[updateEndpoint] failed to write endpoint state to store for pod %s : %w", endpointInfo.PodName, err)
}
return nil
}
return errors.New("[updateEndpoint] endpoint could not be found in the statefile")

Check failure on line 1161 in cns/restserver/ipam.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, ubuntu-latest)

File is not `gofumpt`-ed (gofumpt)

Check failure on line 1161 in cns/restserver/ipam.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

File is not `gofumpt`-ed (gofumpt)
}

// verifyUpdateEndpointStateRequest verify the CNI request body for the UpdateENdpointState API
func verifyUpdateEndpointStateRequest(req map[string]*IPInfo) error {
for ifName, InterfaceInfo := range req {
if InterfaceInfo.HostVethName == "" && InterfaceInfo.HnsEndpointID == "" && InterfaceInfo.NICType == "" {
return errors.New("[updateEndpoint] No NicType, HnsEndpointID or HostVethName has been provided")
}
if ifName == "" {
return errors.New("[updateEndpoint] No Interface has been provided")
}
}
return nil
}
9 changes: 5 additions & 4 deletions cns/restserver/restserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,14 @@ type EndpointInfo struct {
PodName string
PodNamespace string
IfnameToIPMap map[string]*IPInfo // key : interface name, value : IPInfo
HnsEndpointID string
HostVethName string
}

type IPInfo struct {
IPv4 []net.IPNet
IPv6 []net.IPNet
IPv4 []net.IPNet
IPv6 []net.IPNet
HnsEndpointID string
HostVethName string
behzad-mir marked this conversation as resolved.
Show resolved Hide resolved
NICType cns.NICType
}

type GetHTTPServiceDataResponse struct {
Expand Down
69 changes: 50 additions & 19 deletions network/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"sync"
"time"

"github.com/Azure/azure-container-networking/cns"
cnsclient "github.com/Azure/azure-container-networking/cns/client"
"github.com/Azure/azure-container-networking/cns/restserver"
"github.com/Azure/azure-container-networking/common"
"github.com/Azure/azure-container-networking/log"
"github.com/Azure/azure-container-networking/netio"
Expand Down Expand Up @@ -70,7 +72,7 @@

// NetworkManager manages the set of container networking resources.
type networkManager struct {
StatelessCniMode bool
statelessCniMode bool
CnsClient *cnsclient.Client
Version string
TimeStamp time.Time
Expand Down Expand Up @@ -149,7 +151,7 @@

// SetStatelessCNIMode enable the statelessCNI falg and inititlizes a CNSClient
func (nm *networkManager) SetStatelessCNIMode() error {
nm.StatelessCniMode = true
nm.statelessCniMode = true
// Create CNS client
client, err := cnsclient.New(cnsBaseURL, cnsReqTimeout)
if err != nil {
Expand All @@ -161,7 +163,7 @@

// IsStatelessCNIMode checks if the Stateless CNI mode has been enabled or not
func (nm *networkManager) IsStatelessCNIMode() bool {
return nm.StatelessCniMode
return nm.statelessCniMode
}

// Restore reads network manager state from persistent store.
Expand Down Expand Up @@ -421,8 +423,9 @@
// UpdateEndpointState will make a call to CNS updatEndpointState API in the stateless CNI mode
// It will add HNSEndpointID or HostVeth name to the endpoint state
func (nm *networkManager) UpdateEndpointState(ep *endpoint) error {
ifnameToIPInfoMap := generateCNSIPInfoMap(ep) // key : interface name, value : IPInfo
logger.Info("Calling cns updateEndpoint API with ", zap.String("containerID: ", ep.ContainerID), zap.String("HnsId: ", ep.HnsId), zap.String("HostIfName: ", ep.HostIfName))
response, err := nm.CnsClient.UpdateEndpoint(context.TODO(), ep.ContainerID, ep.HnsId, ep.HostIfName)
response, err := nm.CnsClient.UpdateEndpoint(context.TODO(), ep.ContainerID, ifnameToIPInfoMap)
if err != nil {
return errors.Wrapf(err, "Update endpoint API returend with error")
}
Expand All @@ -437,28 +440,15 @@
if err != nil {
return nil, errors.Wrapf(err, "Get endpoint API returend with error")
}
epInfo := &EndpointInfo{
Id: endpointID,
IfIndex: EndpointIfIndex, // Azure CNI supports only one interface
IfName: endpointResponse.EndpointInfo.HostVethName,
ContainerID: endpointID,
PODName: endpointResponse.EndpointInfo.PodName,
PODNameSpace: endpointResponse.EndpointInfo.PodNamespace,
NetworkContainerID: endpointID,
HNSEndpointID: endpointResponse.EndpointInfo.HnsEndpointID,
}

for _, ip := range endpointResponse.EndpointInfo.IfnameToIPMap {
epInfo.IPAddresses = ip.IPv4
epInfo.IPAddresses = append(epInfo.IPAddresses, ip.IPv6...)
epInfo := cnsEndpointInfotoCNIEpInfo(endpointResponse.EndpointInfo, endpointID)

}
if epInfo.IsEndpointStateIncomplete() {
epInfo, err = epInfo.GetEndpointInfoByIPImpl(epInfo.IPAddresses, networkID)
if err != nil {
return nil, errors.Wrapf(err, "Get endpoint API returend with error")
}
}

logger.Info("returning getEndpoint API with", zap.String("Endpoint Info: ", epInfo.PrettyString()), zap.String("HNISID : ", epInfo.HNSEndpointID))
return epInfo, nil
}
Expand Down Expand Up @@ -698,3 +688,44 @@
}
return containerID + "-" + ifName
}

func cnsEndpointInfotoCNIEpInfo(endpointInfo restserver.EndpointInfo, endpointID string) *EndpointInfo {
epInfo := &EndpointInfo{
Id: endpointID,
IfIndex: EndpointIfIndex, // Azure CNI supports only one interface
ContainerID: endpointID,
PODName: endpointInfo.PodName,
PODNameSpace: endpointInfo.PodNamespace,
NetworkContainerID: endpointID,
}

for ifName, ipInfo := range endpointInfo.IfnameToIPMap {
if ifName != InfraInterfaceName {
// TODO: filling out the SecondaryNICs from the state for Swift 2.0
continue
}
// filling out the InfraNIC from the state
epInfo.IPAddresses = ipInfo.IPv4
tamilmani1989 marked this conversation as resolved.
Show resolved Hide resolved
epInfo.IPAddresses = append(epInfo.IPAddresses, ipInfo.IPv6...)
epInfo.IfName = ifName
epInfo.HostIfName = ipInfo.HostVethName
epInfo.HNSEndpointID = ipInfo.HnsEndpointID
}
return epInfo
}

func generateCNSIPInfoMap(ep *endpoint) map[string]*restserver.IPInfo {
ifNametoIPInfoMap := make(map[string]*restserver.IPInfo) // key : interface name, value : IPInfo
if ep.IfName != "" {
ifNametoIPInfoMap[ep.IfName].NICType = cns.InfraNIC
ifNametoIPInfoMap[ep.IfName].HnsEndpointID = ep.HnsId
ifNametoIPInfoMap[ep.IfName].HostVethName = ep.HostIfName
}
if ep.SecondaryInterfaces != nil {
for ifName, InterfaceInfo := range ep.SecondaryInterfaces {
ifNametoIPInfoMap[ifName].NICType = InterfaceInfo.NICType
}

Check failure on line 728 in network/manager.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, ubuntu-latest)

File is not `gofumpt`-ed (gofumpt)

Check failure on line 728 in network/manager.go

View workflow job for this annotation

GitHub Actions / Lint (1.21.x, windows-latest)

File is not `gofumpt`-ed (gofumpt)
}
return ifNametoIPInfoMap
}
Loading