From dce76743df986d8111cb302424bc2c4aa8e00ea1 Mon Sep 17 00:00:00 2001 From: killianmuldoon Date: Tue, 31 May 2022 14:44:19 +0100 Subject: [PATCH] RuntimeSDK:implement aggregateResponse for Runtime client Signed-off-by: killianmuldoon --- .../hooks/api/v1alpha1/common_types.go | 27 + .../api/v1alpha1/lifecyclehooks_types.go | 33 +- .../api/v1alpha1/topologymutation_types.go | 20 +- .../api/v1alpha1/zz_generated.deepcopy.go | 21 + .../api/v1alpha1/zz_generated.openapi.go | 36 +- internal/runtime/client/client.go | 54 +- internal/runtime/client/client_test.go | 598 ++++++++++++------ internal/runtime/test/v1alpha1/doc.go | 2 +- internal/runtime/test/v1alpha1/fake_types.go | 88 ++- .../test/v1alpha1/groupversion_info.go | 43 ++ .../test/v1alpha1/zz_generated.deepcopy.go | 51 ++ internal/runtime/test/v1alpha2/doc.go | 20 + internal/runtime/test/v1alpha2/fake_types.go | 39 +- .../test/v1alpha2/groupversion_info.go | 37 ++ .../test/v1alpha2/zz_generated.deepcopy.go | 76 +++ 15 files changed, 858 insertions(+), 287 deletions(-) create mode 100644 internal/runtime/test/v1alpha1/groupversion_info.go create mode 100644 internal/runtime/test/v1alpha2/doc.go create mode 100644 internal/runtime/test/v1alpha2/groupversion_info.go create mode 100644 internal/runtime/test/v1alpha2/zz_generated.deepcopy.go diff --git a/exp/runtime/hooks/api/v1alpha1/common_types.go b/exp/runtime/hooks/api/v1alpha1/common_types.go index 974381f442d8..2314447f0ad3 100644 --- a/exp/runtime/hooks/api/v1alpha1/common_types.go +++ b/exp/runtime/hooks/api/v1alpha1/common_types.go @@ -30,6 +30,15 @@ type ResponseObject interface { SetStatus(status ResponseStatus) } +// RetryResponseObject is a ResponseObject which additionally defines the functionality +// for a response to signal a retry. +// +kubebuilder:object:generate=false +type RetryResponseObject interface { + ResponseObject + GetRetryAfterSeconds() int32 + SetRetryAfterSeconds(retryAfterSeconds int32) +} + // CommonResponse is the data structure common to all response types. type CommonResponse struct { // Status of the call. One of "Success" or "Failure". @@ -70,3 +79,21 @@ const ( // ResponseStatusFailure represents a failure response. ResponseStatusFailure ResponseStatus = "Failure" ) + +// CommonRetryResponse is the data structure which contains all +// common retry fields. +type CommonRetryResponse struct { + // RetryAfterSeconds when set to a non-zero value signifies that the hook + // will be called again at a future time. + RetryAfterSeconds int32 `json:"retryAfterSeconds"` +} + +// GetRetryAfterSeconds sets the RetryAfterSeconds value. +func (r *CommonRetryResponse) GetRetryAfterSeconds() int32 { + return r.RetryAfterSeconds +} + +// SetRetryAfterSeconds returns the RetryAfterSeconds value. +func (r *CommonRetryResponse) SetRetryAfterSeconds(retryAfterSeconds int32) { + r.RetryAfterSeconds = retryAfterSeconds +} diff --git a/exp/runtime/hooks/api/v1alpha1/lifecyclehooks_types.go b/exp/runtime/hooks/api/v1alpha1/lifecyclehooks_types.go index 4738160513e3..bdb9399f1334 100644 --- a/exp/runtime/hooks/api/v1alpha1/lifecyclehooks_types.go +++ b/exp/runtime/hooks/api/v1alpha1/lifecyclehooks_types.go @@ -32,6 +32,8 @@ type BeforeClusterCreateRequest struct { Cluster clusterv1.Cluster `json:"cluster"` } +var _ RetryResponseObject = &BeforeClusterCreateResponse{} + // BeforeClusterCreateResponse is the response of the BeforeClusterCreate hook. // +kubebuilder:object:root=true type BeforeClusterCreateResponse struct { @@ -40,9 +42,8 @@ type BeforeClusterCreateResponse struct { // CommonResponse contains Status and Message fields common to all response types. CommonResponse `json:",inline"` - // RetryAfterSeconds when set to a non-zero value signifies that the hook - // will be called again at a future time. - RetryAfterSeconds int `json:"retryAfterSeconds"` + // CommonRetryResponse contains RetryAfterSeconds field common to all retry response types. + CommonRetryResponse `json:",inline"` } // BeforeClusterCreate is the runtime hook that will be called right before a Cluster is created. @@ -57,6 +58,8 @@ type AfterControlPlaneInitializedRequest struct { Cluster clusterv1.Cluster `json:"cluster"` } +var _ ResponseObject = &AfterControlPlaneInitializedResponse{} + // AfterControlPlaneInitializedResponse is the response of the AfterControlPlaneInitialized hook. // +kubebuilder:object:root=true type AfterControlPlaneInitializedResponse struct { @@ -80,10 +83,13 @@ type BeforeClusterUpgradeRequest struct { // The current Kubernetes version of the cluster. FromKubernetesVersion string `json:"fromKubernetesVersion"` + // The target Kubernetes version of upgrade. ToKubernetesVersion string `json:"toKubernetesVersion"` } +var _ RetryResponseObject = &BeforeClusterUpgradeResponse{} + // BeforeClusterUpgradeResponse is the response of the BeforeClusterUpgrade hook. // +kubebuilder:object:root=true type BeforeClusterUpgradeResponse struct { @@ -92,9 +98,8 @@ type BeforeClusterUpgradeResponse struct { // CommonResponse contains Status and Message fields common to all response types. CommonResponse `json:",inline"` - // RetryAfterSeconds when set to a non-zero value signifies that the hook - // needs to be retried at a future time. - RetryAfterSeconds int `json:"retryAfterSeconds"` + // CommonRetryResponse contains RetryAfterSeconds field common to all retry response types. + CommonRetryResponse `json:",inline"` } // BeforeClusterUpgrade is the runtime hook that will be called after a cluster.spec.version is upgraded and @@ -113,6 +118,8 @@ type AfterControlPlaneUpgradeRequest struct { KubernetesVersion string `json:"kubernetesVersion"` } +var _ RetryResponseObject = &AfterControlPlaneUpgradeResponse{} + // AfterControlPlaneUpgradeResponse is the response of the AfterControlPlaneUpgrade hook. // +kubebuilder:object:root=true type AfterControlPlaneUpgradeResponse struct { @@ -121,9 +128,8 @@ type AfterControlPlaneUpgradeResponse struct { // CommonResponse contains Status and Message fields common to all response types. CommonResponse `json:",inline"` - // RetryAfterSeconds when set to a non-zero value signifies that the hook - // needs to be retried at a future time. - RetryAfterSeconds int `json:"retryAfterSeconds"` + // CommonRetryResponse contains RetryAfterSeconds field common to all retry response types. + CommonRetryResponse `json:",inline"` } // AfterControlPlaneUpgrade is the runtime hook called after the control plane is successfully upgraded to the target @@ -142,6 +148,8 @@ type AfterClusterUpgradeRequest struct { KubernetesVersion string `json:"kubernetesVersion"` } +var _ ResponseObject = &AfterClusterUpgradeResponse{} + // AfterClusterUpgradeResponse is the response of the AfterClusterUpgrade hook. // +kubebuilder:object:root=true type AfterClusterUpgradeResponse struct { @@ -164,6 +172,8 @@ type BeforeClusterDeleteRequest struct { Cluster clusterv1.Cluster `json:"cluster"` } +var _ RetryResponseObject = &BeforeClusterDeleteResponse{} + // BeforeClusterDeleteResponse is the response of the BeforeClusterDelete hook. // +kubebuilder:object:root=true type BeforeClusterDeleteResponse struct { @@ -172,9 +182,8 @@ type BeforeClusterDeleteResponse struct { // CommonResponse contains Status and Message fields common to all response types. CommonResponse `json:",inline"` - // RetryAfterSeconds when set to a non-zero value signifies that the hook - // needs to be retried at a future time. - RetryAfterSeconds int `json:"retryAfterSeconds"` + // CommonRetryResponse contains RetryAfterSeconds field common to all retry response types. + CommonRetryResponse `json:",inline"` } // BeforeClusterDelete is the runtime hook that is called after a delete is issued on a cluster diff --git a/exp/runtime/hooks/api/v1alpha1/topologymutation_types.go b/exp/runtime/hooks/api/v1alpha1/topologymutation_types.go index cf584b834fd9..13071cf188b0 100644 --- a/exp/runtime/hooks/api/v1alpha1/topologymutation_types.go +++ b/exp/runtime/hooks/api/v1alpha1/topologymutation_types.go @@ -56,6 +56,8 @@ type GeneratePatchesRequestItem struct { Variables []Variable `json:"variables"` } +var _ ResponseObject = &GeneratePatchesResponse{} + // GeneratePatchesResponse is the response of the GeneratePatches hook. // NOTE: The patches in GeneratePatchesResponse will be applied in the order in which they are defined to the // templates of the request. Thus applying changes consecutively when iterating through internal and external patches. @@ -63,12 +65,8 @@ type GeneratePatchesRequestItem struct { type GeneratePatchesResponse struct { metav1.TypeMeta `json:",inline"` - // Status of the call. - // One of: "Success" or "Failure". - Status ResponseStatus `json:"status"` - - // A human-readable description of the status of the call. - Message string `json:"message,omitempty"` + // CommonResponse contains Status and Message fields common to all response types. + CommonResponse `json:",inline"` // Items is the list of generated patches. Items []GeneratePatchesResponseItem `json:"items"` @@ -131,17 +129,15 @@ type ValidateTopologyRequestItem struct { Variables []Variable `json:"variables"` } +var _ ResponseObject = &ValidateTopologyResponse{} + // ValidateTopologyResponse is the response of the ValidateTopology hook. // +kubebuilder:object:root=true type ValidateTopologyResponse struct { metav1.TypeMeta `json:",inline"` - // Status of the call. - // One of: "Success" or "Failure". - Status ResponseStatus `json:"status"` - - // A human-readable description of the status of the call. - Message string `json:"message"` + // CommonResponse contains Status and Message fields common to all response types. + CommonResponse `json:",inline"` } // Variable represents a variable value. diff --git a/exp/runtime/hooks/api/v1alpha1/zz_generated.deepcopy.go b/exp/runtime/hooks/api/v1alpha1/zz_generated.deepcopy.go index 5b1f4b60b26b..f4a552ec6dce 100644 --- a/exp/runtime/hooks/api/v1alpha1/zz_generated.deepcopy.go +++ b/exp/runtime/hooks/api/v1alpha1/zz_generated.deepcopy.go @@ -155,6 +155,7 @@ func (in *AfterControlPlaneUpgradeResponse) DeepCopyInto(out *AfterControlPlaneU *out = *in out.TypeMeta = in.TypeMeta out.CommonResponse = in.CommonResponse + out.CommonRetryResponse = in.CommonRetryResponse } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AfterControlPlaneUpgradeResponse. @@ -205,6 +206,7 @@ func (in *BeforeClusterCreateResponse) DeepCopyInto(out *BeforeClusterCreateResp *out = *in out.TypeMeta = in.TypeMeta out.CommonResponse = in.CommonResponse + out.CommonRetryResponse = in.CommonRetryResponse } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeforeClusterCreateResponse. @@ -255,6 +257,7 @@ func (in *BeforeClusterDeleteResponse) DeepCopyInto(out *BeforeClusterDeleteResp *out = *in out.TypeMeta = in.TypeMeta out.CommonResponse = in.CommonResponse + out.CommonRetryResponse = in.CommonRetryResponse } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeforeClusterDeleteResponse. @@ -305,6 +308,7 @@ func (in *BeforeClusterUpgradeResponse) DeepCopyInto(out *BeforeClusterUpgradeRe *out = *in out.TypeMeta = in.TypeMeta out.CommonResponse = in.CommonResponse + out.CommonRetryResponse = in.CommonRetryResponse } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeforeClusterUpgradeResponse. @@ -340,6 +344,21 @@ func (in *CommonResponse) DeepCopy() *CommonResponse { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CommonRetryResponse) DeepCopyInto(out *CommonRetryResponse) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonRetryResponse. +func (in *CommonRetryResponse) DeepCopy() *CommonRetryResponse { + if in == nil { + return nil + } + out := new(CommonRetryResponse) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DiscoveryRequest) DeepCopyInto(out *DiscoveryRequest) { *out = *in @@ -488,6 +507,7 @@ func (in *GeneratePatchesRequestItem) DeepCopy() *GeneratePatchesRequestItem { func (in *GeneratePatchesResponse) DeepCopyInto(out *GeneratePatchesResponse) { *out = *in out.TypeMeta = in.TypeMeta + out.CommonResponse = in.CommonResponse if in.Items != nil { in, out := &in.Items, &out.Items *out = make([]GeneratePatchesResponseItem, len(*in)) @@ -635,6 +655,7 @@ func (in *ValidateTopologyRequestItem) DeepCopy() *ValidateTopologyRequestItem { func (in *ValidateTopologyResponse) DeepCopyInto(out *ValidateTopologyResponse) { *out = *in out.TypeMeta = in.TypeMeta + out.CommonResponse = in.CommonResponse } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidateTopologyResponse. diff --git a/exp/runtime/hooks/api/v1alpha1/zz_generated.openapi.go b/exp/runtime/hooks/api/v1alpha1/zz_generated.openapi.go index 47d3c82ffcaa..57a987b57b22 100644 --- a/exp/runtime/hooks/api/v1alpha1/zz_generated.openapi.go +++ b/exp/runtime/hooks/api/v1alpha1/zz_generated.openapi.go @@ -43,6 +43,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1.BeforeClusterUpgradeRequest": schema_runtime_hooks_api_v1alpha1_BeforeClusterUpgradeRequest(ref), "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1.BeforeClusterUpgradeResponse": schema_runtime_hooks_api_v1alpha1_BeforeClusterUpgradeResponse(ref), "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1.CommonResponse": schema_runtime_hooks_api_v1alpha1_CommonResponse(ref), + "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1.CommonRetryResponse": schema_runtime_hooks_api_v1alpha1_CommonRetryResponse(ref), "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1.DiscoveryRequest": schema_runtime_hooks_api_v1alpha1_DiscoveryRequest(ref), "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1.DiscoveryResponse": schema_runtime_hooks_api_v1alpha1_DiscoveryResponse(ref), "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1.ExtensionHandler": schema_runtime_hooks_api_v1alpha1_ExtensionHandler(ref), @@ -313,7 +314,7 @@ func schema_runtime_hooks_api_v1alpha1_AfterControlPlaneUpgradeResponse(ref comm }, "retryAfterSeconds": { SchemaProps: spec.SchemaProps{ - Description: "RetryAfterSeconds when set to a non-zero value signifies that the hook needs to be retried at a future time.", + Description: "RetryAfterSeconds when set to a non-zero value signifies that the hook will be called again at a future time.", Default: 0, Type: []string{"integer"}, Format: "int32", @@ -491,7 +492,7 @@ func schema_runtime_hooks_api_v1alpha1_BeforeClusterDeleteResponse(ref common.Re }, "retryAfterSeconds": { SchemaProps: spec.SchemaProps{ - Description: "RetryAfterSeconds when set to a non-zero value signifies that the hook needs to be retried at a future time.", + Description: "RetryAfterSeconds when set to a non-zero value signifies that the hook will be called again at a future time.", Default: 0, Type: []string{"integer"}, Format: "int32", @@ -596,7 +597,7 @@ func schema_runtime_hooks_api_v1alpha1_BeforeClusterUpgradeResponse(ref common.R }, "retryAfterSeconds": { SchemaProps: spec.SchemaProps{ - Description: "RetryAfterSeconds when set to a non-zero value signifies that the hook needs to be retried at a future time.", + Description: "RetryAfterSeconds when set to a non-zero value signifies that the hook will be called again at a future time.", Default: 0, Type: []string{"integer"}, Format: "int32", @@ -639,6 +640,28 @@ func schema_runtime_hooks_api_v1alpha1_CommonResponse(ref common.ReferenceCallba } } +func schema_runtime_hooks_api_v1alpha1_CommonRetryResponse(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CommonRetryResponse is the data structure which contains all common retry fields.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "retryAfterSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "RetryAfterSeconds when set to a non-zero value signifies that the hook will be called again at a future time.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"retryAfterSeconds"}, + }, + }, + } +} + func schema_runtime_hooks_api_v1alpha1_DiscoveryRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -912,7 +935,7 @@ func schema_runtime_hooks_api_v1alpha1_GeneratePatchesResponse(ref common.Refere }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status of the call. One of: \"Success\" or \"Failure\".\n\nPossible enum values:\n - `\"Failure\"` represents a failure response.\n - `\"Success\"` represents the success response.", + Description: "Status of the call. One of \"Success\" or \"Failure\".\n\nPossible enum values:\n - `\"Failure\"` represents a failure response.\n - `\"Success\"` represents the success response.", Default: "", Type: []string{"string"}, Format: "", @@ -921,6 +944,7 @@ func schema_runtime_hooks_api_v1alpha1_GeneratePatchesResponse(ref common.Refere "message": { SchemaProps: spec.SchemaProps{ Description: "A human-readable description of the status of the call.", + Default: "", Type: []string{"string"}, Format: "", }, @@ -940,7 +964,7 @@ func schema_runtime_hooks_api_v1alpha1_GeneratePatchesResponse(ref common.Refere }, }, }, - Required: []string{"status", "items"}, + Required: []string{"status", "message", "items"}, }, }, Dependencies: []string{ @@ -1193,7 +1217,7 @@ func schema_runtime_hooks_api_v1alpha1_ValidateTopologyResponse(ref common.Refer }, "status": { SchemaProps: spec.SchemaProps{ - Description: "Status of the call. One of: \"Success\" or \"Failure\".\n\nPossible enum values:\n - `\"Failure\"` represents a failure response.\n - `\"Success\"` represents the success response.", + Description: "Status of the call. One of \"Success\" or \"Failure\".\n\nPossible enum values:\n - `\"Failure\"` represents a failure response.\n - `\"Success\"` represents the success response.", Default: "", Type: []string{"string"}, Format: "", diff --git a/internal/runtime/client/client.go b/internal/runtime/client/client.go index 470fc5773f97..c59304dab49b 100644 --- a/internal/runtime/client/client.go +++ b/internal/runtime/client/client.go @@ -180,10 +180,12 @@ func (c *client) CallAllExtensions(ctx context.Context, hook runtimecatalog.Hook if err := c.catalog.ValidateResponse(gvh, response); err != nil { return errors.Wrapf(err, "response object is invalid for hook %s", gvh) } + registrations, err := c.registry.List(gvh.GroupHook()) if err != nil { return errors.Wrapf(err, "failed to retrieve ExtensionHandlers for %s", gvh.GroupHook()) } + responses := []runtimehooksv1.ResponseObject{} for _, registration := range registrations { // Creates a new instance of the response parameter. @@ -192,6 +194,7 @@ func (c *client) CallAllExtensions(ctx context.Context, hook runtimecatalog.Hook return errors.Wrapf(err, "ExtensionHandler %s failed", registration.Name) } tmpResponse := responseObject.(runtimehooksv1.ResponseObject) + err = c.CallExtension(ctx, hook, registration.Name, request, tmpResponse) // If one of the extension handlers fails lets short-circuit here and return early. if err != nil { @@ -199,15 +202,50 @@ func (c *client) CallAllExtensions(ctx context.Context, hook runtimecatalog.Hook } responses = append(responses, tmpResponse) } - if err := aggregateResponses(responses); err != nil { - return errors.Wrap(err, "failed to aggregate responses") - } + + // Aggregate all responses into a single response. + // Note: we only get here if all the extension handlers succeeded. + aggregateSuccessfulResponses(response, responses) + return nil } -func aggregateResponses(responses []runtimehooksv1.ResponseObject) error { - // TODO:(killianmuldoon) implement proper aggregation logic. - return nil +// aggregateSuccessfulResponses aggregates all successful responses into a single response. +func aggregateSuccessfulResponses(aggregatedResponse runtimehooksv1.ResponseObject, responses []runtimehooksv1.ResponseObject) { + // At this point the Status should always be ResponseStatusSuccess and the Message should be empty. + // So let's set those values to avoid keeping values that could have been set by the caller of CallAllExtensions. + aggregatedResponse.SetMessage("") + aggregatedResponse.SetStatus(runtimehooksv1.ResponseStatusSuccess) + + if _, ok := aggregatedResponse.(runtimehooksv1.RetryResponseObject); !ok { + // If the aggregated response is not a RetryResponseObject then we're done. + return + } + + // Note: as all responses have the same type we can assume now that + // they all implement the RetryResponseObject interface. + aggregatedRetryableResponse := aggregatedResponse.(runtimehooksv1.RetryResponseObject) + + for _, resp := range responses { + aggregatedRetryableResponse.SetRetryAfterSeconds(lowestNonZeroRetryAfterSeconds( + aggregatedRetryableResponse.GetRetryAfterSeconds(), + resp.(runtimehooksv1.RetryResponseObject).GetRetryAfterSeconds(), + )) + } +} + +// lowestNonZeroRetryAfterSeconds returns the lowest non-zero value of the two provided values. +func lowestNonZeroRetryAfterSeconds(i, j int32) int32 { + if i == 0 { + return j + } + if j == 0 { + return i + } + if i < j { + return i + } + return j } // CallExtension make the call to the extension with the given name. @@ -235,6 +273,7 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, na if err := c.catalog.ValidateResponse(hookGVH, response); err != nil { return errors.Wrapf(err, "response object is invalid for hook %s", hookGVH) } + registration, err := c.registry.Get(name) if err != nil { return errors.Wrapf(err, "failed to retrieve ExtensionHandler with name %q", name) @@ -242,6 +281,7 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, na if hookGVH.GroupHook() != registration.GroupVersionHook.GroupHook() { return errors.Errorf("ExtensionHandler %q does not match group %s, hook %s", name, hookGVH.Group, hookGVH.Hook) } + var timeoutDuration time.Duration if registration.TimeoutSeconds != nil { timeoutDuration = time.Duration(*registration.TimeoutSeconds) * time.Second @@ -267,10 +307,12 @@ func (c *client) CallExtension(ctx context.Context, hook runtimecatalog.Hook, na } return errors.Wrap(err, "failed to call ExtensionHandler") } + // If the received response is a failure then return an error. if response.GetStatus() == runtimehooksv1.ResponseStatusFailure { return errors.Errorf("ExtensionHandler %s failed with message %s", name, response.GetMessage()) } + // Received a successful response from the extension handler. The `response` object // is populated with the result. Return no error. return nil diff --git a/internal/runtime/client/client_test.go b/internal/runtime/client/client_test.go index 85f33ec4d7a5..0790f0a394d5 100644 --- a/internal/runtime/client/client_test.go +++ b/internal/runtime/client/client_test.go @@ -23,6 +23,8 @@ import ( "net" "net/http" "net/http/httptest" + "reflect" + "regexp" "testing" . "github.com/onsi/gomega" @@ -324,8 +326,6 @@ func TestURLForExtension(t *testing.T) { } func TestClient_CallExtension(t *testing.T) { - g := NewWithT(t) - testHostPort := "127.0.0.1:9090" fpFail := runtimev1.FailurePolicyFail @@ -351,14 +351,6 @@ func TestClient_CallExtension(t *testing.T) { }, }, } - registryWithExtensionHandlerWithFailPolicy := runtimeregistry.New() - err := registryWithExtensionHandlerWithFailPolicy.WarmUp(&runtimev1.ExtensionConfigList{ - Items: []runtimev1.ExtensionConfig{ - validExtensionHandlerWithFailPolicy, - }, - }) - g.Expect(err).NotTo(HaveOccurred()) - validExtensionHandlerWithIgnorePolicy := runtimev1.ExtensionConfig{ Spec: runtimev1.ExtensionConfigSpec{ ClientConfig: runtimev1.ClientConfig{ @@ -380,34 +372,6 @@ func TestClient_CallExtension(t *testing.T) { }, } - registryWithExtensionHandlerWithIgnorePolicy := runtimeregistry.New() - err = registryWithExtensionHandlerWithIgnorePolicy.WarmUp(&runtimev1.ExtensionConfigList{ - Items: []runtimev1.ExtensionConfig{ - validExtensionHandlerWithIgnorePolicy, - }, - }) - g.Expect(err).NotTo(HaveOccurred()) - - successResponse := &fakev1alpha1.FakeResponse{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeResponse", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, - CommonResponse: runtimehooksv1.CommonResponse{ - Status: runtimehooksv1.ResponseStatusSuccess, - }, - } - - failureResponse := &fakev1alpha1.FakeResponse{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeResponse", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, - CommonResponse: runtimehooksv1.CommonResponse{ - Status: runtimehooksv1.ResponseStatusFailure, - }, - } - type args struct { hook runtimecatalog.Hook name string @@ -415,222 +379,343 @@ func TestClient_CallExtension(t *testing.T) { response runtimehooksv1.ResponseObject } tests := []struct { - name string - registry runtimeregistry.ExtensionRegistry - args args - testServer testServerConfig - wantErr bool + name string + registeredExtensionConfigs []runtimev1.ExtensionConfig + args args + testServer testServerConfig + wantErr bool }{ { - name: "should fail if ExtensionHandler information is not registered", - registry: runtimeregistry.New(), + name: "should fail when hook and request/response are not compatible", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{validExtensionHandlerWithFailPolicy}, testServer: testServerConfig{ - start: true, - response: &fakev1alpha1.FakeResponse{}, - responseStatusCode: http.StatusOK, + start: false, }, args: args{ hook: fakev1alpha1.FakeHook, - name: "unregistered-extension", - request: &fakev1alpha1.FakeRequest{}, - response: &fakev1alpha1.FakeResponse{}, + name: "valid-extension", + request: &fakev1alpha1.SecondFakeRequest{}, + response: &fakev1alpha1.SecondFakeResponse{}, }, wantErr: true, }, { - name: "should fail when extension handler from the registry is not compatible with the hook arg", - registry: registryWithExtensionHandlerWithFailPolicy, + name: "should fail when hook GVH does not match the registered ExtensionHandler", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{validExtensionHandlerWithFailPolicy}, testServer: testServerConfig{ start: false, }, args: args{ - hook: fakev1alpha1.SecondFakeHook, - name: "valid-extension", - request: &fakev1alpha1.FakeRequest{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeRequest", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, - }, - response: &fakev1alpha1.FakeResponse{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeResponse", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, - }, + hook: fakev1alpha1.SecondFakeHook, + name: "valid-extension", + request: &fakev1alpha1.SecondFakeRequest{}, + response: &fakev1alpha1.SecondFakeResponse{}, }, wantErr: true, }, { - name: "should succeed when calling ExtensionHandler with success response and FailurePolicyFail", - registry: registryWithExtensionHandlerWithFailPolicy, + name: "should fail if ExtensionHandler is not registered", + registeredExtensionConfigs: nil, testServer: testServerConfig{ - start: true, - response: successResponse, - responseStatusCode: http.StatusOK, - }, args: args{ - hook: fakev1alpha1.FakeHook, - name: "valid-extension", - request: &fakev1alpha1.FakeRequest{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeRequest", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, + start: true, + responses: map[string]testServerResponse{ + "/*": response(runtimehooksv1.ResponseStatusSuccess), }, - response: &fakev1alpha1.FakeResponse{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeResponse", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, + }, + args: args{ + hook: fakev1alpha1.FakeHook, + name: "unregistered-extension", + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, + }, + wantErr: true, + }, + { + name: "should succeed when calling ExtensionHandler with success response and FailurePolicyFail", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{validExtensionHandlerWithFailPolicy}, + testServer: testServerConfig{ + start: true, + responses: map[string]testServerResponse{ + "/*": response(runtimehooksv1.ResponseStatusSuccess), }, + }, args: args{ + hook: fakev1alpha1.FakeHook, + name: "valid-extension", + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, }, wantErr: false, }, { - name: "should succeed when calling ExtensionHandler with success response and FailurePolicyIgnore", - registry: registryWithExtensionHandlerWithIgnorePolicy, + name: "should succeed when calling ExtensionHandler with success response and FailurePolicyIgnore", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{validExtensionHandlerWithIgnorePolicy}, testServer: testServerConfig{ - start: true, - response: successResponse, - responseStatusCode: http.StatusOK, - }, args: args{ - hook: fakev1alpha1.FakeHook, - name: "valid-extension", - request: &fakev1alpha1.FakeRequest{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeRequest", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, - }, - response: &fakev1alpha1.FakeResponse{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeResponse", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, + start: true, + responses: map[string]testServerResponse{ + "/*": response(runtimehooksv1.ResponseStatusSuccess), }, + }, args: args{ + hook: fakev1alpha1.FakeHook, + name: "valid-extension", + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, }, wantErr: false, }, { - name: "should fail when calling ExtensionHandler with failure response and FailurePolicyFail", - registry: registryWithExtensionHandlerWithFailPolicy, + name: "should fail when calling ExtensionHandler with failure response and FailurePolicyFail", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{validExtensionHandlerWithFailPolicy}, testServer: testServerConfig{ - start: true, - response: failureResponse, - responseStatusCode: http.StatusOK, - }, args: args{ - hook: fakev1alpha1.FakeHook, - name: "valid-extension", - request: &fakev1alpha1.FakeRequest{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeRequest", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, - }, - response: &fakev1alpha1.FakeResponse{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeResponse", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, + start: true, + responses: map[string]testServerResponse{ + "/*": response(runtimehooksv1.ResponseStatusFailure), }, + }, args: args{ + hook: fakev1alpha1.FakeHook, + name: "valid-extension", + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, }, wantErr: true, }, { - name: "should fail when calling ExtensionHandler with failure response and FailurePolicyIgnore", - registry: registryWithExtensionHandlerWithIgnorePolicy, + name: "should fail when calling ExtensionHandler with failure response and FailurePolicyIgnore", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{validExtensionHandlerWithIgnorePolicy}, testServer: testServerConfig{ - start: true, - response: failureResponse, - responseStatusCode: http.StatusOK, - }, args: args{ - hook: fakev1alpha1.FakeHook, - name: "valid-extension", - request: &fakev1alpha1.FakeRequest{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeRequest", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, - }, - response: &fakev1alpha1.FakeResponse{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeResponse", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, + start: true, + responses: map[string]testServerResponse{ + "/*": response(runtimehooksv1.ResponseStatusFailure), }, + }, args: args{ + hook: fakev1alpha1.FakeHook, + name: "valid-extension", + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, }, wantErr: true, }, { - name: "should succeed with unreachable extension and Ignore failure policy", - registry: registryWithExtensionHandlerWithIgnorePolicy, + name: "should succeed with unreachable extension and FailurePolicyIgnore", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{validExtensionHandlerWithIgnorePolicy}, testServer: testServerConfig{ start: false, }, args: args{ - hook: fakev1alpha1.FakeHook, - name: "valid-extension", - request: &fakev1alpha1.FakeRequest{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeRequest", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, - }, - response: &fakev1alpha1.FakeResponse{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeResponse", - APIVersion: fakev1alpha1.GroupVersion.String(), - }, - }, + hook: fakev1alpha1.FakeHook, + name: "valid-extension", + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, }, wantErr: false, }, { - name: "should fail with unreachable extension and Fail failure policy", - registry: registryWithExtensionHandlerWithFailPolicy, + name: "should fail with unreachable extension and FailurePolicyFail", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{validExtensionHandlerWithFailPolicy}, testServer: testServerConfig{ start: false, }, args: args{ - hook: fakev1alpha1.FakeHook, - name: "valid-extension", - request: &fakev1alpha1.FakeRequest{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeRequest", + hook: fakev1alpha1.FakeHook, + name: "valid-extension", + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + if tt.testServer.start { + if tt.testServer.hostPort == "" { + tt.testServer.hostPort = testHostPort + } + srv := createTestServer(g, tt.testServer) + srv.Start() + defer srv.Close() + } + + cat := runtimecatalog.New() + _ = fakev1alpha1.AddToCatalog(cat) + _ = fakev1alpha2.AddToCatalog(cat) + + c := New(Options{ + Catalog: cat, + Registry: registry(tt.registeredExtensionConfigs), + }) + + err := c.CallExtension(context.Background(), tt.args.hook, tt.args.name, tt.args.request, tt.args.response) + + if tt.wantErr { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).NotTo(HaveOccurred()) + } + }) + } +} + +func Test_client_CallAllExtensions(t *testing.T) { + testHostPort := "127.0.0.1:9090" + + fpFail := runtimev1.FailurePolicyFail + + extensionConfig := runtimev1.ExtensionConfig{ + Spec: runtimev1.ExtensionConfigSpec{ + ClientConfig: runtimev1.ClientConfig{ + URL: pointer.String(fmt.Sprintf("http://%s/", testHostPort)), + }, + }, + Status: runtimev1.ExtensionConfigStatus{ + Handlers: []runtimev1.ExtensionHandler{ + { + Name: "first-extension", + RequestHook: runtimev1.GroupVersionHook{ APIVersion: fakev1alpha1.GroupVersion.String(), + Hook: "FakeHook", }, + TimeoutSeconds: pointer.Int32Ptr(1), + FailurePolicy: &fpFail, }, - response: &fakev1alpha1.FakeResponse{ - TypeMeta: metav1.TypeMeta{ - Kind: "FakeResponse", + { + Name: "second-extension", + RequestHook: runtimev1.GroupVersionHook{ APIVersion: fakev1alpha1.GroupVersion.String(), + Hook: "FakeHook", }, + TimeoutSeconds: pointer.Int32Ptr(1), + FailurePolicy: &fpFail, + }, + { + Name: "third-extension", + RequestHook: runtimev1.GroupVersionHook{ + APIVersion: fakev1alpha1.GroupVersion.String(), + Hook: "FakeHook", + }, + TimeoutSeconds: pointer.Int32Ptr(1), + FailurePolicy: &fpFail, }, }, - wantErr: true, }, } + type args struct { + hook runtimecatalog.Hook + request runtime.Object + response runtimehooksv1.ResponseObject + } + tests := []struct { + name string + registeredExtensionConfigs []runtimev1.ExtensionConfig + args args + testServer testServerConfig + wantErr bool + }{ + { + name: "should fail when hook and request/response are not compatible", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{extensionConfig}, + testServer: testServerConfig{ + start: false, + }, + args: args{ + hook: fakev1alpha1.SecondFakeHook, + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, + }, + wantErr: true, + }, + { + name: "should succeed when no ExtensionHandlers are registered for the hook", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{}, + testServer: testServerConfig{ + start: false, + }, + args: args{ + hook: fakev1alpha1.FakeHook, + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, + }, + wantErr: false, + }, + { + name: "should succeed when calling ExtensionHandlers with success responses", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{extensionConfig}, + testServer: testServerConfig{ + start: true, + responses: map[string]testServerResponse{ + "/*": response(runtimehooksv1.ResponseStatusSuccess), + }, + }, + args: args{ + hook: fakev1alpha1.FakeHook, + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, + }, + wantErr: false, + }, + { + name: "should fail when calling ExtensionHandlers with failure responses", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{extensionConfig}, + testServer: testServerConfig{ + start: true, + responses: map[string]testServerResponse{ + "/*": response(runtimehooksv1.ResponseStatusFailure), + }, + }, + args: args{ + hook: fakev1alpha1.FakeHook, + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, + }, + wantErr: true, + }, + { + name: "should fail when one of the ExtensionHandlers returns a failure responses", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{extensionConfig}, + testServer: testServerConfig{ + start: true, + responses: map[string]testServerResponse{ + "/test.runtime.cluster.x-k8s.io/v1alpha1/fakehook/first-extension.*": response(runtimehooksv1.ResponseStatusSuccess), + "/test.runtime.cluster.x-k8s.io/v1alpha1/fakehook/second-extension.*": response(runtimehooksv1.ResponseStatusFailure), + "/test.runtime.cluster.x-k8s.io/v1alpha1/fakehook/third-extension.*": response(runtimehooksv1.ResponseStatusSuccess), + }, + }, + args: args{ + hook: fakev1alpha1.FakeHook, + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, + }, + wantErr: true, + }, + { + name: "should fail when one of the ExtensionHandlers returns 404", + registeredExtensionConfigs: []runtimev1.ExtensionConfig{extensionConfig}, + testServer: testServerConfig{ + start: true, + responses: map[string]testServerResponse{ + "/test.runtime.cluster.x-k8s.io/v1alpha1/fakehook/first-extension.*": response(runtimehooksv1.ResponseStatusSuccess), + "/test.runtime.cluster.x-k8s.io/v1alpha1/fakehook/second-extension.*": response(runtimehooksv1.ResponseStatusFailure), + // third-extension has no handler. + }, + }, + args: args{ + hook: fakev1alpha1.FakeHook, + request: &fakev1alpha1.FakeRequest{}, + response: &fakev1alpha1.FakeResponse{}, + }, + wantErr: true, + }, + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) if tt.testServer.start { - l, err := net.Listen("tcp", testHostPort) - g.Expect(err).NotTo(HaveOccurred()) - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - respBody, err := json.Marshal(tt.testServer.response) - if err != nil { - panic(err) - } - w.WriteHeader(tt.testServer.responseStatusCode) - _, _ = w.Write(respBody) - }) - srv := httptest.NewUnstartedServer(mux) - // NewUnstartedServer creates a listener. Close that listener and replace - // with the one we created. - g.Expect(srv.Listener.Close()).To(Succeed()) - srv.Listener = l + if tt.testServer.hostPort == "" { + tt.testServer.hostPort = testHostPort + } + srv := createTestServer(g, tt.testServer) srv.Start() defer srv.Close() } @@ -641,11 +726,10 @@ func TestClient_CallExtension(t *testing.T) { c := New(Options{ Catalog: cat, - Registry: tt.registry, + Registry: registry(tt.registeredExtensionConfigs), }) - ctx := context.Background() - err := c.CallExtension(ctx, tt.args.hook, tt.args.name, tt.args.request, tt.args.response) + err := c.CallAllExtensions(context.Background(), tt.args.hook, tt.args.request, tt.args.response) if tt.wantErr { g.Expect(err).To(HaveOccurred()) @@ -656,8 +740,144 @@ func TestClient_CallExtension(t *testing.T) { } } +func Test_aggregateResponses(t *testing.T) { + tests := []struct { + name string + aggregateResponse runtimehooksv1.ResponseObject + responses []runtimehooksv1.ResponseObject + want runtimehooksv1.ResponseObject + }{ + { + name: "Aggregate response if there is only one response", + aggregateResponse: fakeSuccessResponse(), + responses: []runtimehooksv1.ResponseObject{ + fakeSuccessResponse(), + }, + want: fakeSuccessResponse(), + }, + { + name: "Aggregate retry response if there is only one response", + aggregateResponse: fakeRetryableSuccessResponse(0), + responses: []runtimehooksv1.ResponseObject{ + fakeRetryableSuccessResponse(5), + }, + want: fakeRetryableSuccessResponse(5), + }, + { + name: "Aggregate retry responses to lowest non-zero retryAfterSeconds value", + aggregateResponse: fakeRetryableSuccessResponse(0), + responses: []runtimehooksv1.ResponseObject{ + fakeRetryableSuccessResponse(0), + fakeRetryableSuccessResponse(1), + fakeRetryableSuccessResponse(5), + fakeRetryableSuccessResponse(4), + fakeRetryableSuccessResponse(3), + }, + want: fakeRetryableSuccessResponse(1), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + aggregateSuccessfulResponses(tt.aggregateResponse, tt.responses) + + if !reflect.DeepEqual(tt.aggregateResponse, tt.want) { + t.Errorf("aggregateSuccessfulResponses() got = %v, want %v", tt.aggregateResponse, tt.want) + } + }) + } +} + type testServerConfig struct { - start bool + start bool + responses map[string]testServerResponse + hostPort string +} + +type testServerResponse struct { response runtime.Object responseStatusCode int } + +func response(status runtimehooksv1.ResponseStatus) testServerResponse { + return testServerResponse{ + response: &fakev1alpha1.FakeResponse{ + CommonResponse: runtimehooksv1.CommonResponse{ + Status: status, + }, + }, + responseStatusCode: http.StatusOK, + } +} + +func createTestServer(g *WithT, server testServerConfig) *httptest.Server { + l, err := net.Listen("tcp", server.hostPort) + g.Expect(err).NotTo(HaveOccurred()) + + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // Write the response for the first match in tt.testServer.responses. + for pathRegex, resp := range server.responses { + if !regexp.MustCompile(pathRegex).MatchString(r.URL.Path) { + continue + } + + respBody, err := json.Marshal(resp.response) + if err != nil { + panic(err) + } + w.WriteHeader(resp.responseStatusCode) + _, _ = w.Write(respBody) + return + } + + // Otherwise write a 404. + w.WriteHeader(http.StatusNotFound) + }) + srv := httptest.NewUnstartedServer(mux) + // NewUnstartedServer creates a listener. Close that listener and replace + // with the one we created. + g.Expect(srv.Listener.Close()).To(Succeed()) + srv.Listener = l + + return srv +} + +func registry(configs []runtimev1.ExtensionConfig) runtimeregistry.ExtensionRegistry { + registry := runtimeregistry.New() + err := registry.WarmUp(&runtimev1.ExtensionConfigList{ + Items: configs, + }) + if err != nil { + panic(err) + } + return registry +} + +func fakeSuccessResponse() *fakev1alpha1.FakeResponse { + return &fakev1alpha1.FakeResponse{ + TypeMeta: metav1.TypeMeta{ + Kind: "FakeResponse", + APIVersion: "v1alpha1", + }, + CommonResponse: runtimehooksv1.CommonResponse{ + Message: "", + Status: runtimehooksv1.ResponseStatusSuccess, + }, + } +} + +func fakeRetryableSuccessResponse(retryAfterSeconds int32) *fakev1alpha1.RetryableFakeResponse { + return &fakev1alpha1.RetryableFakeResponse{ + TypeMeta: metav1.TypeMeta{ + Kind: "FakeResponse", + APIVersion: "v1alpha1", + }, + CommonResponse: runtimehooksv1.CommonResponse{ + Message: "", + Status: runtimehooksv1.ResponseStatusSuccess, + }, + CommonRetryResponse: runtimehooksv1.CommonRetryResponse{ + RetryAfterSeconds: retryAfterSeconds, + }, + } +} diff --git a/internal/runtime/test/v1alpha1/doc.go b/internal/runtime/test/v1alpha1/doc.go index b1809d0875cf..f7487aab641a 100644 --- a/internal/runtime/test/v1alpha1/doc.go +++ b/internal/runtime/test/v1alpha1/doc.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package v1alpha1 contains types for catalog tests +// Package v1alpha1 contains types for tests. // Note: they have to be in a separate package because otherwise it wouldn't // be possible to register different versions of the same hook. // +k8s:conversion-gen=sigs.k8s.io/cluster-api/internal/runtime/test/v1alpha2 diff --git a/internal/runtime/test/v1alpha1/fake_types.go b/internal/runtime/test/v1alpha1/fake_types.go index 3e8c1d6ad60c..5d9d5e090674 100644 --- a/internal/runtime/test/v1alpha1/fake_types.go +++ b/internal/runtime/test/v1alpha1/fake_types.go @@ -21,38 +21,41 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" clusterv1alpha4 "sigs.k8s.io/cluster-api/api/v1alpha4" runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" runtimecatalog "sigs.k8s.io/cluster-api/internal/runtime/catalog" ) -var ( - // GroupVersion is group version identifying rpc services defined in this package - // and their request and response types. - GroupVersion = schema.GroupVersion{Group: "test.runtime.cluster.x-k8s.io", Version: "v1alpha1"} +// FakeRequest is a response for testing +// +kubebuilder:object:root=true +type FakeRequest struct { + metav1.TypeMeta `json:",inline"` - // catalogBuilder is used to add rpc services and their request and response types - // to a Catalog. - catalogBuilder = &runtimecatalog.Builder{GroupVersion: GroupVersion} + Cluster clusterv1alpha4.Cluster - // AddToCatalog adds rpc services defined in this package and their request and - // response types to a catalog. - AddToCatalog = catalogBuilder.AddToCatalog + Second string + First int +} - // localSchemeBuilder provide access to the SchemeBuilder used for managing rpc - // method's request and response types defined in this package. - // NOTE: this object is required to allow registration of automatically generated - // conversions func. - localSchemeBuilder = catalogBuilder -) +var _ runtimehooksv1.ResponseObject = &FakeResponse{} + +// FakeResponse is a response for testing. +// +kubebuilder:object:root=true +type FakeResponse struct { + metav1.TypeMeta `json:",inline"` + + runtimehooksv1.CommonResponse `json:",inline"` + + Second string + First int +} func FakeHook(*FakeRequest, *FakeResponse) {} -// FakeRequest is a response for testing +// SecondFakeRequest is a response for testing // +kubebuilder:object:root=true -type FakeRequest struct { +type SecondFakeRequest struct { metav1.TypeMeta `json:",inline"` Cluster clusterv1alpha4.Cluster @@ -61,10 +64,13 @@ type FakeRequest struct { First int } -// FakeResponse is a response for testing +var _ runtimehooksv1.ResponseObject = &SecondFakeResponse{} + +// SecondFakeResponse is a response for testing. // +kubebuilder:object:root=true -type FakeResponse struct { - metav1.TypeMeta `json:",inline"` +type SecondFakeResponse struct { + metav1.TypeMeta `json:",inline"` + runtimehooksv1.CommonResponse `json:",inline"` Second string @@ -73,9 +79,9 @@ type FakeResponse struct { func SecondFakeHook(*SecondFakeRequest, *SecondFakeResponse) {} -// SecondFakeRequest is a response for testing +// RetryableFakeRequest is a request for testing hooks with retryAfterSeconds. // +kubebuilder:object:root=true -type SecondFakeRequest struct { +type RetryableFakeRequest struct { metav1.TypeMeta `json:",inline"` Cluster clusterv1alpha4.Cluster @@ -84,27 +90,43 @@ type SecondFakeRequest struct { First int } -// SecondFakeResponse is a response for testing +var _ runtimehooksv1.RetryResponseObject = &RetryableFakeResponse{} + +// RetryableFakeResponse is a request for testing hooks with retryAfterSeconds. // +kubebuilder:object:root=true -type SecondFakeResponse struct { - metav1.TypeMeta `json:",inline"` +type RetryableFakeResponse struct { + metav1.TypeMeta `json:",inline"` + runtimehooksv1.CommonResponse `json:",inline"` - Second string - First int + + runtimehooksv1.CommonRetryResponse `json:",inline"` + + Second string + First int } +// RetryableFakeHook is a request for testing hooks with retryAfterSeconds. +func RetryableFakeHook(*RetryableFakeRequest, *RetryableFakeResponse) {} + func init() { catalogBuilder.RegisterHook(FakeHook, &runtimecatalog.HookMeta{ Tags: []string{"fake-tag"}, - Summary: "Fake summary", - Description: "Fake description", + Summary: "FakeHook summary", + Description: "FakeHook description", Deprecated: true, }) catalogBuilder.RegisterHook(SecondFakeHook, &runtimecatalog.HookMeta{ Tags: []string{"fake-tag"}, - Summary: "Second Fake summary", - Description: "Second Fake description", + Summary: "SecondFakeHook summary", + Description: "SecondFakeHook description", + Deprecated: true, + }) + + catalogBuilder.RegisterHook(RetryableFakeHook, &runtimecatalog.HookMeta{ + Tags: []string{"fake-tag"}, + Summary: "RetryableFakeHook summary", + Description: "RetryableFakeHook description", Deprecated: true, }) } diff --git a/internal/runtime/test/v1alpha1/groupversion_info.go b/internal/runtime/test/v1alpha1/groupversion_info.go new file mode 100644 index 000000000000..7fcc026bcce6 --- /dev/null +++ b/internal/runtime/test/v1alpha1/groupversion_info.go @@ -0,0 +1,43 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + + runtimecatalog "sigs.k8s.io/cluster-api/internal/runtime/catalog" +) + +var ( + // GroupVersion is group version identifying rpc services defined in this package + // and their request and response types. + GroupVersion = schema.GroupVersion{Group: "test.runtime.cluster.x-k8s.io", Version: "v1alpha1"} + + // catalogBuilder is used to add rpc services and their request and response types + // to a Catalog. + catalogBuilder = &runtimecatalog.Builder{GroupVersion: GroupVersion} + + // AddToCatalog adds rpc services defined in this package and their request and + // response types to a catalog. + AddToCatalog = catalogBuilder.AddToCatalog + + // localSchemeBuilder provide access to the SchemeBuilder used for managing rpc + // method's request and response types defined in this package. + // NOTE: this object is required to allow registration of automatically generated + // conversions func. + localSchemeBuilder = catalogBuilder +) diff --git a/internal/runtime/test/v1alpha1/zz_generated.deepcopy.go b/internal/runtime/test/v1alpha1/zz_generated.deepcopy.go index 0d32116ea8ef..14818d9f9f27 100644 --- a/internal/runtime/test/v1alpha1/zz_generated.deepcopy.go +++ b/internal/runtime/test/v1alpha1/zz_generated.deepcopy.go @@ -75,6 +75,57 @@ func (in *FakeResponse) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RetryableFakeRequest) DeepCopyInto(out *RetryableFakeRequest) { + *out = *in + out.TypeMeta = in.TypeMeta + in.Cluster.DeepCopyInto(&out.Cluster) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryableFakeRequest. +func (in *RetryableFakeRequest) DeepCopy() *RetryableFakeRequest { + if in == nil { + return nil + } + out := new(RetryableFakeRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RetryableFakeRequest) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RetryableFakeResponse) DeepCopyInto(out *RetryableFakeResponse) { + *out = *in + out.TypeMeta = in.TypeMeta + out.CommonResponse = in.CommonResponse + out.CommonRetryResponse = in.CommonRetryResponse +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryableFakeResponse. +func (in *RetryableFakeResponse) DeepCopy() *RetryableFakeResponse { + if in == nil { + return nil + } + out := new(RetryableFakeResponse) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RetryableFakeResponse) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecondFakeRequest) DeepCopyInto(out *SecondFakeRequest) { *out = *in diff --git a/internal/runtime/test/v1alpha2/doc.go b/internal/runtime/test/v1alpha2/doc.go new file mode 100644 index 000000000000..f621e072cbc2 --- /dev/null +++ b/internal/runtime/test/v1alpha2/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1alpha2 contains types for tests. +// Note: they have to be in a separate package because otherwise it wouldn't +// be possible to register different versions of the same hook. +package v1alpha2 diff --git a/internal/runtime/test/v1alpha2/fake_types.go b/internal/runtime/test/v1alpha2/fake_types.go index 9148eb6d5bc6..6d772393f7e2 100644 --- a/internal/runtime/test/v1alpha2/fake_types.go +++ b/internal/runtime/test/v1alpha2/fake_types.go @@ -21,30 +21,14 @@ package v1alpha2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" runtimecatalog "sigs.k8s.io/cluster-api/internal/runtime/catalog" ) -var ( - // GroupVersion is group version identifying rpc services defined in this package - // and their request and response types. - GroupVersion = schema.GroupVersion{Group: "test.runtime.cluster.x-k8s.io", Version: "v1alpha2"} - - // catalogBuilder is used to add rpc services and their request and response types - // to a Catalog. - catalogBuilder = &runtimecatalog.Builder{GroupVersion: GroupVersion} - - // AddToCatalog adds rpc services defined in this package and their request and - // response types to a catalog. - AddToCatalog = catalogBuilder.AddToCatalog -) - -func FakeHook(*FakeRequest, *FakeResponse) {} - +// FakeRequest is a response for testing +// +kubebuilder:object:root=true type FakeRequest struct { metav1.TypeMeta `json:",inline"` @@ -54,27 +38,26 @@ type FakeRequest struct { First int } -func (in *FakeRequest) DeepCopyObject() runtime.Object { - panic("implement me!") -} +var _ runtimehooksv1.ResponseObject = &FakeResponse{} +// FakeResponse is a response for testing. +// +kubebuilder:object:root=true type FakeResponse struct { metav1.TypeMeta `json:",inline"` runtimehooksv1.CommonResponse `json:",inline"` - Second string - First int -} -func (in *FakeResponse) DeepCopyObject() runtime.Object { - panic("implement me!") + Second string + First int } +func FakeHook(*FakeRequest, *FakeResponse) {} + func init() { catalogBuilder.RegisterHook(FakeHook, &runtimecatalog.HookMeta{ Tags: []string{"fake-tag"}, - Summary: "Fake summary", - Description: "Fake description", + Summary: "FakeHook summary", + Description: "FakeHook description", Deprecated: true, }) } diff --git a/internal/runtime/test/v1alpha2/groupversion_info.go b/internal/runtime/test/v1alpha2/groupversion_info.go new file mode 100644 index 000000000000..69d668a86bba --- /dev/null +++ b/internal/runtime/test/v1alpha2/groupversion_info.go @@ -0,0 +1,37 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + + runtimecatalog "sigs.k8s.io/cluster-api/internal/runtime/catalog" +) + +var ( + // GroupVersion is group version identifying rpc services defined in this package + // and their request and response types. + GroupVersion = schema.GroupVersion{Group: "test.runtime.cluster.x-k8s.io", Version: "v1alpha2"} + + // catalogBuilder is used to add rpc services and their request and response types + // to a Catalog. + catalogBuilder = &runtimecatalog.Builder{GroupVersion: GroupVersion} + + // AddToCatalog adds rpc services defined in this package and their request and + // response types to a catalog. + AddToCatalog = catalogBuilder.AddToCatalog +) diff --git a/internal/runtime/test/v1alpha2/zz_generated.deepcopy.go b/internal/runtime/test/v1alpha2/zz_generated.deepcopy.go new file mode 100644 index 000000000000..02dc8af57184 --- /dev/null +++ b/internal/runtime/test/v1alpha2/zz_generated.deepcopy.go @@ -0,0 +1,76 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FakeRequest) DeepCopyInto(out *FakeRequest) { + *out = *in + out.TypeMeta = in.TypeMeta + in.Cluster.DeepCopyInto(&out.Cluster) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeRequest. +func (in *FakeRequest) DeepCopy() *FakeRequest { + if in == nil { + return nil + } + out := new(FakeRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FakeRequest) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FakeResponse) DeepCopyInto(out *FakeResponse) { + *out = *in + out.TypeMeta = in.TypeMeta + out.CommonResponse = in.CommonResponse +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FakeResponse. +func (in *FakeResponse) DeepCopy() *FakeResponse { + if in == nil { + return nil + } + out := new(FakeResponse) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FakeResponse) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +}