Skip to content

Commit 6e23a03

Browse files
authored
Support Argo Rollout controller object as a first class citizen (#241)
* Support Argo Rollout controller object as a first class citizen * Revert chart bump for the moment * Bump chart version
1 parent 1c67bef commit 6e23a03

File tree

13 files changed

+275
-64
lines changed

13 files changed

+275
-64
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
NAME ?= adobe/k8s-shredder
44
K8S_SHREDDER_VERSION ?= "dev"
5-
KINDNODE_VERSION ?= "v1.28.0"
5+
KINDNODE_VERSION ?= "v1.30.4"
66
COMMIT ?= $(shell git rev-parse --short HEAD)
77
TEST_CLUSTERNAME ?= "k8s-shredder-test-cluster"
88

charts/k8s-shredder/Chart.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ maintainers:
1212
1313
url: https://adobe.com
1414

15-
version: 0.1.0
15+
version: 0.1.1
1616
appVersion: v0.2.1

charts/k8s-shredder/templates/cluster-role.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ rules:
1515
- apiGroups: [apps, extensions]
1616
resources: [statefulsets, deployments, replicasets]
1717
verbs: [get, list, watch, update, patch]
18+
- apiGroups: [ "argoproj.io" ]
19+
resources: [ rollouts ]
20+
verbs: [ get, list, watch, update, patch ]
1821
{{ end }}

cmd/root.go

+2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ func discoverConfig() {
114114
viper.SetDefault("RestartedAtAnnotation", "shredder.ethos.adobe.net/restartedAt")
115115
viper.SetDefault("AllowEvictionLabel", "shredder.ethos.adobe.net/allow-eviction")
116116
viper.SetDefault("ToBeDeletedTaint", "ToBeDeletedByClusterAutoscaler")
117+
viper.SetDefault("ArgoRolloutsAPIVersion", "v1alpha1")
117118

118119
err := viper.ReadInConfig()
119120
if err != nil {
@@ -144,6 +145,7 @@ func parseConfig() {
144145
"RestartedAtAnnotation": cfg.RestartedAtAnnotation,
145146
"AllowEvictionLabel": cfg.AllowEvictionLabel,
146147
"ToBeDeletedTaint": cfg.ToBeDeletedTaint,
148+
"ArgoRolloutsAPIVersion": cfg.ArgoRolloutsAPIVersion,
147149
}).Info("Loaded configuration")
148150
}
149151

internal/testing/e2e_test.go

+83-39
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
log "github.com/sirupsen/logrus"
2424
"golang.org/x/exp/slices"
2525
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27+
"k8s.io/apimachinery/pkg/runtime/schema"
2628
"os"
2729
"strings"
2830
"testing"
@@ -47,6 +49,56 @@ var (
4749
}
4850
)
4951

52+
func grabMetrics(shredderMetrics []string, t *testing.T) []string {
53+
results := make([]string, 0)
54+
warnings := make([]string, 0)
55+
56+
for _, shredderMetric := range shredderMetrics {
57+
result, warning, err := prometheusQuery(shredderMetric)
58+
if err != nil {
59+
t.Errorf("Error querying Prometheus: %v\n", err)
60+
}
61+
warnings = append(warnings, warning...)
62+
results = append(results, result.String())
63+
}
64+
65+
if len(warnings) > 0 {
66+
t.Logf("Warnings: %v\n", strings.Join(warnings, "\n"))
67+
}
68+
69+
t.Logf("Results: \n%v\n", strings.Join(results, "\n"))
70+
71+
return results
72+
}
73+
74+
func prometheusQuery(query string) (model.Value, v1.Warnings, error) {
75+
76+
client, err := api.NewClient(api.Config{
77+
Address: "http://localhost:30007",
78+
})
79+
if err != nil {
80+
fmt.Printf("Error creating client: %v\n", err)
81+
os.Exit(1)
82+
}
83+
84+
v1api := v1.NewAPI(client)
85+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
86+
defer cancel()
87+
return v1api.Query(ctx, query, time.Now(), v1.WithTimeout(5*time.Second))
88+
}
89+
90+
func compareTime(expirationTime time.Time, t *testing.T, ch chan time.Time) {
91+
currentTime := time.Now().UTC()
92+
93+
for !currentTime.After(expirationTime.UTC()) {
94+
t.Logf("Node TTL haven't expired yet: current time(UTC): %s, expire time(UTC): %s", currentTime, expirationTime.UTC())
95+
time.Sleep(10 * time.Second)
96+
currentTime = time.Now().UTC()
97+
98+
}
99+
ch <- currentTime
100+
}
101+
50102
// Validates that k8s-shredder cleanup a parked node after its TTL expires
51103
func TestNodeIsCleanedUp(t *testing.T) {
52104
var err error
@@ -110,18 +162,7 @@ func TestNodeIsCleanedUp(t *testing.T) {
110162
}
111163
}
112164

