diff --git a/common/ioshim_windows.go b/common/ioshim_windows.go index 9dd5ceb82c..56878a2005 100644 --- a/common/ioshim_windows.go +++ b/common/ioshim_windows.go @@ -9,6 +9,8 @@ import ( utilexec "k8s.io/utils/exec" ) +const FakeHNSNetworkID = "1234" + type IOShim struct { Exec utilexec.Interface Hns hnswrapper.HnsV2WrapperInterface @@ -24,7 +26,7 @@ func NewIOShim() *IOShim { func NewMockIOShim(calls []testutils.TestCmd) *IOShim { hns := hnswrapper.NewHnsv2wrapperFake() network := &hcn.HostComputeNetwork{ - Id: "1234", + Id: FakeHNSNetworkID, Name: "azure", } diff --git a/network/hnswrapper/hnsv2wrapperfake.go b/network/hnswrapper/hnsv2wrapperfake.go index bc6a9f9775..50f392c884 100644 --- a/network/hnswrapper/hnsv2wrapperfake.go +++ b/network/hnswrapper/hnsv2wrapperfake.go @@ -317,10 +317,13 @@ func (f Hnsv2wrapperFake) GetEndpointByName(endpointName string) (*hcn.HostCompu } type FakeHNSCache struct { - networks map[string]*FakeHostComputeNetwork + // networks maps network name to network object + networks map[string]*FakeHostComputeNetwork + // endpoints maps endpoint ID to endpoint object endpoints map[string]*FakeHostComputeEndpoint } +// SetPolicy returns the first SetPolicy found with this ID in any network. func (fCache FakeHNSCache) SetPolicy(setID string) *hcn.SetPolicySetting { for _, network := range fCache.networks { for _, policy := range network.Policies { @@ -332,6 +335,35 @@ func (fCache FakeHNSCache) SetPolicy(setID string) *hcn.SetPolicySetting { return nil } +func (fCache FakeHNSCache) PrettyString() string { + networkStrings := make([]string, 0, len(fCache.networks)) + for _, network := range fCache.networks { + networkStrings = append(networkStrings, fmt.Sprintf("[%+v]", network.PrettyString())) + } + + endpointStrings := make([]string, 0, len(fCache.endpoints)) + for _, endpoint := range fCache.endpoints { + endpointStrings = append(endpointStrings, fmt.Sprintf("[%+v]", endpoint.PrettyString())) + } + + return fmt.Sprintf("networks: %s\nendpoints: %s", strings.Join(networkStrings, ","), strings.Join(endpointStrings, ",")) +} + +// AllSetPolicies returns all SetPolicies in a given network as a map of SetPolicy ID to SetPolicy object. +func (fCache FakeHNSCache) AllSetPolicies(networkID string) map[string]*hcn.SetPolicySetting { + setPolicies := make(map[string]*hcn.SetPolicySetting) + for _, network := range fCache.networks { + if network.ID == networkID { + for _, setPolicy := range network.Policies { + setPolicies[setPolicy.Id] = setPolicy + } + break + } + } + return setPolicies +} + +// ACLPolicies returns a map of the inputed Endpoint IDs to Policies with the given policyID. func (fCache FakeHNSCache) ACLPolicies(epList map[string]string, policyID string) (map[string][]*FakeEndpointPolicy, error) { aclPols := make(map[string][]*FakeEndpointPolicy) for ip, epID := range epList { @@ -354,6 +386,7 @@ func (fCache FakeHNSCache) ACLPolicies(epList map[string]string, policyID string return aclPols, nil } +// GetAllACLs maps all Endpoint IDs to ACLs func (fCache FakeHNSCache) GetAllACLs() map[string][]*FakeEndpointPolicy { aclPols := make(map[string][]*FakeEndpointPolicy) for _, ep := range fCache.endpoints { @@ -362,9 +395,20 @@ func (fCache FakeHNSCache) GetAllACLs() map[string][]*FakeEndpointPolicy { return aclPols } +// EndpointIP returns the Endpoint's IP or an empty string if the Endpoint doesn't exist. +func (fCache FakeHNSCache) EndpointIP(id string) string { + for _, ep := range fCache.endpoints { + if ep.ID == id { + return ep.IPConfiguration + } + } + return "" +} + type FakeHostComputeNetwork struct { - ID string - Name string + ID string + Name string + // Policies maps SetPolicy ID to SetPolicy object Policies map[string]*hcn.SetPolicySetting } @@ -376,10 +420,33 @@ func NewFakeHostComputeNetwork(network *hcn.HostComputeNetwork) *FakeHostCompute } } +func (fNetwork *FakeHostComputeNetwork) PrettyString() string { + setPolicyStrings := make([]string, 0, len(fNetwork.Policies)) + for _, setPolicy := range fNetwork.Policies { + setPolicyStrings = append(setPolicyStrings, fmt.Sprintf("[%+v]", setPolicy)) + } + return fmt.Sprintf("ID: %s, Name: %s, SetPolicies: [%s]", fNetwork.ID, fNetwork.Name, strings.Join(setPolicyStrings, ",")) +} + func (fNetwork *FakeHostComputeNetwork) GetHCNObj() *hcn.HostComputeNetwork { + setPolicies := make([]hcn.NetworkPolicy, 0) + for _, setPolicy := range fNetwork.Policies { + rawSettings, err := json.Marshal(setPolicy) + if err != nil { + fmt.Printf("FakeHostComputeNetwork: error marshalling SetPolicy: %+v. err: %s\n", setPolicy, err.Error()) + continue + } + policy := hcn.NetworkPolicy{ + Type: hcn.SetPolicy, + Settings: rawSettings, + } + setPolicies = append(setPolicies, policy) + } + return &hcn.HostComputeNetwork{ - Id: fNetwork.ID, - Name: fNetwork.Name, + Id: fNetwork.ID, + Name: fNetwork.Name, + Policies: setPolicies, } } @@ -404,29 +471,41 @@ func NewFakeHostComputeEndpoint(endpoint *hcn.HostComputeEndpoint) *FakeHostComp } } -func (fEndpoint *FakeHostComputeEndpoint) GetHCNObj() *hcn.HostComputeEndpoint { - // NOTE: not including other policy types like perhaps SetPolicies - hcnEndpoint := &hcn.HostComputeEndpoint{ - Id: fEndpoint.ID, - Name: fEndpoint.Name, - HostComputeNetwork: fEndpoint.HostComputeNetwork, - Policies: make([]hcn.EndpointPolicy, 0), +func (fEndpoint *FakeHostComputeEndpoint) PrettyString() string { + aclStrings := make([]string, 0, len(fEndpoint.Policies)) + for _, acl := range fEndpoint.Policies { + aclStrings = append(aclStrings, fmt.Sprintf("[%+v]", acl)) } + return fmt.Sprintf("ID: %s, Name: %s, IP: %s, ACLs: [%s]", + fEndpoint.ID, fEndpoint.Name, fEndpoint.IPConfiguration, strings.Join(aclStrings, ",")) +} - for _, fakeEndpointPol := range fEndpoint.Policies { - rawJSON, err := json.Marshal(fakeEndpointPol) +func (fEndpoint *FakeHostComputeEndpoint) GetHCNObj() *hcn.HostComputeEndpoint { + acls := make([]hcn.EndpointPolicy, 0) + for _, acl := range fEndpoint.Policies { + rawSettings, err := json.Marshal(acl) if err != nil { - fmt.Printf("FAILURE marshalling fake endpoint policy: %s\n", err.Error()) - } else { - hcnPolicy := hcn.EndpointPolicy{ - Type: hcn.ACL, - Settings: rawJSON, - } - hcnEndpoint.Policies = append(hcnEndpoint.Policies, hcnPolicy) + fmt.Printf("FakeHostComputeEndpoint: error marshalling ACL: %+v. err: %s\n", acl, err.Error()) + continue + } + policy := hcn.EndpointPolicy{ + Type: hcn.ACL, + Settings: rawSettings, } + acls = append(acls, policy) } - return hcnEndpoint + return &hcn.HostComputeEndpoint{ + Id: fEndpoint.ID, + Name: fEndpoint.Name, + HostComputeNetwork: fEndpoint.HostComputeNetwork, + IpConfigurations: []hcn.IpConfig{ + { + IpAddress: fEndpoint.IPConfiguration, + }, + }, + Policies: acls, + } } func (fEndpoint *FakeHostComputeEndpoint) RemovePolicy(toRemovePol *FakeEndpointPolicy) error { diff --git a/npm/pkg/controlplane/controllers/v2/podController.go b/npm/pkg/controlplane/controllers/v2/podController.go index b9a84e0b7b..83877c3cd7 100644 --- a/npm/pkg/controlplane/controllers/v2/podController.go +++ b/npm/pkg/controlplane/controllers/v2/podController.go @@ -573,6 +573,8 @@ func (c *PodController) cleanUpDeletedPod(cachedNpmPodKey string) error { func (c *PodController) manageNamedPortIpsets(portList []corev1.ContainerPort, podKey, podIP, nodeName string, namedPortOperation NamedPortOperation) error { if util.IsWindowsDP() { + // NOTE: if we support namedport operations, need to be careful of implications of including the node name in the pod metadata below + // since we say the node name is "" in cleanUpDeletedPod klog.Warningf("Windows Dataplane does not support NamedPort operations. Operation: %s portList is %+v", namedPortOperation, portList) return nil } diff --git a/npm/pkg/dataplane/dataplane-test-cases_windows_test.go b/npm/pkg/dataplane/dataplane-test-cases_windows_test.go new file mode 100644 index 0000000000..0812682fba --- /dev/null +++ b/npm/pkg/dataplane/dataplane-test-cases_windows_test.go @@ -0,0 +1,448 @@ +package dataplane + +import ( + "github.com/Azure/azure-container-networking/network/hnswrapper" + "github.com/Azure/azure-container-networking/npm/pkg/dataplane/ipsets" + "github.com/Azure/azure-container-networking/npm/pkg/dataplane/policies" + dptestutils "github.com/Azure/azure-container-networking/npm/pkg/dataplane/testutils" + "github.com/Microsoft/hcsshim/hcn" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// tags +const ( + podCrudTag Tag = "pod-crud" + nsCrudTag Tag = "namespace-crud" + netpolCrudTag Tag = "netpol-crud" +) + +const ( + thisNode = "this-node" + otherNode = "other-node" + + ip1 = "10.0.0.1" + ip2 = "10.0.0.2" + + endpoint1 = "test1" + endpoint2 = "test2" +) + +// IPSet constants +var ( + podK1Set = ipsets.NewIPSetMetadata("k1", ipsets.KeyLabelOfPod) + podK1V1Set = ipsets.NewIPSetMetadata("k1:v1", ipsets.KeyValueLabelOfPod) + podK2Set = ipsets.NewIPSetMetadata("k2", ipsets.KeyLabelOfPod) + podK2V2Set = ipsets.NewIPSetMetadata("k2:v2", ipsets.KeyValueLabelOfPod) + + // emptySet is a member of a list if enabled in the dp Config + // in Windows, this Config option is actually forced to be enabled in NewDataPlane() + emptySet = ipsets.NewIPSetMetadata("emptyhashset", ipsets.EmptyHashSet) + allNamespaces = ipsets.NewIPSetMetadata("all-namespaces", ipsets.KeyLabelOfNamespace) + nsXSet = ipsets.NewIPSetMetadata("x", ipsets.Namespace) + nsYSet = ipsets.NewIPSetMetadata("y", ipsets.Namespace) + + nsK1Set = ipsets.NewIPSetMetadata("k1", ipsets.KeyLabelOfNamespace) + nsK1V1Set = ipsets.NewIPSetMetadata("k1:v1", ipsets.KeyValueLabelOfNamespace) + nsK2Set = ipsets.NewIPSetMetadata("k2", ipsets.KeyLabelOfNamespace) + nsK2V2Set = ipsets.NewIPSetMetadata("k2:v2", ipsets.KeyValueLabelOfNamespace) +) + +// DP Configs +var ( + defaultWindowsDPCfg = &Config{ + IPSetManagerCfg: &ipsets.IPSetManagerCfg{ + IPSetMode: ipsets.ApplyAllIPSets, + AddEmptySetToLists: true, + }, + PolicyManagerCfg: &policies.PolicyManagerCfg{ + PolicyMode: policies.IPSetPolicyMode, + }, + } +) + +func policyXBaseOnK1V1() *networkingv1.NetworkPolicy { + return &networkingv1.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "base", + Namespace: "x", + }, + Spec: networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "k1": "v1", + }, + }, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + Egress: []networkingv1.NetworkPolicyEgressRule{ + {}, + }, + PolicyTypes: []networkingv1.PolicyType{ + networkingv1.PolicyTypeIngress, + networkingv1.PolicyTypeEgress, + }, + }, + } +} + +func getAllSerialTests() []*SerialTestCase { + return []*SerialTestCase{ + { + Description: "pod created", + Actions: []*Action{ + CreateEndpoint(endpoint1, ip1), + CreatePod("x", "a", ip1, thisNode, map[string]string{"k1": "v1"}), + ApplyDP(), + }, + TestCaseMetadata: &TestCaseMetadata{ + Tags: []Tag{ + podCrudTag, + }, + DpCfg: defaultWindowsDPCfg, + InitialEndpoints: nil, + ExpectedSetPolicies: []*hcn.SetPolicySetting{ + dptestutils.SetPolicy(emptySet), + dptestutils.SetPolicy(allNamespaces, emptySet.GetHashedName(), nsXSet.GetHashedName()), + dptestutils.SetPolicy(nsXSet, ip1), + dptestutils.SetPolicy(podK1Set, ip1), + dptestutils.SetPolicy(podK1V1Set, ip1), + }, + ExpectedEnpdointACLs: map[string][]*hnswrapper.FakeEndpointPolicy{ + endpoint1: {}, + }, + }, + }, + { + Description: "pod created, then pod deleted", + Actions: []*Action{ + CreateEndpoint(endpoint1, ip1), + CreatePod("x", "a", ip1, thisNode, map[string]string{"k1": "v1"}), + ApplyDP(), + DeleteEndpoint(endpoint1), + DeletePod("x", "a", ip1, map[string]string{"k1": "v1"}), + ApplyDP(), + }, + TestCaseMetadata: &TestCaseMetadata{ + Tags: []Tag{ + podCrudTag, + }, + DpCfg: defaultWindowsDPCfg, + InitialEndpoints: nil, + ExpectedSetPolicies: []*hcn.SetPolicySetting{ + dptestutils.SetPolicy(emptySet), + dptestutils.SetPolicy(allNamespaces, emptySet.GetHashedName(), nsXSet.GetHashedName()), + dptestutils.SetPolicy(nsXSet), + dptestutils.SetPolicy(podK1Set), + dptestutils.SetPolicy(podK1V1Set), + }, + ExpectedEnpdointACLs: nil, + }, + }, + { + Description: "pod created, then pod deleted, then ipsets garbage collected", + Actions: []*Action{ + CreateEndpoint(endpoint1, ip1), + CreatePod("x", "a", ip1, thisNode, map[string]string{"k1": "v1"}), + ApplyDP(), + DeleteEndpoint(endpoint1), + DeletePod("x", "a", ip1, map[string]string{"k1": "v1"}), + ApplyDP(), + ReconcileDP(), + ApplyDP(), + }, + TestCaseMetadata: &TestCaseMetadata{ + Tags: []Tag{ + podCrudTag, + }, + DpCfg: defaultWindowsDPCfg, + InitialEndpoints: nil, + ExpectedSetPolicies: []*hcn.SetPolicySetting{ + dptestutils.SetPolicy(emptySet), + dptestutils.SetPolicy(allNamespaces, emptySet.GetHashedName(), nsXSet.GetHashedName()), + dptestutils.SetPolicy(nsXSet), + }, + ExpectedEnpdointACLs: nil, + }, + }, + { + Description: "policy created with no pods", + Actions: []*Action{ + UpdatePolicy(policyXBaseOnK1V1()), + }, + TestCaseMetadata: &TestCaseMetadata{ + Tags: []Tag{ + netpolCrudTag, + }, + DpCfg: defaultWindowsDPCfg, + InitialEndpoints: nil, + ExpectedSetPolicies: []*hcn.SetPolicySetting{ + // will not be an all-namespaces IPSet unless there's a Pod/Namespace event + dptestutils.SetPolicy(nsXSet), + // Policies do not create the KeyLabelOfPod type IPSet if the selector has a key-value requirement + dptestutils.SetPolicy(podK1V1Set), + }, + }, + }, + { + Description: "pod created on node, then relevant policy created", + Actions: []*Action{ + CreateEndpoint(endpoint1, ip1), + CreatePod("x", "a", ip1, thisNode, map[string]string{"k1": "v1"}), + // will apply dirty ipsets from CreatePod + UpdatePolicy(policyXBaseOnK1V1()), + }, + TestCaseMetadata: &TestCaseMetadata{ + Tags: []Tag{ + podCrudTag, + netpolCrudTag, + }, + DpCfg: defaultWindowsDPCfg, + InitialEndpoints: nil, + ExpectedSetPolicies: []*hcn.SetPolicySetting{ + dptestutils.SetPolicy(emptySet), + dptestutils.SetPolicy(allNamespaces, emptySet.GetHashedName(), nsXSet.GetHashedName()), + dptestutils.SetPolicy(nsXSet, ip1), + dptestutils.SetPolicy(podK1Set, ip1), + dptestutils.SetPolicy(podK1V1Set, ip1), + }, + ExpectedEnpdointACLs: map[string][]*hnswrapper.FakeEndpointPolicy{ + endpoint1: { + { + ID: "azure-acl-x-base", + Protocols: "", + Action: "Allow", + Direction: "In", + LocalAddresses: "", + RemoteAddresses: "", + LocalPorts: "", + RemotePorts: "", + Priority: 222, + }, + { + ID: "azure-acl-x-base", + Protocols: "", + Action: "Allow", + Direction: "Out", + LocalAddresses: "", + RemoteAddresses: "", + LocalPorts: "", + RemotePorts: "", + Priority: 222, + }, + }, + }, + }, + }, + { + Description: "pod created on node, then relevant policy created, then policy deleted", + Actions: []*Action{ + CreateEndpoint(endpoint1, ip1), + CreatePod("x", "a", ip1, thisNode, map[string]string{"k1": "v1"}), + // will apply dirty ipsets from CreatePod + UpdatePolicy(policyXBaseOnK1V1()), + DeletePolicyByObject(policyXBaseOnK1V1()), + }, + TestCaseMetadata: &TestCaseMetadata{ + Tags: []Tag{ + podCrudTag, + netpolCrudTag, + }, + DpCfg: defaultWindowsDPCfg, + InitialEndpoints: nil, + ExpectedSetPolicies: []*hcn.SetPolicySetting{ + dptestutils.SetPolicy(emptySet), + dptestutils.SetPolicy(allNamespaces, emptySet.GetHashedName(), nsXSet.GetHashedName()), + dptestutils.SetPolicy(nsXSet, ip1), + dptestutils.SetPolicy(podK1Set, ip1), + dptestutils.SetPolicy(podK1V1Set, ip1), + }, + ExpectedEnpdointACLs: map[string][]*hnswrapper.FakeEndpointPolicy{ + endpoint1: {}, + }, + }, + }, + { + Description: "pod created off node (no local endpoint), then relevant policy created", + Actions: []*Action{ + CreatePod("x", "a", ip1, otherNode, map[string]string{"k1": "v1"}), + // will apply dirty ipsets from CreatePod + UpdatePolicy(policyXBaseOnK1V1()), + }, + TestCaseMetadata: &TestCaseMetadata{ + Tags: []Tag{ + podCrudTag, + netpolCrudTag, + }, + DpCfg: defaultWindowsDPCfg, + InitialEndpoints: nil, + ExpectedSetPolicies: []*hcn.SetPolicySetting{ + dptestutils.SetPolicy(emptySet), + dptestutils.SetPolicy(allNamespaces, emptySet.GetHashedName(), nsXSet.GetHashedName()), + dptestutils.SetPolicy(nsXSet, ip1), + dptestutils.SetPolicy(podK1Set, ip1), + dptestutils.SetPolicy(podK1V1Set, ip1), + }, + ExpectedEnpdointACLs: nil, + }, + }, + { + Description: "policy created, then pod created which satisfies policy", + Actions: []*Action{ + UpdatePolicy(policyXBaseOnK1V1()), + CreateEndpoint(endpoint1, ip1), + CreatePod("x", "a", ip1, thisNode, map[string]string{"k1": "v1"}), + ApplyDP(), + }, + TestCaseMetadata: &TestCaseMetadata{ + Tags: []Tag{ + podCrudTag, + netpolCrudTag, + }, + DpCfg: defaultWindowsDPCfg, + InitialEndpoints: nil, + ExpectedSetPolicies: []*hcn.SetPolicySetting{ + dptestutils.SetPolicy(emptySet), + dptestutils.SetPolicy(allNamespaces, emptySet.GetHashedName(), nsXSet.GetHashedName()), + dptestutils.SetPolicy(nsXSet, ip1), + dptestutils.SetPolicy(podK1Set, ip1), + dptestutils.SetPolicy(podK1V1Set, ip1), + }, + ExpectedEnpdointACLs: map[string][]*hnswrapper.FakeEndpointPolicy{ + endpoint1: { + { + ID: "azure-acl-x-base", + Protocols: "", + Action: "Allow", + Direction: "In", + LocalAddresses: "", + RemoteAddresses: "", + LocalPorts: "", + RemotePorts: "", + Priority: 222, + }, + { + ID: "azure-acl-x-base", + Protocols: "", + Action: "Allow", + Direction: "Out", + LocalAddresses: "", + RemoteAddresses: "", + LocalPorts: "", + RemotePorts: "", + Priority: 222, + }, + }, + }, + }, + }, + { + Description: "policy created, then pod created which satisfies policy, then pod relabeled and no longer satisfies policy", + Actions: []*Action{ + UpdatePolicy(policyXBaseOnK1V1()), + CreateEndpoint(endpoint1, ip1), + CreatePod("x", "a", ip1, thisNode, map[string]string{"k1": "v1"}), + ApplyDP(), + UpdatePodLabels("x", "a", ip1, thisNode, map[string]string{"k1": "v1"}, map[string]string{"k2": "v2"}), + ApplyDP(), + }, + TestCaseMetadata: &TestCaseMetadata{ + Tags: []Tag{ + podCrudTag, + netpolCrudTag, + }, + DpCfg: defaultWindowsDPCfg, + InitialEndpoints: nil, + ExpectedSetPolicies: []*hcn.SetPolicySetting{ + dptestutils.SetPolicy(emptySet), + dptestutils.SetPolicy(allNamespaces, emptySet.GetHashedName(), nsXSet.GetHashedName()), + dptestutils.SetPolicy(nsXSet, ip1), + // old labels (not yet garbage collected) + dptestutils.SetPolicy(podK1Set), + dptestutils.SetPolicy(podK1V1Set), + // new labels + dptestutils.SetPolicy(podK2Set, ip1), + dptestutils.SetPolicy(podK2V2Set, ip1), + }, + ExpectedEnpdointACLs: map[string][]*hnswrapper.FakeEndpointPolicy{ + endpoint1: {}, + }, + }, + }, + } +} + +func getAllMultiJobTests() []*MultiJobTestCase { + return []*MultiJobTestCase{ + { + Description: "create namespaces, pods, and a policy which applies to a pod", + Jobs: map[string][]*Action{ + "namespace_controller": { + CreateNamespace("x", map[string]string{"k1": "v1"}), + CreateNamespace("y", map[string]string{"k2": "v2"}), + ApplyDP(), + }, + "pod_controller": { + CreatePod("x", "a", ip1, thisNode, map[string]string{"k1": "v1"}), + CreatePod("y", "a", ip2, otherNode, map[string]string{"k1": "v1"}), + ApplyDP(), + }, + "policy_controller": { + UpdatePolicy(policyXBaseOnK1V1()), + }, + }, + TestCaseMetadata: &TestCaseMetadata{ + Tags: []Tag{ + nsCrudTag, + podCrudTag, + netpolCrudTag, + }, + DpCfg: defaultWindowsDPCfg, + InitialEndpoints: []*hcn.HostComputeEndpoint{ + dptestutils.Endpoint(endpoint1, ip1), + dptestutils.Endpoint(endpoint2, ip2), + }, + ExpectedSetPolicies: []*hcn.SetPolicySetting{ + dptestutils.SetPolicy(emptySet), + dptestutils.SetPolicy(allNamespaces, emptySet.GetHashedName(), nsXSet.GetHashedName(), nsYSet.GetHashedName()), + dptestutils.SetPolicy(nsXSet, ip1), + dptestutils.SetPolicy(nsYSet, ip2), + dptestutils.SetPolicy(nsK1Set, emptySet.GetHashedName(), nsXSet.GetHashedName()), + dptestutils.SetPolicy(nsK1V1Set, emptySet.GetHashedName(), nsXSet.GetHashedName()), + dptestutils.SetPolicy(nsK2Set, emptySet.GetHashedName(), nsYSet.GetHashedName()), + dptestutils.SetPolicy(nsK2V2Set, emptySet.GetHashedName(), nsYSet.GetHashedName()), + dptestutils.SetPolicy(podK1Set, ip1, ip2), + dptestutils.SetPolicy(podK1V1Set, ip1, ip2), + }, + ExpectedEnpdointACLs: map[string][]*hnswrapper.FakeEndpointPolicy{ + endpoint1: { + { + ID: "azure-acl-x-base", + Protocols: "", + Action: "Allow", + Direction: "In", + LocalAddresses: "", + RemoteAddresses: "", + LocalPorts: "", + RemotePorts: "", + Priority: 222, + }, + { + ID: "azure-acl-x-base", + Protocols: "", + Action: "Allow", + Direction: "Out", + LocalAddresses: "", + RemoteAddresses: "", + LocalPorts: "", + RemotePorts: "", + Priority: 222, + }, + }, + endpoint2: {}, + }, + }, + }, + } +} diff --git a/npm/pkg/dataplane/dataplane.go b/npm/pkg/dataplane/dataplane.go index a2514c2847..b967adeef5 100644 --- a/npm/pkg/dataplane/dataplane.go +++ b/npm/pkg/dataplane/dataplane.go @@ -240,7 +240,7 @@ func (dp *DataPlane) ApplyDataPlane() error { delete(dp.updatePodCache.cache, podKey) } if aggregateErr != nil { - return fmt.Errorf("[DataPlane] error while updating pods: %w", err) + return fmt.Errorf("[DataPlane] error while updating pods: %w", aggregateErr) } } return nil diff --git a/npm/pkg/dataplane/dataplane_windows_test.go b/npm/pkg/dataplane/dataplane_windows_test.go new file mode 100644 index 0000000000..1eec813e44 --- /dev/null +++ b/npm/pkg/dataplane/dataplane_windows_test.go @@ -0,0 +1,113 @@ +package dataplane + +import ( + "fmt" + "sync" + "testing" + "time" + + "github.com/Azure/azure-container-networking/common" + "github.com/Azure/azure-container-networking/npm/pkg/dataplane/ipsets" + dptestutils "github.com/Azure/azure-container-networking/npm/pkg/dataplane/testutils" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +const ( + defaultHNSLatency = time.Duration(0) + threadedHNSLatency = time.Duration(1 * time.Second) +) + +func TestAllSerialCases(t *testing.T) { + tests := getAllSerialTests() + for i, tt := range tests { + i := i + tt := tt + t.Run(tt.Description, func(t *testing.T) { + t.Logf("beginning test #%d. Description: [%s]. Tags: %+v", i, tt.Description, tt.Tags) + + hns := ipsets.GetHNSFake(t) + hns.Delay = defaultHNSLatency + io := common.NewMockIOShimWithFakeHNS(hns) + for _, ep := range tt.InitialEndpoints { + _, err := hns.CreateEndpoint(ep) + require.Nil(t, err, "failed to create initial endpoint %+v", ep) + } + + dp, err := NewDataPlane(thisNode, io, tt.DpCfg, nil) + require.NoError(t, err, "failed to initialize dp") + + for j, a := range tt.Actions { + var err error + if a.HNSAction != nil { + err = a.HNSAction.Do(hns) + } else if a.DPAction != nil { + err = a.DPAction.Do(dp) + } + + require.Nil(t, err, "failed to run action %d", j) + } + + dptestutils.VerifyHNSCache(t, hns, tt.ExpectedSetPolicies, tt.ExpectedEnpdointACLs) + }) + } +} + +func TestAllMultiJobCases(t *testing.T) { + tests := getAllMultiJobTests() + for i, tt := range tests { + i := i + tt := tt + t.Run(tt.Description, func(t *testing.T) { + t.Logf("beginning test #%d. Description: [%s]. Tags: %+v", i, tt.Description, tt.Tags) + + hns := ipsets.GetHNSFake(t) + hns.Delay = threadedHNSLatency + io := common.NewMockIOShimWithFakeHNS(hns) + for _, ep := range tt.InitialEndpoints { + _, err := hns.CreateEndpoint(ep) + require.Nil(t, err, "failed to create initial endpoint %+v", ep) + } + + // the dp is necessary for NPM tests + dp, err := NewDataPlane(thisNode, io, tt.DpCfg, nil) + require.NoError(t, err, "failed to initialize dp") + + backgroundErrors := make(chan error, len(tt.Jobs)) + wg := new(sync.WaitGroup) + wg.Add(len(tt.Jobs)) + for jobName, job := range tt.Jobs { + jobName := jobName + job := job + go func() { + defer wg.Done() + for k, a := range job { + var err error + if a.HNSAction != nil { + err = a.HNSAction.Do(hns) + } else if a.DPAction != nil { + err = a.DPAction.Do(dp) + } + + if err != nil { + backgroundErrors <- errors.Wrapf(err, "failed to run action %d in job %s", k, jobName) + break + } + } + }() + } + + wg.Wait() + close(backgroundErrors) + if len(backgroundErrors) > 0 { + errStrings := make([]string, 0) + for err := range backgroundErrors { + errStrings = append(errStrings, fmt.Sprintf("[%s]", err.Error())) + } + require.FailNow(t, "encountered errors in multi-job test: %+v", errStrings) + } + + dptestutils.VerifyHNSCache(t, hns, tt.ExpectedSetPolicies, tt.ExpectedEnpdointACLs) + }) + } +} diff --git a/npm/pkg/dataplane/ipsets/testutils_windows.go b/npm/pkg/dataplane/ipsets/testutils_windows.go index 3421b3ee85..f5c780f981 100644 --- a/npm/pkg/dataplane/ipsets/testutils_windows.go +++ b/npm/pkg/dataplane/ipsets/testutils_windows.go @@ -3,16 +3,17 @@ package ipsets import ( "testing" + "github.com/Azure/azure-container-networking/common" "github.com/Azure/azure-container-networking/network/hnswrapper" testutils "github.com/Azure/azure-container-networking/test/utils" - "github.com/stretchr/testify/require" "github.com/Microsoft/hcsshim/hcn" + "github.com/stretchr/testify/require" ) func GetHNSFake(t *testing.T) *hnswrapper.Hnsv2wrapperFake { hns := hnswrapper.NewHnsv2wrapperFake() network := &hcn.HostComputeNetwork{ - Id: "1234", + Id: common.FakeHNSNetworkID, Name: "azure", } diff --git a/npm/pkg/dataplane/testutils/utils_windows.go b/npm/pkg/dataplane/testutils/utils_windows.go new file mode 100644 index 0000000000..a2da51a62c --- /dev/null +++ b/npm/pkg/dataplane/testutils/utils_windows.go @@ -0,0 +1,148 @@ +package dptestutils + +import ( + "fmt" + "sort" + "strings" + "testing" + + "github.com/Azure/azure-container-networking/common" + "github.com/Azure/azure-container-networking/network/hnswrapper" + "github.com/Azure/azure-container-networking/npm/pkg/dataplane/ipsets" + "github.com/Microsoft/hcsshim/hcn" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/klog" +) + +func PrefixNames(sets []*ipsets.IPSetMetadata) []string { + a := make([]string, len(sets)) + for k, s := range sets { + a[k] = s.GetPrefixName() + } + return a +} + +func Endpoint(epID, ip string) *hcn.HostComputeEndpoint { + return &hcn.HostComputeEndpoint{ + Id: epID, + Name: epID, + HostComputeNetwork: common.FakeHNSNetworkID, + IpConfigurations: []hcn.IpConfig{ + { + IpAddress: ip, + }, + }, + } +} + +func SetPolicy(setMetadata *ipsets.IPSetMetadata, members ...string) *hcn.SetPolicySetting { + pType := hcn.SetPolicyType("") + switch setMetadata.GetSetKind() { + case ipsets.ListSet: + pType = hcn.SetPolicyTypeNestedIpSet + case ipsets.HashSet: + pType = hcn.SetPolicyTypeIpSet + case ipsets.UnknownKind: + pType = hcn.SetPolicyType("") + } + + // sort for easier comparison + sort.Strings(members) + + return &hcn.SetPolicySetting{ + Id: setMetadata.GetHashedName(), + Name: setMetadata.GetPrefixName(), + PolicyType: pType, + Values: strings.Join(members, ","), + } +} + +// VerifyHNSCache asserts that HNS has the correct state. +func VerifyHNSCache(t *testing.T, hns *hnswrapper.Hnsv2wrapperFake, expectedSetPolicies []*hcn.SetPolicySetting, expectedEndpointACLs map[string][]*hnswrapper.FakeEndpointPolicy) { + t.Helper() + + PrintGetAllOutput(hns) + + // we want to evaluate both verify functions even if one fails, so don't write as verifySetPolicies() && verifyACLs() in case of short-circuiting + success := VerifySetPolicies(t, hns, expectedSetPolicies) + success = VerifyACLs(t, hns, expectedEndpointACLs) && success + + if !success { + require.FailNow(t, fmt.Sprintf("hns cache had unexpected state. printing hns cache...\n%s", hns.Cache.PrettyString())) + } +} + +// VerifySetPolicies is true if HNS strictly has the expected SetPolicies. +func VerifySetPolicies(t *testing.T, hns *hnswrapper.Hnsv2wrapperFake, expectedSetPolicies []*hcn.SetPolicySetting) bool { + t.Helper() + + cachedSetPolicies := hns.Cache.AllSetPolicies(common.FakeHNSNetworkID) + + success := assert.Equal(t, len(expectedSetPolicies), len(cachedSetPolicies), "unexpected number of SetPolicies") + for _, expectedSetPolicy := range expectedSetPolicies { + cachedSetPolicy, ok := cachedSetPolicies[expectedSetPolicy.Id] + success = assert.True(t, ok, fmt.Sprintf("expected SetPolicy not found. ID %s, name: %s", expectedSetPolicy.Id, expectedSetPolicy.Name)) && success + if !ok { + continue + } + + members := strings.Split(cachedSetPolicy.Values, ",") + sort.Strings(members) + copyOfCachedSetPolicy := *cachedSetPolicy + copyOfCachedSetPolicy.Values = strings.Join(members, ",") + + // required that the expectedSetPolicy already has sorted members + success = assert.Equal(t, expectedSetPolicy, ©OfCachedSetPolicy, fmt.Sprintf("SetPolicy has unexpected contents. ID %s, name: %s", expectedSetPolicy.Id, expectedSetPolicy.Name)) && success + } + + return success +} + +// verifyACLs is true if HNS strictly has the expected Endpoints and ACLs. +func VerifyACLs(t *testing.T, hns *hnswrapper.Hnsv2wrapperFake, expectedEndpointACLs map[string][]*hnswrapper.FakeEndpointPolicy) bool { + t.Helper() + + cachedEndpointACLs := hns.Cache.GetAllACLs() + + success := assert.Equal(t, len(expectedEndpointACLs), len(cachedEndpointACLs), "unexpected number of Endpoints") + for epID, expectedACLs := range expectedEndpointACLs { + cachedACLs, ok := cachedEndpointACLs[epID] + success = assert.True(t, ok, fmt.Sprintf("expected ACL not found for endpoint %s", epID)) && success + if !ok { + continue + } + + success = assert.Equal(t, len(expectedACLs), len(cachedACLs), fmt.Sprintf("unexpected number of ACLs for Endpoint with ID: %s", epID)) && success + for _, expectedACL := range expectedACLs { + foundACL := false + for _, cacheACL := range cachedACLs { + if expectedACL.ID == cacheACL.ID { + if cmp.Equal(expectedACL, cacheACL) { + foundACL = true + break + } + } + } + success = assert.True(t, foundACL, fmt.Sprintf("missing expected ACL. ID: %s, full ACL: %+v", expectedACL.ID, expectedACL)) && success + } + } + return success +} + +// helpful for debugging if there's a discrepancy between GetAll functions and the HNS PrettyString +func PrintGetAllOutput(hns *hnswrapper.Hnsv2wrapperFake) { + klog.Info("SETPOLICIES...") + for _, setPol := range hns.Cache.AllSetPolicies(common.FakeHNSNetworkID) { + klog.Infof("%+v", setPol) + } + klog.Info("Endpoint ACLs...") + for id, acls := range hns.Cache.GetAllACLs() { + a := make([]string, len(acls)) + for k, v := range acls { + a[k] = fmt.Sprintf("%+v", v) + } + klog.Infof("%s: %s", id, strings.Join(a, ",")) + } +} diff --git a/npm/pkg/dataplane/types.go b/npm/pkg/dataplane/types.go index 2df765ddfa..d8fd26afc3 100644 --- a/npm/pkg/dataplane/types.go +++ b/npm/pkg/dataplane/types.go @@ -1,6 +1,8 @@ package dataplane import ( + "strings" + "github.com/Azure/azure-container-networking/npm/pkg/dataplane/ipsets" "github.com/Azure/azure-container-networking/npm/pkg/dataplane/policies" "github.com/Azure/azure-container-networking/npm/util" @@ -51,6 +53,10 @@ func NewPodMetadata(podKey, podIP, nodeName string) *PodMetadata { } } +func (p *PodMetadata) Namespace() string { + return strings.Split(p.PodKey, "/")[0] +} + func newUpdateNPMPod(podMetadata *PodMetadata) *updateNPMPod { return &updateNPMPod{ PodMetadata: podMetadata, diff --git a/npm/pkg/dataplane/types_windows_test.go b/npm/pkg/dataplane/types_windows_test.go new file mode 100644 index 0000000000..7902341186 --- /dev/null +++ b/npm/pkg/dataplane/types_windows_test.go @@ -0,0 +1,446 @@ +package dataplane + +import ( + "fmt" + + "github.com/Azure/azure-container-networking/network/hnswrapper" + "github.com/Azure/azure-container-networking/npm/pkg/controlplane/translation" + "github.com/Azure/azure-container-networking/npm/pkg/dataplane/ipsets" + dptestutils "github.com/Azure/azure-container-networking/npm/pkg/dataplane/testutils" + "github.com/Microsoft/hcsshim/hcn" + "github.com/pkg/errors" + networkingv1 "k8s.io/api/networking/v1" +) + +type Tag string + +type SerialTestCase struct { + Description string + Actions []*Action + *TestCaseMetadata +} + +type MultiJobTestCase struct { + Description string + Jobs map[string][]*Action + *TestCaseMetadata +} + +type TestCaseMetadata struct { + Tags []Tag + InitialEndpoints []*hcn.HostComputeEndpoint + DpCfg *Config + ExpectedSetPolicies []*hcn.SetPolicySetting + ExpectedEnpdointACLs map[string][]*hnswrapper.FakeEndpointPolicy +} + +// Action represents a single action (either an HNSAction or a DPAction). +// Exactly one of HNSAction or DPAction should be non-nil. +type Action struct { + HNSAction + DPAction +} + +type HNSAction interface { + // Do models events in HNS + Do(hns *hnswrapper.Hnsv2wrapperFake) error +} + +type EndpointCreateAction struct { + ID string + IP string +} + +func CreateEndpoint(id, ip string) *Action { + return &Action{ + HNSAction: &EndpointCreateAction{ + ID: id, + IP: ip, + }, + } +} + +// Do models endpoint creation in HNS +func (e *EndpointCreateAction) Do(hns *hnswrapper.Hnsv2wrapperFake) error { + ep := dptestutils.Endpoint(e.ID, e.IP) + _, err := hns.CreateEndpoint(ep) + if err != nil { + return errors.Wrapf(err, "[EndpointCreateAction] failed to create endpoint. ep: [%+v]", ep) + } + return nil +} + +type EndpointDeleteAction struct { + ID string +} + +func DeleteEndpoint(id string) *Action { + return &Action{ + HNSAction: &EndpointDeleteAction{ + ID: id, + }, + } +} + +// Do models endpoint deletion in HNS +func (e *EndpointDeleteAction) Do(hns *hnswrapper.Hnsv2wrapperFake) error { + ep := &hcn.HostComputeEndpoint{ + Id: e.ID, + } + if err := hns.DeleteEndpoint(ep); err != nil { + return errors.Wrapf(err, "[EndpointDeleteAction] failed to delete endpoint. ep: [%+v]", ep) + } + return nil +} + +type DPAction interface { + // Do models interactions with the DataPlane + Do(dp *DataPlane) error +} + +type ApplyDPAction struct{} + +func ApplyDP() *Action { + return &Action{ + DPAction: &ApplyDPAction{}, + } +} + +// Do applies the dataplane +func (*ApplyDPAction) Do(dp *DataPlane) error { + if err := dp.ApplyDataPlane(); err != nil { + return errors.Wrapf(err, "[ApplyDPAction] failed to apply") + } + return nil +} + +type ReconcileDPAction struct{} + +func ReconcileDP() *Action { + return &Action{ + DPAction: &ReconcileDPAction{}, + } +} + +// Do reconciles the IPSetManager and PolicyManager +func (*ReconcileDPAction) Do(dp *DataPlane) error { + dp.ipsetMgr.Reconcile() + // currently does nothing in windows + dp.policyMgr.Reconcile() + return nil +} + +type PodCreateAction struct { + Pod *PodMetadata + Labels map[string]string +} + +func CreatePod(namespace, name, ip, node string, labels map[string]string) *Action { + podKey := fmt.Sprintf("%s/%s", namespace, name) + return &Action{ + DPAction: &PodCreateAction{ + Pod: NewPodMetadata(podKey, ip, node), + Labels: labels, + }, + } +} + +// Do models pod creation in the PodController +func (p *PodCreateAction) Do(dp *DataPlane) error { + context := fmt.Sprintf("create context: [pod: %+v. labels: %+v]", p.Pod, p.Labels) + + nsIPSet := []*ipsets.IPSetMetadata{ipsets.NewIPSetMetadata(p.Pod.Namespace(), ipsets.Namespace)} + // PodController technically wouldn't call this if the namespace already existed + if err := dp.AddToLists([]*ipsets.IPSetMetadata{allNamespaces}, nsIPSet); err != nil { + return errors.Wrapf(err, "[PodCreateAction] failed to add ns set to all-namespaces list. %s", context) + } + + if err := dp.AddToSets(nsIPSet, p.Pod); err != nil { + return errors.Wrapf(err, "[PodCreateAction] failed to add pod ip to ns set. %s", context) + } + + for key, val := range p.Labels { + keyVal := fmt.Sprintf("%s:%s", key, val) + labelIPSets := []*ipsets.IPSetMetadata{ + ipsets.NewIPSetMetadata(key, ipsets.KeyLabelOfPod), + ipsets.NewIPSetMetadata(keyVal, ipsets.KeyValueLabelOfPod), + } + + if err := dp.AddToSets(labelIPSets, p.Pod); err != nil { + return errors.Wrapf(err, "[PodCreateAction] failed to add pod ip to label sets %+v. %s", labelIPSets, context) + } + } + + return nil +} + +type PodUpdateAction struct { + OldPod *PodMetadata + NewPod *PodMetadata + LabelsToRemove map[string]string + LabelsToAdd map[string]string +} + +func UpdatePod(namespace, name, oldIP, oldNode, newIP, newNode string, labelsToRemove, labelsToAdd map[string]string) *Action { + podKey := fmt.Sprintf("%s/%s", namespace, name) + return &Action{ + DPAction: &PodUpdateAction{ + OldPod: NewPodMetadata(podKey, oldIP, oldNode), + NewPod: NewPodMetadata(podKey, newIP, newNode), + LabelsToRemove: labelsToRemove, + LabelsToAdd: labelsToAdd, + }, + } +} + +func UpdatePodLabels(namespace, name, ip, node string, labelsToRemove, labelsToAdd map[string]string) *Action { + return UpdatePod(namespace, name, ip, node, ip, node, labelsToRemove, labelsToAdd) +} + +// Do models pod updates in the PodController +func (p *PodUpdateAction) Do(dp *DataPlane) error { + context := fmt.Sprintf("update context: [old pod: %+v. current IP: %+v. old labels: %+v. new labels: %+v]", p.OldPod, p.NewPod.PodIP, p.LabelsToRemove, p.LabelsToAdd) + + // think it's impossible for this to be called on an UPDATE + // dp.AddToLists([]*ipsets.IPSetMetadata{allNamespaces}, []*ipsets.IPSetMetadata{nsIPSet}) + + for k, v := range p.LabelsToRemove { + keyVal := fmt.Sprintf("%s:%s", k, v) + sets := []*ipsets.IPSetMetadata{ + ipsets.NewIPSetMetadata(k, ipsets.KeyLabelOfPod), + ipsets.NewIPSetMetadata(keyVal, ipsets.KeyValueLabelOfPod), + } + for _, toRemoveSet := range sets { + if err := dp.RemoveFromSets([]*ipsets.IPSetMetadata{toRemoveSet}, p.OldPod); err != nil { + return errors.Wrapf(err, "[PodUpdateAction] failed to remove old pod ip from set %s. %s", toRemoveSet.GetPrefixName(), context) + } + } + } + + for k, v := range p.LabelsToAdd { + keyVal := fmt.Sprintf("%s:%s", k, v) + sets := []*ipsets.IPSetMetadata{ + ipsets.NewIPSetMetadata(k, ipsets.KeyLabelOfPod), + ipsets.NewIPSetMetadata(keyVal, ipsets.KeyValueLabelOfPod), + } + for _, toAddSet := range sets { + if err := dp.AddToSets([]*ipsets.IPSetMetadata{toAddSet}, p.NewPod); err != nil { + return errors.Wrapf(err, "[PodUpdateAction] failed to add new pod ip to set %s. %s", toAddSet.GetPrefixName(), context) + } + } + } + + return nil +} + +type PodDeleteAction struct { + Pod *PodMetadata + Labels map[string]string +} + +func DeletePod(namespace, name, ip string, labels map[string]string) *Action { + podKey := fmt.Sprintf("%s/%s", namespace, name) + return &Action{ + DPAction: &PodDeleteAction{ + // currently, the PodController doesn't share the node name + Pod: NewPodMetadata(podKey, ip, ""), + Labels: labels, + }, + } +} + +// Do models pod deletion in the PodController +func (p *PodDeleteAction) Do(dp *DataPlane) error { + context := fmt.Sprintf("delete context: [pod: %+v. labels: %+v]", p.Pod, p.Labels) + + nsIPSet := []*ipsets.IPSetMetadata{ipsets.NewIPSetMetadata(p.Pod.Namespace(), ipsets.Namespace)} + if err := dp.RemoveFromSets(nsIPSet, p.Pod); err != nil { + return errors.Wrapf(err, "[PodDeleteAction] failed to remove pod ip from ns set. %s", context) + } + + for key, val := range p.Labels { + keyVal := fmt.Sprintf("%s:%s", key, val) + labelIPSets := []*ipsets.IPSetMetadata{ + ipsets.NewIPSetMetadata(key, ipsets.KeyLabelOfPod), + ipsets.NewIPSetMetadata(keyVal, ipsets.KeyValueLabelOfPod), + } + + if err := dp.RemoveFromSets(labelIPSets, p.Pod); err != nil { + return errors.Wrapf(err, "[PodDeleteAction] failed to remove pod ip from label set %+v. %s", labelIPSets, context) + } + } + + return nil +} + +type NamespaceCreateAction struct { + NS string + Labels map[string]string +} + +func CreateNamespace(ns string, labels map[string]string) *Action { + return &Action{ + DPAction: &NamespaceCreateAction{ + NS: ns, + Labels: labels, + }, + } +} + +// Do models namespace creation in the NamespaceController +func (n *NamespaceCreateAction) Do(dp *DataPlane) error { + nsIPSet := []*ipsets.IPSetMetadata{ipsets.NewIPSetMetadata(n.NS, ipsets.Namespace)} + + listsToAddTo := []*ipsets.IPSetMetadata{allNamespaces} + for k, v := range n.Labels { + keyVal := fmt.Sprintf("%s:%s", k, v) + listsToAddTo = append(listsToAddTo, + ipsets.NewIPSetMetadata(k, ipsets.KeyLabelOfNamespace), + ipsets.NewIPSetMetadata(keyVal, ipsets.KeyValueLabelOfNamespace)) + } + + if err := dp.AddToLists(listsToAddTo, nsIPSet); err != nil { + return errors.Wrapf(err, "[NamespaceCreateAction] failed to add ns ipset to all lists. Action: %+v", n) + } + + return nil +} + +type NamespaceUpdateAction struct { + NS string + LabelsToRemove map[string]string + LabelsToAdd map[string]string +} + +func UpdateNamespace(ns string, labelsToRemove, labelsToAdd map[string]string) *Action { + return &Action{ + DPAction: &NamespaceUpdateAction{ + NS: ns, + LabelsToRemove: labelsToRemove, + LabelsToAdd: labelsToAdd, + }, + } +} + +// Do models namespace updates in the NamespaceController +func (n *NamespaceUpdateAction) Do(dp *DataPlane) error { + nsIPSet := []*ipsets.IPSetMetadata{ipsets.NewIPSetMetadata(n.NS, ipsets.Namespace)} + + for k, v := range n.LabelsToRemove { + keyVal := fmt.Sprintf("%s:%s", k, v) + lists := []*ipsets.IPSetMetadata{ + ipsets.NewIPSetMetadata(k, ipsets.KeyLabelOfNamespace), + ipsets.NewIPSetMetadata(keyVal, ipsets.KeyValueLabelOfNamespace), + } + for _, listToRemoveFrom := range lists { + if err := dp.RemoveFromList(listToRemoveFrom, nsIPSet); err != nil { + return errors.Wrapf(err, "[NamespaceUpdateAction] failed to remove ns ipset from list %s. Action: %+v", listToRemoveFrom.GetPrefixName(), n) + } + } + } + + for k, v := range n.LabelsToAdd { + keyVal := fmt.Sprintf("%s:%s", k, v) + lists := []*ipsets.IPSetMetadata{ + ipsets.NewIPSetMetadata(k, ipsets.KeyLabelOfNamespace), + ipsets.NewIPSetMetadata(keyVal, ipsets.KeyValueLabelOfNamespace), + } + for _, listToAddTo := range lists { + if err := dp.RemoveFromList(listToAddTo, nsIPSet); err != nil { + return errors.Wrapf(err, "[NamespaceUpdateAction] failed to add ns ipset to list %s. Action: %+v", listToAddTo.GetPrefixName(), n) + } + } + } + + return nil +} + +type NamespaceDeleteAction struct { + NS string + Labels map[string]string +} + +func DeleteNamespace(ns string, labels map[string]string) *Action { + return &Action{ + DPAction: &NamespaceDeleteAction{ + NS: ns, + Labels: labels, + }, + } +} + +// Do models namespace deletion in the NamespaceController +func (n *NamespaceDeleteAction) Do(dp *DataPlane) error { + nsIPSet := []*ipsets.IPSetMetadata{ipsets.NewIPSetMetadata(n.NS, ipsets.Namespace)} + + for k, v := range n.Labels { + keyVal := fmt.Sprintf("%s:%s", k, v) + lists := []*ipsets.IPSetMetadata{ + ipsets.NewIPSetMetadata(k, ipsets.KeyLabelOfNamespace), + ipsets.NewIPSetMetadata(keyVal, ipsets.KeyValueLabelOfNamespace), + } + for _, listToRemoveFrom := range lists { + if err := dp.RemoveFromList(listToRemoveFrom, nsIPSet); err != nil { + return errors.Wrapf(err, "[NamespaceDeleteAction] failed to remove ns ipset from list %s. Action: %+v", listToRemoveFrom.GetPrefixName(), n) + } + } + } + + if err := dp.RemoveFromList(allNamespaces, nsIPSet); err != nil { + return errors.Wrapf(err, "[NamespaceDeleteAction] failed to remove ns ipset from all-namespaces list. Action: %+v", n) + } + + return nil +} + +type PolicyUpdateAction struct { + Policy *networkingv1.NetworkPolicy +} + +func UpdatePolicy(policy *networkingv1.NetworkPolicy) *Action { + return &Action{ + DPAction: &PolicyUpdateAction{ + Policy: policy, + }, + } +} + +// Do models policy updates in the NetworkPolicyController +func (p *PolicyUpdateAction) Do(dp *DataPlane) error { + npmNetPol, err := translation.TranslatePolicy(p.Policy) + if err != nil { + return errors.Wrapf(err, "[PolicyUpdateAction] failed to translate policy with key %s/%s", p.Policy.Namespace, p.Policy.Name) + } + + if err := dp.UpdatePolicy(npmNetPol); err != nil { + return errors.Wrapf(err, "[PolicyUpdateAction] failed to update policy with key %s/%s", p.Policy.Namespace, p.Policy.Name) + } + return nil +} + +type PolicyDeleteAction struct { + Namespace string + Name string +} + +func DeletePolicy(namespace, name string) *Action { + return &Action{ + DPAction: &PolicyDeleteAction{ + Namespace: namespace, + Name: name, + }, + } +} + +func DeletePolicyByObject(policy *networkingv1.NetworkPolicy) *Action { + return DeletePolicy(policy.Namespace, policy.Name) +} + +// Do models policy deletion in the NetworkPolicyController +func (p *PolicyDeleteAction) Do(dp *DataPlane) error { + policyKey := fmt.Sprintf("%s/%s", p.Namespace, p.Name) + if err := dp.RemovePolicy(policyKey); err != nil { + return errors.Wrapf(err, "[PolicyDeleteAction] failed to update policy with key %s", policyKey) + } + return nil +}