Skip to content

Commit 42a0e3a

Browse files
committed
[dev/rejector] Add the tool we use to test PodRejection under "dev"
1 parent 72f87e0 commit 42a0e3a

File tree

4 files changed

+352
-0
lines changed

4 files changed

+352
-0
lines changed

dev/rejector/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rejector

dev/rejector/go.mod

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
module gitpod.io/rejector/v2
2+
3+
go 1.22.2
4+
5+
require (
6+
k8s.io/api v0.31.1
7+
k8s.io/apimachinery v0.31.1
8+
k8s.io/client-go v0.31.1
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
13+
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
14+
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
15+
github.com/go-logr/logr v1.4.2 // indirect
16+
github.com/go-openapi/jsonpointer v0.19.6 // indirect
17+
github.com/go-openapi/jsonreference v0.20.2 // indirect
18+
github.com/go-openapi/swag v0.22.4 // indirect
19+
github.com/gogo/protobuf v1.3.2 // indirect
20+
github.com/golang/protobuf v1.5.4 // indirect
21+
github.com/google/gnostic-models v0.6.8 // indirect
22+
github.com/google/go-cmp v0.6.0 // indirect
23+
github.com/google/gofuzz v1.2.0 // indirect
24+
github.com/google/uuid v1.6.0 // indirect
25+
github.com/imdario/mergo v0.3.6 // indirect
26+
github.com/josharian/intern v1.0.0 // indirect
27+
github.com/json-iterator/go v1.1.12 // indirect
28+
github.com/mailru/easyjson v0.7.7 // indirect
29+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
30+
github.com/modern-go/reflect2 v1.0.2 // indirect
31+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
32+
github.com/spf13/pflag v1.0.5 // indirect
33+
github.com/x448/float16 v0.8.4 // indirect
34+
golang.org/x/net v0.26.0 // indirect
35+
golang.org/x/oauth2 v0.21.0 // indirect
36+
golang.org/x/sys v0.21.0 // indirect
37+
golang.org/x/term v0.21.0 // indirect
38+
golang.org/x/text v0.16.0 // indirect
39+
golang.org/x/time v0.3.0 // indirect
40+
google.golang.org/protobuf v1.34.2 // indirect
41+
gopkg.in/inf.v0 v0.9.1 // indirect
42+
gopkg.in/yaml.v2 v2.4.0 // indirect
43+
gopkg.in/yaml.v3 v3.0.1 // indirect
44+
k8s.io/klog/v2 v2.130.1 // indirect
45+
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
46+
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
47+
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
48+
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
49+
sigs.k8s.io/yaml v1.4.0 // indirect
50+
)

dev/rejector/go.sum

