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

Add "-run" filter for antctl check installation command #6333

Merged
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
101 changes: 77 additions & 24 deletions pkg/antctl/raw/check/installation/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"net"
"os"
"regexp"
"time"

"github.com/fatih/color"
Expand All @@ -41,12 +42,14 @@ func Command() *cobra.Command {
return Run(o)
},
}
command.Flags().StringVarP(&o.antreaNamespace, "Namespace", "n", o.antreaNamespace, "Configure Namespace in which Antrea is running")
command.Flags().StringVarP(&o.antreaNamespace, "namespace", "n", o.antreaNamespace, "Configure Namespace in which Antrea is running")
command.Flags().StringVar(&o.runFilter, "run", o.runFilter, "Run only the tests that match the provided regex")
return command
}

type options struct {
antreaNamespace string
runFilter string
}

func newOptions() *options {
Expand Down Expand Up @@ -100,38 +103,48 @@ type testContext struct {
echoSameNodePod *corev1.Pod
echoOtherNodePod *corev1.Pod
namespace string
// A nil regex indicates that all the tests should be run.
runFilterRegex *regexp.Regexp
}

type testStats struct {
numSuccess int
numFailure int
numSkipped int
}

func compileRunFilter(runFilter string) (*regexp.Regexp, error) {
if runFilter == "" {
return nil, nil
}
re, err := regexp.Compile(runFilter)
if err != nil {
return nil, fmt.Errorf("invalid regex for run filter: %w", err)
}
return re, nil
}

func Run(o *options) error {
runFilterRegex, err := compileRunFilter(o.runFilter)
if err != nil {
return err
}

client, config, clusterName, err := check.NewClient()
if err != nil {
return fmt.Errorf("unable to create Kubernetes client: %s", err)
return fmt.Errorf("unable to create Kubernetes client: %w", err)
}
ctx := context.Background()
testContext := NewTestContext(client, config, clusterName, o)
testContext := NewTestContext(client, config, clusterName, o.antreaNamespace, runFilterRegex)
if err := testContext.setup(ctx); err != nil {
return err
}
var numSuccess, numFailure, numSkipped int
for name, test := range testsRegistry {
testContext.Header("Running test: %s", name)
if err := test.Run(ctx, testContext); err != nil {
if errors.As(err, new(notRunnableError)) {
testContext.Warning("Test %s was skipped: %v", name, err)
numSkipped++
} else {
testContext.Fail("Test %s failed: %v", name, err)
numFailure++
}
} else {
testContext.Success("Test %s passed", name)
numSuccess++
}
}
testContext.Log("Test finished: %v tests succeeded, %v tests failed, %v tests were skipped", numSuccess, numFailure, numSkipped)
stats := testContext.runTests(ctx)

testContext.Log("Test finished: %v tests succeeded, %v tests failed, %v tests were skipped", stats.numSuccess, stats.numFailure, stats.numSkipped)
check.Teardown(ctx, testContext.client, testContext.clusterName, testContext.namespace)
if numFailure > 0 {
return fmt.Errorf("%v/%v tests failed", numFailure, len(testsRegistry))
if stats.numFailure > 0 {
return fmt.Errorf("%v/%v tests failed", stats.numFailure, len(testsRegistry))
}
return nil
}
Expand All @@ -156,13 +169,20 @@ func newService(name string, selector map[string]string, port int) *corev1.Servi
}
}

