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

Move glooctl debug unit tests to e2e tests #10511

Merged
merged 12 commits into from
Dec 20, 2024
13 changes: 13 additions & 0 deletions changelog/v1.19.0-beta3/fix-glooctl-debug-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
changelog:
- type: NON_USER_FACING
issueLink: https://github.com/k8sgateway/k8sgateway/issues/10400
description: >-
fixes flaky glooctl debug tests by moving them from unit to e2e tests
skipCI-docs-build:true
- type: NON_USER_FACING
issueLink: https://github.com/solo-io/gloo/issues/10482
description: >-
fixes flaky glooctl debug tests by moving them from unit to e2e tests
skipCI-docs-build:true
99 changes: 0 additions & 99 deletions projects/gloo/cli/pkg/cmd/debug/root_test.go

This file was deleted.

24 changes: 24 additions & 0 deletions projects/gloo/cli/pkg/testutils/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/spf13/cobra"

"github.com/solo-io/gloo/pkg/cliutil/testutil"
cli "github.com/solo-io/gloo/projects/gloo/cli/pkg/cmd"
)

Expand Down Expand Up @@ -65,6 +66,29 @@ func (c *GlooCli) DebugLogs(ctx context.Context, extraArgs ...string) error {
return ExecuteCommandWithArgs(c.NewCommand(ctx), debugLogsArgs...)
}

// Debug attempts to write debug information to a specified directory, depending on the consent of the user to do so.
// It runs a function passed by the caller on the returned error (even if nil),
// the intention of which is to allow the caller to make assertions about the error, e.g. by passing s.NoError as the errAssertions func
func (c *GlooCli) Debug(ctx context.Context, userConsent bool, errAssertions func(error, ...interface{}) bool, extraArgs ...string) {
debugLogsArgs := append([]string{
"debug",
}, extraArgs...)

testutil.ExpectInteractive(func(c *testutil.Console) {
c.ExpectString("This command will overwrite")
c.SendLine(func() string {
if userConsent {
return "y"
}
return "N"
}())
c.ExpectEOF()
}, func() {
err := ExecuteCommandWithArgs(c.NewCommand(ctx), debugLogsArgs...)
errAssertions(err)
}, nil)
}

