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

cleanup logging, return errors from pkg/ code #165

Merged
merged 1 commit into from
Apr 3, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/update-deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ jobs:
delete-branch: true
labels: ok-to-test
body: |
Updating go.mod with latest kubernetes related dependencies...
Updating go.mod with latest Kubernetes related dependencies...
60 changes: 44 additions & 16 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,37 +50,65 @@ var (

var rootCmd = &cobra.Command{
Use: "hydrophone",
Short: "Hydrophone is a lightweight runner for kubernetes tests.",
Long: `Hydrophone is a lightweight runner for kubernetes tests.`,
Short: "Hydrophone is a lightweight runner for Kubernetes tests.",
Long: `Hydrophone is a lightweight runner for Kubernetes tests.`,
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()

client := client.NewClient()
config, clientSet := service.Init(viper.GetString("kubeconfig"))
config, clientSet, err := service.Init(viper.GetString("kubeconfig"))
if err != nil {
log.Fatalf("Failed to init kube client: %v.", err)
}

client.ClientSet = clientSet
common.SetDefaults(client.ClientSet, config)
err = common.SetDefaults(client.ClientSet, config)
if err != nil {
log.Fatalf("Failed to apply default values: %v.", err)
}

if cleanup {
service.Cleanup(ctx, client.ClientSet)
if err := service.Cleanup(ctx, client.ClientSet); err != nil {
log.Fatalf("Failed to cleanup: %v.", err)
}
} else if listImages {
service.PrintListImages(ctx, client.ClientSet)
if err := service.PrintListImages(ctx, client.ClientSet); err != nil {
log.Fatalf("Failed to list images: %v.", err)
}
} else {
if err := common.ValidateConformanceArgs(); err != nil {
log.Fatal(err)
log.Fatalf("Invalid arguments: %v.", err)
}

if err := service.RunE2E(ctx, client.ClientSet); err != nil {
log.Fatalf("Failed to run tests: %v.", err)
}
spinner := common.NewSpinner(os.Stdout)

service.RunE2E(ctx, client.ClientSet)
spinner := common.NewSpinner(os.Stdout)
spinner.Start()
// PrintE2ELogs is a long running method
client.PrintE2ELogs(ctx)
spinner.Stop()
client.FetchFiles(ctx, config, clientSet, viper.GetString("output-dir"))
client.FetchExitCode(ctx)
service.Cleanup(ctx, client.ClientSet)
if err := client.PrintE2ELogs(ctx); err != nil {
log.Fatalf("Failed to get test logs: %v.", err)
}
spinner.Stop()

if err := client.FetchFiles(ctx, config, clientSet, viper.GetString("output-dir")); err != nil {
log.Fatalf("Failed to download results: %v.", err)
}
if err := client.FetchExitCode(ctx); err != nil {
log.Fatalf("Failed to determine exit code: %v.", err)
}
if err := service.Cleanup(ctx, client.ClientSet); err != nil {
log.Fatalf("Failed to cleanup: %v.", err)
}
}

if client.ExitCode == 0 {
log.Println("Tests completed successfully.")
} else {
log.Printf("Tests failed (code %d).", client.ExitCode)
os.Exit(client.ExitCode)
}
log.Println("Exiting with code: ", client.ExitCode)
os.Exit(client.ExitCode)
},
}

Expand Down
22 changes: 14 additions & 8 deletions pkg/client/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ type streamLogs struct {
}

// PrintE2ELogs checks for Pod and start a go routine if new deployment added
func (c *Client) PrintE2ELogs(ctx context.Context) {
func (c *Client) PrintE2ELogs(ctx context.Context) error {
informerFactory := informers.NewSharedInformerFactory(c.ClientSet, 10*time.Second)

podInformer := informerFactory.Core().V1().Pods()

podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{})
if _, err := podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{}); err != nil {
return fmt.Errorf("failed to add event handler: %w", err)
}

informerFactory.Start(wait.NeverStop)
informerFactory.WaitForCacheSync(wait.NeverStop)
Expand Down Expand Up @@ -79,25 +81,27 @@ func (c *Client) PrintE2ELogs(ctx context.Context) {
break
}
}

return nil
}

// FetchExitCode waits for pod to be in terminated state and get the exit code
func (c *Client) FetchExitCode(ctx context.Context) {
func (c *Client) FetchExitCode(ctx context.Context) error {
// Watching the pod's status
watchInterface, err := c.ClientSet.CoreV1().Pods(viper.GetString("namespace")).Watch(ctx, metav1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s", common.PodName),
})
if err != nil {
log.Fatal(err)
return fmt.Errorf("failed to watch Pods: %w", err)
}

log.Println("Waiting for pod to terminate...")
log.Println("Waiting for Pod to terminate...")
for event := range watchInterface.ResultChan() {
pod, ok := event.Object.(*v1.Pod)
if !ok {
log.Println("unexpected type")
log.Printf("Received unexpected %T object from Watch.", pod)
c.ExitCode = -1
return
return nil
}

if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodFailed {
Expand All @@ -113,7 +117,7 @@ func (c *Client) FetchExitCode(ctx context.Context) {
for _, containerStatus := range pod.Status.ContainerStatuses {
if containerStatus.State.Terminated != nil {
terminated = true
log.Printf("container %s terminated.\n", containerStatus.Name)
log.Printf("Container %s terminated.", containerStatus.Name)
if containerStatus.Name == common.ConformanceContainer {
c.ExitCode = int(containerStatus.State.Terminated.ExitCode)
}
Expand All @@ -124,4 +128,6 @@ func (c *Client) FetchExitCode(ctx context.Context) {
}
}
}

return nil
}
39 changes: 22 additions & 17 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,30 @@ func NewClient() *Client {

// FetchFiles downloads the e2e.log and junit_01.xml files from the pod
// and writes them to the output directory
func (c *Client) FetchFiles(ctx context.Context, config *rest.Config, clientset *kubernetes.Clientset, outputDir string) {
log.Println("downloading e2e.log to ", filepath.Join(outputDir, "e2e.log"))
e2eLogFile, err := os.OpenFile(filepath.Join(outputDir, "e2e.log"), os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
log.Fatalf("unable to create e2e.log: %v\n", err)
}
defer e2eLogFile.Close()
err = downloadFile(ctx, config, clientset, viper.GetString("namespace"), common.PodName, common.OutputContainer, "/tmp/results/e2e.log", e2eLogFile)
if err != nil {
log.Fatalf("unable to download e2e.log: %v\n", err)
func (c *Client) FetchFiles(ctx context.Context, config *rest.Config, clientset *kubernetes.Clientset, outputDir string) error {
if err := c.fetchFile(ctx, config, clientset, outputDir, "e2e.log"); err != nil {
return err
}
log.Println("downloading junit_01.xml to ", filepath.Join(outputDir, "junit_01.xml"))
junitXMLFile, err := os.OpenFile(filepath.Join(outputDir, "junit_01.xml"), os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
log.Fatalf("unable to create junit_01.xml: %v\n", err)

if err := c.fetchFile(ctx, config, clientset, outputDir, "junit_01.xml"); err != nil {
return err
}
defer junitXMLFile.Close()
err = downloadFile(ctx, config, clientset, viper.GetString("namespace"), common.PodName, common.OutputContainer, "/tmp/results/junit_01.xml", junitXMLFile)

return nil
}

// FetchFiles downloads a single file from the output container to the local machine.
func (c *Client) fetchFile(ctx context.Context, config *rest.Config, clientset *kubernetes.Clientset, outputDir string, filename string) error {
dest := filepath.Join(outputDir, filename)
log.Printf("Downloading %s to %s...", filename, dest)

localFile, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
log.Fatalf("unable to download junit_01.xml: %v\n", err)
return err
}
defer localFile.Close()

containerFile := "/tmp/results/" + filename

return downloadFile(ctx, config, clientset, viper.GetString("namespace"), common.PodName, common.OutputContainer, containerFile, localFile)
}
15 changes: 12 additions & 3 deletions pkg/client/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ limitations under the License.
package client

import (
"bytes"
"context"
"fmt"
"io"

corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -45,7 +47,7 @@ func downloadFile(ctx context.Context, config *rest.Config, clientset *kubernete
// Configure exec options
option := &corev1.PodExecOptions{
Stdout: true,
Stderr: false,
Stderr: true,
Command: []string{"cat", filePath},
}
parameterCodec := runtime.NewParameterCodec(scheme)
Expand All @@ -58,10 +60,17 @@ func downloadFile(ctx context.Context, config *rest.Config, clientset *kubernete
}

// Stream the file content from the container to the writer
return exec.StreamWithContext(
var stderr bytes.Buffer

err = exec.StreamWithContext(
ctx,
remotecommand.StreamOptions{
Stdout: writer,
Stderr: nil,
Stderr: &stderr,
})
if err != nil {
return fmt.Errorf("download failed: %w (stderr: %s)", err, stderr.String())
}

return nil
}
39 changes: 22 additions & 17 deletions pkg/common/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@ import (

// SetDefaults sets the default values for various configuration options used in the application.
// Finally, it logs the API endpoint, server version, namespace, conformance image, and busybox image.
func SetDefaults(clientSet *kubernetes.Clientset, config *rest.Config) {
serverVersion, err := clientSet.ServerVersion()
func SetDefaults(clientset *kubernetes.Clientset, config *rest.Config) error {
serverVersion, err := clientset.ServerVersion()
if err != nil {
log.Fatalf("Error fetching server version: %v", err)
return fmt.Errorf("failed fetching server version: %w", err)
}
trimmedVersion, err := trimVersion(serverVersion.String())
if err != nil {
log.Fatalf("Error trimming server version: %v", err)
return fmt.Errorf("failed parsing server version: %w", err)
}

if viper.Get("conformance-image") == "" {
viper.Set("conformance-image", fmt.Sprintf("registry.k8s.io/conformance:%s", trimmedVersion))
}
Expand All @@ -49,23 +50,29 @@ func SetDefaults(clientSet *kubernetes.Clientset, config *rest.Config) {
if viper.Get("namespace") == "" {
viper.Set("namespace", DefaultNamespace)
}
log.Printf("API endpoint : %s", config.Host)
log.Printf("Server version : %#v", *serverVersion)
log.Printf("Using namespace : '%s'", viper.Get("namespace"))
log.Printf("Using conformance image : '%s'", viper.Get("conformance-image"))
log.Printf("Using busybox image : '%s'", viper.Get("busybox-image"))

log.Printf("API endpoint: %s", config.Host)
log.Printf("Server version: %#v", *serverVersion)
log.Printf("Using namespace: %s", viper.Get("namespace"))
log.Printf("Using conformance image: %s", viper.Get("conformance-image"))
log.Printf("Using busybox image: %s", viper.Get("busybox-image"))

if viper.GetBool("dry-run") {
log.Println("Dry-run enabled.")
}

return nil
}

// ValidateConformanceArgs validates the arguments passed to the program
// and creates the output directory if it doesn't exist
func ValidateConformanceArgs() error {

if viper.Get("focus") == "" {
viper.Set("focus", "\\[Conformance\\]")
}

if viper.Get("skip") != "" {
log.Printf("Skipping tests : '%s'", viper.Get("skip"))
log.Printf("Skipping tests: %s", viper.Get("skip"))
}

if extraArgs := viper.GetStringSlice("extra-args"); len(extraArgs) != 0 {
Expand All @@ -81,14 +88,12 @@ func ValidateConformanceArgs() error {
}
}

log.Printf("Test framework will start '%d' threads and use verbosity '%d'",
log.Printf("Test framework will start %d thread(s) and use verbosity level %d.",
viper.Get("parallel"), viper.Get("verbosity"))

outputDir := viper.GetString("output-dir")
if _, err := os.Stat(outputDir); os.IsNotExist(err) {
if err = os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("error creating output directory [%s] : %v", outputDir, err)
}
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("error creating output directory [%s]: %w", outputDir, err)
}
return nil
}
Expand All @@ -98,7 +103,7 @@ func trimVersion(version string) (string, error) {

parsedVersion, err := semver.Parse(version)
if err != nil {
return "", fmt.Errorf("error parsing conformance image tag: %v", err)
return "", fmt.Errorf("error parsing conformance image tag: %w", err)
}

return "v" + parsedVersion.FinalizeVersion(), nil
Expand Down
Loading