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

E2E test for MC ACNP copy-span #3435

Merged
merged 1 commit into from
Mar 25, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion ci/jenkins/test-mc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,12 @@ function deliver_multicluster_controller {
leader_ip=$(kubectl get nodes -o wide --no-headers=true ${LEADER_CLUSTER_CONFIG} | awk -v role="$CONTROL_PLANE_NODE_ROLE" '$3 == role {print $6}')
sed -i "s|<LEADER_CLUSTER_IP>|${leader_ip}|" ./multicluster/test/yamls/east-member-cluster.yml
sed -i "s|<LEADER_CLUSTER_IP>|${leader_ip}|" ./multicluster/test/yamls/west-member-cluster.yml
rsync -avr --progress --inplace -e "ssh -o StrictHostKeyChecking=no" ./multicluster/test/yamls/test-acnp-copy-span-ns-isolation.yml jenkins@["${leader_ip}"]:"${WORKDIR}"/test-acnp-copy-span-ns-isolation.yml

for kubeconfig in "${membercluter_kubeconfigs[@]}"
do
ip=$(kubectl get nodes -o wide --no-headers=true ${EAST_CLUSTER_CONFIG} | awk -v role="$CONTROL_PLANE_NODE_ROLE" '$3 == role {print $6}')
rsync -avr --progress --inplace -e "ssh -o StrictHostKeyChecking=no" ./multicluster/test/yamls/test-east-serviceexport.yml jenkins@[${ip}]:${WORKDIR}/serviceexport.yml
rsync -avr --progress --inplace -e "ssh -o StrictHostKeyChecking=no" ./multicluster/test/yamls/test-east-serviceexport.yml jenkins@["${ip}"]:"${WORKDIR}"/serviceexport.yml
done
}

Expand Down
167 changes: 167 additions & 0 deletions multicluster/test/e2e/antreapolicy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2022 Antrea Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e

import (
"fmt"
"testing"
"time"

log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"

antreae2e "antrea.io/antrea/test/e2e"
)

const (
// Provide enough time for policies to be enforced & deleted by the CNI plugin.
networkPolicyDelay = 2 * time.Second
acnpIsolationResourceExport = "test-acnp-copy-span-ns-isolation.yml"
acnpName = "antrea-mc-strict-namespace-isolation"
)

var (
allPodsPerCluster []antreae2e.Pod
perNamespacePods, perClusterNamespaces []string
podsByNamespace map[string][]antreae2e.Pod
clusterK8sUtilsMap map[string]*antreae2e.KubernetesUtils
)

func failOnError(err error, t *testing.T) {
if err != nil {
log.Errorf("%+v", err)
for _, k8sUtils := range clusterK8sUtilsMap {
k8sUtils.Cleanup(perClusterNamespaces)
}
t.Fatalf("test failed: %v", err)
}
}

// initializeForPolicyTest creates three Pods in three test Namespaces for each test cluster.
func initializeForPolicyTest(t *testing.T, data *MCTestData) {
perNamespacePods = []string{"a", "b", "c"}
perClusterNamespaces = []string{"x", "y", "z"}

allPodsPerCluster = []antreae2e.Pod{}
podsByNamespace = make(map[string][]antreae2e.Pod)
clusterK8sUtilsMap = make(map[string]*antreae2e.KubernetesUtils)

for _, podName := range perNamespacePods {
for _, ns := range perClusterNamespaces {
allPodsPerCluster = append(allPodsPerCluster, antreae2e.NewPod(ns, podName))
podsByNamespace[ns] = append(podsByNamespace[ns], antreae2e.NewPod(ns, podName))
}
}
for clusterName := range data.clusterTestDataMap {
d := data.clusterTestDataMap[clusterName]
k8sUtils, err := antreae2e.NewKubernetesUtils(&d)
failOnError(err, t)
_, err = k8sUtils.Bootstrap(perClusterNamespaces, perNamespacePods)
failOnError(err, t)
clusterK8sUtilsMap[clusterName] = k8sUtils
}
}

// tearDownForPolicyTest deletes the test Namespaces specific for policy tests.
func tearDownForPolicyTest() {
for _, k8sUtils := range clusterK8sUtilsMap {
k8sUtils.Cleanup(perClusterNamespaces)
}
}

