From 9858100c6fb2f0bc3b88f72dfc3a122d4627464e Mon Sep 17 00:00:00 2001 From: morvencao Date: Fri, 17 Jan 2025 12:34:56 +0000 Subject: [PATCH] unify CloudEvent payload for single and bundle resources. Signed-off-by: morvencao --- cmd/maestro/agent/cmd.go | 2 +- cmd/maestro/server/event_server.go | 1 - cmd/maestro/server/grpc_broker.go | 36 ++---- cmd/maestro/server/grpc_server.go | 76 +++--------- pkg/api/presenters/resource.go | 79 ------------- pkg/api/presenters/resource_bundle.go | 43 +++++++ pkg/api/resource_bundle_types.go | 63 +++++----- pkg/api/resource_bundle_types_test.go | 127 ++++++--------------- pkg/api/resource_types.go | 110 ++++++++++++------ pkg/api/resource_types_test.go | 29 +++-- pkg/client/cloudevents/bundle_codec.go | 104 ----------------- pkg/client/cloudevents/codec.go | 5 +- pkg/client/cloudevents/source_client.go | 16 +-- pkg/dao/mocks/resource.go | 14 --- pkg/dao/resource.go | 10 -- pkg/services/resource.go | 13 +-- pkg/services/resource_test.go | 24 ++-- pkg/services/validation.go | 12 +- pkg/services/validation_test.go | 28 ++--- test/e2e/pkg/grpc_test.go | 34 +++--- test/helper.go | 12 +- test/integration/controller_test.go | 6 +- test/integration/resource_test.go | 52 ++++----- test/integration/status_dispatcher_test.go | 4 +- 24 files changed, 311 insertions(+), 589 deletions(-) create mode 100644 pkg/api/presenters/resource_bundle.go delete mode 100644 pkg/client/cloudevents/bundle_codec.go diff --git a/cmd/maestro/agent/cmd.go b/cmd/maestro/agent/cmd.go index 4e23a36e..18288f08 100644 --- a/cmd/maestro/agent/cmd.go +++ b/cmd/maestro/agent/cmd.go @@ -32,7 +32,7 @@ const maxJSONRawLength int32 = 1024 * 1024 func NewAgentCommand() *cobra.Command { agentOption.MaxJSONRawLength = maxJSONRawLength - agentOption.CloudEventsClientCodecs = []string{"manifest", "manifestbundle"} + agentOption.CloudEventsClientCodecs = []string{"manifestbundle"} cfg := spoke.NewWorkAgentConfig(commonOptions, agentOption) cmdConfig := commonOptions.CommonOpts. NewControllerCommandConfig("maestro-agent", version.Get(), cfg.RunWorkloadAgent) diff --git a/cmd/maestro/server/event_server.go b/cmd/maestro/server/event_server.go index a8f5f744..b6e112cf 100644 --- a/cmd/maestro/server/event_server.go +++ b/cmd/maestro/server/event_server.go @@ -177,7 +177,6 @@ func handleStatusUpdate(ctx context.Context, resource *api.Resource, resourceSer // set the resource source and type back for broadcast resource.Source = found.Source - resource.Type = found.Type // convert the resource status to cloudevent statusEvent, err := api.JSONMAPToCloudEvent(resource.Status) diff --git a/cmd/maestro/server/grpc_broker.go b/cmd/maestro/server/grpc_broker.go index 15021aed..84a08c24 100644 --- a/cmd/maestro/server/grpc_broker.go +++ b/cmd/maestro/server/grpc_broker.go @@ -174,7 +174,7 @@ func (bkr *GRPCBroker) Publish(ctx context.Context, pubReq *pbv1.PublishRequest) // handler resync request if eventType.Action == types.ResyncRequestAction { - err := bkr.respondResyncSpecRequest(ctx, eventType.CloudEventsDataType, evt) + err := bkr.respondResyncSpecRequest(ctx, evt) if err != nil { return nil, fmt.Errorf("failed to respond resync spec request: %v", err) } @@ -182,7 +182,7 @@ func (bkr *GRPCBroker) Publish(ctx context.Context, pubReq *pbv1.PublishRequest) } // decode the cloudevent data as resource with status - resource, err := decodeResourceStatus(eventType.CloudEventsDataType, evt) + resource, err := decodeResourceStatus(evt) if err != nil { return nil, fmt.Errorf("failed to decode cloudevent: %v", err) } @@ -278,7 +278,7 @@ func (bkr *GRPCBroker) Subscribe(subReq *pbv1.SubscriptionRequest, subServer pbv } // decodeResourceStatus translates a CloudEvent into a resource containing the status JSON map. -func decodeResourceStatus(eventDataType types.CloudEventsDataType, evt *ce.Event) (*api.Resource, error) { +func decodeResourceStatus(evt *ce.Event) (*api.Resource, error) { evtExtensions := evt.Context.GetExtensions() clusterName, err := cetypes.ToString(evtExtensions[types.ExtensionClusterName]) @@ -311,15 +311,6 @@ func decodeResourceStatus(eventDataType types.CloudEventsDataType, evt *ce.Event Status: status, } - switch eventDataType { - case workpayload.ManifestEventDataType: - resource.Type = api.ResourceTypeSingle - case workpayload.ManifestBundleEventDataType: - resource.Type = api.ResourceTypeBundle - default: - return nil, fmt.Errorf("unsupported cloudevents data type %s", eventDataType) - } - return resource, nil } @@ -331,13 +322,10 @@ func encodeResourceSpec(resource *api.Resource) (*ce.Event, error) { } eventType := types.CloudEventsType{ - CloudEventsDataType: workpayload.ManifestEventDataType, + CloudEventsDataType: workpayload.ManifestBundleEventDataType, SubResource: types.SubResourceSpec, Action: types.EventAction("create_request"), } - if resource.Type == api.ResourceTypeBundle { - eventType.CloudEventsDataType = workpayload.ManifestBundleEventDataType - } evt.SetType(eventType.String()) evt.SetSource("maestro") // TODO set resource.Source with a new extension attribute if the agent needs @@ -360,14 +348,11 @@ func encodeResourceSpec(resource *api.Resource) (*ce.Event, error) { // resend the resource. // - If the requested resource version is older than the source's current maintained resource version, the source // sends the resource. -func (bkr *GRPCBroker) respondResyncSpecRequest(ctx context.Context, eventDataType types.CloudEventsDataType, evt *ce.Event) error { +// - If the resource does not exist on the source, but exists on the agent, the source sends a delete event for the +// resource. +func (bkr *GRPCBroker) respondResyncSpecRequest(ctx context.Context, evt *ce.Event) error { log := logger.NewOCMLogger(ctx) - resyncType := api.ResourceTypeSingle - if eventDataType == workpayload.ManifestBundleEventDataType { - resyncType = api.ResourceTypeBundle - } - resourceVersions, err := payload.DecodeSpecResyncRequest(*evt) if err != nil { return err @@ -379,7 +364,7 @@ func (bkr *GRPCBroker) respondResyncSpecRequest(ctx context.Context, eventDataTy } clusterName := fmt.Sprintf("%s", clusterNameValue) - objs, err := bkr.resourceService.List(types.ListOptions{ClusterName: clusterName, CloudEventsDataType: eventDataType}) + objs, err := bkr.resourceService.List(types.ListOptions{ClusterName: clusterName}) if err != nil { return err } @@ -390,10 +375,6 @@ func (bkr *GRPCBroker) respondResyncSpecRequest(ctx context.Context, eventDataTy } for _, obj := range objs { - // only respond with the resource of the resync type - if obj.Type != resyncType { - continue - } // respond with the deleting resource regardless of the resource version if !obj.GetDeletionTimestamp().IsZero() { bkr.handleRes(obj) @@ -427,7 +408,6 @@ func (bkr *GRPCBroker) respondResyncSpecRequest(ctx context.Context, eventDataTy }, Version: int32(rv.ResourceVersion), ConsumerName: clusterName, - Type: resyncType, } // mark the resource as deleting obj.Meta.DeletedAt.Time = time.Now() diff --git a/cmd/maestro/server/grpc_server.go b/cmd/maestro/server/grpc_server.go index 801aa6e3..32e9fc40 100644 --- a/cmd/maestro/server/grpc_server.go +++ b/cmd/maestro/server/grpc_server.go @@ -7,12 +7,10 @@ import ( "fmt" "net" "os" - "time" ce "github.com/cloudevents/sdk-go/v2" "github.com/cloudevents/sdk-go/v2/binding" cetypes "github.com/cloudevents/sdk-go/v2/types" - "github.com/google/uuid" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/keepalive" @@ -24,7 +22,6 @@ import ( "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" "open-cluster-management.io/sdk-go/pkg/cloudevents/work/common" workpayload "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" - "open-cluster-management.io/sdk-go/pkg/cloudevents/work/source/codec" "github.com/openshift-online/maestro/pkg/api" "github.com/openshift-online/maestro/pkg/client/cloudevents" @@ -184,14 +181,14 @@ func (svr *GRPCServer) Publish(ctx context.Context, pubReq *pbv1.PublishRequest) // handler resync request if eventType.Action == types.ResyncRequestAction { - err := svr.respondResyncStatusRequest(ctx, eventType.CloudEventsDataType, evt) + err := svr.respondResyncStatusRequest(ctx, evt) if err != nil { return nil, fmt.Errorf("failed to respond resync status request: %v", err) } return &emptypb.Empty{}, nil } - res, err := decodeResourceSpec(eventType.CloudEventsDataType, evt) + res, err := decodeResourceSpec(evt) if err != nil { return nil, fmt.Errorf("failed to decode cloudevent: %v", err) } @@ -203,20 +200,17 @@ func (svr *GRPCServer) Publish(ctx context.Context, pubReq *pbv1.PublishRequest) return nil, fmt.Errorf("failed to create resource: %v", err) } case common.UpdateRequestAction: - if res.Type == api.ResourceTypeBundle { - found, err := svr.resourceService.Get(ctx, res.ID) - if err != nil { - return nil, fmt.Errorf("failed to get resource: %v", err) - } + found, err := svr.resourceService.Get(ctx, res.ID) + if err != nil { + return nil, fmt.Errorf("failed to get resource: %v", err) + } - if res.Version == 0 { - // the resource version is not guaranteed to be increased by source client, - // using the latest resource version. - res.Version = found.Version - } + if res.Version == 0 { + // the resource version is not guaranteed to be increased by source client, + // using the latest resource version. + res.Version = found.Version } - _, err := svr.resourceService.Update(ctx, res) - if err != nil { + if _, err = svr.resourceService.Update(ctx, res); err != nil { return nil, fmt.Errorf("failed to update resource: %v", err) } case common.DeleteRequestAction: @@ -282,7 +276,7 @@ func (svr *GRPCServer) Subscribe(subReq *pbv1.SubscriptionRequest, subServer pbv } // decodeResourceSpec translates a CloudEvent into a resource containing the spec JSON map. -func decodeResourceSpec(eventDataType types.CloudEventsDataType, evt *ce.Event) (*api.Resource, error) { +func decodeResourceSpec(evt *ce.Event) (*api.Resource, error) { evtExtensions := evt.Context.GetExtensions() clusterName, err := cetypes.ToString(evtExtensions[types.ExtensionClusterName]) @@ -322,46 +316,19 @@ func decodeResourceSpec(eventDataType types.CloudEventsDataType, evt *ce.Event) return nil, fmt.Errorf("failed to convert cloudevent to resource payload: %v", err) } resource.Payload = payload - - switch eventDataType { - case workpayload.ManifestEventDataType: - resource.Type = api.ResourceTypeSingle - case workpayload.ManifestBundleEventDataType: - resource.Type = api.ResourceTypeBundle - default: - return nil, fmt.Errorf("unsupported cloudevents data type %s", eventDataType) - } + // set the resource type to bundle from grpc source + resource.Type = api.ResourceTypeBundle return resource, nil } // encodeResourceStatus translates a resource status JSON map into a CloudEvent. func encodeResourceStatus(resource *api.Resource) (*ce.Event, error) { - if resource.Type == api.ResourceTypeSingle { - // single resource, return the status directly - return api.JSONMAPToCloudEvent(resource.Status) - } - statusEvt, err := api.JSONMAPToCloudEvent(resource.Status) if err != nil { return nil, err } - // set basic fields - evt := ce.NewEvent() - evt.SetID(uuid.New().String()) - evt.SetTime(time.Now()) - evt.SetType(statusEvt.Type()) - evt.SetSource(statusEvt.Source()) - for key, val := range statusEvt.Extensions() { - evt.SetExtension(key, val) - } - - // set work meta back from status event - if workMeta, ok := statusEvt.Extensions()[codec.ExtensionWorkMeta]; ok { - evt.SetExtension(codec.ExtensionWorkMeta, workMeta) - } - // manifest bundle status from the resource status manifestBundleStatus := &workpayload.ManifestBundleStatus{} if err := statusEvt.DataAs(manifestBundleStatus); err != nil { @@ -382,16 +349,16 @@ func encodeResourceStatus(resource *api.Resource) (*ce.Event, error) { manifestBundleStatus.ManifestBundle = manifestBundle } - if err := evt.SetData(ce.ApplicationJSON, manifestBundleStatus); err != nil { + if err := statusEvt.SetData(ce.ApplicationJSON, manifestBundleStatus); err != nil { return nil, err } - return &evt, nil + return statusEvt, nil } // respondResyncStatusRequest responds to the status resync request by comparing the status hash of the resources // from the database and the status hash in the request, and then respond the resources whose status is changed. -func (svr *GRPCServer) respondResyncStatusRequest(ctx context.Context, eventDataType types.CloudEventsDataType, evt *ce.Event) error { +func (svr *GRPCServer) respondResyncStatusRequest(ctx context.Context, evt *ce.Event) error { objs, serviceErr := svr.resourceService.FindBySource(ctx, evt.Source()) if serviceErr != nil { return fmt.Errorf("failed to list resources: %s", serviceErr) @@ -411,16 +378,7 @@ func (svr *GRPCServer) respondResyncStatusRequest(ctx context.Context, eventData return nil } - resyncType := api.ResourceTypeSingle - if eventDataType == workpayload.ManifestBundleEventDataType { - resyncType = api.ResourceTypeBundle - } - for _, obj := range objs { - if obj.Type != resyncType { - continue - } - lastHash, ok := findStatusHash(string(obj.GetUID()), statusHashes.Hashes) if !ok { // ignore the resource that is not on the source, but exists on the maestro, wait for the source deleting it diff --git a/pkg/api/presenters/resource.go b/pkg/api/presenters/resource.go index c582bc09..915992c4 100755 --- a/pkg/api/presenters/resource.go +++ b/pkg/api/presenters/resource.go @@ -1,9 +1,6 @@ package presenters import ( - "encoding/json" - "fmt" - "gorm.io/datatypes" "github.com/openshift-online/maestro/pkg/api" @@ -70,79 +67,3 @@ func PresentResource(resource *api.Resource) (*openapi.Resource, error) { return res, nil } - -// PresentResourceBundle converts a resource from the API to the openapi representation. -func PresentResourceBundle(resource *api.Resource) (*openapi.ResourceBundle, error) { - metadata, manifestBundle, err := api.DecodeManifestBundle(resource.Payload) - if err != nil { - return nil, err - } - status, err := api.DecodeBundleStatus(resource.Status) - if err != nil { - return nil, err - } - - reference := openapi.ObjectReference{ - Id: openapi.PtrString(resource.ID), - Kind: openapi.PtrString("ResourceBundle"), - Href: openapi.PtrString(fmt.Sprintf("%s/%s/%s", BasePath, "resource-bundles", resource.ID)), - } - - manifests := make([]map[string]interface{}, 0, len(manifestBundle.Manifests)) - for _, manifest := range manifestBundle.Manifests { - mbytes, err := json.Marshal(manifest) - if err != nil { - return nil, err - } - m := map[string]interface{}{} - if err := json.Unmarshal(mbytes, &m); err != nil { - return nil, err - } - manifests = append(manifests, m) - } - deleteOption := map[string]interface{}{} - if manifestBundle.DeleteOption != nil { - dbytes, err := json.Marshal(manifestBundle.DeleteOption) - if err != nil { - return nil, err - } - if err := json.Unmarshal(dbytes, &deleteOption); err != nil { - return nil, err - } - } - manifestConfigs := make([]map[string]interface{}, 0, len(manifestBundle.ManifestConfigs)) - for _, manifestConfig := range manifestBundle.ManifestConfigs { - mbytes, err := json.Marshal(manifestConfig) - if err != nil { - return nil, err - } - m := map[string]interface{}{} - if err := json.Unmarshal(mbytes, &m); err != nil { - return nil, err - } - manifestConfigs = append(manifestConfigs, m) - } - - res := &openapi.ResourceBundle{ - Id: reference.Id, - Kind: reference.Kind, - Href: reference.Href, - Name: openapi.PtrString(resource.Name), - ConsumerName: openapi.PtrString(resource.ConsumerName), - Version: openapi.PtrInt32(resource.Version), - CreatedAt: openapi.PtrTime(resource.CreatedAt), - UpdatedAt: openapi.PtrTime(resource.UpdatedAt), - Metadata: metadata, - Manifests: manifests, - DeleteOption: deleteOption, - ManifestConfigs: manifestConfigs, - Status: status, - } - - // set the deletedAt field if the resource has been marked as deleted - if !resource.DeletedAt.Time.IsZero() { - res.DeletedAt = openapi.PtrTime(resource.DeletedAt.Time) - } - - return res, nil -} diff --git a/pkg/api/presenters/resource_bundle.go b/pkg/api/presenters/resource_bundle.go new file mode 100644 index 00000000..2e13b402 --- /dev/null +++ b/pkg/api/presenters/resource_bundle.go @@ -0,0 +1,43 @@ +package presenters + +import ( + "fmt" + + "github.com/openshift-online/maestro/pkg/api" + "github.com/openshift-online/maestro/pkg/api/openapi" +) + +// PresentResourceBundle converts a resource from the API to the openapi representation. +func PresentResourceBundle(resource *api.Resource) (*openapi.ResourceBundle, error) { + metadata, manifests, manifestConfigs, deleteOption, err := api.DecodeManifestBundle(resource.Payload) + if err != nil { + return nil, err + } + status, err := api.DecodeBundleStatus(resource.Status) + if err != nil { + return nil, err + } + + res := &openapi.ResourceBundle{ + Id: openapi.PtrString(resource.ID), + Kind: openapi.PtrString("ResourceBundle"), + Href: openapi.PtrString(fmt.Sprintf("%s/%s/%s", BasePath, "resource-bundles", resource.ID)), + Name: openapi.PtrString(resource.Name), + ConsumerName: openapi.PtrString(resource.ConsumerName), + Version: openapi.PtrInt32(resource.Version), + CreatedAt: openapi.PtrTime(resource.CreatedAt), + UpdatedAt: openapi.PtrTime(resource.UpdatedAt), + Metadata: metadata, + Manifests: manifests, + DeleteOption: deleteOption, + ManifestConfigs: manifestConfigs, + Status: status, + } + + // set the deletedAt field if the resource has been marked as deleted + if !resource.DeletedAt.Time.IsZero() { + res.DeletedAt = openapi.PtrTime(resource.DeletedAt.Time) + } + + return res, nil +} diff --git a/pkg/api/resource_bundle_types.go b/pkg/api/resource_bundle_types.go index 84cc8c5a..6e1aa61b 100755 --- a/pkg/api/resource_bundle_types.go +++ b/pkg/api/resource_bundle_types.go @@ -19,15 +19,15 @@ type ResourceBundleStatus struct { } // DecodeManifestBundle converts a CloudEvent JSONMap representation of a list of resource manifest -// into manifest bundle payload. -func DecodeManifestBundle(manifest datatypes.JSONMap) (map[string]any, *workpayload.ManifestBundle, error) { +// into metadata, a list of manifests, a list of manifest configs, and a delete option for openapi output. +func DecodeManifestBundle(manifest datatypes.JSONMap) (map[string]any, []map[string]any, []map[string]any, map[string]any, error) { if len(manifest) == 0 { - return nil, nil, nil + return nil, nil, nil, nil, nil } evt, err := JSONMAPToCloudEvent(manifest) if err != nil { - return nil, nil, fmt.Errorf("failed to convert resource manifest to cloudevent: %v", err) + return nil, nil, nil, nil, fmt.Errorf("failed to convert resource manifest bundle to cloudevent: %v", err) } metaData := map[string]any{} @@ -35,48 +35,51 @@ func DecodeManifestBundle(manifest datatypes.JSONMap) (map[string]any, *workpayl if meta, ok := extensions[codec.ExtensionWorkMeta]; ok { metaJson, err := cloudeventstypes.ToString(meta) if err != nil { - return nil, nil, err + return nil, nil, nil, nil, fmt.Errorf("failed to get work meta extension: %v", err) } if err := json.Unmarshal([]byte(metaJson), &metaData); err != nil { - return nil, nil, err + return nil, nil, nil, nil, fmt.Errorf("failed to unmarshal work meta extension: %v", err) } } eventPayload := &workpayload.ManifestBundle{} if err := evt.DataAs(eventPayload); err != nil { - return nil, nil, fmt.Errorf("failed to decode cloudevent payload as resource manifest bundle: %v", err) + return nil, nil, nil, nil, fmt.Errorf("failed to decode cloudevent payload as resource manifest bundle: %v", err) } - return metaData, eventPayload, nil -} - -// DecodeManifestBundleToObjects converts a CloudEvent JSONMap representation of a list of resource manifest -// into a list of resource object (map[string]interface{}). -func DecodeManifestBundleToObjects(manifest datatypes.JSONMap) ([]map[string]interface{}, error) { - if len(manifest) == 0 { - return nil, nil + manifests := make([]map[string]interface{}, 0, len(eventPayload.Manifests)) + for _, manifest := range eventPayload.Manifests { + m := map[string]interface{}{} + if err := json.Unmarshal(manifest.Raw, &m); err != nil { + return nil, nil, nil, nil, fmt.Errorf("failed to unmarshal manifest raw in bundle: %v", err) + } + manifests = append(manifests, m) } - - _, eventPayload, err := DecodeManifestBundle(manifest) - if err != nil { - return nil, err + manifestConfigs := make([]map[string]interface{}, 0, len(eventPayload.ManifestConfigs)) + for _, manifestConfig := range eventPayload.ManifestConfigs { + mbytes, err := json.Marshal(manifestConfig) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("failed to marshal manifest config in bundle: %v", err) + } + m := map[string]interface{}{} + if err := json.Unmarshal(mbytes, &m); err != nil { + return nil, nil, nil, nil, fmt.Errorf("failed to unmarshal manifest config in bundle: %v", err) + } + manifestConfigs = append(manifestConfigs, m) } - - objects := make([]map[string]interface{}, 0, len(eventPayload.Manifests)) - for _, m := range eventPayload.Manifests { - if len(m.Raw) == 0 { - return nil, fmt.Errorf("manifest in bundle is empty") + deleteOption := map[string]interface{}{} + if eventPayload.DeleteOption != nil { + dbytes, err := json.Marshal(eventPayload.DeleteOption) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("failed to marshal delete option in bundle: %v", err) } - // unmarshal the raw JSON into the object - obj := &map[string]interface{}{} - if err := json.Unmarshal(m.Raw, obj); err != nil { - return nil, fmt.Errorf("failed to unmarshal manifest in bundle: %v", err) + if err := json.Unmarshal(dbytes, &deleteOption); err != nil { + return nil, nil, nil, nil, fmt.Errorf("failed to unmarshal delete option in bundle: %v", err) } - objects = append(objects, *obj) } - return objects, nil + return metaData, manifests, manifestConfigs, deleteOption, nil } // DecodeBundleStatus converts a CloudEvent JSONMap representation of a resource bundle status diff --git a/pkg/api/resource_bundle_types_test.go b/pkg/api/resource_bundle_types_test.go index f2592b53..e7da2ecc 100644 --- a/pkg/api/resource_bundle_types_test.go +++ b/pkg/api/resource_bundle_types_test.go @@ -6,121 +6,64 @@ import ( "gorm.io/datatypes" "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/runtime" - workv1 "open-cluster-management.io/api/work/v1" - workpayload "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" ) func TestDecodeManifestBundle(t *testing.T) { cases := []struct { - name string - input datatypes.JSONMap - expected *workpayload.ManifestBundle - expectedErrorMsg string + name string + input datatypes.JSONMap + expectedMetaData map[string]any + expectedManifests []map[string]any + expectedManifestConfigs []map[string]any + expectedDeleteOption map[string]any + expectedErrorMsg string }{ { - name: "empty", - input: datatypes.JSONMap{}, - expected: nil, - expectedErrorMsg: "", + name: "empty", + input: datatypes.JSONMap{}, + expectedMetaData: map[string]any{}, + expectedManifests: []map[string]any{}, + expectedManifestConfigs: []map[string]any{}, + expectedDeleteOption: map[string]any{}, + expectedErrorMsg: "", }, { - name: "valid", - input: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), - expected: &workpayload.ManifestBundle{ - Manifests: []workv1.Manifest{ - { - RawExtension: runtime.RawExtension{ - Raw: []byte("{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}}"), - }, - }, - { - RawExtension: runtime.RawExtension{ - Raw: []byte("{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"nginx\"}},\"spec\":{\"containers\":[{\"image\":\"nginxinc/nginx-unprivileged\",\"name\":\"nginx\"}]}}}}"), - }, - }, - }, - DeleteOption: &workv1.DeleteOption{ - PropagationPolicy: "Foreground", - }, - ManifestConfigs: []workv1.ManifestConfigOption{ - { - UpdateStrategy: &workv1.UpdateStrategy{ - Type: "ServerSideApply", - }, - ResourceIdentifier: workv1.ResourceIdentifier{ - Name: "nginx", - Group: "apps", - Resource: "deployments", - Namespace: "default", - }, - }, - }, + name: "valid", + input: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), + expectedMetaData: map[string]any{}, + expectedManifests: newJSONMAPList(t, []string{ + "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}}", + "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"nginx\"}},\"spec\":{\"containers\":[{\"image\":\"nginxinc/nginx-unprivileged\",\"name\":\"nginx\"}]}}}}", + }...), + expectedManifestConfigs: newJSONMAPList(t, []string{ + "{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}", + }...), + expectedDeleteOption: map[string]any{ + "propagationPolicy": "Foreground", }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - _, got, err := DecodeManifestBundle(c.input) + gotMetaData, gotManifests, gotManifestConfigs, gotDeleteOption, err := DecodeManifestBundle(c.input) if err != nil { if err.Error() != c.expectedErrorMsg { t.Errorf("expected %#v but got: %#v", c.expectedErrorMsg, err) } return } - if c.expected != nil && got != nil { - if !equality.Semantic.DeepDerivative(c.expected.Manifests[1], got.Manifests[1]) { - t.Errorf("expected Manifests %s but got: %s", c.expected.Manifests[1].RawExtension.Raw, got.Manifests[1].RawExtension.Raw) - } + if !equality.Semantic.DeepEqual(c.expectedMetaData, gotMetaData) { + t.Errorf("expected metaData %#v but got: %#v", c.expectedMetaData, gotMetaData) } - if !equality.Semantic.DeepDerivative(c.expected, got) { - t.Errorf("expected %#v but got: %#v", c.expected, got) + if !equality.Semantic.DeepEqual(c.expectedManifests, gotManifests) { + t.Errorf("expected manifests %#v but got: %#v", c.expectedManifests, gotManifests) } - }) - } - -} - -func TestDecodeManifestBundleToObjects(t *testing.T) { - cases := []struct { - name string - input datatypes.JSONMap - expected []map[string]interface{} - expectedErrorMsg string - }{ - { - name: "empty", - input: datatypes.JSONMap{}, - expected: nil, - expectedErrorMsg: "", - }, - { - name: "valid", - input: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), - expected: []map[string]interface{}{ - newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}}"), - newJSONMap(t, "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}"), - }, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - gotObjects, err := DecodeManifestBundleToObjects(c.input) - if err != nil { - if err.Error() != c.expectedErrorMsg { - t.Errorf("expected %#v but got: %#v", c.expectedErrorMsg, err) - } - return - } - if len(gotObjects) != len(c.expected) { - t.Errorf("expected %d resource in manifest bundle but got: %d", len(c.expected), len(gotObjects)) - return + if !equality.Semantic.DeepEqual(c.expectedManifestConfigs, gotManifestConfigs) { + t.Errorf("expected manifestConfigs %#v but got: %#v", c.expectedManifestConfigs, gotManifestConfigs) } - for i, expected := range c.expected { - if !equality.Semantic.DeepDerivative(expected, gotObjects[i]) { - t.Errorf("expected %#v but got: %#v", expected, gotObjects[i]) - } + if !equality.Semantic.DeepEqual(c.expectedDeleteOption, gotDeleteOption) { + t.Errorf("expected deleteOption %#v but got: %#v", c.expectedDeleteOption, gotDeleteOption) } }) } diff --git a/pkg/api/resource_types.go b/pkg/api/resource_types.go index e2c06387..dc1cb7cb 100755 --- a/pkg/api/resource_types.go +++ b/pkg/api/resource_types.go @@ -9,10 +9,13 @@ import ( cloudeventstypes "github.com/cloudevents/sdk-go/v2/types" "gorm.io/datatypes" "gorm.io/gorm" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" ktypes "k8s.io/apimachinery/pkg/types" + "open-cluster-management.io/api/utils/work/v1/utils" workv1 "open-cluster-management.io/api/work/v1" cetypes "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" workpayload "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" @@ -109,7 +112,7 @@ func JSONMAPToCloudEvent(res datatypes.JSONMap) (*cloudevents.Event, error) { metaJson, err := json.Marshal(metadata) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to marshal metadata to JSON: %v", err) } resCopy[codec.ExtensionWorkMeta] = string(metaJson) @@ -151,7 +154,7 @@ func CloudEventToJSONMap(evt *cloudevents.Event) (datatypes.JSONMap, error) { objectMeta := map[string]any{} if err := json.Unmarshal([]byte(fmt.Sprintf("%s", metadata)), &objectMeta); err != nil { - return nil, err + return nil, fmt.Errorf("failed to unmarshal metadata extension to object: %v", err) } res[codec.ExtensionWorkMeta] = objectMeta } @@ -205,22 +208,47 @@ func EncodeManifest(manifest, deleteOption, updateStrategy map[string]interface{ // create a cloud event with the manifest as the data evt := cetypes.NewEventBuilder("maestro", cetypes.CloudEventsType{}).NewEvent() - eventPayload := &workpayload.Manifest{ - Manifest: unstructured.Unstructured{Object: manifest}, + var restMapper meta.RESTMapper + manifestBytes, err := json.Marshal(manifest) + if err != nil { + return nil, fmt.Errorf("failed to marshal manifest: %v", err) + } + unstructuredObj := &unstructured.Unstructured{ + Object: manifest, + } + _, gvr, err := utils.BuildResourceMeta(0, unstructuredObj, restMapper) + if err != nil { + return nil, fmt.Errorf("failed to get manifest GVR from manifest: %v", err) + } + + eventPayload := &workpayload.ManifestBundle{ + Manifests: []workv1.Manifest{ + { + RawExtension: runtime.RawExtension{Raw: manifestBytes}, + }, + }, DeleteOption: delOption, - ConfigOption: &workpayload.ManifestConfigOption{ - FeedbackRules: []workv1.FeedbackRule{ - { - Type: workv1.JSONPathsType, - JsonPaths: []workv1.JsonPath{ - { - Name: "status", - Path: ".status", + ManifestConfigs: []workv1.ManifestConfigOption{ + { + ResourceIdentifier: workv1.ResourceIdentifier{ + Group: gvr.Group, + Resource: gvr.Resource, + Name: unstructuredObj.GetName(), + Namespace: unstructuredObj.GetNamespace(), + }, + FeedbackRules: []workv1.FeedbackRule{ + { + Type: workv1.JSONPathsType, + JsonPaths: []workv1.JsonPath{ + { + Name: "status", + Path: ".status", + }, }, }, }, + UpdateStrategy: upStrategy, }, - UpdateStrategy: upStrategy, }, } @@ -229,7 +257,7 @@ func EncodeManifest(manifest, deleteOption, updateStrategy map[string]interface{ } // convert cloudevent to JSONMap - manifest, err := CloudEventToJSONMap(&evt) + manifest, err = CloudEventToJSONMap(&evt) if err != nil { return nil, fmt.Errorf("failed to convert cloudevent to resource manifest: %v", err) } @@ -238,7 +266,7 @@ func EncodeManifest(manifest, deleteOption, updateStrategy map[string]interface{ } // DecodeManifest converts a CloudEvent JSONMap representation of a resource manifest -// into resource manifest, deleteOption and updateStrategy (map[string]interface{}). +// into resource manifest, deleteOption and updateStrategy (map[string]interface{}) for openapi output. func DecodeManifest(manifest datatypes.JSONMap) (map[string]interface{}, map[string]interface{}, map[string]interface{}, error) { if len(manifest) == 0 { return nil, nil, nil, nil @@ -249,34 +277,43 @@ func DecodeManifest(manifest datatypes.JSONMap) (map[string]interface{}, map[str return nil, nil, nil, fmt.Errorf("failed to convert resource manifest to cloudevent: %v", err) } - eventPayload := &workpayload.Manifest{} + eventPayload := &workpayload.ManifestBundle{} if err := evt.DataAs(eventPayload); err != nil { return nil, nil, nil, fmt.Errorf("failed to decode cloudevent payload as resource manifest: %v", err) } - deleteOptionObj := &map[string]interface{}{} + deleteOptionObj := map[string]interface{}{} if eventPayload.DeleteOption != nil { deleteOptionJsonData, err := json.Marshal(eventPayload.DeleteOption) if err != nil { return nil, nil, nil, fmt.Errorf("failed to marshal deleteOption to json: %v", err) } - if err := json.Unmarshal(deleteOptionJsonData, deleteOptionObj); err != nil { + if err := json.Unmarshal(deleteOptionJsonData, &deleteOptionObj); err != nil { return nil, nil, nil, fmt.Errorf("failed to unmarshal deleteOption to cloudevent: %v", err) } } - - updateStrategyObj := &map[string]interface{}{} - if eventPayload.ConfigOption != nil && eventPayload.ConfigOption.UpdateStrategy != nil { - updateStrategyJsonData, err := json.Marshal(eventPayload.ConfigOption.UpdateStrategy) + manifestObj := map[string]interface{}{} + if len(eventPayload.Manifests) != 1 { + return nil, nil, nil, fmt.Errorf("invalid number of manifests in the event payload: %d", len(eventPayload.Manifests)) + } + if err := json.Unmarshal(eventPayload.Manifests[0].Raw, &manifestObj); err != nil { + return nil, nil, nil, fmt.Errorf("failed to unmarshal manifest raw to manifest: %v", err) + } + if len(eventPayload.ManifestConfigs) != 1 { + return nil, nil, nil, fmt.Errorf("invalid number of manifestConfigs in the event payload: %d", len(eventPayload.ManifestConfigs)) + } + updateStrategyObj := map[string]interface{}{} + if eventPayload.ManifestConfigs[0].UpdateStrategy != nil { + updateStrategyJsonData, err := json.Marshal(eventPayload.ManifestConfigs[0].UpdateStrategy) if err != nil { return nil, nil, nil, fmt.Errorf("failed to marshal updateStrategy to json: %v", err) } - if err := json.Unmarshal(updateStrategyJsonData, updateStrategyObj); err != nil { - return nil, nil, nil, fmt.Errorf("failed to unmarshal updateStrategy to cloudevent: %v", err) + if err := json.Unmarshal(updateStrategyJsonData, &updateStrategyObj); err != nil { + return nil, nil, nil, fmt.Errorf("failed to unmarshal updateStrategy json to object: %v", err) } } - return eventPayload.Manifest.Object, *deleteOptionObj, *updateStrategyObj, nil + return manifestObj, deleteOptionObj, updateStrategyObj, nil } // DecodeStatus converts a CloudEvent JSONMap representation of a resource status @@ -309,21 +346,22 @@ func DecodeStatus(status datatypes.JSONMap) (map[string]interface{}, error) { }, } - eventPayload := &workpayload.ManifestStatus{} + eventPayload := &workpayload.ManifestBundleStatus{} if err := evt.DataAs(eventPayload); err != nil { return nil, fmt.Errorf("failed to decode cloudevent data as resource status: %v", err) } - if eventPayload.Status != nil { - resourceStatus.ReconcileStatus.Conditions = eventPayload.Status.Conditions - for _, value := range eventPayload.Status.StatusFeedbacks.Values { - if value.Name == "status" { - contentStatus := make(map[string]interface{}) - if err := json.Unmarshal([]byte(*value.Value.JsonRaw), &contentStatus); err != nil { - return nil, fmt.Errorf("failed to convert status feedback value to content status: %v", err) - } - resourceStatus.ContentStatus = contentStatus + if len(eventPayload.ResourceStatus) != 1 { + return nil, fmt.Errorf("invalid number of resource status in the event payload: %d", len(eventPayload.ResourceStatus)) + } + resourceStatus.ReconcileStatus.Conditions = eventPayload.ResourceStatus[0].Conditions + for _, value := range eventPayload.ResourceStatus[0].StatusFeedbacks.Values { + if value.Name == "status" { + contentStatus := make(map[string]interface{}) + if err := json.Unmarshal([]byte(*value.Value.JsonRaw), &contentStatus); err != nil { + return nil, fmt.Errorf("failed to convert status feedback value to content status: %v", err) } + resourceStatus.ContentStatus = contentStatus } } @@ -333,7 +371,7 @@ func DecodeStatus(status datatypes.JSONMap) (map[string]interface{}, error) { } statusMap := make(map[string]interface{}) if err := json.Unmarshal(resourceStatusJSON, &statusMap); err != nil { - return nil, fmt.Errorf("failed to unmarshal resource status JSON to map: %v", err) + return nil, fmt.Errorf("failed to unmarshal resource status JSON to object: %v", err) } return statusMap, nil diff --git a/pkg/api/resource_types_test.go b/pkg/api/resource_types_test.go index 26b867fa..fe361ce3 100644 --- a/pkg/api/resource_types_test.go +++ b/pkg/api/resource_types_test.go @@ -11,7 +11,7 @@ import ( func TestEncodeManifest(t *testing.T) { cases := []struct { name string - input map[string]interface{} + manifest map[string]interface{} deleteOption map[string]interface{} updateStrategy map[string]interface{} expected datatypes.JSONMap @@ -19,25 +19,25 @@ func TestEncodeManifest(t *testing.T) { }{ { name: "empty", - input: map[string]interface{}{}, + manifest: map[string]interface{}{}, expected: datatypes.JSONMap{}, }, { name: "valid", - input: newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}"), - expected: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + manifest: newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}"), + expected: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"feedbackRules\":[{\"jsonPaths\":[{\"name\":\"status\",\"path\":\".status\"}],\"type\":\"JSONPaths\"}],\"resourceIdentifier\":{\"group\":\"\",\"name\":\"test\",\"namespace\":\"test\",\"resource\":\"\"},\"updateStrategy\":{\"type\":\"ServerSideApply\"}}],\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}]}}"), }, { name: "valid", deleteOption: newJSONMap(t, "{\"propagationPolicy\": \"Orphan\"}"), updateStrategy: newJSONMap(t, "{\"type\": \"CreateOnly\"}"), - input: newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}"), - expected: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"configOption\":{\"updateStrategy\": {\"type\": \"CreateOnly\"}},\"deleteOption\": {\"propagationPolicy\": \"Orphan\"},\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + manifest: newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}"), + expected: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"deleteOption\":{\"propagationPolicy\":\"Orphan\"},\"manifestConfigs\":[{\"feedbackRules\":[{\"jsonPaths\":[{\"name\":\"status\",\"path\":\".status\"}],\"type\":\"JSONPaths\"}],\"resourceIdentifier\":{\"group\":\"\",\"name\":\"test\",\"namespace\":\"test\",\"resource\":\"\"},\"updateStrategy\":{\"type\":\"CreateOnly\"}}],\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}]}}"), }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - gotManifest, err := EncodeManifest(c.input, c.deleteOption, c.updateStrategy) + gotManifest, err := EncodeManifest(c.manifest, c.deleteOption, c.updateStrategy) if err != nil { if err.Error() != c.expectedErrorMsg { t.Errorf("expected %#v but got: %#v", c.expectedErrorMsg, err) @@ -70,7 +70,7 @@ func TestDecodeManifest(t *testing.T) { }, { name: "valid", - input: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"configOption\":{\"updateStrategy\": {\"type\": \"CreateOnly\"}},\"deleteOption\": {\"propagationPolicy\": \"Orphan\"},\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + input: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"deleteOption\":{\"propagationPolicy\":\"Orphan\"},\"manifestConfigs\":[{\"feedbackRules\":[{\"jsonPaths\":[{\"name\":\"status\",\"path\":\".status\"}],\"type\":\"JSONPaths\"}],\"resourceIdentifier\":{\"group\":\"\",\"name\":\"test\",\"namespace\":\"test\",\"resource\":\"\"},\"updateStrategy\":{\"type\":\"CreateOnly\"}}],\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}]}}"), expectedManifest: newJSONMap(t, "{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}"), expectedDeleteOption: newJSONMap(t, "{\"propagationPolicy\": \"Orphan\"}"), expectedUpdateStrategy: newJSONMap(t, "{\"type\": \"CreateOnly\"}"), @@ -113,8 +113,8 @@ func TestDecodeStatus(t *testing.T) { }, { name: "valid", - input: newJSONMap(t, "{\"id\":\"1f21fcbe-3e41-4639-ab8d-1713c578e4cd\",\"time\":\"2024-03-07T03:29:12.094854533Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.status.update_request\",\"source\":\"maestro-agent-59d9c485d9-7bvwb\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"b9368296-3200-42ec-bfbb-f7d44a06c4e0\",\"sequenceid\":\"1765580430112722944\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"originalsource\":\"maestro\",\"resourceversion\":\"1\",\"data\":{\"status\":{\"conditions\":[{\"type\":\"Applied\",\"reason\":\"AppliedManifestComplete\",\"status\":\"True\",\"message\":\"Apply manifest complete\",\"lastTransitionTime\":\"2024-03-07T03:29:03Z\"},{\"type\":\"Available\",\"reason\":\"ResourceAvailable\",\"status\":\"True\",\"message\":\"Resource is available\",\"lastTransitionTime\":\"2024-03-07T03:29:03Z\"},{\"type\":\"StatusFeedbackSynced\",\"reason\":\"StatusFeedbackSynced\",\"status\":\"True\",\"message\":\"\",\"lastTransitionTime\":\"2024-03-07T03:29:03Z\"}],\"resourceMeta\":{\"kind\":\"Deployment\",\"name\":\"nginx1\",\"group\":\"apps\",\"ordinal\":0,\"version\":\"v1\",\"resource\":\"deployments\",\"namespace\":\"default\"},\"statusFeedback\":{\"values\":[{\"name\":\"status\",\"fieldValue\":{\"type\":\"JsonRaw\",\"jsonRaw\":\"{\\\"availableReplicas\\\":1,\\\"conditions\\\":[{\\\"lastTransitionTime\\\":\\\"2024-03-07T03:29:06Z\\\",\\\"lastUpdateTime\\\":\\\"2024-03-07T03:29:06Z\\\",\\\"message\\\":\\\"Deployment has minimum availability.\\\",\\\"reason\\\":\\\"MinimumReplicasAvailable\\\",\\\"status\\\":\\\"True\\\",\\\"type\\\":\\\"Available\\\"},{\\\"lastTransitionTime\\\":\\\"2024-03-07T03:29:03Z\\\",\\\"lastUpdateTime\\\":\\\"2024-03-07T03:29:06Z\\\",\\\"message\\\":\\\"ReplicaSet \\\\\\\"nginx1-5d6b548959\\\\\\\" has successfully progressed.\\\",\\\"reason\\\":\\\"NewReplicaSetAvailable\\\",\\\"status\\\":\\\"True\\\",\\\"type\\\":\\\"Progressing\\\"}],\\\"observedGeneration\\\":1,\\\"readyReplicas\\\":1,\\\"replicas\\\":1,\\\"updatedReplicas\\\":1}\"}}]}},\"conditions\":[{\"type\":\"Applied\",\"reason\":\"AppliedManifestWorkComplete\",\"status\":\"True\",\"message\":\"Apply manifest work complete\",\"lastTransitionTime\":\"2024-03-07T03:29:03Z\"},{\"type\":\"Available\",\"reason\":\"ResourcesAvailable\",\"status\":\"True\",\"message\":\"All resources are available\",\"lastTransitionTime\":\"2024-03-07T03:29:03Z\"}]}}"), - expected: newJSONMap(t, "{\"ContentStatus\":{\"availableReplicas\":1,\"observedGeneration\":1,\"readyReplicas\":1,\"replicas\":1,\"updatedReplicas\":1,\"conditions\":[{\"lastTransitionTime\":\"2024-03-07T03:29:06Z\",\"lastUpdateTime\":\"2024-03-07T03:29:06Z\",\"message\":\"Deployment has minimum availability.\",\"reason\":\"MinimumReplicasAvailable\",\"status\":\"True\",\"type\":\"Available\"},{\"lastTransitionTime\":\"2024-03-07T03:29:03Z\",\"lastUpdateTime\":\"2024-03-07T03:29:06Z\",\"message\":\"ReplicaSet \\\"nginx1-5d6b548959\\\" has successfully progressed.\",\"reason\":\"NewReplicaSetAvailable\",\"status\":\"True\",\"type\":\"Progressing\"}]},\"ReconcileStatus\":{\"Conditions\":[{\"lastTransitionTime\":\"2024-03-07T03:29:03Z\",\"message\":\"Apply manifest complete\",\"reason\":\"AppliedManifestComplete\",\"status\":\"True\",\"type\":\"Applied\"},{\"lastTransitionTime\":\"2024-03-07T03:29:03Z\",\"message\":\"Resource is available\",\"reason\":\"ResourceAvailable\",\"status\":\"True\",\"type\":\"Available\"},{\"lastTransitionTime\":\"2024-03-07T03:29:03Z\",\"message\":\"\",\"reason\":\"StatusFeedbackSynced\",\"status\":\"True\",\"type\":\"StatusFeedbackSynced\"}],\"ObservedVersion\":1,\"SequenceID\":\"1765580430112722944\"}}"), + input: newJSONMap(t, "{\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"id\":\"1f21fcbe-3e41-4639-ab8d-1713c578e4cd\",\"time\":\"2024-03-07T03:29:12.094854533Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.status.update_request\",\"source\":\"maestro-agent-59d9c485d9-7bvwb\",\"resourceid\":\"b9368296-3200-42ec-bfbb-f7d44a06c4e0\",\"sequenceid\":\"1765580430112722944\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"originalsource\":\"maestro\",\"resourceversion\":\"1\",\"data\":{\"conditions\":[{\"type\":\"Applied\",\"reason\":\"AppliedManifestWorkComplete\",\"status\":\"True\",\"message\":\"Apply manifest work complete\",\"lastTransitionTime\":\"2024-03-07T03:56:35Z\"},{\"type\":\"Available\",\"reason\":\"ResourcesAvailable\",\"status\":\"True\",\"message\":\"All resources are available\",\"lastTransitionTime\":\"2024-03-07T03:56:35Z\"}],\"resourceStatus\":[{\"conditions\":[{\"type\":\"Applied\",\"reason\":\"AppliedManifestComplete\",\"status\":\"True\",\"message\":\"Apply manifest complete\",\"lastTransitionTime\":\"2024-03-07T03:56:35Z\"},{\"type\":\"Available\",\"reason\":\"ResourceAvailable\",\"status\":\"True\",\"message\":\"Resource is available\",\"lastTransitionTime\":\"2024-03-07T03:56:35Z\"},{\"type\":\"StatusFeedbackSynced\",\"reason\":\"StatusFeedbackSynced\",\"status\":\"True\",\"message\":\"\",\"lastTransitionTime\":\"2024-03-07T03:56:35Z\"}],\"resourceMeta\":{\"kind\":\"Deployment\",\"name\":\"nginx\",\"group\":\"apps\",\"ordinal\":1,\"version\":\"v1\",\"resource\":\"deployments\",\"namespace\":\"default\"},\"statusFeedback\":{\"values\":[{\"name\":\"status\",\"fieldValue\":{\"type\":\"JsonRaw\",\"jsonRaw\":\"{\\\"availableReplicas\\\":2,\\\"conditions\\\":[{\\\"lastTransitionTime\\\":\\\"2024-03-07T03:56:35Z\\\",\\\"lastUpdateTime\\\":\\\"2024-03-07T03:56:38Z\\\",\\\"message\\\":\\\"ReplicaSet \\\\\\\"nginx-5d6b548959\\\\\\\" has successfully progressed.\\\",\\\"reason\\\":\\\"NewReplicaSetAvailable\\\",\\\"status\\\":\\\"True\\\",\\\"type\\\":\\\"Progressing\\\"},{\\\"lastTransitionTime\\\":\\\"2024-03-07T03:58: 26Z\\\",\\\"lastUpdateTime\\\":\\\"2024-03-07T03:58:26Z\\\",\\\"message\\\":\\\"Deployment has minimum availability.\\\",\\\"reason\\\":\\\"MinimumReplicasAvailable\\\",\\\"status\\\":\\\"True\\\",\\\"type\\\":\\\"Available\\\"}],\\\"observedGeneration\\\":2,\\\"readyReplicas\\\":2,\\\"replicas\\\":2,\\\"updatedReplicas\\\":2}\"}}]}}]}}"), + expected: newJSONMap(t, "{\"ContentStatus\":{\"availableReplicas\":2,\"conditions\":[{\"lastTransitionTime\":\"2024-03-07T03:56:35Z\",\"lastUpdateTime\":\"2024-03-07T03:56:38Z\",\"message\":\"ReplicaSet \\\"nginx-5d6b548959\\\" has successfully progressed.\",\"reason\":\"NewReplicaSetAvailable\",\"status\":\"True\",\"type\":\"Progressing\"},{\"lastTransitionTime\":\"2024-03-07T03:58: 26Z\",\"lastUpdateTime\":\"2024-03-07T03:58:26Z\",\"message\":\"Deployment has minimum availability.\",\"reason\":\"MinimumReplicasAvailable\",\"status\":\"True\",\"type\":\"Available\"}],\"observedGeneration\":2,\"readyReplicas\":2,\"replicas\":2,\"updatedReplicas\":2},\"ReconcileStatus\":{\"Conditions\":[{\"lastTransitionTime\":\"2024-03-07T03:56:35Z\",\"message\":\"Apply manifest complete\",\"reason\":\"AppliedManifestComplete\",\"status\":\"True\",\"type\":\"Applied\"},{\"lastTransitionTime\":\"2024-03-07T03:56:35Z\",\"message\":\"Resource is available\",\"reason\":\"ResourceAvailable\",\"status\":\"True\",\"type\":\"Available\"},{\"lastTransitionTime\":\"2024-03-07T03:56:35Z\",\"message\":\"\",\"reason\":\"StatusFeedbackSynced\",\"status\":\"True\",\"type\":\"StatusFeedbackSynced\"}],\"ObservedVersion\":1,\"SequenceID\":\"1765580430112722944\"}}"), }, } for _, c := range cases { @@ -141,3 +141,12 @@ func newJSONMap(t *testing.T, data string) datatypes.JSONMap { return jsonmap } + +func newJSONMAPList(t *testing.T, data ...string) []map[string]any { + jsonmapList := []map[string]any{} + for _, d := range data { + jsonmapList = append(jsonmapList, newJSONMap(t, d)) + } + + return jsonmapList +} diff --git a/pkg/client/cloudevents/bundle_codec.go b/pkg/client/cloudevents/bundle_codec.go deleted file mode 100644 index 6450d3b8..00000000 --- a/pkg/client/cloudevents/bundle_codec.go +++ /dev/null @@ -1,104 +0,0 @@ -package cloudevents - -import ( - "fmt" - "time" - - cloudevents "github.com/cloudevents/sdk-go/v2" - cloudeventstypes "github.com/cloudevents/sdk-go/v2/types" - "github.com/google/uuid" - cegeneric "open-cluster-management.io/sdk-go/pkg/cloudevents/generic" - cetypes "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" - workpayload "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" - - "github.com/openshift-online/maestro/pkg/api" -) - -type BundleCodec struct { - sourceID string -} - -var _ cegeneric.Codec[*api.Resource] = &BundleCodec{} - -func (codec *BundleCodec) EventDataType() cetypes.CloudEventsDataType { - return workpayload.ManifestBundleEventDataType -} - -func (codec *BundleCodec) Encode(source string, eventType cetypes.CloudEventsType, res *api.Resource) (*cloudevents.Event, error) { - evt, err := api.JSONMAPToCloudEvent(res.Payload) - if err != nil { - return nil, fmt.Errorf("failed to convert resource payload to cloudevent: %v", err) - } - - evt.SetSource(source) - evt.SetType(eventType.String()) - // TODO set resource.Source with a new extension attribute if the agent needs - evt.SetExtension(cetypes.ExtensionResourceID, res.ID) - evt.SetExtension(cetypes.ExtensionResourceVersion, int64(res.Version)) - evt.SetExtension(cetypes.ExtensionClusterName, res.ConsumerName) - - if !res.GetDeletionTimestamp().IsZero() { - // in the deletion case, the event ID and time remain unchanged in storage. - // set the event ID and time before publishing, so the agent can identify the deletion event. - evt.SetID(uuid.New().String()) - evt.SetTime(time.Now()) - // set deletion timestamp extension - evt.SetExtension(cetypes.ExtensionDeletionTimestamp, res.GetDeletionTimestamp().Time) - } - - return evt, nil -} - -func (codec *BundleCodec) Decode(evt *cloudevents.Event) (*api.Resource, error) { - eventType, err := cetypes.ParseCloudEventsType(evt.Type()) - if err != nil { - return nil, fmt.Errorf("failed to parse cloud event type %s, %v", evt.Type(), err) - } - - if eventType.CloudEventsDataType != workpayload.ManifestBundleEventDataType { - return nil, fmt.Errorf("unsupported cloudevents data type %s", eventType.CloudEventsDataType) - } - - evtExtensions := evt.Context.GetExtensions() - - resourceID, err := cloudeventstypes.ToString(evtExtensions[cetypes.ExtensionResourceID]) - if err != nil { - return nil, fmt.Errorf("failed to get resourceid extension: %v", err) - } - - resourceVersion, err := cloudeventstypes.ToInteger(evtExtensions[cetypes.ExtensionResourceVersion]) - if err != nil { - return nil, fmt.Errorf("failed to get resourceversion extension: %v", err) - } - - clusterName, err := cloudeventstypes.ToString(evtExtensions[cetypes.ExtensionClusterName]) - if err != nil { - return nil, fmt.Errorf("failed to get clustername extension: %v", err) - } - - originalSource, err := cloudeventstypes.ToString(evtExtensions[cetypes.ExtensionOriginalSource]) - if err != nil { - return nil, fmt.Errorf("failed to get originalsource extension: %v", err) - } - - status, err := api.CloudEventToJSONMap(evt) - if err != nil { - return nil, fmt.Errorf("failed to convert cloudevent to resource status: %v", err) - } - - if originalSource != codec.sourceID { - return nil, fmt.Errorf("unmatched original source id %s for resource %s", originalSource, resourceID) - } - - resource := &api.Resource{ - Meta: api.Meta{ - ID: resourceID, - }, - Version: resourceVersion, - ConsumerName: clusterName, - Type: api.ResourceTypeBundle, - Status: status, - } - - return resource, nil -} diff --git a/pkg/client/cloudevents/codec.go b/pkg/client/cloudevents/codec.go index 23e787e1..51140704 100644 --- a/pkg/client/cloudevents/codec.go +++ b/pkg/client/cloudevents/codec.go @@ -21,7 +21,7 @@ type Codec struct { var _ cegeneric.Codec[*api.Resource] = &Codec{} func (codec *Codec) EventDataType() cetypes.CloudEventsDataType { - return workpayload.ManifestEventDataType + return workpayload.ManifestBundleEventDataType } func (codec *Codec) Encode(source string, eventType cetypes.CloudEventsType, res *api.Resource) (*cloudevents.Event, error) { @@ -55,7 +55,7 @@ func (codec *Codec) Decode(evt *cloudevents.Event) (*api.Resource, error) { return nil, fmt.Errorf("failed to parse cloud event type %s, %v", evt.Type(), err) } - if eventType.CloudEventsDataType != workpayload.ManifestEventDataType { + if eventType.CloudEventsDataType != workpayload.ManifestBundleEventDataType { return nil, fmt.Errorf("unsupported cloudevents data type %s", eventType.CloudEventsDataType) } @@ -96,7 +96,6 @@ func (codec *Codec) Decode(evt *cloudevents.Event) (*api.Resource, error) { }, Version: resourceVersion, ConsumerName: clusterName, - Type: api.ResourceTypeSingle, Status: status, } diff --git a/pkg/client/cloudevents/source_client.go b/pkg/client/cloudevents/source_client.go index 62c2444e..8c2f47a9 100644 --- a/pkg/client/cloudevents/source_client.go +++ b/pkg/client/cloudevents/source_client.go @@ -30,16 +30,15 @@ type SourceClient interface { type SourceClientImpl struct { Codec cegeneric.Codec[*api.Resource] - BundleCodec cegeneric.Codec[*api.Resource] CloudEventSourceClient *cegeneric.CloudEventSourceClient[*api.Resource] ResourceService services.ResourceService } func NewSourceClient(sourceOptions *ceoptions.CloudEventsSourceOptions, resourceService services.ResourceService) (SourceClient, error) { ctx := context.Background() - codec, bundleCodec := &Codec{sourceID: sourceOptions.SourceID}, &BundleCodec{sourceID: sourceOptions.SourceID} + codec := &Codec{sourceID: sourceOptions.SourceID} ceSourceClient, err := cegeneric.NewCloudEventSourceClient[*api.Resource](ctx, sourceOptions, - resourceService, ResourceStatusHashGetter, codec, bundleCodec) + resourceService, ResourceStatusHashGetter, codec) if err != nil { return nil, err } @@ -49,7 +48,6 @@ func NewSourceClient(sourceOptions *ceoptions.CloudEventsSourceOptions, resource return &SourceClientImpl{ Codec: codec, - BundleCodec: bundleCodec, CloudEventSourceClient: ceSourceClient, ResourceService: resourceService, }, nil @@ -69,9 +67,6 @@ func (s *SourceClientImpl) OnCreate(ctx context.Context, id string) error { SubResource: cetypes.SubResourceSpec, Action: cetypes.EventAction("create_request"), } - if resource.Type == api.ResourceTypeBundle { - eventType.CloudEventsDataType = s.BundleCodec.EventDataType() - } if err := s.CloudEventSourceClient.Publish(ctx, eventType, resource); err != nil { logger.Error(fmt.Sprintf("Failed to publish resource %s: %s", resource.ID, err)) return err @@ -94,9 +89,6 @@ func (s *SourceClientImpl) OnUpdate(ctx context.Context, id string) error { SubResource: cetypes.SubResourceSpec, Action: cetypes.EventAction("update_request"), } - if resource.Type == api.ResourceTypeBundle { - eventType.CloudEventsDataType = s.BundleCodec.EventDataType() - } if err := s.CloudEventSourceClient.Publish(ctx, eventType, resource); err != nil { logger.Error(fmt.Sprintf("Failed to publish resource %s: %s", resource.ID, err)) return err @@ -123,9 +115,6 @@ func (s *SourceClientImpl) OnDelete(ctx context.Context, id string) error { SubResource: cetypes.SubResourceSpec, Action: cetypes.EventAction("delete_request"), } - if resource.Type == api.ResourceTypeBundle { - eventType.CloudEventsDataType = s.BundleCodec.EventDataType() - } if err := s.CloudEventSourceClient.Publish(ctx, eventType, resource); err != nil { logger.Error(fmt.Sprintf("Failed to publish resource %s: %s", resource.ID, err)) return err @@ -142,7 +131,6 @@ func (s *SourceClientImpl) Resync(ctx context.Context, consumers []string) error logger := logger.NewOCMLogger(ctx) logger.V(4).Infof("Resyncing resource status from consumers %v", consumers) - for _, consumer := range consumers { if err := s.CloudEventSourceClient.Resync(ctx, consumer); err != nil { return err diff --git a/pkg/dao/mocks/resource.go b/pkg/dao/mocks/resource.go index d6d998e0..f75bd07d 100755 --- a/pkg/dao/mocks/resource.go +++ b/pkg/dao/mocks/resource.go @@ -57,20 +57,6 @@ func (d *resourceDaoMock) FindByConsumerName(ctx context.Context, consumerID str return resources, nil } -func (d *resourceDaoMock) FindByConsumerNameAndResourceType(ctx context.Context, consumerName string, resourceType api.ResourceType) (api.ResourceList, error) { - var resources api.ResourceList - for _, resource := range d.resources { - if resource.ConsumerName != consumerName { - continue - } - if resource.Type != resourceType { - continue - } - resources = append(resources, resource) - } - return resources, nil -} - func (d *resourceDaoMock) FindBySource(ctx context.Context, source string) (api.ResourceList, error) { var resources api.ResourceList for _, resource := range d.resources { diff --git a/pkg/dao/resource.go b/pkg/dao/resource.go index 0dd3dc21..2ef1333e 100755 --- a/pkg/dao/resource.go +++ b/pkg/dao/resource.go @@ -17,7 +17,6 @@ type ResourceDao interface { FindByIDs(ctx context.Context, ids []string) (api.ResourceList, error) FindBySource(ctx context.Context, source string) (api.ResourceList, error) FindByConsumerName(ctx context.Context, consumerName string) (api.ResourceList, error) - FindByConsumerNameAndResourceType(ctx context.Context, consumerName string, resourceType api.ResourceType) (api.ResourceList, error) All(ctx context.Context) (api.ResourceList, error) FirstByConsumerName(ctx context.Context, name string, unscoped bool) (api.Resource, error) } @@ -99,15 +98,6 @@ func (d *sqlResourceDao) FindByConsumerName(ctx context.Context, consumerName st return resources, nil } -func (d *sqlResourceDao) FindByConsumerNameAndResourceType(ctx context.Context, consumerName string, resourceType api.ResourceType) (api.ResourceList, error) { - g2 := (*d.sessionFactory).New(ctx) - resources := api.ResourceList{} - if err := g2.Unscoped().Where("consumer_name = ? and type = ?", consumerName, resourceType).Find(&resources).Error; err != nil { - return nil, err - } - return resources, nil -} - func (d *sqlResourceDao) All(ctx context.Context) (api.ResourceList, error) { g2 := (*d.sessionFactory).New(ctx) resources := api.ResourceList{} diff --git a/pkg/services/resource.go b/pkg/services/resource.go index d596c60e..1adb8a96 100755 --- a/pkg/services/resource.go +++ b/pkg/services/resource.go @@ -14,7 +14,6 @@ import ( cegeneric "open-cluster-management.io/sdk-go/pkg/cloudevents/generic" cetypes "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" - "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" "github.com/openshift-online/maestro/pkg/api" "github.com/openshift-online/maestro/pkg/errors" @@ -312,17 +311,7 @@ var _ cegeneric.Lister[*api.Resource] = &sqlResourceService{} // For more details, refer to the cegeneric.Lister interface: // https://github.com/open-cluster-management-io/sdk-go/blob/d3c47c228d7905ebb20f331f9b72bc5ff6a84789/pkg/cloudevents/generic/interface.go#L36-L39 func (s *sqlResourceService) List(listOpts cetypes.ListOptions) ([]*api.Resource, error) { - var resourceType api.ResourceType - resourceEventDataType := listOpts.CloudEventsDataType - switch resourceEventDataType { - case payload.ManifestEventDataType: - resourceType = api.ResourceTypeSingle - case payload.ManifestBundleEventDataType: - resourceType = api.ResourceTypeBundle - default: - return nil, fmt.Errorf("unsupported resource event data type %v", resourceEventDataType) - } - resourceList, err := s.resourceDao.FindByConsumerNameAndResourceType(context.TODO(), listOpts.ClusterName, resourceType) + resourceList, err := s.resourceDao.FindByConsumerName(context.TODO(), listOpts.ClusterName) if err != nil { return nil, err } diff --git a/pkg/services/resource_test.go b/pkg/services/resource_test.go index 8d82b20f..fcfa4b55 100755 --- a/pkg/services/resource_test.go +++ b/pkg/services/resource_test.go @@ -28,12 +28,12 @@ func TestResourceFindByConsumerID(t *testing.T) { resourceService := NewResourceService(dbmocks.NewMockAdvisoryLockFactory(), resourceDAO, events, nil) resources := api.ResourceList{ - &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}")}, + &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeBundle, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, - &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}")}, - &api.Resource{ConsumerName: Seismosaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}")}, + &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, + &api.Resource{ConsumerName: Seismosaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, &api.Resource{ConsumerName: Seismosaurus, Type: api.ResourceTypeBundle, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"e3eb7db1-b124-4a4d-8bb6-cc779c01b402\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, - &api.Resource{ConsumerName: Breviceratops, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}")}, + &api.Resource{ConsumerName: Breviceratops, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, &api.Resource{ConsumerName: Breviceratops, Type: api.ResourceTypeBundle, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, } for _, resource := range resources { @@ -70,7 +70,7 @@ func TestCreateInvalidResource(t *testing.T) { gm.Expect(len(invalidations)).To(gm.Equal(0)) } -func TestList(t *testing.T) { +func TestResourceList(t *testing.T) { gm.RegisterTestingT(t) resourceDAO := mocks.NewResourceDao() @@ -78,10 +78,10 @@ func TestList(t *testing.T) { resourceService := NewResourceService(dbmocks.NewMockAdvisoryLockFactory(), resourceDAO, events, nil) resources := api.ResourceList{ - &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}")}, - &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}")}, + &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, + &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, &api.Resource{ConsumerName: Fukuisaurus, Type: api.ResourceTypeBundle, Payload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, - &api.Resource{ConsumerName: Seismosaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}")}, + &api.Resource{ConsumerName: Seismosaurus, Type: api.ResourceTypeSingle, Payload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}")}, } for _, resource := range resources { _, err := resourceService.Create(context.Background(), resource) @@ -89,15 +89,13 @@ func TestList(t *testing.T) { } resoruces, err := resourceService.List(types.ListOptions{ - ClusterName: Fukuisaurus, - CloudEventsDataType: payload.ManifestEventDataType, + ClusterName: Fukuisaurus, }) gm.Expect(err).To(gm.BeNil()) - gm.Expect(len(resoruces)).To(gm.Equal(2)) + gm.Expect(len(resoruces)).To(gm.Equal(3)) resoruces, err = resourceService.List(types.ListOptions{ - ClusterName: Fukuisaurus, - CloudEventsDataType: payload.ManifestBundleEventDataType, + ClusterName: Seismosaurus, }) gm.Expect(err).To(gm.BeNil()) gm.Expect(len(resoruces)).To(gm.Equal(1)) diff --git a/pkg/services/validation.go b/pkg/services/validation.go index 254015c5..fa3d1125 100644 --- a/pkg/services/validation.go +++ b/pkg/services/validation.go @@ -48,12 +48,9 @@ func ValidateManifest(resType api.ResourceType, manifest datatypes.JSONMap) erro if err != nil { return fmt.Errorf("failed to decode manifest: %v", err) } - if len(obj) == 0 { - return fmt.Errorf("manifest is empty") - } return ValidateObject(obj) case api.ResourceTypeBundle: - objs, err := api.DecodeManifestBundleToObjects(manifest) + _, objs, _, _, err := api.DecodeManifestBundle(manifest) if err != nil { return fmt.Errorf("failed to decode manifest bundle: %v", err) } @@ -113,16 +110,13 @@ func ValidateManifestUpdate(resType api.ResourceType, new, old datatypes.JSONMap if err != nil { return fmt.Errorf("failed to decode old manifest: %v", err) } - if len(newObj) == 0 || len(oldObj) == 0 { - return fmt.Errorf("new or old manifest is empty") - } return ValidateObjectUpdate(newObj, oldObj) case api.ResourceTypeBundle: - newObjs, err := api.DecodeManifestBundleToObjects(new) + _, newObjs, _, _, err := api.DecodeManifestBundle(new) if err != nil { return fmt.Errorf("failed to decode new manifest bundle: %v", err) } - oldObjs, err := api.DecodeManifestBundleToObjects(old) + _, oldObjs, _, _, err := api.DecodeManifestBundle(old) if err != nil { return fmt.Errorf("failed to decode old manifest bundle: %v", err) } diff --git a/pkg/services/validation_test.go b/pkg/services/validation_test.go index 58167773..2624cd4d 100644 --- a/pkg/services/validation_test.go +++ b/pkg/services/validation_test.go @@ -96,7 +96,7 @@ func TestValidateNewManifest(t *testing.T) { { name: "validated single manifest", resType: api.ResourceTypeSingle, - manifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + manifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), }, { name: "validated bundle manifest", @@ -107,18 +107,18 @@ func TestValidateNewManifest(t *testing.T) { name: "invalidated single manifest", resType: api.ResourceTypeSingle, manifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), - expectedErrorMsg: "manifest is empty", + expectedErrorMsg: "failed to decode manifest: invalid number of manifests in the event payload: 2", }, { name: "invalidated bundle manifest", resType: api.ResourceTypeBundle, - manifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + manifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), expectedErrorMsg: "manifest bundle is empty", }, { name: "invalidated resource type", resType: "invalid", - manifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + manifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), expectedErrorMsg: "unknown resource type: invalid", }, } @@ -216,34 +216,34 @@ func TestValidateUpdateManifest(t *testing.T) { { name: "validated single manifest", resType: api.ResourceTypeSingle, - newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), - oldManifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), + oldManifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), }, { name: "validated bundle manifest", resType: api.ResourceTypeBundle, - newPayload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), + newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), oldManifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), }, { name: "invalidated single manifest", resType: api.ResourceTypeSingle, - newPayload: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"grpc\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"}},{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), - oldManifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), - expectedErrorMsg: "new or old manifest is empty", + newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"resourceid\":\"c4df9ff0-bfeb-5bc6-a0ab-4c9128d698b4\",\"clustername\":\"b288a9da-8bfe-4c82-94cc-2b48e773fc46\",\"resourceversion\":1,\"data\":{\"manifests\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"app\":\"nginx\"}},\"template\":{\"spec\":{\"containers\":[{\"name\":\"nginx\",\"image\":\"nginxinc/nginx-unprivileged\"}]},\"metadata\":{\"labels\":{\"app\":\"nginx\"}}}}}],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), + oldManifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifests\":[],\"deleteOption\":{\"propagationPolicy\":\"Foreground\"},\"manifestConfigs\":[{\"updateStrategy\":{\"type\":\"ServerSideApply\"},\"resourceIdentifier\":{\"name\":\"nginx\",\"group\":\"apps\",\"resource\":\"deployments\",\"namespace\":\"default\"}}]}}"), + expectedErrorMsg: "failed to decode old manifest: invalid number of manifests in the event payload: 0", }, { name: "invalidated bundle manifest", resType: api.ResourceTypeBundle, - newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), - oldManifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + oldManifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), expectedErrorMsg: "new or old manifest bundle is empty", }, { name: "invalidated resource type", resType: "invalid", - newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), - oldManifest: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-03-07T03:29:03.194843266Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifests.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + newPayload: newPayload(t, "{\"id\":\"75479c10-b537-4261-8058-ca2e36bac384\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), + oldManifest: newPayload(t, "{\"id\":\"266a8cd2-2fab-4e89-9bf0-a56425ebcdf8\",\"time\":\"2024-02-05T17:31:05Z\",\"type\":\"io.open-cluster-management.works.v1alpha1.manifestbundles.spec.create_request\",\"source\":\"maestro\",\"specversion\":\"1.0\",\"datacontenttype\":\"application/json\",\"data\":{\"manifest\":{\"apiVersion\":\"v1\",\"kind\":\"ConfigMap\",\"metadata\":{\"name\":\"test\",\"namespace\":\"test\"}}}}"), expectedErrorMsg: "unknown resource type: invalid", }, } diff --git a/test/e2e/pkg/grpc_test.go b/test/e2e/pkg/grpc_test.go index 34c1ea95..d2d382e9 100644 --- a/test/e2e/pkg/grpc_test.go +++ b/test/e2e/pkg/grpc_test.go @@ -21,7 +21,6 @@ import ( pbv1 "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc/protobuf/v1" grpcprotocol "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc/protocol" "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/types" - "open-cluster-management.io/sdk-go/pkg/cloudevents/work/common" "open-cluster-management.io/sdk-go/pkg/cloudevents/work/payload" ) @@ -69,25 +68,22 @@ var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { } resourceStatus.ReconcileStatus.ObservedVersion = resourceVersion - manifestStatus := &payload.ManifestStatus{} - if err := evt.DataAs(manifestStatus); err != nil { + manifestBundleStatus := &payload.ManifestBundleStatus{} + if err := evt.DataAs(manifestBundleStatus); err != nil { continue } - if manifestStatus.Status != nil { - resourceStatus.ReconcileStatus.Conditions = manifestStatus.Status.Conditions - if meta.IsStatusConditionTrue(manifestStatus.Conditions, common.ManifestsDeleted) { - deletedCondition := meta.FindStatusCondition(manifestStatus.Conditions, common.ManifestsDeleted) - resourceStatus.ReconcileStatus.Conditions = append(resourceStatus.ReconcileStatus.Conditions, *deletedCondition) - } - for _, value := range manifestStatus.Status.StatusFeedbacks.Values { - if value.Name == "status" { - contentStatus := make(map[string]interface{}) - if err := json.Unmarshal([]byte(*value.Value.JsonRaw), &contentStatus); err != nil { - continue - } - resourceStatus.ContentStatus = contentStatus + if len(manifestBundleStatus.ResourceStatus) != 1 { + return + } + resourceStatus.ReconcileStatus.Conditions = manifestBundleStatus.ResourceStatus[0].Conditions + for _, value := range manifestBundleStatus.ResourceStatus[0].StatusFeedbacks.Values { + if value.Name == "status" { + contentStatus := make(map[string]interface{}) + if err := json.Unmarshal([]byte(*value.Value.JsonRaw), &contentStatus); err != nil { + continue } + resourceStatus.ContentStatus = contentStatus } } } @@ -95,7 +91,7 @@ var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { }) It("publish a resource spec using grpc client", func() { - evt := helper.NewEvent(sourceID, "create_request", agentTestOpts.consumerName, resourceID, deployName, 1, 1) + evt := helper.NewBundleEvent(sourceID, "create_request", agentTestOpts.consumerName, resourceID, deployName, 1, 1) pbEvt := &pbv1.CloudEvent{} err := grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) Expect(err).To(BeNil(), "failed to convert spec from cloudevent to protobuf") @@ -152,7 +148,7 @@ var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { }) It("publish a resource spec with update request using grpc client", func() { - evt := helper.NewEvent(sourceID, "update_request", agentTestOpts.consumerName, resourceID, deployName, 1, 2) + evt := helper.NewBundleEvent(sourceID, "update_request", agentTestOpts.consumerName, resourceID, deployName, 1, 2) pbEvt := &pbv1.CloudEvent{} err := grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) Expect(err).To(BeNil(), "failed to convert spec from cloudevent to protobuf") @@ -209,7 +205,7 @@ var _ = Describe("GRPC", Ordered, Label("e2e-tests-grpc"), func() { }) It("publish a resource spec with delete request using grpc client", func() { - evt := helper.NewEvent(sourceID, "delete_request", agentTestOpts.consumerName, resourceID, deployName, 2, 2) + evt := helper.NewBundleEvent(sourceID, "delete_request", agentTestOpts.consumerName, resourceID, deployName, 2, 2) pbEvt := &pbv1.CloudEvent{} err := grpcprotocol.WritePBMessage(ctx, binding.ToMessage(evt), pbEvt) Expect(err).To(BeNil(), "failed to convert spec from cloudevent to protobuf") diff --git a/test/helper.go b/test/helper.go index ea060f97..d5b22a6e 100755 --- a/test/helper.go +++ b/test/helper.go @@ -21,7 +21,6 @@ import ( workinformers "open-cluster-management.io/api/client/work/informers/externalversions" workv1informers "open-cluster-management.io/api/client/work/informers/externalversions/work/v1" - workv1 "open-cluster-management.io/api/work/v1" "open-cluster-management.io/sdk-go/pkg/cloudevents/generic" grpcoptions "open-cluster-management.io/sdk-go/pkg/cloudevents/generic/options/grpc" @@ -285,7 +284,7 @@ func (helper *Helper) StartControllerManager(ctx context.Context) { go helper.ControllerManager.Start(ctx) } -func (helper *Helper) StartWorkAgent(ctx context.Context, clusterName string, bundle bool) { +func (helper *Helper) StartWorkAgent(ctx context.Context, clusterName string) { var brokerConfig any if helper.Broker != "grpc" { // initilize the mqtt options @@ -301,19 +300,12 @@ func (helper *Helper) StartWorkAgent(ctx context.Context, clusterName string, bu brokerConfig = grpcOptions } - var workCodec generic.Codec[*workv1.ManifestWork] - if bundle { - workCodec = codec.NewManifestBundleCodec() - } else { - workCodec = codec.NewManifestCodec(nil) - } - watcherStore := store.NewAgentInformerWatcherStore() clientHolder, err := work.NewClientHolderBuilder(brokerConfig). WithClientID(clusterName). WithClusterName(clusterName). - WithCodecs(workCodec). + WithCodecs(codec.NewManifestBundleCodec()). WithWorkClientWatcherStore(watcherStore). NewAgentClientHolder(ctx) if err != nil { diff --git a/test/integration/controller_test.go b/test/integration/controller_test.go index 9f4cb965..e23ff156 100755 --- a/test/integration/controller_test.go +++ b/test/integration/controller_test.go @@ -28,7 +28,7 @@ func TestControllerRacing(t *testing.T) { // start work agent so that grpc broker can work consumer := h.CreateConsumer("cluster-" + rand.String(5)) - h.StartWorkAgent(ctx, consumer.Name, false) + h.StartWorkAgent(ctx, consumer.Name) eventDao := dao.NewEventDao(&h.Env().Database.SessionFactory) statusEventDao := dao.NewStatusEventDao(&h.Env().Database.SessionFactory) @@ -163,7 +163,7 @@ func TestControllerReconcile(t *testing.T) { // start work agent so that grpc broker can work consumer := h.CreateConsumer("cluster-" + rand.String(5)) - h.StartWorkAgent(ctx, consumer.Name, false) + h.StartWorkAgent(ctx, consumer.Name) eventDao := dao.NewEventDao(&h.Env().Database.SessionFactory) statusEventDao := dao.NewStatusEventDao(&h.Env().Database.SessionFactory) @@ -285,7 +285,7 @@ func TestControllerSync(t *testing.T) { // start work agent so that grpc broker can work consumer := h.CreateConsumer("cluster-" + rand.String(5)) - h.StartWorkAgent(ctx, consumer.Name, false) + h.StartWorkAgent(ctx, consumer.Name) // create two resources with resource dao resource4ID := uuid.New().String() diff --git a/test/integration/resource_test.go b/test/integration/resource_test.go index 1b05cc7e..b3174c69 100755 --- a/test/integration/resource_test.go +++ b/test/integration/resource_test.go @@ -77,7 +77,7 @@ func TestResourcePost(t *testing.T) { deployName := fmt.Sprintf("nginx-%s", rand.String(5)) res := h.NewAPIResource(consumer.Name, deployName, 1) h.StartControllerManager(ctx) - h.StartWorkAgent(ctx, consumer.Name, false) + h.StartWorkAgent(ctx, consumer.Name) clientHolder := h.WorkAgentHolder agentWorkClient := clientHolder.ManifestWorks(consumer.Name) @@ -187,7 +187,7 @@ func TestResourcePost(t *testing.T) { labels := []*prommodel.LabelPair{ {Name: strPtr("source"), Value: strPtr("maestro")}, {Name: strPtr("cluster"), Value: strPtr(clusterName)}, - {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifests")}, + {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, } checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 2.0) checkServerCounterMetric(t, families, "cloudevents_received_total", labels, 2.0) @@ -276,7 +276,7 @@ func TestResourcePatch(t *testing.T) { consumer := h.CreateConsumer("") h.StartControllerManager(ctx) - h.StartWorkAgent(ctx, consumer.ID, false) + h.StartWorkAgent(ctx, consumer.ID) clientHolder := h.WorkAgentHolder agentWorkClient := clientHolder.ManifestWorks(consumer.ID) @@ -595,14 +595,14 @@ func TestResourceFromGRPC(t *testing.T) { res.ID = uuid.NewString() h.StartControllerManager(ctx) - h.StartWorkAgent(ctx, consumer.Name, false) + h.StartWorkAgent(ctx, consumer.Name) clientHolder := h.WorkAgentHolder agentWorkClient := clientHolder.ManifestWorks(consumer.Name) // use grpc client to create resource h.StartGRPCResourceSourceClient() err := h.GRPCSourceClient.Publish(ctx, types.CloudEventsType{ - CloudEventsDataType: payload.ManifestEventDataType, + CloudEventsDataType: payload.ManifestBundleEventDataType, SubResource: types.SubResourceSpec, Action: common.CreateRequestAction, }, res) @@ -611,17 +611,17 @@ func TestResourceFromGRPC(t *testing.T) { // for real case, the controller should have a mapping between resource (replicated) in maestro and resource (root) in kubernetes // so call subscribe method can return the resource // for testing, just list the resource via restful api. - resources, _, err := client.DefaultApi.ApiMaestroV1ResourcesGet(ctx).Execute() + resourceBundles, _, err := client.DefaultApi.ApiMaestroV1ResourceBundlesGet(ctx).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting object: %v", err) - Expect(resources.Items).NotTo(BeEmpty(), "Expected returned resource list is not empty") + Expect(resourceBundles.Items).NotTo(BeEmpty(), "Expected returned resource list is not empty") - resource, resp, err := client.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, *resources.Items[0].Id).Execute() + resourceBundle, resp, err := client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, *resourceBundles.Items[0].Id).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*resource.Id).To(Equal(res.ID)) - Expect(*resource.Kind).To(Equal("Resource")) - Expect(*resource.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resources/%s", *resource.Id))) - Expect(*resource.Version).To(Equal(int32(1))) + Expect(*resourceBundle.Id).To(Equal(res.ID)) + Expect(*resourceBundle.Kind).To(Equal("ResourceBundle")) + Expect(*resourceBundle.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resource-bundles/%s", *resourceBundle.Id))) + Expect(*resourceBundle.Version).To(Equal(int32(1))) // add the resource to the store h.Store.Add(res) @@ -703,26 +703,26 @@ func TestResourceFromGRPC(t *testing.T) { }, 10*time.Second, 1*time.Second).Should(Succeed()) newRes := h.NewResource(consumer.Name, deployName, 2, 1) - newRes.ID = *resource.Id - newRes.Version = *resource.Version + newRes.ID = *resourceBundle.Id + newRes.Version = *resourceBundle.Version err = h.GRPCSourceClient.Publish(ctx, types.CloudEventsType{ - CloudEventsDataType: payload.ManifestEventDataType, + CloudEventsDataType: payload.ManifestBundleEventDataType, SubResource: types.SubResourceSpec, Action: common.UpdateRequestAction, }, newRes) Expect(err).NotTo(HaveOccurred(), "Error publishing resource with grpc source client: %v", err) - resource, resp, err = client.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, newRes.ID).Execute() + resourceBundle, resp, err = client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, newRes.ID).Execute() Expect(err).NotTo(HaveOccurred(), "Error getting object: %v", err) Expect(resp.StatusCode).To(Equal(http.StatusOK)) - Expect(*resource.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") - Expect(*resource.Kind).To(Equal("Resource")) - Expect(*resource.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resources/%s", *resource.Id))) - Expect(*resource.Version).To(Equal(int32(2))) + Expect(*resourceBundle.Id).NotTo(BeEmpty(), "Expected ID assigned on creation") + Expect(*resourceBundle.Kind).To(Equal("ResourceBundle")) + Expect(*resourceBundle.Href).To(Equal(fmt.Sprintf("/api/maestro/v1/resource-bundles/%s", *resourceBundle.Id))) + Expect(*resourceBundle.Version).To(Equal(int32(2))) Eventually(func() error { // ensure the work can be get by work client - work, err = agentWorkClient.Get(ctx, *resource.Id, metav1.GetOptions{}) + work, err = agentWorkClient.Get(ctx, *resourceBundle.Id, metav1.GetOptions{}) if err != nil { return err } @@ -773,8 +773,8 @@ func TestResourceFromGRPC(t *testing.T) { Expect(updateWorkStatus(ctx, agentWorkClient, work, deletingWorkStatus)).NotTo(HaveOccurred()) Eventually(func() error { - resource, _, err = client.DefaultApi.ApiMaestroV1ResourcesIdGet(ctx, newRes.ID).Execute() - if resource != nil { + resourceBundle, _, err = client.DefaultApi.ApiMaestroV1ResourceBundlesIdGet(ctx, newRes.ID).Execute() + if resourceBundle != nil { return fmt.Errorf("resource %s is not deleted", newRes.ID) } return nil @@ -818,8 +818,8 @@ func TestResourceFromGRPC(t *testing.T) { {Name: strPtr("cluster"), Value: strPtr(clusterName)}, {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, } - checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 1.0) - checkServerCounterMetric(t, families, "cloudevents_received_total", labels, 1.0) + checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 6.0) + checkServerCounterMetric(t, families, "cloudevents_received_total", labels, 4.0) } } @@ -838,7 +838,7 @@ func TestResourceBundleFromGRPC(t *testing.T) { res.ID = uuid.NewString() h.StartControllerManager(ctx) - h.StartWorkAgent(ctx, consumer.Name, true) + h.StartWorkAgent(ctx, consumer.Name) clientHolder := h.WorkAgentHolder agentWorkClient := clientHolder.ManifestWorks(consumer.Name) diff --git a/test/integration/status_dispatcher_test.go b/test/integration/status_dispatcher_test.go index 9055491a..550ffc30 100644 --- a/test/integration/status_dispatcher_test.go +++ b/test/integration/status_dispatcher_test.go @@ -67,13 +67,13 @@ func TestStatusDispatcher(t *testing.T) { labels := []*prommodel.LabelPair{ {Name: strPtr("source"), Value: strPtr("maestro")}, {Name: strPtr("cluster"), Value: strPtr(consumer1)}, - {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifests")}, + {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, } checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 1.0) labels = []*prommodel.LabelPair{ {Name: strPtr("source"), Value: strPtr("maestro")}, {Name: strPtr("cluster"), Value: strPtr(consumer2)}, - {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifests")}, + {Name: strPtr("type"), Value: strPtr("io.open-cluster-management.works.v1alpha1.manifestbundles")}, } checkServerCounterMetric(t, families, "cloudevents_sent_total", labels, 2.0) }