Skip to content

Commit

Permalink
mockkubeapiserver: ensure CRDs become ready
Browse files Browse the repository at this point in the history
Simulate the CRD being accepted into the apiserver and this being
reflected into status, for clients that check status.

Also fix a nil-pointer error due to lack of proper initialization.
  • Loading branch information
justinsb committed Oct 23, 2023
1 parent 96dd7e1 commit b027d66
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 6 deletions.
2 changes: 1 addition & 1 deletion mockkubeapiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func NewMockKubeAPIServer(addr string, options ...Option) (*MockKubeAPIServer, e
}

// These hooks mock behaviour that would otherwise require full controllers
s.storage.AddStorageHook(&hooks.CRDHook{})
s.storage.AddStorageHook(&hooks.CRDHook{Storage: s.storage})
s.storage.AddStorageHook(&hooks.NamespaceHook{})
s.storage.AddStorageHook(&hooks.DeploymentHook{})

Expand Down
74 changes: 72 additions & 2 deletions mockkubeapiserver/hooks/crd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,92 @@ limitations under the License.
package hooks

import (
"fmt"

"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
"sigs.k8s.io/kubebuilder-declarative-pattern/mockkubeapiserver/storage"
)

// CRDHook implements functionality for CRD objects (the definitions themselves, not instances of CRDs)
type CRDHook struct {
storage storage.Storage
Storage storage.Storage
}

func (s *CRDHook) OnWatchEvent(ev *storage.WatchEvent) {
switch ev.GroupKind() {
case schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}:
// When a CRD is created, we notify the storage layer so it can store instances of the CRD
if err := s.storage.UpdateCRD(ev); err != nil {
if err := s.Storage.UpdateCRD(ev); err != nil {
klog.Warningf("crd change was invalid: %v", err)
}

if err := s.updateCRDConditions(ev); err != nil {
klog.Fatalf("could not update crd status: %v", err)
}
}
}
func (s *CRDHook) updateCRDConditions(ev *storage.WatchEvent) error {
u := ev.Unstructured()

// So that CRDs become ready, we immediately update the status.
// We could do something better here, like e.g. a 1 second pause before changing the status
statusObj := u.Object["status"]
if statusObj == nil {
statusObj = make(map[string]interface{})
u.Object["status"] = statusObj
}
status, ok := statusObj.(map[string]interface{})
if !ok {
return fmt.Errorf("status was of unexpected type %T", statusObj)
}

generation := u.GetGeneration()
if generation == 0 {
generation = 1
u.SetGeneration(generation)
}

var conditions []interface{}
conditions = append(conditions, map[string]interface{}{
"type": "NamesAccepted",
"status": "True",
"reason": "NoConflicts",
"message": "no conflicts found",
})
conditions = append(conditions, map[string]interface{}{
"type": "Established",
"status": "True",
"reason": "InitialNamesAccepted",
"message": "the initial names have been accepted",
})
status["conditions"] = conditions

// TODO: More of status? Here is an example of the full status
// status:
// acceptedNames:
// kind: VolumeSnapshot
// listKind: VolumeSnapshotList
// plural: volumesnapshots
// singular: volumesnapshot
// conditions:
// - lastTransitionTime: "2023-09-21T01:04:36Z"
// message: no conflicts found
// reason: NoConflicts
// status: "True"
// type: NamesAccepted
// - lastTransitionTime: "2023-09-21T01:04:36Z"
// message: the initial names have been accepted
// reason: InitialNamesAccepted
// status: "True"
// type: Established
// - lastTransitionTime: "2023-09-21T01:04:36Z"
// message: approved in https://github.com/kubernetes-csi/external-snapshotter/pull/419
// reason: ApprovedAnnotation
// status: "True"
// type: KubernetesAPIApprovalPolicyConformant
// storedVersions:
// - v1

return nil
}
7 changes: 4 additions & 3 deletions mockkubeapiserver/storage/memorystorage/crd.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ func (s *MemoryStorage) UpdateCRD(ev *storage.WatchEvent) error {
gr := gvr.GroupResource()

storage := &resourceStorage{
GroupResource: gr,
objects: make(map[types.NamespacedName]*unstructured.Unstructured),
parent: s,
GroupResource: gr,
objects: make(map[types.NamespacedName]*unstructured.Unstructured),
parent: s,
resourceVersionClock: &s.resourceVersionClock,
}

// TODO: share storage across different versions
Expand Down

0 comments on commit b027d66

Please sign in to comment.