func testMCAntreaPolicy(t *testing.T, data *MCTestData) {
data.testAntreaPolicyCopySpanNSIsolation(t)
}

// testAntreaPolicyCopySpanNSIsolation tests that after applying a ResourceExport of an ACNP
// for Namespace isolation, strict Namespace isolation is enforced in each of the member clusters.
func (data *MCTestData) testAntreaPolicyCopySpanNSIsolation(t *testing.T) {
setup := func() {
err := data.deployACNPResourceExport(acnpIsolationResourceExport)
failOnError(err, t)
}
teardown := func() {
err := data.deleteACNPResourceExport(acnpIsolationResourceExport)
failOnError(err, t)
}
reachability := antreae2e.NewReachability(allPodsPerCluster, antreae2e.Dropped)
reachability.ExpectAllSelfNamespace(antreae2e.Connected)
testStep := &antreae2e.TestStep{
Name: "Port 80",
Reachability: reachability,
Ports: []int32{80},
Protocol: v1.ProtocolTCP,
}
testCaseList := []*antreae2e.TestCase{
{
Name: "ACNP strict Namespace isolation for all clusters",
Steps: []*antreae2e.TestStep{testStep},
},
}
executeTestsOnAllMemberClusters(t, testCaseList, setup, teardown)
}

func executeTestsOnAllMemberClusters(t *testing.T, testList []*antreae2e.TestCase, setup, teardown func()) {
setup()
time.Sleep(networkPolicyDelay)
for _, testCase := range testList {
log.Infof("Running test case %s", testCase.Name)
for _, step := range testCase.Steps {
log.Infof("Running step %s of test case %s", step.Name, testCase.Name)
reachability := step.Reachability
if reachability != nil {
for clusterName, k8sUtils := range clusterK8sUtilsMap {
if clusterName == leaderCluster {
// skip traffic test for the leader cluster
continue
}
if _, err := k8sUtils.GetACNP(acnpName); err != nil {
t.Errorf("Failed to get ACNP to be replicated in cluster %s", clusterName)
}
start := time.Now()
k8sUtils.Validate(allPodsPerCluster, reachability, step.Ports, step.Protocol)
step.Duration = time.Now().Sub(start)
_, wrong, _ := step.Reachability.Summary()
if wrong != 0 {
t.Errorf("Failure in cluster %s -- %d wrong results", clusterName, wrong)
reachability.PrintSummary(true, true, true)
Copy link
Contributor

@luolanzone luolanzone Mar 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not introduced in this PR, but I see all callers of reachability.PrintSummary() are using (true, true, true), maybe we can remove all three parameters. do you think if this can be enhanced in #3286 by enhao?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. /cc @enhaocui for awareness.

}
}
}
}
}
teardown()
}

func (data *MCTestData) deployACNPResourceExport(reFileName string) error {
var rc int
var err error
log.Infof("Creating ResourceExport %s in the leader cluster", reFileName)
rc, _, _, err = provider.RunCommandOnNode(leaderCluster, fmt.Sprintf("kubectl apply -f %s", reFileName))
if err != nil || rc != 0 {
return fmt.Errorf("error when deploying the ACNP ResourceExport in leader cluster: %v", err)
}
return nil
}