113-
func compareTime(expirationTime time.Time, t *testing.T, ch chan time.Time) {
114-
currentTime := time.Now().UTC()
115-
116-
for !currentTime.After(expirationTime.UTC()) {
117-
t.Logf("Node TTL haven't expired yet: current time(UTC): %s, expire time(UTC): %s", currentTime, expirationTime.UTC())
118-
time.Sleep(10 * time.Second)
119-
currentTime = time.Now().UTC()
120-
121-
}
122-
ch <- currentTime
123-
}
124-
165+
// Validates shredder metrics
125166
func TestShredderMetrics(t *testing.T) {
126167

127168
// Intentionally skipped the gauge metrics as they are going to be wiped out before every eviction loop
@@ -140,40 +181,43 @@ func TestShredderMetrics(t *testing.T) {
140181
}
141182
}
142183

143-
func grabMetrics(shredderMetrics []string, t *testing.T) []string {
144-
results := make([]string, 0)
145-
warnings := make([]string, 0)
184+
func TestArgoRolloutRestartAt(t *testing.T) {
185+
var err error
146186

147-
for _, shredderMetric := range shredderMetrics {
148-
result, warning, err := prometheusQuery(shredderMetric)
149-
if err != nil {
150-
t.Errorf("Error querying Prometheus: %v\n", err)
151-
}
152-
warnings = append(warnings, warning...)
153-
results = append(results, result.String())
154-
}
187+
appContext, err := utils.NewAppContext(config.Config{
188+
ParkedNodeTTL: 30 * time.Second,
189+
EvictionLoopInterval: 10 * time.Second,
190+
RollingRestartThreshold: 0.1,
191+
UpgradeStatusLabel: "shredder.ethos.adobe.net/upgrade-status",
192+
ExpiresOnLabel: "shredder.ethos.adobe.net/parked-node-expires-on",
193+
NamespacePrefixSkipInitialEviction: "",
194+
RestartedAtAnnotation: "shredder.ethos.adobe.net/restartedAt",
195+
AllowEvictionLabel: "shredder.ethos.adobe.net/allow-eviction",
196+
ToBeDeletedTaint: "ToBeDeletedByClusterAutoscaler",
197+
ArgoRolloutsAPIVersion: "v1alpha1",
198+
}, false)
155199

156-
if len(warnings) > 0 {
157-
t.Logf("Warnings: %v\n", strings.Join(warnings, "\n"))
200+
if err != nil {
201+
log.Fatalf("Failed to setup application context: %s", err)
158202
}
159203

160-
t.Logf("Results: \n%v\n", strings.Join(results, "\n"))
161-
162-
return results
163-
}
204+
gvr := schema.GroupVersionResource{
205+
Group: "argoproj.io",
206+
Version: appContext.Config.ArgoRolloutsAPIVersion,
207+
Resource: "rollouts",
208+
}
164209

165-
func prometheusQuery(query string) (model.Value, v1.Warnings, error) {
210+
rollout, err := appContext.DynamicK8SClient.Resource(gvr).Namespace("ns-team-k8s-shredder-test").Get(appContext.Context, "test-app-argo-rollout", metav1.GetOptions{})
211+
if err != nil {
212+
log.Fatalf("Failed to get the Argo Rollout object: %s", err)
213+
}
214+
_, found, err := unstructured.NestedString(rollout.Object, "spec", "restartAt")
166215

167-
client, err := api.NewClient(api.Config{
168-
Address: "http://localhost:30007",
169-
})
170216
if err != nil {
171-
fmt.Printf("Error creating client: %v\n", err)
172-
os.Exit(1)
217+
log.Fatalf("Failed to get the Argo Rollout spec.restartAt field: %s", err)
173218
}
174219

175-
v1api := v1.NewAPI(client)
176-
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
177-
defer cancel()
178-
return v1api.Query(ctx, query, time.Now(), v1.WithTimeout(5*time.Second))
220+
if !found {
221+
t.Fatalf("Argo Rollout object does not have the spec.restartAt field set")
222+
}
179223
}

internal/testing/local_env_prep.sh

+8
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,17 @@ kubectl apply -f "${test_dir}/k8s-shredder.yaml"
4141
echo "KIND: deploying prometheus..."
4242
kubectl apply -f "${test_dir}/prometheus_stuffs.yaml"
4343

44+
echo "KIND: deploying Argo Rollouts CRD..."
45+
kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/v1.7.2/manifests/crds/rollout-crd.yaml
46+
4447
echo "KIND: deploying test applications..."
4548
kubectl apply -f "${test_dir}/test_apps.yaml"
4649

50+
# Adjust the correct UID for the test-app-argo-rollout ownerReference
51+
rollout_uid=$(kubectl -n ns-team-k8s-shredder-test get rollout test-app-argo-rollout -ojsonpath='{.metadata.uid}')
52+
cat "${test_dir}/test_apps.yaml" | sed "s/REPLACE_WITH_ROLLOUT_UID/${rollout_uid}/" | kubectl apply -f -
53+
54+
4755
echo "K8S_SHREDDER: waiting for k8s-shredder deployment to become ready!"
4856
retry_count=0
4957
i=1

internal/testing/prometheus_stuffs.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ spec:
1818
spec:
1919
containers:
2020
- name: prometheus
21-
image: prom/prometheus:v2.42.0
21+
image: prom/prometheus:v2.54.1
2222
args:
2323
- "--storage.tsdb.retention.time=1h"
2424
- "--config.file=/etc/prometheus/prometheus.yml"

internal/testing/rbac.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,7 @@ rules:
3131
- apiGroups: [apps, extensions]
3232
resources: [statefulsets, deployments, replicasets]
3333
verbs: [get, list, watch, update, patch]
34+
- apiGroups: [ "argoproj.io" ]
35+
resources: [ rollouts ]
36+
verbs: [ get, list, watch, update, patch ]
3437

internal/testing/test_apps.yaml

+66-1
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,69 @@ spec:
239239
minAvailable: 1
240240
selector:
241241
matchLabels:
242-
app: test-app-statefulset
242+
app: test-app-statefulset
243+
#### FLEX ####
244+
# 1. Good citizen Argo Rollout in Flex world
245+
---
246+
apiVersion: apps/v1
247+
kind: ReplicaSet
248+
metadata:
249+
name: test-app-argo-rollout
250+
namespace: ns-team-k8s-shredder-test
251+
ownerReferences:
252+
- apiVersion: argoproj.io/v1alpha1
253+
kind: Rollout
254+
blockOwnerDeletion: true
255+
name: test-app-argo-rollout
256+
uid: REPLACE_WITH_ROLLOUT_UID
257+
spec:
258+
replicas: 2
259+
selector:
260+
matchLabels:
261+
app: test-app-argo-rollout
262+
template:
263+
metadata:
264+
labels:
265+
app: test-app-argo-rollout
266+
spec:
267+
affinity:
268+
podAntiAffinity:
269+
preferredDuringSchedulingIgnoredDuringExecution:
270+
- weight: 100
271+
podAffinityTerm:
272+
labelSelector:
273+
matchExpressions:
274+
- key: app
275+
operator: In
276+
values:
277+
- test-app-argo-rollout
278+
topologyKey: kubernetes.io/hostname
279+
containers:
280+
- name: test-app-argo-rollout
281+
image: aaneci/canary
282+
ports:
283+
- containerPort: 8080
284+
name: web
285+
---
286+
apiVersion: argoproj.io/v1alpha1
287+
kind: Rollout
288+
metadata:
289+
name: test-app-argo-rollout
290+
namespace: ns-team-k8s-shredder-test
291+
spec:
292+
replicas: 2
293+
workloadRef:
294+
apiVersion: apps/v1
295+
kind: ReplicaSet
296+
name: test-app-argo-rollout
297+
---
298+
apiVersion: policy/v1
299+
kind: PodDisruptionBudget
300+
metadata:
301+
name: test-app-argo-rollout
302+
namespace: ns-team-k8s-shredder-test
303+
spec:
304+
minAvailable: 10
305+
selector:
306+
matchLabels:
307+
app: test-app-argo-rollout

pkg/config/config.go

+2
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ type Config struct {
3333
AllowEvictionLabel string
3434
// ToBeDeletedTaint is used for skipping a subset of parked nodes
3535
ToBeDeletedTaint string
36+
// ArgoRolloutsAPIVersion is used for specifying the API version from `argoproj.io` apigroup to be used while handling Argo Rollouts objects
37+
ArgoRolloutsAPIVersion string
3638
}

0 commit comments

Comments
 (0)