Skip to content

Commit 41553d4

Browse files
authored
[VC-35738] Stop the API server when the context is cancelled (#604)
* Test that one-shot mode exits * Stop the server when the context is cancelled
1 parent c87f7c3 commit 41553d4

File tree

5 files changed

+169
-12
lines changed

5 files changed

+169
-12
lines changed

pkg/agent/run.go

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"fmt"
99
"io"
10+
"net"
1011
"net/http"
1112
"os"
1213
"strings"
@@ -25,6 +26,7 @@ import (
2526
"k8s.io/client-go/kubernetes"
2627
clientgocorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
2728
"k8s.io/client-go/tools/record"
29+
"k8s.io/klog/v2"
2830
"sigs.k8s.io/controller-runtime/pkg/manager"
2931

3032
"github.com/jetstack/preflight/api"
@@ -50,7 +52,12 @@ const schemaVersion string = "v2.0.0"
5052
// Run starts the agent process
5153
func Run(cmd *cobra.Command, args []string) {
5254
logs.Log.Printf("Preflight agent version: %s (%s)", version.PreflightVersion, version.Commit)
53-
ctx, cancel := context.WithCancel(context.Background())
55+
ctx, cancel := context.WithCancel(
56+
klog.NewContext(
57+
context.Background(),
58+
klog.Background(),
59+
),
60+
)
5461
defer cancel()
5562

5663
file, err := os.Open(Flags.ConfigFilePath)
@@ -83,11 +90,13 @@ func Run(cmd *cobra.Command, args []string) {
8390
}
8491
}()
8592

86-
group.Go(func() error {
93+
{
8794
server := http.NewServeMux()
95+
const serverAddress = ":8081"
96+
log := klog.FromContext(ctx).WithName("APIServer").WithValues("addr", serverAddress)
8897

8998
if Flags.Profiling {
90-
logs.Log.Printf("pprof profiling was enabled.")
99+
log.Info("Profiling endpoints enabled", "path", "/debug/pprof")
91100
server.HandleFunc("/debug/pprof/", pprof.Index)
92101
server.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
93102
server.HandleFunc("/debug/pprof/profile", pprof.Profile)
@@ -96,7 +105,7 @@ func Run(cmd *cobra.Command, args []string) {
96105
}
97106

98107
if Flags.Prometheus {
99-
logs.Log.Printf("Prometheus was enabled.\nRunning prometheus on port :8081")
108+
log.Info("Metrics endpoints enabled", "path", "/metrics")
100109
prometheus.MustRegister(metricPayloadSize)
101110
server.Handle("/metrics", promhttp.Handler())
102111
}
@@ -105,21 +114,32 @@ func Run(cmd *cobra.Command, args []string) {
105114
// what "ready" means for the agent, we just return 200 OK inconditionally.
106115
// The goal is to satisfy some Kubernetes distributions, like OpenShift,
107116
// that require a liveness and health probe to be present for each pod.
117+
log.Info("Healthz endpoints enabled", "path", "/healthz")
108118
server.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
109119
w.WriteHeader(http.StatusOK)
110120
})
121+
log.Info("Readyz endpoints enabled", "path", "/readyz")
111122
server.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
112123
w.WriteHeader(http.StatusOK)
113124
})
114125

115-
err := http.ListenAndServe(":8081", server)
116-
if err != nil && !errors.Is(err, http.ErrServerClosed) {
117-
return fmt.Errorf("failed to run the health check server: %s", err)
118-
}
119-
// The agent must stop if the management server stops
120-
cancel()
121-
return nil
122-
})
126+
group.Go(func() error {
127+
err := listenAndServe(
128+
klog.NewContext(gctx, log),
129+
&http.Server{
130+
Addr: serverAddress,
131+
Handler: server,
132+
BaseContext: func(_ net.Listener) context.Context {
133+
return gctx
134+
},
135+
},
136+
)
137+
if err != nil {
138+
return fmt.Errorf("APIServer: %s", err)
139+
}
140+
return nil
141+
})
142+
}
123143