func (data *MCTestData) deleteACNPResourceExport(reFileName string) error {
var rc int
var err error
rc, _, _, err = provider.RunCommandOnNode(leaderCluster, fmt.Sprintf("kubectl delete -f %s", reFileName))
if err != nil || rc != 0 {
return fmt.Errorf("error when deleting the ACNP ResourceExport in leader cluster: %v", err)
}
return nil
}
17 changes: 17 additions & 0 deletions multicluster/test/e2e/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,20 @@ func testMain(m *testing.M) int {
func TestMain(m *testing.M) {
os.Exit(testMain(m))
}

func TestConnectivity(t *testing.T) {
data, err := setupTest(t)
if err != nil {
t.Fatalf("Error when setting up test: %v", err)
}
defer teardownTest(t, data)

t.Run("testServiceExport", func(t *testing.T) {
testServiceExport(t, data)
})
t.Run("testAntreaPolicy", func(t *testing.T) {
initializeForPolicyTest(t, data)
testMCAntreaPolicy(t, data)
tearDownForPolicyTest()
})
}
12 changes: 0 additions & 12 deletions multicluster/test/e2e/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,6 @@ import (
e2euttils "antrea.io/antrea/test/e2e/utils"
)

func TestConnectivity(t *testing.T) {
data, err := setupTest(t)
if err != nil {
t.Fatalf("Error when setting up test: %v", err)
}
defer teardownTest(t, data)

t.Run("testServiceExport", func(t *testing.T) {
testServiceExport(t, data)
})
}

func testServiceExport(t *testing.T, data *MCTestData) {
data.testServiceExport(t)
}
Expand Down
23 changes: 23 additions & 0 deletions multicluster/test/yamls/test-acnp-copy-span-ns-isolation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
apiVersion: multicluster.crd.antrea.io/v1alpha1
kind: ResourceExport
metadata:
name: strict-namespace-isolation-for-test-clusterset
namespace: antrea-mcs-ns
spec:
kind: AntreaClusterNetworkPolicy
name: strict-namespace-isolation
clusternetworkpolicy:
priority: 1
tier: securityops
appliedTo:
- namespaceSelector: # Selects all non-system Namespaces in the cluster
matchExpressions:
- {key: kubernetes.io/metadata.name, operator: NotIn, values: [kube-system]}
ingress:
- action: Pass
from:
- namespaces:
match: Self
- action: Drop
from:
- namespaceSelector: {}
31 changes: 0 additions & 31 deletions test/e2e/antreapolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,24 +104,6 @@ func failOnError(err error, t *testing.T) {
}
}

// TestCase is a collection of TestSteps to be tested against.
type TestCase struct {
Name string
Steps []*TestStep
}

// TestStep is a single unit of testing spec. It includes the policy specs that need to be
// applied for this test, the port to test traffic on and the expected Reachability matrix.
type TestStep struct {
Name string
Reachability *Reachability
TestResources []metav1.Object
Ports []int32
Protocol v1.Protocol
Duration time.Duration
CustomProbes []*CustomProbe
}

// podToAddrTestStep is a single unit of testing the connectivity from a Pod to an
// arbitrary destination address.
type podToAddrTestStep struct {
Expand All @@ -131,19 +113,6 @@ type podToAddrTestStep struct {
expectedConnectivity PodConnectivityMark
}

// CustomProbe will spin up (or update) SourcePod and DestPod such that Add event of Pods
// can be tested against expected connectivity among those Pods.
type CustomProbe struct {
// Create or update a source Pod.
SourcePod CustomPod
// Create or update a destination Pod.
DestPod CustomPod
// Port on which the probe will be made.
Port int32
// Set the expected connectivity.
ExpectConnectivity PodConnectivityMark
}

func initialize(t *testing.T, data *TestData) {
p80 = 80
p81 = 81
Expand Down
31 changes: 31 additions & 0 deletions test/e2e/k8s_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,37 @@ func NewKubernetesUtils(data *TestData) (*KubernetesUtils, error) {
}, nil
}

// TestCase is a collection of TestSteps to be tested against.
type TestCase struct {
Name string
Steps []*TestStep
}

// TestStep is a single unit of testing spec. It includes the policy specs that need to be
// applied for this test, the port to test traffic on and the expected Reachability matrix.
type TestStep struct {
Name string
Reachability *Reachability
TestResources []metav1.Object
Ports []int32
Protocol v1.Protocol
Duration time.Duration
CustomProbes []*CustomProbe
}

// CustomProbe will spin up (or update) SourcePod and DestPod such that Add event of Pods
// can be tested against expected connectivity among those Pods.
type CustomProbe struct {
// Create or update a source Pod.
SourcePod CustomPod
// Create or update a destination Pod.
DestPod CustomPod
// Port on which the probe will be made.
Port int32
// Set the expected connectivity.
ExpectConnectivity PodConnectivityMark
}

// GetPodByLabel returns a Pod with the matching Namespace and "pod" label.
func (k *KubernetesUtils) GetPodByLabel(ns string, name string) (*v1.Pod, error) {
pods, err := k.getPodsUncached(ns, "pod", name)
Expand Down