Lines changed: 154 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dev/rejector/main.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"flag"
7+
"fmt"
8+
"os"
9+
"os/signal"
10+
"path/filepath"
11+
"syscall"
12+
13+
corev1 "k8s.io/api/core/v1"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/apimachinery/pkg/types"
16+
"k8s.io/client-go/kubernetes"
17+
"k8s.io/client-go/rest"
18+
"k8s.io/client-go/tools/clientcmd"
19+
)
20+
21+
// This is a test utility that is used to inject a very specific error condition into workspace pods, so that we can test the behavior of the ws-manager+ws-daemon in handling such cases.
22+
23+
type patchStringValue struct {
24+
Op string `json:"op"`
25+
Path string `json:"path"`
26+
Value string `json:"value"`
27+
}
28+
29+
func main() {
30+
// Get Kubernetes client
31+
clientset, err := getClient()
32+
if err != nil {
33+
fmt.Printf("Error creating Kubernetes client: %v\n", err)
34+
os.Exit(1)
35+
}
36+
37+
namespace := "default"
38+
ctx := context.Background()
39+
40+
// Listen for pod events
41+
podWatcher, err := clientset.CoreV1().Pods(namespace).Watch(ctx, metav1.ListOptions{
42+
LabelSelector: "component=workspace",
43+
})
44+
if err != nil {
45+
fmt.Printf("Error watching pods: %v\n", err)
46+
os.Exit(1)
47+
}
48+
49+
// Handle pod events
50+
ch := podWatcher.ResultChan()
51+
stopChan := make(chan os.Signal, 1)
52+
signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM)
53+
54+
fmt.Println("Starting rejector...")
55+
56+
knownPodVersions := map[string]string{}
57+
podRejectedCount := map[string]int{}
58+
59+
for {
60+
select {
61+
case event := <-ch:
62+
pod, ok := event.Object.(*corev1.Pod)
63+
if !ok {
64+
fmt.Println("Unexpected type")
65+
continue
66+
}
67+
68+
marked := true
69+
// marked := slices.ContainsFunc(pod.Spec.Containers[0].Env, func(e corev1.EnvVar) bool {
70+
// return e.Name == "GITPOD_WORKSPACE_CONTEXT_URL" && strings.Contains(e.Value, "geropl")
71+
// })
72+
73+
knownVersion, known := knownPodVersions[pod.Name]
74+
if known && knownVersion >= pod.ResourceVersion {
75+
fmt.Printf("Skipping pod %s bc of outdated version...\n", pod.Name)
76+
continue
77+
}
78+
79+
if count := podRejectedCount[pod.Name]; count > 0 || !marked {
80+
fmt.Printf("Skipping pod %s...\n", pod.Name)
81+
continue
82+
}
83+
fmt.Printf("Found marked pod %s\n", pod.Name)
84+
85+
if pod.Status.Phase == corev1.PodPending && pod.Spec.NodeName != "" {
86+
fmt.Printf("found marked pending & scheduled pod: %s\n", pod.Name)
87+
patch := []patchStringValue{
88+
{
89+
Path: "/status/phase",
90+
Op: "replace",
91+
Value: string(corev1.PodFailed),
92+
},
93+
{
94+
Path: "/status/reason",
95+
Op: "replace",
96+
Value: "NodeAffinity",
97+
},
98+
{
99+
Path: "/status/message",
100+
Op: "replace",
101+
Value: "Pod was rejected",
102+
},
103+
}
104+
patchBytes, _ := json.Marshal(patch)
105+
pUpdated, err := clientset.CoreV1().Pods(namespace).Patch(ctx, pod.Name, types.JSONPatchType, patchBytes, metav1.PatchOptions{}, "status")
106+
if err != nil {
107+
fmt.Printf("error patching pod %s: %v\n", pod.Name, err)
108+
}
109+
podRejectedCount[pod.Name] = podRejectedCount[pod.Name] + 1
110+
knownPodVersions[pUpdated.Name] = pUpdated.ResourceVersion
111+
fmt.Printf("Applied status: %s\n", pUpdated.Status.Phase)
112+
}
113+
114+
case <-stopChan:
115+
fmt.Println("Shutting down rejector...")
116+
return
117+
}
118+
}
119+
}
120+
121+
// Function to get the Kubernetes client
122+
func getClient() (*kubernetes.Clientset, error) {
123+
var config *rest.Config
124+
var err error
125+
126+
// Try to get in-cluster config
127+
config, err = rest.InClusterConfig()
128+
if err != nil {
129+
// Fall back to using kubeconfig file if not running in a cluster
130+
kubeconfigFlag := flag.String("kubeconfig", "~/.kube/config", "location of your kubeconfig file")
131+
flag.Parse()
132+
kubeconfig, err := filepath.Abs(*kubeconfigFlag)
133+
if err != nil {
134+
fmt.Printf("Cannot resolve kubeconfig path: %s", *kubeconfigFlag)
135+
}
136+
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
137+
if err != nil {
138+
return nil, err
139+
}
140+
}
141+
142+
clientset, err := kubernetes.NewForConfig(config)
143+
if err != nil {
144+
return nil, err
145+
}
146+
return clientset, nil
147+
}

0 commit comments

Comments
 (0)