func NewTestContext(client kubernetes.Interface, config *rest.Config, clusterName string, o *options) *testContext {
func NewTestContext(
client kubernetes.Interface,
config *rest.Config,
clusterName string,
antreaNamespace string,
runFilterRegex *regexp.Regexp,
) *testContext {
return &testContext{
client: client,
config: config,
clusterName: clusterName,
antreaNamespace: o.antreaNamespace,
antreaNamespace: antreaNamespace,
namespace: check.GenerateRandomNamespace(testNamespacePrefix),
runFilterRegex: runFilterRegex,
}
}

Expand Down Expand Up @@ -305,6 +325,39 @@ func (t *testContext) setup(ctx context.Context) error {
return nil
}

func (t *testContext) runTests(ctx context.Context) testStats {
var stats testStats
for name, test := range testsRegistry {
if t.runFilterRegex != nil && !t.runFilterRegex.MatchString(name) {
continue
}
t.Header("Running test: %s", name)
if err := test.Run(ctx, t); err != nil {
if errors.As(err, new(notRunnableError)) {
t.Warning("Test %s was skipped: %v", name, err)
stats.numSkipped++
} else {
t.Fail("Test %s failed: %v", name, err)
stats.numFailure++
}
} else {
t.Success("Test %s passed", name)
stats.numSuccess++
}
}
return stats
}

func (t *testContext) runAgnhostConnect(ctx context.Context, clientPodName string, container string, target string, targetPort int) error {
cmd := agnhostConnectCommand(target, fmt.Sprint(targetPort))
_, stderr, err := check.ExecInPod(ctx, t.client, t.config, t.namespace, clientPodName, container, cmd)
if err != nil {
// We log the contents of stderr here for troubleshooting purposes.
t.Log("/agnhost command failed - stderr: %s", stderr)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe including the full cmd can also help here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's ok right now because the command is always the same, and we have enough context from the surrounding log lines. If we add more complex commands, we can start logging them as well.

}
return err
}

func (t *testContext) Log(format string, a ...interface{}) {
fmt.Fprintf(os.Stdout, fmt.Sprintf("[%s] ", t.clusterName)+format+"\n", a...)
}
Expand Down
106 changes: 106 additions & 0 deletions pkg/antctl/raw/check/installation/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2024 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 installation

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func overrideTestsRegistry(t *testing.T, registry map[string]Test) {
oldRegistry := testsRegistry
testsRegistry = registry
t.Cleanup(func() {
testsRegistry = oldRegistry
})
}

type notRunnableTest struct{}

func (t *notRunnableTest) Run(ctx context.Context, testContext *testContext) error {
return newNotRunnableError("not runnable")
}

type failedTest struct{}

func (t *failedTest) Run(ctx context.Context, testContext *testContext) error {
return fmt.Errorf("failed")
}

type successfulTest struct{}

func (t *successfulTest) Run(ctx context.Context, testContext *testContext) error {
return nil
}

func TestRun(t *testing.T) {
ctx := context.Background()

registry := map[string]Test{
"not-runnable": &notRunnableTest{},
"failure": &failedTest{},
"success": &successfulTest{},
}

testCases := []struct {
name string
registry map[string]Test
runFilter string
expectedStats testStats
}{
{
name: "no test in registry",
expectedStats: testStats{},
},
{
name: "run all tests",
registry: registry,
expectedStats: testStats{
numSuccess: 1,
numFailure: 1,
numSkipped: 1,
},
},
{
name: "run single test",
registry: registry,
runFilter: "success",
expectedStats: testStats{
numSuccess: 1,
},
},
{
name: "no matching test",
registry: registry,
runFilter: "my-test",
expectedStats: testStats{},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
overrideTestsRegistry(t, tc.registry)
runFilterRegex, err := compileRunFilter(tc.runFilter)
require.NoError(t, err)
testContext := NewTestContext(nil, nil, "test-cluster", "kube-system", runFilterRegex)
stats := testContext.runTests(ctx)
assert.Equal(t, tc.expectedStats, stats)
})
}
}
5 changes: 1 addition & 4 deletions pkg/antctl/raw/check/installation/test_podtointernet.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ package installation
import (
"context"
"fmt"

"antrea.io/antrea/pkg/antctl/raw/check"
)