// IstioInject inject istio-proxy and sds
func (c *GlooCli) IstioInject(ctx context.Context, installNamespace, kubeContext string, extraArgs ...string) (string, error) {
injectArgs := append([]string{
Expand Down
142 changes: 142 additions & 0 deletions test/kubernetes/e2e/features/glooctl/debug_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import (
"context"
"os"
"path/filepath"
"strings"

"github.com/solo-io/gloo/test/kubernetes/e2e"
"github.com/stretchr/testify/suite"
)

var _ e2e.NewSuiteFunc = NewDebugSuite

var kubeStateFile = func(outDir string) string {
return filepath.Join(outDir, "kube-state.log")
}

// debugSuite contains the set of tests to validate the behavior of `glooctl debug`
// These tests attempt to mirror: https://github.com/solo-io/gloo/blob/v1.16.x/test/kube2e/glooctl/debug_test.go
type debugSuite struct {
Expand Down Expand Up @@ -68,3 +73,140 @@ func (s *debugSuite) TestLogsFile() {
_, err = os.Stat(outputFile)
s.NoError(err, "Output file should have been generated")
}

func (s *debugSuite) TestDebugDeny() {
outputDir := filepath.Join(s.tmpDir, "debug")

// should error and abort if the user does not consent
s.testInstallation.Actions.Glooctl().Debug(s.ctx, false,
func(err error, msgAndArgs ...interface{}) bool {
return s.ErrorContains(err, "Aborting: cannot proceed without overwriting \""+outputDir+"\" directory")
},
"-N", s.testInstallation.Metadata.InstallNamespace, "--directory", outputDir)

s.NoDirExists(outputDir)
}

func (s *debugSuite) TestDebugFile() {
outputDir := filepath.Join(s.tmpDir, "debug")

s.testInstallation.Actions.Glooctl().Debug(s.ctx, true, s.NoError,
"-N", s.testInstallation.Metadata.InstallNamespace, "--directory", outputDir)

// should populate the kube-state.log file
s.checkNonEmptyFile(kubeStateFile(outputDir))

// should populate ns directory
s.DirExists(filepath.Join(outputDir, s.testInstallation.Metadata.InstallNamespace))

// _pods directory should contain expected logs
s.checkPodLogsDir(filepath.Join(outputDir, s.testInstallation.Metadata.InstallNamespace, "_pods"))

// ns dir should contain envoy or gloo controller info for each pod
s.checkEnvoyAndGlooControllerInfo(filepath.Join(outputDir, s.testInstallation.Metadata.InstallNamespace))

// ns dir should contain dirs for expected CRDs with expected files for each CR
s.checkCRs(filepath.Join(outputDir, s.testInstallation.Metadata.InstallNamespace))

// default dir should not exist
s.NoDirExists("debug")
}

func (s *debugSuite) checkPodLogsDir(podsDir string) {
files, err := os.ReadDir(podsDir)
s.NoError(err)

s.GreaterOrEqual(len(files), 4)
var glooPods, gatewayProxyPods, publicGwPods int
for _, file := range files {
fileName := file.Name()
s.True(strings.HasSuffix(fileName, ".log"))

if strings.HasPrefix(fileName, "gloo-") && !strings.HasPrefix(fileName, "gloo-resource-") {
glooPods += 1
} else if strings.HasPrefix(file.Name(), "gateway-proxy-") {
gatewayProxyPods += 1
} else if strings.HasPrefix(file.Name(), "public-gw-") {
publicGwPods += 1
}

s.checkNonEmptyFile(filepath.Join(podsDir, fileName))
}
s.Equal(glooPods, 1)
s.Equal(gatewayProxyPods, 1)
s.Equal(publicGwPods, 2)
}

func (s *debugSuite) checkEnvoyAndGlooControllerInfo(nsDir string) {
files, err := os.ReadDir(nsDir)
s.NoError(err)

var fileIdx int
for _, file := range files {
if file.IsDir() {
continue
}

s.Less(fileIdx, 4*4) // 4 pods, 4 pieces of info each

// rely on os.ReadDir returns dir sorted by filename
var prefix string
switch fileIdx / 4 {
case 0:
prefix = "gateway-proxy-"
case 1:
prefix = "gloo-"
case 2, 3:
prefix = "public-gw-"
}

var gwSuffix string
var glooSuffix string
switch fileIdx % 4 {
case 0:
gwSuffix = ".clusters.log"
glooSuffix = ".controller.log"
case 1:
gwSuffix = ".config.log"
glooSuffix = ".krt_snapshot.log"
case 2:
gwSuffix = ".listeners.log"
glooSuffix = ".metrics.log"
case 3:
gwSuffix = ".stats.log"
glooSuffix = ".xds_snapshot.log"
}

fileName := file.Name()
s.True(strings.HasPrefix(fileName, prefix), "expected file %s at index %d to have prefix %s", fileName, fileIdx, prefix)
if prefix == "gloo-" {
s.True(strings.HasSuffix(fileName, glooSuffix), "expected file %s at index %d to have suffix %s", fileName, fileIdx, glooSuffix)
} else {
s.True(strings.HasSuffix(fileName, gwSuffix), "expected file %s at index %d to have suffix %s", fileName, fileIdx, gwSuffix)
}

s.checkNonEmptyFile(filepath.Join(nsDir, fileName))

fileIdx++
}
}

func (s *debugSuite) checkCRs(nsDir string) {
gws, err := os.ReadDir(filepath.Join(nsDir, "gateways.gateway.solo.io"))
s.NoError(err)
s.Len(gws, 3)
s.checkNonEmptyFile(filepath.Join(nsDir, "gateways.gateway.solo.io", "gateway-proxy.yaml"))
s.checkNonEmptyFile(filepath.Join(nsDir, "gateways.gateway.solo.io", "gateway-proxy-ssl.yaml"))
s.checkNonEmptyFile(filepath.Join(nsDir, "gateways.gateway.solo.io", "public-gw-ssl.yaml"))

settings, err := os.ReadDir(filepath.Join(nsDir, "settings.gloo.solo.io"))
s.NoError(err)
s.Len(settings, 1)
s.checkNonEmptyFile(filepath.Join(nsDir, "settings.gloo.solo.io", "default.yaml"))
}

func (s *debugSuite) checkNonEmptyFile(filepath string) {
bytes, err := os.ReadFile(filepath)
s.NoError(err, filepath+" file should have been generated")
s.NotEmpty(bytes, filepath+" file should not be empty")
}