124144
_, isVenConn := preflightClient.(*client.VenConnClient)
125145
if isVenConn {
@@ -412,3 +432,48 @@ func postData(config CombinedConfig, preflightClient client.Client, readings []*
412432

413433
return nil
414434
}
435+
436+
// listenAndServe starts the supplied HTTP server and stops it gracefully when
437+
// the supplied context is cancelled.
438+
// It returns when the graceful server shutdown is complete or when the server
439+
// exits with an error.
440+
// If the server fails to start, it returns the server error.
441+
// If the server fails to shutdown gracefully, it returns the shutdown error.
442+
// The server is given 3 seconds to shutdown gracefully before it is stopped
443+
// forcefully.
444+
func listenAndServe(ctx context.Context, server *http.Server) error {
445+
log := klog.FromContext(ctx).WithName("ListenAndServe")
446+
447+
log.V(1).Info("Starting")
448+
449+
listenCTX, listenCancelCause := context.WithCancelCause(context.WithoutCancel(ctx))
450+
go func() {
451+
err := server.ListenAndServe()
452+
listenCancelCause(fmt.Errorf("ListenAndServe: %s", err))
453+
}()
454+
455+
select {
456+
case <-listenCTX.Done():
457+
log.V(1).Info("Shutdown skipped", "reason", "Server already stopped")
458+
return context.Cause(listenCTX)
459+
460+
case <-ctx.Done():
461+
log.V(1).Info("Shutting down")
462+
}
463+
464+
shutdownCTX, shutdownCancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second*3)
465+
shutdownErr := server.Shutdown(shutdownCTX)
466+
shutdownCancel()
467+
if shutdownErr != nil {
468+
shutdownErr = fmt.Errorf("Shutdown: %s", shutdownErr)
469+
}
470+
471+
closeErr := server.Close()
472+
if closeErr != nil {
473+
closeErr = fmt.Errorf("Close: %s", closeErr)
474+
}
475+
476+
log.V(1).Info("Shutdown complete")
477+
478+
return errors.Join(shutdownErr, closeErr)
479+
}

pkg/agent/run_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package agent
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"os"
7+
"os/exec"
8+
"testing"
9+
"time"
10+
11+
"github.com/spf13/cobra"
12+
"github.com/stretchr/testify/require"
13+
"k8s.io/klog/v2"
14+
15+
"github.com/jetstack/preflight/pkg/logs"
16+
)
17+
18+
// TestRunOneShot runs the agent in `--one-shot` mode and verifies that it exits
19+
// after the first data gathering iteration.
20+
func TestRunOneShot(t *testing.T) {
21+
if _, found := os.LookupEnv("GO_CHILD"); found {
22+
// Silence the warning about missing pod name for event generation
23+
// TODO(wallrj): This should not be required when an `--input-file` has been supplied.
24+
t.Setenv("POD_NAME", "venafi-kubernetes-e2e")
25+
// Silence the error about missing kubeconfig.
26+
// TODO(wallrj): This should not be required when an `--input-file` has been supplied.
27+
t.Setenv("KUBECONFIG", "testdata/one-shot/success/kubeconfig.yaml")
28+
29+
c := &cobra.Command{}
30+
InitAgentCmdFlags(c, &Flags)
31+
logs.AddFlags(c.Flags())
32+
33+
err := c.ParseFlags([]string{
34+
"--one-shot",
35+
// TODO(wallrj): This should not be required when an `--input-file` has been supplied.
36+
"--api-token=should-not-be-required",
37+
// TODO(wallrj): This should not be required when an `--input-file` has been supplied.
38+
"--install-namespace=default",
39+
"--agent-config-file=testdata/one-shot/success/config.yaml",
40+
"--input-path=testdata/one-shot/success/input.json",
41+
"--output-path=/dev/null",
42+
"-v=1",
43+
})
44+
require.NoError(t, err)
45+
46+
logs.Initialize()
47+
Run(c, nil)
48+
klog.Flush()
49+
return
50+
}
51+
t.Log("Running child process")
52+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
53+
defer cancel()
54+
cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestRunOneShot$")
55+
var (
56+
stdout bytes.Buffer
57+
stderr bytes.Buffer
58+
)
59+
cmd.Stdout = &stdout
60+
cmd.Stderr = &stderr
61+
cmd.Env = append(
62+
os.Environ(),
63+
"GO_CHILD=true",
64+
)
65+
err := cmd.Run()
66+
67+
stdoutStr := stdout.String()
68+
stderrStr := stderr.String()
69+
t.Logf("STDOUT\n%s\n", stdoutStr)
70+
t.Logf("STDERR\n%s\n", stderrStr)
71+
require.NoError(t, err, context.Cause(ctx))
72+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Just enough venafi-kubernetes-agent config to allow it to run with an input
2+
# file in one-shot mode.
3+
cluster_id: "venafi-kubernetes-agent-e2e"
4+
organization_id: "venafi-kubernetes-agent-e2e"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Just enough kubeconfig to satisfy client-go
2+
apiVersion: v1
3+
kind: Config
4+
current-context: cluster-1
5+
contexts:
6+
- name: cluster-1
7+
context:
8+
cluster: cluster-1
9+
user: user-1
10+
clusters:
11+
- name: cluster-1
12+
cluster:
13+
server: https://192.0.2.1:8443
14+
preferences: {}
15+
users: []

0 commit comments

Comments
 (0)