npm/pkg/dataplane/ipsets/ipsetmanager_windows.go (402 lines of code) (raw):
package ipsets
import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/Azure/azure-container-networking/npm/metrics"
"github.com/Azure/azure-container-networking/npm/util"
npmerrors "github.com/Azure/azure-container-networking/npm/util/errors"
"github.com/Microsoft/hcsshim/hcn"
"k8s.io/klog"
)
const (
// SetPolicyTypeNestedIPSet as a temporary measure adding it here
// update HCSShim to version 0.9.23 or above to support nestedIPSets
SetPolicyTypeNestedIPSet hcn.SetPolicyType = "NESTEDIPSET"
resetIPSetsTrue = true
donotResetIPSets = false
)
var errUnsupportedNetwork = errors.New("only 'azure' and 'Calico' networks are supported")
type networkPolicyBuilder struct {
toAddSets map[string]*hcn.SetPolicySetting
toUpdateSets map[string]*hcn.SetPolicySetting
toDeleteSets map[string]*hcn.SetPolicySetting
}
func (iMgr *IPSetManager) DoesIPSatisfySelectorIPSets(ip, podKey string, setList map[string]struct{}) (bool, error) {
if len(setList) == 0 {
klog.Infof("[ipset manager] unexpectedly encountered empty selector list")
return true, nil
}
iMgr.Lock()
defer iMgr.Unlock()
if err := iMgr.validateSelectorIPSets(setList); err != nil {
return false, err
}
for setName := range setList {
set := iMgr.setMap[setName]
if !set.isIPAffiliated(ip, podKey) {
return false, nil
}
}
return true, nil
}
// GetIPsFromSelectorIPSets will take in a map of prefixedSetNames and return an intersection of IPs mapped to pod key
func (iMgr *IPSetManager) GetIPsFromSelectorIPSets(setList map[string]struct{}) (map[string]string, error) {
ips := make(map[string]string)
if len(setList) == 0 {
return ips, nil
}
iMgr.Lock()
defer iMgr.Unlock()
if err := iMgr.validateSelectorIPSets(setList); err != nil {
return nil, err
}
// the following is a space/time optimized way to get the intersection of IPs from the selector sets
// we should always take the hash set branch because a pod selector always includes a namespace ipset,
// which is a hash set, and we favor hash sets for firstSet
var firstSet *IPSet
for setName := range setList {
firstSet = iMgr.setMap[setName]
if firstSet.Kind == HashSet {
// firstSet can be any set, but ideally is a hash set for efficiency (compare the branch for hash sets to the one for lists below)
break
}
}
if firstSet.Kind == HashSet {
// include every IP in firstSet that is also affiliated with every other selector set
for ip, podKey := range firstSet.IPPodKey {
isAffiliated := true
for otherSetName := range setList {
if otherSetName == firstSet.Name {
continue
}
otherSet := iMgr.setMap[otherSetName]
if !otherSet.isIPAffiliated(ip, podKey) {
isAffiliated = false
break
}
}
if isAffiliated {
ips[ip] = podKey
}
}
} else {
// should never reach this branch (see note above)
// include every IP affiliated with firstSet that is also affiliated with every other selector set
// identical to the hash set case, except we have to make space for all IPs affiliated with firstSet
// only loop over the unique affiliated IPs
for _, memberSet := range firstSet.MemberIPSets {
for ip, podKey := range memberSet.IPPodKey {
if oldKey, ok := ips[ip]; ok && oldKey != podKey {
// this could lead to unintentionally considering this Pod (Pod B) to be part of the selector set if:
// 1. Pod B has the same IP as a previous Pod A
// 2. Pod B create is somehow processed before Pod A delete
// 3. This method is called before Pod A delete
// again, this
klog.Warningf("[GetIPsFromSelectorIPSets] IP currently associated with two different pod keys. to ensure no issues occur with network policies, restart this ip: %s", ip)
}
ips[ip] = podKey
}
}
for ip, podKey := range ips {
// identical to the hash set case
isAffiliated := true
for otherSetName := range setList {
if otherSetName == firstSet.Name {
continue
}
otherSet := iMgr.setMap[otherSetName]
if !otherSet.isIPAffiliated(ip, podKey) {
isAffiliated = false
break
}
}
if !isAffiliated {
delete(ips, ip)
}
}
}
return ips, nil
}
func (iMgr *IPSetManager) GetSelectorReferencesBySet(setName string) (map[string]struct{}, error) {
iMgr.Lock()
defer iMgr.Unlock()
if !iMgr.exists(setName) {
return nil, npmerrors.Errorf(
npmerrors.GetSelectorReference,
false,
fmt.Sprintf("[ipset manager] selector ipset %s does not exist", setName))
}
set := iMgr.setMap[setName]
m := make(map[string]struct{}, len(set.SelectorReference))
for r := range set.SelectorReference {
m[r] = struct{}{}
}
return m, nil
}
func (iMgr *IPSetManager) validateSelectorIPSets(setList map[string]struct{}) error {
for setName := range setList {
if !iMgr.exists(setName) {
return npmerrors.Errorf(
npmerrors.GetSelectorReference,
false,
fmt.Sprintf("[ipset manager] selector ipset %s does not exist", setName))
}
set := iMgr.setMap[setName]
if !set.canSetBeSelectorIPSet() {
return npmerrors.Errorf(
npmerrors.IPSetIntersection,
false,
fmt.Sprintf("[IPSet] Selector IPSet cannot be of type %s", set.Type.String()))
}
}
return nil
}
func (iMgr *IPSetManager) resetIPSets() error {
klog.Infof("[IPSetManager Windows] Resetting Dataplane")
network, err := iMgr.getHCnNetwork()
if err != nil {
return err
}
_, toDeleteSets := iMgr.segregateSetPolicies(network.Policies, resetIPSetsTrue)
if len(toDeleteSets) == 0 {
klog.Infof("[IPSetManager Windows] No IPSets to delete")
return nil
}
klog.Infof("[IPSetManager Windows] Deleting %d Set Policies", len(toDeleteSets))
err = iMgr.modifySetPolicies(network, hcn.RequestTypeRemove, toDeleteSets)
if err != nil {
klog.Infof("[IPSetManager Windows] Update set policies failed with error %s", err.Error())
return err
}
return nil
}
func (iMgr *IPSetManager) applyIPSets() error {
network, err := iMgr.getHCnNetwork()
if err != nil {
return err
}
setPolicyBuilder, err := iMgr.calculateNewSetPolicies(network.Policies)
if err != nil {
return err
}
if len(setPolicyBuilder.toAddSets) > 0 {
err = iMgr.modifySetPolicies(network, hcn.RequestTypeAdd, setPolicyBuilder.toAddSets)
if err != nil {
klog.Infof("[IPSetManager Windows] Add set policies failed with error %s", err.Error())
return err
}
}
if len(setPolicyBuilder.toUpdateSets) > 0 {
err = iMgr.modifySetPolicies(network, hcn.RequestTypeUpdate, setPolicyBuilder.toUpdateSets)
if err != nil {
klog.Infof("[IPSetManager Windows] Update set policies failed with error %s", err.Error())
return err
}
}
iMgr.dirtyCache.resetAddOrUpdateCache()
if len(setPolicyBuilder.toDeleteSets) > 0 {
err = iMgr.modifySetPolicies(network, hcn.RequestTypeRemove, setPolicyBuilder.toDeleteSets)
if err != nil {
klog.Infof("[IPSetManager Windows] Delete set policies failed with error %s", err.Error())
return err
}
}
klog.Info("[IPSetManager Windows] Done applying IPSets.")
iMgr.clearDirtyCache()
return nil
}
// calculateNewSetPolicies will take in existing setPolicies on network in HNS and the dirty cache, will return back
// networkPolicyBuild which contains the new setPolicies to be added, updated and deleted
// Assumes that the dirty cache is locked (or equivalently, the ipsetmanager itself).
// toAddSets:
//
// this function will loop through the dirty cache and adds non-existing sets to toAddSets
//
// toUpdateSets:
//
// this function will loop through the dirty cache and adds existing sets in HNS to toUpdateSets
// this function will update all existing sets in HNS with their latest goal state irrespective of any change to the object
//
// toDeleteSets:
//
// this function will loop through the dirty delete cache and adds existing set obj in HNS to toDeleteSets
func (iMgr *IPSetManager) calculateNewSetPolicies(networkPolicies []hcn.NetworkPolicy) (*networkPolicyBuilder, error) {
setPolicyBuilder := &networkPolicyBuilder{
toAddSets: map[string]*hcn.SetPolicySetting{},
toUpdateSets: map[string]*hcn.SetPolicySetting{},
toDeleteSets: map[string]*hcn.SetPolicySetting{},
}
existingSets, toDeleteSets := iMgr.segregateSetPolicies(networkPolicies, donotResetIPSets)
// some of this below logic can be abstracted a step above
toAddUpdateSetNames := iMgr.dirtyCache.setsToAddOrUpdate()
setPolicyBuilder.toDeleteSets = toDeleteSets
// for faster look up changing a slice to map
existingSetNames := make(map[string]struct{})
for _, setName := range existingSets {
existingSetNames[setName] = struct{}{}
}
for setName := range toAddUpdateSetNames {
set, exists := iMgr.setMap[setName] // check if the Set exists
if !exists {
return nil, npmerrors.Errorf(npmerrors.AppendIPSet, false, fmt.Sprintf("ipset %s does not exist", setName))
}
setPol, err := convertToSetPolicy(set)
if err != nil {
return nil, err
}
// TODO we should add members first and then the Lists
_, ok := existingSetNames[setName]
if ok {
setPolicyBuilder.toUpdateSets[setName] = setPol
} else {
setPolicyBuilder.toAddSets[setName] = setPol
}
if set.Kind == ListSet {
for _, memberSet := range set.MemberIPSets {
// Always use prefixed name because we read setpolicy Name from HNS
if setPolicyBuilder.setNameExists(memberSet.Name) {
continue
}
setPol, err = convertToSetPolicy(memberSet)
if err != nil {
return nil, err
}
_, ok := existingSetNames[memberSet.Name]
if !ok {
setPolicyBuilder.toAddSets[memberSet.Name] = setPol
}
}
}
}
return setPolicyBuilder, nil
}
func (iMgr *IPSetManager) getHCnNetwork() (*hcn.HostComputeNetwork, error) {
if iMgr.iMgrCfg.NetworkName == "" {
iMgr.iMgrCfg.NetworkName = util.AzureNetworkName
}
if iMgr.iMgrCfg.NetworkName != util.AzureNetworkName && iMgr.iMgrCfg.NetworkName != util.CalicoNetworkName {
return nil, errUnsupportedNetwork
}
timer := metrics.StartNewTimer()
network, err := iMgr.ioShim.Hns.GetNetworkByName(iMgr.iMgrCfg.NetworkName)
metrics.RecordGetNetworkLatency(timer)
if err != nil {
metrics.IncGetNetworkFailures()
return nil, err
}
return network, nil
}
func (iMgr *IPSetManager) modifySetPolicies(network *hcn.HostComputeNetwork, operation hcn.RequestType, setPolicies map[string]*hcn.SetPolicySetting) error {
klog.Infof("[IPSetManager Windows] %s operation on set policies is called", operation)
/*
Due to complexities in HNS, we need to do the following:
for (Add)
1. Add 1st level set policies to HNS
2. then add nested set policies to HNS
for (delete)
1. delete nested set policies from HNS
2. then delete 1st level set policies from HNS
*/
policySettingsOrder := []hcn.SetPolicyType{hcn.SetPolicyTypeIpSet, SetPolicyTypeNestedIPSet}
if operation == hcn.RequestTypeRemove {
policySettingsOrder = []hcn.SetPolicyType{SetPolicyTypeNestedIPSet, hcn.SetPolicyTypeIpSet}
}
for _, policyType := range policySettingsOrder {
policyRequest, err := getPolicyNetworkRequestMarshal(setPolicies, policyType)
if err != nil {
klog.Infof("[IPSetManager Windows] Failed to marshal %s operations sets with error %s", operation, err.Error())
return err
}
if policyRequest == nil {
continue
}
requestMessage := &hcn.ModifyNetworkSettingRequest{
ResourceType: hcn.NetworkResourceTypePolicy,
RequestType: operation,
Settings: policyRequest,
}
klog.Infof("[IPSetManager Windows] modifying network settings. operation: %s, policyType: %s", operation, policyType)
// metrics.CreateOp would be for hcn.RequestTypeAdd
op := metrics.CreateOp
if operation == hcn.RequestTypeRemove {
op = metrics.DeleteOp
} else if operation == hcn.RequestTypeUpdate {
op = metrics.UpdateOp
}
isNested := false
if policyType == SetPolicyTypeNestedIPSet {
isNested = true
}
timer := metrics.StartNewTimer()
err = iMgr.ioShim.Hns.ModifyNetworkSettings(network, requestMessage)
metrics.RecordSetPolicyLatency(timer, op, isNested)
if err != nil {
metrics.IncSetPolicyFailures(op, isNested)
klog.Infof("[IPSetManager Windows] %s operation has failed with error %s", operation, err.Error())
return err
}
}
return nil
}
func (iMgr *IPSetManager) segregateSetPolicies(networkPolicies []hcn.NetworkPolicy, reset bool) (toUpdateSets []string, toDeleteSets map[string]*hcn.SetPolicySetting) {
toDeleteSets = make(map[string]*hcn.SetPolicySetting)
toUpdateSets = make([]string, 0)
for _, netpol := range networkPolicies {
if netpol.Type != hcn.SetPolicy {
continue
}
var set hcn.SetPolicySetting
err := json.Unmarshal(netpol.Settings, &set)
if err != nil {
klog.Error(err.Error())
continue
}
if !strings.HasPrefix(set.Id, util.AzureNpmPrefix) {
continue
}
ok := iMgr.dirtyCache.isSetToDelete(set.Name)
if !ok && !reset {
// if the set is not in delete cache, go ahead and add it to update cache
toUpdateSets = append(toUpdateSets, set.Name)
continue
}
// if set is in delete cache, add it to deleteSets
toDeleteSets[set.Name] = &set
}
return
}
func (setPolicyBuilder *networkPolicyBuilder) setNameExists(setName string) bool {
_, ok := setPolicyBuilder.toAddSets[setName]
if ok {
return true
}
_, ok = setPolicyBuilder.toUpdateSets[setName]
return ok
}
func getPolicyNetworkRequestMarshal(setPolicySettings map[string]*hcn.SetPolicySetting, policyType hcn.SetPolicyType) ([]byte, error) {
if len(setPolicySettings) == 0 {
klog.Info("[Dataplane Windows] no set policies to apply on network")
return nil, nil
}
klog.Infof("[Dataplane Windows] marshalling %s(s)", policyType)
policyNetworkRequest := &hcn.PolicyNetworkRequest{
Policies: make([]hcn.NetworkPolicy, 0),
}
for _, setPol := range setPolicySettings {
if setPol.Type != policyType {
continue
}
rawSettings, err := json.Marshal(setPol)
if err != nil {
return nil, err
}
policyNetworkRequest.Policies = append(
policyNetworkRequest.Policies,
hcn.NetworkPolicy{
Type: hcn.SetPolicy,
Settings: rawSettings,
},
)
}
if len(policyNetworkRequest.Policies) == 0 {
klog.Infof("[Dataplane Windows] no %s type of sets to apply", policyType)
return nil, nil
}
policyReqSettings, err := json.Marshal(policyNetworkRequest)
if err != nil {
return nil, err
}
return policyReqSettings, nil
}
func isValidIPSet(set *IPSet) error {
if set.Name == "" {
return fmt.Errorf("IPSet " + set.Name + " is missing Name")
}
if set.Type == UnknownType {
return fmt.Errorf("IPSet " + set.Type.String() + " is missing Type")
}
if set.HashedName == "" {
return fmt.Errorf("IPSet " + set.HashedName + " is missing HashedName")
}
return nil
}
func getSetPolicyType(set *IPSet) hcn.SetPolicyType {
switch set.Kind {
case ListSet:
return SetPolicyTypeNestedIPSet
case HashSet:
return hcn.SetPolicyTypeIpSet
default:
return "Unknown"
}
}
func convertToSetPolicy(set *IPSet) (*hcn.SetPolicySetting, error) {
err := isValidIPSet(set)
if err != nil {
return &hcn.SetPolicySetting{}, err
}
setContents, err := set.GetSetContents()
if err != nil {
return &hcn.SetPolicySetting{}, err
}
setPolicy := &hcn.SetPolicySetting{
Id: set.HashedName,
Name: set.Name,
Type: getSetPolicyType(set),
Values: util.SliceToString(setContents),
}
return setPolicy, nil
}