Skip to content

Commit e4ea4ce

Browse files
authored
Add integration test for shard controller (#490)
* Rework example shard controller * Use `HaveLabel*` matcher in more places * Add integration test for `shard` controller * Deflake `sharder` webhook integration test
1 parent 5804885 commit e4ea4ce

File tree

15 files changed

+538
-131
lines changed

15 files changed

+538
-131
lines changed

cmd/shard/reconciler.go

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package main
1818

1919
import (
2020
"context"
21+
"crypto/sha256"
22+
"encoding/hex"
2123
"fmt"
2224

2325
corev1 "k8s.io/api/core/v1"
@@ -37,7 +39,7 @@ import (
3739
shardcontroller "github.com/timebertt/kubernetes-controller-sharding/pkg/shard/controller"
3840
)
3941

40-
// Reconciler is a reconciler for ConfigMaps that creates a dummy secret for every ConfigMap.
42+
// Reconciler watches Secrets and creates a ConfigMap for every Secret containing the Secret data's checksums.
4143
// It handles the shard and drain label.
4244
type Reconciler struct {
4345
Client client.Client
@@ -54,27 +56,27 @@ func (r *Reconciler) AddToManager(mgr manager.Manager, controllerRingName, shard
5456
// - a predicate that triggers when the drain label is present (even if the actual predicates don't trigger)
5557
// - wrapping the actual reconciler a reconciler that handles the drain operation for us
5658
return builder.ControllerManagedBy(mgr).
57-
Named("configmap").
58-
For(&corev1.ConfigMap{}, builder.WithPredicates(shardcontroller.Predicate(controllerRingName, shardName, ConfigMapDataChanged(), predicate.GenerationChangedPredicate{}))).
59-
Owns(&corev1.Secret{}, builder.WithPredicates(ObjectDeleted())).
59+
Named("secret-checksums").
60+
For(&corev1.Secret{}, builder.WithPredicates(shardcontroller.Predicate(controllerRingName, shardName, SecretDataChanged()))).
61+
Owns(&corev1.ConfigMap{}, builder.WithPredicates(ObjectDeleted())).
6062
WithOptions(controller.Options{
6163
MaxConcurrentReconciles: 5,
6264
}).
6365
Complete(
6466
shardcontroller.NewShardedReconciler(mgr).
65-
For(&corev1.ConfigMap{}).
67+
For(&corev1.Secret{}).
6668
InControllerRing(controllerRingName).
6769
WithShardName(shardName).
6870
MustBuild(r),
6971
)
7072
}
7173

72-
// ConfigMapDataChanged returns a predicate that is similar to predicate.GenerationChangedPredicate but for ConfigMaps
74+
// SecretDataChanged returns a predicate that is similar to predicate.GenerationChangedPredicate but for Secrets
7375
// that don't have a metadata.generation field.
74-
func ConfigMapDataChanged() predicate.Predicate {
76+
func SecretDataChanged() predicate.Predicate {
7577
return predicate.Funcs{
7678
UpdateFunc: func(e event.UpdateEvent) bool {
77-
return apiequality.Semantic.DeepEqual(e.ObjectOld.(*corev1.ConfigMap).Data, e.ObjectNew.(*corev1.ConfigMap).Data)
79+
return apiequality.Semantic.DeepEqual(e.ObjectOld.(*corev1.Secret).Data, e.ObjectNew.(*corev1.Secret).Data)
7880
},
7981
}
8082
}
@@ -93,29 +95,36 @@ func ObjectDeleted() predicate.Predicate {
9395
func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
9496
log := logf.FromContext(ctx)
9597

96-
configMap := &corev1.ConfigMap{}
97-
if err := r.Client.Get(ctx, req.NamespacedName, configMap); err != nil {
98+
secret := &corev1.Secret{}
99+
if err := r.Client.Get(ctx, req.NamespacedName, secret); err != nil {
98100
if apierrors.IsNotFound(err) {
99101
log.V(1).Info("Object is gone, stop reconciling")
100102
return reconcile.Result{}, nil
101103
}
102104
return reconcile.Result{}, fmt.Errorf("error retrieving object from store: %w", err)
103105
}
104106

105-
// perform some dummy operation, this is just an example controller, we don't really care what it actually does
106-
// create a secret with controller reference so that the controller also serves as an example with controlled objects
107+
// Perform a typical operation in this example controller.
108+
// Create a ConfigMap with a controller reference to the watched Secret.
107109
log.V(1).Info("Reconciling object")
108110

109-
secret := &corev1.Secret{
111+
configMap := &corev1.ConfigMap{
110112
ObjectMeta: metav1.ObjectMeta{
111-
Name: "dummy-" + configMap.Name,
112-
Namespace: configMap.Namespace,
113+
Name: "checksums-" + secret.Name,
114+
Namespace: secret.Namespace,
113115
},
116+
Data: make(map[string]string, len(secret.Data)),
114117
}
115118

116-
if err := controllerutil.SetControllerReference(configMap, secret, r.Client.Scheme()); err != nil {
119+
// Calculate the checksum for every Secret key and populate it in the ConfigMap.
120+
for key, data := range secret.Data {
121+
checksum := sha256.Sum256(data)
122+
configMap.Data[key] = hex.EncodeToString(checksum[:])
123+
}
124+
125+
if err := controllerutil.SetControllerReference(secret, configMap, r.Client.Scheme()); err != nil {
117126
return reconcile.Result{}, err
118127
}
119128

120-
return reconcile.Result{}, client.IgnoreAlreadyExists(r.Client.Create(ctx, secret))
129+
return reconcile.Result{}, client.IgnoreAlreadyExists(r.Client.Create(ctx, configMap))
121130
}

docs/design.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The sharder webhook is called on `CREATE` and `UPDATE` requests for configured r
3232
The sharder uses the consistent hashing ring to determine the desired shard and adds the shard label during admission accordingly.
3333
Shards then use a label selector for the shard label with their own instance name to restrict the cache and controller to the subset of objects assigned to them.
3434

35-
For the controller's "main" object (configured in `ControllerRing.spec.resources[]`), the object's `apiVersion`, `kind`, `namespace`, and `name` are concatenated to form its hash key.
35+
For the controller's "main" object (configured in `ControllerRing.spec.resources[]`), the object's API group, `kind`, `namespace`, and `name` are concatenated to form its hash key.
3636
For objects controlled by other objects (configured in `ControllerRing.spec.resources[].controlledResources[]`), the sharder utilizes information about the controlling object (`ownerReference` with `controller=true`) to calculate the object's hash key.
3737
This ensures that owned objects are consistently assigned to the same shard as their owner.
3838

docs/development.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ You should see that the shard successfully announced itself to the sharder:
9494
```bash
9595
$ kubectl get lease -L alpha.sharding.timebertt.dev/controllerring,alpha.sharding.timebertt.dev/state
9696
NAME HOLDER AGE CONTROLLERRING STATE
97-
shard-fkpxhjk8 shard-fkpxhjk8 18s example ready
97+
shard-5pv57c6c shard-5pv57c6c 18s example ready
9898

9999
$ kubectl get controllerring
100100
NAME READY AVAILABLE SHARDS AGE
@@ -113,19 +113,19 @@ make run-shard
113113

114114
## Testing the Sharding Setup
115115

116-
Independent of the used setup (skaffold-based or running on the host machine), you should be able to create sharded `ConfigMaps` in the `default` namespace as configured in the `example` `ControllerRing`.
117-
The `Secrets` created by the example shard controller should be assigned to the same shard as the owning `ConfigMap`:
116+
Independent of the used setup (skaffold-based or running on the host machine), you should be able to create sharded `Secrets` in the `default` namespace as configured in the `example` `ControllerRing`.
117+
The `ConfigMaps` created by the example shard controller should be assigned to the same shard as the owning `Secret`:
118118

119119
```bash
120-
$ kubectl create cm foo
121-
configmap/foo created
120+
$ kubectl create secret generic foo --from-literal foo=bar
121+
secret/foo created
122122

123123
$ kubectl get cm,secret -L shard.alpha.sharding.timebertt.dev/example
124-
NAME DATA AGE EXAMPLE
125-
configmap/foo 0 1s shard-656d588475-5746d
124+
NAME DATA AGE EXAMPLE
125+
configmap/checksums-foo 1 1s shard-5pv57c6c
126126

127-
NAME TYPE DATA AGE EXAMPLE
128-
secret/dummy-foo Opaque 0 1s shard-656d588475-5746d
127+
NAME TYPE DATA AGE EXAMPLE
128+
secret/foo Opaque 1 1s shard-5pv57c6c
129129
```
130130

131131
## Monitoring

0 commit comments

Comments
 (0)