From 4e68ec24f8cdb57e102b1d4e896dd87873cf4187 Mon Sep 17 00:00:00 2001 From: Casey Davenport Date: Sat, 23 Sep 2017 23:26:16 -0700 Subject: [PATCH] k8s conversion code handles 1.7 clusters --- lib/backend/k8s/conversion.go | 12 +- lib/backend/k8s/conversion_test.go | 415 +++++++++++++++++++++++++++++ 2 files changed, 425 insertions(+), 2 deletions(-) diff --git a/lib/backend/k8s/conversion.go b/lib/backend/k8s/conversion.go index f8cb713d0..b8c39676a 100644 --- a/lib/backend/k8s/conversion.go +++ b/lib/backend/k8s/conversion.go @@ -223,15 +223,23 @@ func (c Converter) NetworkPolicyToPolicy(np *extensions.NetworkPolicy) (*model.K types := []string{} if ingress { types = append(types, "ingress") - } else if len(inboundRules) > 0 { - log.Warn("K8s PolicyTypes don't include 'ingress', but NetworkPolicy has ingress rules.") } if egress { types = append(types, "egress") } else if len(outboundRules) > 0 { + // Egress was introduced at the same time as policyTypes. It shouldn't be possible to + // receive a NetworkPolicy with an egress rule but without "egress" specified in its types, + // but we'll warn about it anyway. log.Warn("K8s PolicyTypes don't include 'egress', but NetworkPolicy has egress rules.") } + // If no types were specified in the policy, then we're running on a cluster that doesn't + // include support for that field in the API. In that case, the correct behavior is for the policy + // to apply to only ingress traffic. + if len(types) == 0 { + types = append(types, "ingress") + } + // Build and return the KVPair. return &model.KVPair{ Key: model.PolicyKey{ diff --git a/lib/backend/k8s/conversion_test.go b/lib/backend/k8s/conversion_test.go index 5483b1392..d7df490e0 100644 --- a/lib/backend/k8s/conversion_test.go +++ b/lib/backend/k8s/conversion_test.go @@ -846,6 +846,421 @@ var _ = Describe("Test NetworkPolicy conversion", func() { }) }) +// This suite of tests is useful for ensuring we continue to support kubernetes apiserver +// versions <= 1.7.x, and can be removed when that is no longer required. +var _ = Describe("Test NetworkPolicy conversion (k8s <= 1.7, no policyTypes)", func() { + + // Use a single instance of the Converter for these tests. + c := Converter{} + + It("should parse a basic NetworkPolicy to a Policy", func() { + port80 := intstr.FromInt(80) + np := extensions.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPolicy", + Namespace: "default", + }, + Spec: extensions.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label": "value", + "label2": "value2", + }, + }, + Ingress: []extensions.NetworkPolicyIngressRule{ + { + Ports: []extensions.NetworkPolicyPort{ + {Port: &port80}, + }, + From: []extensions.NetworkPolicyPeer{ + { + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "k": "v", + "k2": "v2", + }, + }, + }, + }, + }, + }, + }, + } + + // Parse the policy. + pol, err := c.NetworkPolicyToPolicy(&np) + Expect(err).NotTo(HaveOccurred()) + + // Assert key fields are correct. + Expect(pol.Key.(model.PolicyKey).Name).To(Equal("knp.default.default.testPolicy")) + + // Assert value fields are correct. + Expect(int(*pol.Value.(*model.Policy).Order)).To(Equal(1000)) + // Check the selector is correct, and that the matches are sorted. + Expect(pol.Value.(*model.Policy).Selector).To(Equal( + "calico/k8s_ns == 'default' && label == 'value' && label2 == 'value2'")) + protoTCP := numorstring.ProtocolFromString("tcp") + Expect(pol.Value.(*model.Policy).InboundRules).To(ConsistOf(model.Rule{ + Action: "allow", + Protocol: &protoTCP, // Defaulted to TCP. + SrcSelector: "calico/k8s_ns == 'default' && k == 'v' && k2 == 'v2'", + DstPorts: []numorstring.Port{numorstring.SinglePort(80)}, + })) + + // There should be no OutboundRules + Expect(len(pol.Value.(*model.Policy).OutboundRules)).To(Equal(0)) + + // Check that Types field exists and has only 'ingress' + Expect(len(pol.Value.(*model.Policy).Types)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).Types[0]).To(Equal("ingress")) + }) + + It("should parse a NetworkPolicy with no rules", func() { + np := extensions.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPolicy", + Namespace: "default", + }, + Spec: extensions.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"label": "value"}, + }, + }, + } + + // Parse the policy. + pol, err := c.NetworkPolicyToPolicy(&np) + Expect(err).NotTo(HaveOccurred()) + + // Assert key fields are correct. + Expect(pol.Key.(model.PolicyKey).Name).To(Equal("knp.default.default.testPolicy")) + + // Assert value fields are correct. + Expect(int(*pol.Value.(*model.Policy).Order)).To(Equal(1000)) + Expect(pol.Value.(*model.Policy).Selector).To(Equal("calico/k8s_ns == 'default' && label == 'value'")) + Expect(len(pol.Value.(*model.Policy).InboundRules)).To(Equal(0)) + + // There should be no OutboundRules + Expect(len(pol.Value.(*model.Policy).OutboundRules)).To(Equal(0)) + + // Check that Types field exists and has only 'ingress' + Expect(len(pol.Value.(*model.Policy).Types)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).Types[0]).To(Equal("ingress")) + }) + + It("should parse a NetworkPolicy with multiple peers", func() { + np := extensions.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPolicy", + Namespace: "default", + }, + Spec: extensions.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"label": "value"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{ + extensions.NetworkPolicyIngressRule{ + Ports: []extensions.NetworkPolicyPort{ + extensions.NetworkPolicyPort{}, + }, + From: []extensions.NetworkPolicyPeer{ + extensions.NetworkPolicyPeer{ + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "k": "v", + }, + }, + }, + extensions.NetworkPolicyPeer{ + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "k2": "v2", + }, + }, + }, + }, + }, + }, + }, + } + + var pol *model.KVPair + var err error + By("parsing the policy", func() { + pol, err = c.NetworkPolicyToPolicy(&np) + Expect(err).NotTo(HaveOccurred()) + Expect(pol.Key.(model.PolicyKey).Name).To(Equal("knp.default.default.testPolicy")) + Expect(int(*pol.Value.(*model.Policy).Order)).To(Equal(1000)) + }) + + By("having the correct endpoint selector", func() { + Expect(pol.Value.(*model.Policy).Selector).To(Equal("calico/k8s_ns == 'default' && label == 'value'")) + }) + + By("having the correct peer selectors", func() { + Expect(len(pol.Value.(*model.Policy).InboundRules)).To(Equal(2)) + + // There should be no OutboundRules + Expect(len(pol.Value.(*model.Policy).OutboundRules)).To(Equal(0)) + + // Check that Types field exists and has only 'ingress' + Expect(len(pol.Value.(*model.Policy).Types)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).Types[0]).To(Equal("ingress")) + + Expect(pol.Value.(*model.Policy).InboundRules[0].SrcSelector).To(Equal("calico/k8s_ns == 'default' && k == 'v'")) + Expect(pol.Value.(*model.Policy).InboundRules[1].SrcSelector).To(Equal("calico/k8s_ns == 'default' && k2 == 'v2'")) + }) + }) + + It("should parse a NetworkPolicy with multiple peers and ports", func() { + tcp := extensions.ProtocolTCP + udp := extensions.ProtocolUDP + eighty := intstr.FromInt(80) + ninety := intstr.FromInt(90) + + np := extensions.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPolicy", + Namespace: "default", + }, + Spec: extensions.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"label": "value"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{ + extensions.NetworkPolicyIngressRule{ + Ports: []extensions.NetworkPolicyPort{ + extensions.NetworkPolicyPort{ + Port: &ninety, + Protocol: &udp, + }, + extensions.NetworkPolicyPort{ + Port: &eighty, + Protocol: &tcp, + }, + }, + From: []extensions.NetworkPolicyPeer{ + extensions.NetworkPolicyPeer{ + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "k": "v", + }, + }, + }, + extensions.NetworkPolicyPeer{ + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "k2": "v2", + }, + }, + }, + }, + }, + }, + }, + } + + var pol *model.KVPair + var err error + By("parsing the policy", func() { + pol, err = c.NetworkPolicyToPolicy(&np) + Expect(err).NotTo(HaveOccurred()) + Expect(pol.Key.(model.PolicyKey).Name).To(Equal("knp.default.default.testPolicy")) + Expect(int(*pol.Value.(*model.Policy).Order)).To(Equal(1000)) + }) + + By("having the correct endpoint selector", func() { + Expect(pol.Value.(*model.Policy).Selector).To(Equal("calico/k8s_ns == 'default' && label == 'value'")) + }) + + By("having the correct peer selectors", func() { + eighty, _ := numorstring.PortFromString("80") + ninety, _ := numorstring.PortFromString("90") + Expect(len(pol.Value.(*model.Policy).InboundRules)).To(Equal(4)) + + // There should be no OutboundRules + Expect(len(pol.Value.(*model.Policy).OutboundRules)).To(Equal(0)) + + // Check that Types field exists and has only 'ingress' + Expect(len(pol.Value.(*model.Policy).Types)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).Types[0]).To(Equal("ingress")) + + Expect(pol.Value.(*model.Policy).InboundRules[0].SrcSelector).To(Equal("calico/k8s_ns == 'default' && k == 'v'")) + Expect(pol.Value.(*model.Policy).InboundRules[0].DstPorts).To(Equal([]numorstring.Port{ninety})) + + Expect(pol.Value.(*model.Policy).InboundRules[1].SrcSelector).To(Equal("calico/k8s_ns == 'default' && k2 == 'v2'")) + Expect(pol.Value.(*model.Policy).InboundRules[1].DstPorts).To(Equal([]numorstring.Port{ninety})) + + Expect(pol.Value.(*model.Policy).InboundRules[2].SrcSelector).To(Equal("calico/k8s_ns == 'default' && k == 'v'")) + Expect(pol.Value.(*model.Policy).InboundRules[2].DstPorts).To(Equal([]numorstring.Port{eighty})) + + Expect(pol.Value.(*model.Policy).InboundRules[3].SrcSelector).To(Equal("calico/k8s_ns == 'default' && k2 == 'v2'")) + Expect(pol.Value.(*model.Policy).InboundRules[3].DstPorts).To(Equal([]numorstring.Port{eighty})) + }) + }) + + It("should parse a NetworkPolicy with empty podSelector", func() { + np := extensions.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPolicy", + Namespace: "default", + }, + Spec: extensions.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + }, + } + + // Parse the policy. + pol, err := c.NetworkPolicyToPolicy(&np) + Expect(err).NotTo(HaveOccurred()) + + // Assert key fields are correct. + Expect(pol.Key.(model.PolicyKey).Name).To(Equal("knp.default.default.testPolicy")) + + // Assert value fields are correct. + Expect(int(*pol.Value.(*model.Policy).Order)).To(Equal(1000)) + Expect(pol.Value.(*model.Policy).Selector).To(Equal("calico/k8s_ns == 'default'")) + Expect(len(pol.Value.(*model.Policy).InboundRules)).To(Equal(0)) + + // There should be no OutboundRules + Expect(len(pol.Value.(*model.Policy).OutboundRules)).To(Equal(0)) + + // Check that Types field exists and has only 'ingress' + Expect(len(pol.Value.(*model.Policy).Types)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).Types[0]).To(Equal("ingress")) + }) + + It("should parse a NetworkPolicy with an empty namespaceSelector", func() { + np := extensions.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPolicy", + Namespace: "default", + }, + Spec: extensions.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchLabels: map[string]string{"label": "value"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{ + extensions.NetworkPolicyIngressRule{ + From: []extensions.NetworkPolicyPeer{ + extensions.NetworkPolicyPeer{ + NamespaceSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{}, + }, + }, + }, + }, + }, + }, + } + + // Parse the policy. + pol, err := c.NetworkPolicyToPolicy(&np) + Expect(err).NotTo(HaveOccurred()) + + // Assert key fields are correct. + Expect(pol.Key.(model.PolicyKey).Name).To(Equal("knp.default.default.testPolicy")) + + // Assert value fields are correct. + Expect(int(*pol.Value.(*model.Policy).Order)).To(Equal(1000)) + Expect(pol.Value.(*model.Policy).Selector).To(Equal("calico/k8s_ns == 'default' && label == 'value'")) + Expect(len(pol.Value.(*model.Policy).InboundRules)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).InboundRules[0].SrcSelector).To(Equal("has(calico/k8s_ns)")) + + // There should be no OutboundRules + Expect(len(pol.Value.(*model.Policy).OutboundRules)).To(Equal(0)) + + // Check that Types field exists and has only 'ingress' + Expect(len(pol.Value.(*model.Policy).Types)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).Types[0]).To(Equal("ingress")) + }) + + It("should parse a NetworkPolicy with podSelector.MatchExpressions", func() { + np := extensions.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPolicy", + Namespace: "default", + }, + Spec: extensions.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + metav1.LabelSelectorRequirement{ + Key: "k", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + }, + }, + }, + } + + // Parse the policy. + pol, err := c.NetworkPolicyToPolicy(&np) + Expect(err).NotTo(HaveOccurred()) + + // Assert key fields are correct. + Expect(pol.Key.(model.PolicyKey).Name).To(Equal("knp.default.default.testPolicy")) + + // Assert value fields are correct. + Expect(int(*pol.Value.(*model.Policy).Order)).To(Equal(1000)) + Expect(pol.Value.(*model.Policy).Selector).To(Equal("calico/k8s_ns == 'default' && k in { 'v1', 'v2' }")) + Expect(len(pol.Value.(*model.Policy).InboundRules)).To(Equal(0)) + + // There should be no OutboundRules + Expect(len(pol.Value.(*model.Policy).OutboundRules)).To(Equal(0)) + + // Check that Types field exists and has only 'ingress' + Expect(len(pol.Value.(*model.Policy).Types)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).Types[0]).To(Equal("ingress")) + }) + + It("should parse a NetworkPolicy with Ports only", func() { + protocol := extensions.ProtocolTCP + port := intstr.FromInt(80) + np := extensions.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testPolicy", + Namespace: "default", + }, + Spec: extensions.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + Ingress: []extensions.NetworkPolicyIngressRule{ + extensions.NetworkPolicyIngressRule{ + Ports: []extensions.NetworkPolicyPort{ + extensions.NetworkPolicyPort{ + Protocol: &protocol, + Port: &port, + }, + }, + }, + }, + }, + } + + // Parse the policy. + pol, err := c.NetworkPolicyToPolicy(&np) + Expect(err).NotTo(HaveOccurred()) + + // Assert key fields are correct. + Expect(pol.Key.(model.PolicyKey).Name).To(Equal("knp.default.default.testPolicy")) + + // Assert value fields are correct. + Expect(int(*pol.Value.(*model.Policy).Order)).To(Equal(1000)) + Expect(pol.Value.(*model.Policy).Selector).To(Equal("calico/k8s_ns == 'default'")) + Expect(len(pol.Value.(*model.Policy).InboundRules)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).InboundRules[0].Protocol.String()).To(Equal("tcp")) + Expect(len(pol.Value.(*model.Policy).InboundRules[0].DstPorts)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).InboundRules[0].DstPorts[0].String()).To(Equal("80")) + + // There should be no OutboundRules + Expect(len(pol.Value.(*model.Policy).OutboundRules)).To(Equal(0)) + + // Check that Types field exists and has only 'ingress' + Expect(len(pol.Value.(*model.Policy).Types)).To(Equal(1)) + Expect(pol.Value.(*model.Policy).Types[0]).To(Equal("ingress")) + }) +}) + var _ = Describe("Test Namespace conversion", func() { // Use a single instance of the Converter for these tests.