type PodToInternetConnectivityTest struct{}
Expand All @@ -31,8 +29,7 @@ func (t *PodToInternetConnectivityTest) Run(ctx context.Context, testContext *te
for _, clientPod := range testContext.clientPods {
srcPod := testContext.namespace + "/" + clientPod.Name
testContext.Log("Validating connectivity from Pod %s to the world (google.com)...", srcPod)
_, _, err := check.ExecInPod(ctx, testContext.client, testContext.config, testContext.namespace, clientPod.Name, clientDeploymentName, agnhostConnectCommand("google.com", "80"))
if err != nil {
if err := testContext.runAgnhostConnect(ctx, clientPod.Name, "", "google.com", 80); err != nil {
return fmt.Errorf("Pod %s was not able to connect to google.com: %w", srcPod, err)
}
testContext.Log("Pod %s was able to connect to google.com", srcPod)
Expand Down
5 changes: 1 addition & 4 deletions pkg/antctl/raw/check/installation/test_podtopodinternode.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ package installation
import (
"context"
"fmt"

"antrea.io/antrea/pkg/antctl/raw/check"
)

type PodToPodInterNodeConnectivityTest struct{}
Expand All @@ -37,8 +35,7 @@ func (t *PodToPodInterNodeConnectivityTest) Run(ctx context.Context, testContext
for _, podIP := range testContext.echoOtherNodePod.Status.PodIPs {
echoIP := podIP.IP
testContext.Log("Validating from Pod %s to Pod %s at IP %s...", srcPod, dstPod, echoIP)
_, _, err := check.ExecInPod(ctx, testContext.client, testContext.config, testContext.namespace, clientPod.Name, "", agnhostConnectCommand(echoIP, "80"))
if err != nil {
if err := testContext.runAgnhostConnect(ctx, clientPod.Name, "", echoIP, 80); err != nil {
return fmt.Errorf("client Pod %s was not able to communicate with echo Pod %s (%s): %w", clientPod.Name, testContext.echoOtherNodePod.Name, echoIP, err)
}
testContext.Log("client Pod %s was able to communicate with echo Pod %s (%s)", clientPod.Name, testContext.echoOtherNodePod.Name, echoIP)
Expand Down
5 changes: 1 addition & 4 deletions pkg/antctl/raw/check/installation/test_podtopodintranode.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ package installation
import (
"context"
"fmt"

"antrea.io/antrea/pkg/antctl/raw/check"
)

type PodToPodIntraNodeConnectivityTest struct{}
Expand All @@ -34,8 +32,7 @@ func (t *PodToPodIntraNodeConnectivityTest) Run(ctx context.Context, testContext
for _, podIP := range testContext.echoSameNodePod.Status.PodIPs {
echoIP := podIP.IP
testContext.Log("Validating from Pod %s to Pod %s at IP %s...", srcPod, dstPod, echoIP)
_, _, err := check.ExecInPod(ctx, testContext.client, testContext.config, testContext.namespace, clientPod.Name, "", agnhostConnectCommand(echoIP, "80"))
if err != nil {
if err := testContext.runAgnhostConnect(ctx, clientPod.Name, "", echoIP, 80); err != nil {
return fmt.Errorf("client Pod %s was not able to communicate with echo Pod %s (%s): %w", clientPod.Name, testContext.echoSameNodePod.Name, echoIP, err)
}
testContext.Log("client Pod %s was able to communicate with echo Pod %s (%s)", clientPod.Name, testContext.echoSameNodePod.Name, echoIP)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ package installation
import (
"context"
"fmt"

"antrea.io/antrea/pkg/antctl/raw/check"
)

type PodToServiceInterNodeConnectivityTest struct{}
Expand All @@ -34,8 +32,7 @@ func (t *PodToServiceInterNodeConnectivityTest) Run(ctx context.Context, testCon
service := echoOtherNodeDeploymentName
for _, clientPod := range testContext.clientPods {
testContext.Log("Validating from Pod %s to Service %s in Namespace %s...", clientPod.Name, service, testContext.namespace)
_, _, err := check.ExecInPod(ctx, testContext.client, testContext.config, testContext.namespace, clientPod.Name, "", agnhostConnectCommand(service, "80"))
if err != nil {
if err := testContext.runAgnhostConnect(ctx, clientPod.Name, "", service, 80); err != nil {
return fmt.Errorf("client Pod %s was not able to communicate with Service %s", clientPod.Name, service)
}
testContext.Log("client Pod %s was able to communicate with Service %s", clientPod.Name, service)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ package installation
import (
"context"
"fmt"

"antrea.io/antrea/pkg/antctl/raw/check"
)

type PodToServiceIntraNodeConnectivityTest struct{}
Expand All @@ -31,8 +29,7 @@ func (t *PodToServiceIntraNodeConnectivityTest) Run(ctx context.Context, testCon
service := echoSameNodeDeploymentName
for _, clientPod := range testContext.clientPods {
testContext.Log("Validating from Pod %s to Service %s in Namespace %s...", clientPod.Name, service, testContext.namespace)
_, _, err := check.ExecInPod(ctx, testContext.client, testContext.config, testContext.namespace, clientPod.Name, "", agnhostConnectCommand(service, "80"))
if err != nil {
if err := testContext.runAgnhostConnect(ctx, clientPod.Name, "", service, 80); err != nil {
return fmt.Errorf("client Pod %s was not able to communicate with Service %s", clientPod.Name, service)
}
testContext.Log("client Pod %s was able to communicate with Service %s", clientPod.Name, service)
Expand Down
Loading