Skip to content

Commit

Permalink
Merge pull request #165 from xrstf/improve-logging
Browse files Browse the repository at this point in the history
cleanup logging, return errors from pkg/ code
  • Loading branch information
k8s-ci-robot committed Apr 3, 2024
2 parents c245301 + 1616142 commit b747e98
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 141 deletions.
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

0 comments on commit b747e98

Please sign in to comment.