From eb201c7992ad718005bbff0fc8c35042c7ff6d94 Mon Sep 17 00:00:00 2001 From: sujeet01 Date: Fri, 19 Jul 2024 01:31:57 +0530 Subject: [PATCH] Extend Bucket Runtime Interface for Cross-Cluster Events --- broker/bucketbroker/server/bucket_create.go | 2 +- .../bucketbroker/server/bucket_create_test.go | 65 + .../bucketbroker/server/bucket_delete_test.go | 53 + broker/bucketbroker/server/bucket_list.go | 16 + .../bucketbroker/server/bucket_list_test.go | 44 + .../server/bucketclass_list_test.go | 36 + broker/bucketbroker/server/event_list.go | 102 ++ broker/bucketbroker/server/event_list_test.go | 114 ++ broker/bucketbroker/server/server.go | 1 + .../bucketbroker/server/server_suite_test.go | 158 +++ .../broker-rbac/role.yaml | 8 + iri/apis/bucket/bucket.go | 1 + iri/apis/bucket/v1alpha1/api.pb.go | 1086 +++++++++++++++-- iri/apis/bucket/v1alpha1/api.proto | 17 + iri/apis/machine/v1alpha1/api.proto | 2 +- iri/remote/bucket/runtime.go | 4 + iri/testing/bucket/fake.go | 160 +++ .../irictlbucket/get/event/event.go | 86 ++ .../cmd/irictl-bucket/irictlbucket/get/get.go | 2 + irictl-bucket/tableconverters/event.go | 67 + poollet/bucketpoollet/bem/bem.go | 92 ++ poollet/bucketpoollet/bem/bem_suite_test.go | 150 +++ poollet/bucketpoollet/bem/bem_test.go | 145 +++ .../cmd/bucketpoollet/app/app.go | 6 + .../controllers/bucket_controller_test.go | 93 ++ .../controllers/bucketpool_controller.go | 4 +- .../controllers/bucketpool_controller_test.go | 76 ++ .../controllers/controllers_suite_test.go | 242 ++++ .../controllers/machinepool_controller.go | 2 +- .../controllers/volumepool_controller.go | 2 +- 30 files changed, 2717 insertions(+), 119 deletions(-) create mode 100644 broker/bucketbroker/server/bucket_create_test.go create mode 100644 broker/bucketbroker/server/bucket_delete_test.go create mode 100644 broker/bucketbroker/server/bucket_list_test.go create mode 100644 broker/bucketbroker/server/bucketclass_list_test.go create mode 100644 broker/bucketbroker/server/event_list.go create mode 100644 broker/bucketbroker/server/event_list_test.go create mode 100644 broker/bucketbroker/server/server_suite_test.go create mode 100644 iri/testing/bucket/fake.go create mode 100644 irictl-bucket/cmd/irictl-bucket/irictlbucket/get/event/event.go create mode 100644 irictl-bucket/tableconverters/event.go create mode 100644 poollet/bucketpoollet/bem/bem.go create mode 100644 poollet/bucketpoollet/bem/bem_suite_test.go create mode 100644 poollet/bucketpoollet/bem/bem_test.go create mode 100644 poollet/bucketpoollet/controllers/bucket_controller_test.go create mode 100644 poollet/bucketpoollet/controllers/bucketpool_controller_test.go create mode 100644 poollet/bucketpoollet/controllers/controllers_suite_test.go diff --git a/broker/bucketbroker/server/bucket_create.go b/broker/bucketbroker/server/bucket_create.go index 03cea9602..fcb468daf 100644 --- a/broker/bucketbroker/server/bucket_create.go +++ b/broker/bucketbroker/server/bucket_create.go @@ -67,7 +67,7 @@ func (s *Server) createIronCoreBucket(ctx context.Context, log logr.Logger, buck log.V(1).Info("Patching ironcore bucket as created") if err := apiutils.PatchCreated(ctx, s.client, bucket.Bucket); err != nil { - return fmt.Errorf("error patching ironcore machine as created: %w", err) + return fmt.Errorf("error patching ironcore bucket as created: %w", err) } // Reset cleaner since everything from now on operates on a consistent bucket diff --git a/broker/bucketbroker/server/bucket_create_test.go b/broker/bucketbroker/server/bucket_create_test.go new file mode 100644 index 000000000..77ad3983c --- /dev/null +++ b/broker/bucketbroker/server/bucket_create_test.go @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package server_test + +import ( + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + bucketbrokerv1alpha1 "github.com/ironcore-dev/ironcore/broker/bucketbroker/api/v1alpha1" + "github.com/ironcore-dev/ironcore/broker/machinebroker/apiutils" + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + irimeta "github.com/ironcore-dev/ironcore/iri/apis/meta/v1alpha1" + bucketpoolletv1alpha1 "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/api/v1alpha1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("CreateBucket", func() { + ns, _, srv := SetupTest() + bucketClass := SetupBucketClass("250Mi", "1500") + + It("should correctly create a bucket", func(ctx SpecContext) { + By("creating a bucket") + res, err := srv.CreateBucket(ctx, &iri.CreateBucketRequest{ + Bucket: &iri.Bucket{ + Metadata: &irimeta.ObjectMetadata{ + Labels: map[string]string{ + bucketpoolletv1alpha1.BucketUIDLabel: "foobar", + }, + }, + Spec: &iri.BucketSpec{ + Class: bucketClass.Name, + }, + }, + }) + + Expect(err).NotTo(HaveOccurred()) + Expect(res).NotTo(BeNil()) + + By("getting the ironcore bucket") + ironcoreBucket := &storagev1alpha1.Bucket{} + ironcoreBucketKey := client.ObjectKey{Namespace: ns.Name, Name: res.Bucket.Metadata.Id} + Expect(k8sClient.Get(ctx, ironcoreBucketKey, ironcoreBucket)).To(Succeed()) + + By("inspecting the ironcore bucket") + Expect(ironcoreBucket.Labels).To(Equal(map[string]string{ + bucketbrokerv1alpha1.CreatedLabel: "true", + bucketbrokerv1alpha1.ManagerLabel: bucketbrokerv1alpha1.BucketBrokerManager, + })) + encodedIRIAnnotations, err := apiutils.EncodeAnnotationsAnnotation(nil) + Expect(err).NotTo(HaveOccurred()) + encodedIRILabels, err := apiutils.EncodeLabelsAnnotation(map[string]string{ + bucketpoolletv1alpha1.BucketUIDLabel: "foobar", + }) + Expect(err).NotTo(HaveOccurred()) + Expect(ironcoreBucket.Annotations).To(Equal(map[string]string{ + bucketbrokerv1alpha1.AnnotationsAnnotation: encodedIRIAnnotations, + bucketbrokerv1alpha1.LabelsAnnotation: encodedIRILabels, + })) + Expect(ironcoreBucket.Spec.BucketClassRef.Name).To(Equal(bucketClass.Name)) + + }) +}) diff --git a/broker/bucketbroker/server/bucket_delete_test.go b/broker/bucketbroker/server/bucket_delete_test.go new file mode 100644 index 000000000..424ce7754 --- /dev/null +++ b/broker/bucketbroker/server/bucket_delete_test.go @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package server_test + +import ( + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + irimeta "github.com/ironcore-dev/ironcore/iri/apis/meta/v1alpha1" + bucketpoolletv1alpha1 "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/api/v1alpha1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("DeleteBucket", func() { + ns, _, srv := SetupTest() + bucketClass := SetupBucketClass("250Mi", "1500") + + It("should correctly delete a bucket", func(ctx SpecContext) { + By("creating a bucket") + createRes, err := srv.CreateBucket(ctx, &iri.CreateBucketRequest{ + Bucket: &iri.Bucket{ + Metadata: &irimeta.ObjectMetadata{ + Labels: map[string]string{ + bucketpoolletv1alpha1.BucketUIDLabel: "foobar", + }, + }, + Spec: &iri.BucketSpec{ + Class: bucketClass.Name, + }, + }, + }) + + Expect(err).NotTo(HaveOccurred()) + Expect(createRes).NotTo(BeNil()) + + By("deleting the bucket") + deleteRes, err := srv.DeleteBucket(ctx, &iri.DeleteBucketRequest{ + BucketId: createRes.Bucket.Metadata.Id, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(deleteRes).NotTo(BeNil()) + + By("verifying the bucket is deleted") + ironcoreBucket := &storagev1alpha1.Bucket{} + ironcoreBucketKey := client.ObjectKey{Namespace: ns.Name, Name: createRes.Bucket.Metadata.Id} + err = k8sClient.Get(ctx, ironcoreBucketKey, ironcoreBucket) + Expect(apierrors.IsNotFound(err)).To(BeTrue()) + }) +}) diff --git a/broker/bucketbroker/server/bucket_list.go b/broker/bucketbroker/server/bucket_list.go index 4acb3b5a8..5027c6dc8 100644 --- a/broker/bucketbroker/server/bucket_list.go +++ b/broker/bucketbroker/server/bucket_list.go @@ -9,6 +9,7 @@ import ( storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" bucketbrokerv1alpha1 "github.com/ironcore-dev/ironcore/broker/bucketbroker/api/v1alpha1" + "github.com/ironcore-dev/ironcore/broker/bucketbroker/apiutils" "github.com/ironcore-dev/ironcore/broker/common" iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" "google.golang.org/grpc/codes" @@ -112,6 +113,21 @@ func (s *Server) aggregateIronCoreBucket( }, nil } +func (s *Server) getIronCoreBucket(ctx context.Context, id string) (*storagev1alpha1.Bucket, error) { + ironcoreBucket := &storagev1alpha1.Bucket{} + ironcoreBucketKey := client.ObjectKey{Namespace: s.namespace, Name: id} + if err := s.client.Get(ctx, ironcoreBucketKey, ironcoreBucket); err != nil { + if !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("error getting ironcore bucket %s: %w", id, err) + } + return nil, status.Errorf(codes.NotFound, "bucket %s not found", id) + } + if !apiutils.IsManagedBy(ironcoreBucket, bucketbrokerv1alpha1.BucketBrokerManager) || !apiutils.IsCreated(ironcoreBucket) { + return nil, status.Errorf(codes.NotFound, "bucket %s not found", id) + } + return ironcoreBucket, nil +} + func (s *Server) getAggregateIronCoreBucket(ctx context.Context, id string) (*AggregateIronCoreBucket, error) { ironcoreBucket := &storagev1alpha1.Bucket{} if err := s.getManagedAndCreated(ctx, id, ironcoreBucket); err != nil { diff --git a/broker/bucketbroker/server/bucket_list_test.go b/broker/bucketbroker/server/bucket_list_test.go new file mode 100644 index 000000000..8b333930f --- /dev/null +++ b/broker/bucketbroker/server/bucket_list_test.go @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package server_test + +import ( + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + irimeta "github.com/ironcore-dev/ironcore/iri/apis/meta/v1alpha1" + bucketpoolletv1alpha1 "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/api/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("ListBuckets", func() { + _, _, srv := SetupTest() + bucketClass := SetupBucketClass("250Mi", "1500") + + It("should correctly list buckets", func(ctx SpecContext) { + By("creating multiple buckets") + const noOfBuckets = 3 + + buckets := make([]any, noOfBuckets) + for i := 0; i < noOfBuckets; i++ { + res, err := srv.CreateBucket(ctx, &iri.CreateBucketRequest{ + Bucket: &iri.Bucket{ + Metadata: &irimeta.ObjectMetadata{ + Labels: map[string]string{ + bucketpoolletv1alpha1.BucketUIDLabel: "foobar", + }, + }, + Spec: &iri.BucketSpec{ + Class: bucketClass.Name, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(res).NotTo(BeNil()) + buckets[i] = res.Bucket + } + + By("listing the buckets") + Expect(srv.ListBuckets(ctx, &iri.ListBucketsRequest{})).To(HaveField("Buckets", ConsistOf(buckets...))) + }) +}) diff --git a/broker/bucketbroker/server/bucketclass_list_test.go b/broker/bucketbroker/server/bucketclass_list_test.go new file mode 100644 index 000000000..3161a27aa --- /dev/null +++ b/broker/bucketbroker/server/bucketclass_list_test.go @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package server_test + +import ( + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" +) + +var _ = Describe("ListBucketClasses", func() { + + _, bucketPool, srv := SetupTest() + bucketClass1 := SetupBucketClass("100Mi", "100") + bucketClass2 := SetupBucketClass("200Mi", "200") + + It("should correctly list available bucket classes", func(ctx SpecContext) { + By("patching bucket classes in the bucket pool status") + bucketPool.Status.AvailableBucketClasses = []corev1.LocalObjectReference{ + {Name: bucketClass1.Name}, + {Name: bucketClass2.Name}, + } + Expect(k8sClient.Status().Update(ctx, bucketPool)).To(Succeed()) + + res, err := srv.ListBucketClasses(ctx, &iri.ListBucketClassesRequest{}) + Expect(err).NotTo(HaveOccurred()) + Expect(res.BucketClasses).To(HaveLen(2)) + Expect(res.BucketClasses).To(ContainElements( + &iri.BucketClass{Name: bucketClass1.Name, Capabilities: &iri.BucketClassCapabilities{Tps: 104857600, Iops: 100}}, + &iri.BucketClass{Name: bucketClass2.Name, Capabilities: &iri.BucketClassCapabilities{Tps: 209715200, Iops: 200}}, + )) + }) + +}) diff --git a/broker/bucketbroker/server/event_list.go b/broker/bucketbroker/server/event_list.go new file mode 100644 index 000000000..93e21737f --- /dev/null +++ b/broker/bucketbroker/server/event_list.go @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package server + +import ( + "context" + "fmt" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + "github.com/ironcore-dev/ironcore/broker/bucketbroker/apiutils" + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + irievent "github.com/ironcore-dev/ironcore/iri/apis/event/v1alpha1" +) + +const ( + InvolvedObjectKind = "Bucket" + InvolvedObjectKindSelector = "involvedObject.kind" + InvolvedObjectAPIVersionSelector = "involvedObject.apiVersion" +) + +func (s *Server) listEvents(ctx context.Context) ([]*irievent.Event, error) { + log := ctrl.LoggerFrom(ctx) + bucketEventList := &v1.EventList{} + selectorField := fields.Set{ + InvolvedObjectKindSelector: InvolvedObjectKind, + InvolvedObjectAPIVersionSelector: storagev1alpha1.SchemeGroupVersion.String(), + } + if err := s.client.List(ctx, bucketEventList, + client.InNamespace(s.namespace), client.MatchingFieldsSelector{Selector: selectorField.AsSelector()}, + ); err != nil { + return nil, err + } + + var iriEvents []*irievent.Event + for _, bucketEvent := range bucketEventList.Items { + ironcoreBucket, err := s.getIronCoreBucket(ctx, bucketEvent.InvolvedObject.Name) + if err != nil { + log.V(1).Info("Unable to get ironcore bucket", "BucketName", bucketEvent.InvolvedObject.Name) + continue + } + bucketObjectMetadata, err := apiutils.GetObjectMetadata(&ironcoreBucket.ObjectMeta) + if err != nil { + continue + } + iriEvent := &irievent.Event{ + Spec: &irievent.EventSpec{ + InvolvedObjectMeta: bucketObjectMetadata, + Reason: bucketEvent.Reason, + Message: bucketEvent.Message, + Type: bucketEvent.Type, + EventTime: bucketEvent.LastTimestamp.Unix(), + }, + } + iriEvents = append(iriEvents, iriEvent) + } + return iriEvents, nil +} + +func (s *Server) filterEvents(events []*irievent.Event, filter *iri.EventFilter) []*irievent.Event { + if filter == nil { + return events + } + + var ( + res []*irievent.Event + sel = labels.SelectorFromSet(filter.LabelSelector) + ) + for _, iriEvent := range events { + if !sel.Matches(labels.Set(iriEvent.Spec.InvolvedObjectMeta.Labels)) { + continue + } + + if filter.EventsFromTime > 0 && filter.EventsToTime > 0 { + if iriEvent.Spec.EventTime < filter.EventsFromTime || iriEvent.Spec.EventTime > filter.EventsToTime { + continue + } + } + + res = append(res, iriEvent) + } + return res +} + +func (s *Server) ListEvents(ctx context.Context, req *iri.ListEventsRequest) (*iri.ListEventsResponse, error) { + iriEvents, err := s.listEvents(ctx) + if err != nil { + return nil, fmt.Errorf("error listing bucket events : %w", err) + } + + iriEvents = s.filterEvents(iriEvents, req.Filter) + + return &iri.ListEventsResponse{ + Events: iriEvents, + }, nil +} diff --git a/broker/bucketbroker/server/event_list_test.go b/broker/bucketbroker/server/event_list_test.go new file mode 100644 index 000000000..a9f7a97e7 --- /dev/null +++ b/broker/bucketbroker/server/event_list_test.go @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package server_test + +import ( + "time" + + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + "github.com/ironcore-dev/ironcore/iri/apis/event/v1alpha1" + irimeta "github.com/ironcore-dev/ironcore/iri/apis/meta/v1alpha1" + bucketpoolletv1alpha1 "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/api/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("ListEvents", func() { + ns, _, srv := SetupTest() + bucketClass := SetupBucketClass("250Mi", "1500") + + It("should correctly list events", func(ctx SpecContext) { + Expect(storagev1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) + Expect(err).ToNot(HaveOccurred()) + + By("creating bucket") + res, err := srv.CreateBucket(ctx, &iri.CreateBucketRequest{ + Bucket: &iri.Bucket{ + Metadata: &irimeta.ObjectMetadata{ + Labels: map[string]string{ + bucketpoolletv1alpha1.BucketUIDLabel: "foobar", + }, + }, + Spec: &iri.BucketSpec{ + Class: bucketClass.Name, + }, + }, + }) + + Expect(err).NotTo(HaveOccurred()) + Expect(res).NotTo(BeNil()) + + By("getting the ironcore bucket") + ironcoreBucket := &storagev1alpha1.Bucket{} + ironcoreBucketKey := client.ObjectKey{Namespace: ns.Name, Name: res.Bucket.Metadata.Id} + Expect(k8sClient.Get(ctx, ironcoreBucketKey, ironcoreBucket)).To(Succeed()) + + By("generating the bucket events") + eventGeneratedTime := time.Now() + eventRecorder := k8sManager.GetEventRecorderFor("test-recorder") + eventRecorder.Event(ironcoreBucket, corev1.EventTypeNormal, "testing", "this is test bucket event") + + By("listing the bucket events with no filters") + Eventually(func(g Gomega) []*v1alpha1.Event { + resp, err := srv.ListEvents(ctx, &iri.ListEventsRequest{}) + g.Expect(err).NotTo(HaveOccurred()) + return resp.Events + }).Should((ConsistOf( + HaveField("Spec", SatisfyAll( + HaveField("InvolvedObjectMeta.Id", Equal(ironcoreBucket.Name)), + HaveField("Reason", Equal("testing")), + HaveField("Message", Equal("this is test bucket event")), + HaveField("Type", Equal(corev1.EventTypeNormal)), + )), + ))) + + By("listing the bucket events with matching label and time filters") + resp, err := srv.ListEvents(ctx, &iri.ListEventsRequest{Filter: &iri.EventFilter{ + LabelSelector: map[string]string{bucketpoolletv1alpha1.BucketUIDLabel: "foobar"}, + EventsFromTime: eventGeneratedTime.Unix(), + EventsToTime: time.Now().Unix(), + }}) + + Expect(err).NotTo(HaveOccurred()) + + Expect(resp.Events).To(ConsistOf( + HaveField("Spec", SatisfyAll( + HaveField("InvolvedObjectMeta.Id", Equal(ironcoreBucket.Name)), + HaveField("Reason", Equal("testing")), + HaveField("Message", Equal("this is test bucket event")), + HaveField("Type", Equal(corev1.EventTypeNormal)), + )), + ), + ) + + By("listing the bucket events with non matching label filter") + resp, err = srv.ListEvents(ctx, &iri.ListEventsRequest{Filter: &iri.EventFilter{ + LabelSelector: map[string]string{"foo": "bar"}, + EventsFromTime: eventGeneratedTime.Unix(), + EventsToTime: time.Now().Unix(), + }}) + Expect(err).NotTo(HaveOccurred()) + + Expect(resp.Events).To(BeEmpty()) + + By("listing the bucket events with matching label filter and non matching time filter") + resp, err = srv.ListEvents(ctx, &iri.ListEventsRequest{Filter: &iri.EventFilter{ + LabelSelector: map[string]string{bucketpoolletv1alpha1.BucketUIDLabel: "foobar"}, + EventsFromTime: eventGeneratedTime.Add(-10 * time.Minute).Unix(), + EventsToTime: eventGeneratedTime.Add(-5 * time.Minute).Unix(), + }}) + Expect(err).NotTo(HaveOccurred()) + + Expect(resp.Events).To(BeEmpty()) + }) +}) diff --git a/broker/bucketbroker/server/server.go b/broker/bucketbroker/server/server.go index 8ca96a6c7..b9ce4b99f 100644 --- a/broker/bucketbroker/server/server.go +++ b/broker/bucketbroker/server/server.go @@ -102,6 +102,7 @@ func setOptionsDefaults(o *Options) { var _ iri.BucketRuntimeServer = (*Server)(nil) +//+kubebuilder:rbac:groups="",resources=events,verbs=get;list;watch //+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=storage.ironcore.dev,resources=buckets,verbs=get;list;watch;create;update;patch;delete diff --git a/broker/bucketbroker/server/server_suite_test.go b/broker/bucketbroker/server/server_suite_test.go new file mode 100644 index 000000000..0f18a1662 --- /dev/null +++ b/broker/bucketbroker/server/server_suite_test.go @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package server_test + +import ( + "context" + "testing" + "time" + + "github.com/ironcore-dev/controller-utils/buildutils" + "github.com/ironcore-dev/controller-utils/modutils" + corev1alpha1 "github.com/ironcore-dev/ironcore/api/core/v1alpha1" + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + "github.com/ironcore-dev/ironcore/broker/bucketbroker/server" + utilsenvtest "github.com/ironcore-dev/ironcore/utils/envtest" + "github.com/ironcore-dev/ironcore/utils/envtest/apiserver" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var ( + cfg *rest.Config + testEnv *envtest.Environment + testEnvExt *utilsenvtest.EnvironmentExtensions + k8sClient client.Client +) + +const ( + eventuallyTimeout = 3 * time.Second + pollingInterval = 50 * time.Millisecond + consistentlyDuration = 1 * time.Second + apiServiceTimeout = 5 * time.Minute +) + +func TestServer(t *testing.T) { + SetDefaultConsistentlyPollingInterval(pollingInterval) + SetDefaultEventuallyPollingInterval(pollingInterval) + SetDefaultEventuallyTimeout(eventuallyTimeout) + SetDefaultConsistentlyDuration(consistentlyDuration) + + RegisterFailHandler(Fail) + RunSpecs(t, "Server Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + var err error + By("bootstrapping test environment") + testEnv = &envtest.Environment{} + testEnvExt = &utilsenvtest.EnvironmentExtensions{ + APIServiceDirectoryPaths: []string{ + modutils.Dir("github.com/ironcore-dev/ironcore", "config", "apiserver", "apiservice", "bases"), + }, + ErrorIfAPIServicePathIsMissing: true, + } + + cfg, err = utilsenvtest.StartWithExtensions(testEnv, testEnvExt) + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + DeferCleanup(utilsenvtest.StopWithExtensions, testEnv, testEnvExt) + + Expect(storagev1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) + + // Init package-level k8sClient + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + SetClient(k8sClient) + + apiSrv, err := apiserver.New(cfg, apiserver.Options{ + MainPath: "github.com/ironcore-dev/ironcore/cmd/ironcore-apiserver", + BuildOptions: []buildutils.BuildOption{buildutils.ModModeMod}, + ETCDServers: []string{testEnv.ControlPlane.Etcd.URL.String()}, + Host: testEnvExt.APIServiceInstallOptions.LocalServingHost, + Port: testEnvExt.APIServiceInstallOptions.LocalServingPort, + CertDir: testEnvExt.APIServiceInstallOptions.LocalServingCertDir, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(apiSrv.Start()).To(Succeed()) + DeferCleanup(apiSrv.Stop) + + Expect(utilsenvtest.WaitUntilAPIServicesReadyWithTimeout(apiServiceTimeout, testEnvExt, k8sClient, scheme.Scheme)).To(Succeed()) +}) + +func SetupTest() (*corev1.Namespace, *storagev1alpha1.BucketPool, *server.Server) { + var ( + ns = &corev1.Namespace{} + srv = &server.Server{} + bucketPool = &storagev1alpha1.BucketPool{} + ) + + BeforeEach(func(ctx SpecContext) { + *ns = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-ns-", + }, + } + Expect(k8sClient.Create(ctx, ns)).To(Succeed(), "failed to create test namespace") + DeferCleanup(k8sClient.Delete, ns) + + *bucketPool = storagev1alpha1.BucketPool{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-bp-", + Labels: map[string]string{"pool": "test-pool"}, + }, + } + Expect(k8sClient.Create(ctx, bucketPool)).To(Succeed()) + DeferCleanup(k8sClient.Delete, bucketPool) + + newSrv, err := server.New(cfg, server.Options{ + Namespace: ns.Name, + BucketPoolName: bucketPool.Name, + BucketPoolSelector: map[string]string{ + "pool": "test-pool", + }, + }) + Expect(err).NotTo(HaveOccurred()) + *srv = *newSrv + }) + + return ns, bucketPool, srv +} + +func SetupBucketClass(tps, iops string) *storagev1alpha1.BucketClass { + bucketClass := &storagev1alpha1.BucketClass{} + + BeforeEach(func(ctx SpecContext) { + *bucketClass = storagev1alpha1.BucketClass{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "bucket-class-", + }, + Capabilities: corev1alpha1.ResourceList{ + corev1alpha1.ResourceTPS: resource.MustParse(tps), + corev1alpha1.ResourceIOPS: resource.MustParse(iops), + }, + } + Expect(k8sClient.Create(ctx, bucketClass)).To(Succeed()) + DeferCleanup(func(ctx context.Context) error { + return client.IgnoreNotFound(k8sClient.Delete(ctx, bucketClass)) + }) + }) + + return bucketClass +} diff --git a/config/bucketpoollet-broker/broker-rbac/role.yaml b/config/bucketpoollet-broker/broker-rbac/role.yaml index dba5a3271..b046d838a 100644 --- a/config/bucketpoollet-broker/broker-rbac/role.yaml +++ b/config/bucketpoollet-broker/broker-rbac/role.yaml @@ -4,6 +4,14 @@ kind: Role metadata: name: broker-role rules: +- apiGroups: + - "" + resources: + - events + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/iri/apis/bucket/bucket.go b/iri/apis/bucket/bucket.go index c32b99023..0bdc1de12 100644 --- a/iri/apis/bucket/bucket.go +++ b/iri/apis/bucket/bucket.go @@ -10,6 +10,7 @@ import ( ) type RuntimeService interface { + ListEvents(context.Context, *api.ListEventsRequest) (*api.ListEventsResponse, error) ListBuckets(context.Context, *api.ListBucketsRequest) (*api.ListBucketsResponse, error) CreateBucket(context.Context, *api.CreateBucketRequest) (*api.CreateBucketResponse, error) ListBucketClasses(ctx context.Context, request *api.ListBucketClassesRequest) (*api.ListBucketClassesResponse, error) diff --git a/iri/apis/bucket/v1alpha1/api.pb.go b/iri/apis/bucket/v1alpha1/api.pb.go index a6bf88686..e2339c96e 100644 --- a/iri/apis/bucket/v1alpha1/api.pb.go +++ b/iri/apis/bucket/v1alpha1/api.pb.go @@ -15,6 +15,7 @@ import ( _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys" + v1alpha11 "github.com/ironcore-dev/ironcore/iri/apis/event/v1alpha1" v1alpha1 "github.com/ironcore-dev/ironcore/iri/apis/meta/v1alpha1" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" @@ -60,6 +61,75 @@ func (BucketState) EnumDescriptor() ([]byte, []int) { return fileDescriptor_00212fb1f9d3bf1c, []int{0} } +type EventFilter struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + LabelSelector map[string]string `protobuf:"bytes,2,rep,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + EventsFromTime int64 `protobuf:"varint,3,opt,name=events_from_time,json=eventsFromTime,proto3" json:"events_from_time,omitempty"` + EventsToTime int64 `protobuf:"varint,4,opt,name=events_to_time,json=eventsToTime,proto3" json:"events_to_time,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EventFilter) Reset() { *m = EventFilter{} } +func (*EventFilter) ProtoMessage() {} +func (*EventFilter) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{0} +} +func (m *EventFilter) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventFilter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventFilter.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventFilter) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventFilter.Merge(m, src) +} +func (m *EventFilter) XXX_Size() int { + return m.Size() +} +func (m *EventFilter) XXX_DiscardUnknown() { + xxx_messageInfo_EventFilter.DiscardUnknown(m) +} + +var xxx_messageInfo_EventFilter proto.InternalMessageInfo + +func (m *EventFilter) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *EventFilter) GetLabelSelector() map[string]string { + if m != nil { + return m.LabelSelector + } + return nil +} + +func (m *EventFilter) GetEventsFromTime() int64 { + if m != nil { + return m.EventsFromTime + } + return 0 +} + +func (m *EventFilter) GetEventsToTime() int64 { + if m != nil { + return m.EventsToTime + } + return 0 +} + type BucketFilter struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` LabelSelector map[string]string `protobuf:"bytes,2,rep,name=label_selector,json=labelSelector,proto3" json:"label_selector,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` @@ -70,7 +140,7 @@ type BucketFilter struct { func (m *BucketFilter) Reset() { *m = BucketFilter{} } func (*BucketFilter) ProtoMessage() {} func (*BucketFilter) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{0} + return fileDescriptor_00212fb1f9d3bf1c, []int{1} } func (m *BucketFilter) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -122,7 +192,7 @@ type BucketSpec struct { func (m *BucketSpec) Reset() { *m = BucketSpec{} } func (*BucketSpec) ProtoMessage() {} func (*BucketSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{1} + return fileDescriptor_00212fb1f9d3bf1c, []int{2} } func (m *BucketSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -168,7 +238,7 @@ type BucketStatus struct { func (m *BucketStatus) Reset() { *m = BucketStatus{} } func (*BucketStatus) ProtoMessage() {} func (*BucketStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{2} + return fileDescriptor_00212fb1f9d3bf1c, []int{3} } func (m *BucketStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -222,7 +292,7 @@ type Bucket struct { func (m *Bucket) Reset() { *m = Bucket{} } func (*Bucket) ProtoMessage() {} func (*Bucket) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{3} + return fileDescriptor_00212fb1f9d3bf1c, []int{4} } func (m *Bucket) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -282,7 +352,7 @@ type BucketClassCapabilities struct { func (m *BucketClassCapabilities) Reset() { *m = BucketClassCapabilities{} } func (*BucketClassCapabilities) ProtoMessage() {} func (*BucketClassCapabilities) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{4} + return fileDescriptor_00212fb1f9d3bf1c, []int{5} } func (m *BucketClassCapabilities) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -335,7 +405,7 @@ type BucketClass struct { func (m *BucketClass) Reset() { *m = BucketClass{} } func (*BucketClass) ProtoMessage() {} func (*BucketClass) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{5} + return fileDescriptor_00212fb1f9d3bf1c, []int{6} } func (m *BucketClass) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -388,7 +458,7 @@ type BucketAccess struct { func (m *BucketAccess) Reset() { *m = BucketAccess{} } func (*BucketAccess) ProtoMessage() {} func (*BucketAccess) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{6} + return fileDescriptor_00212fb1f9d3bf1c, []int{7} } func (m *BucketAccess) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -431,6 +501,96 @@ func (m *BucketAccess) GetSecretData() map[string][]byte { return nil } +type ListEventsRequest struct { + Filter *EventFilter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListEventsRequest) Reset() { *m = ListEventsRequest{} } +func (*ListEventsRequest) ProtoMessage() {} +func (*ListEventsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{8} +} +func (m *ListEventsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ListEventsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ListEventsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ListEventsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListEventsRequest.Merge(m, src) +} +func (m *ListEventsRequest) XXX_Size() int { + return m.Size() +} +func (m *ListEventsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ListEventsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ListEventsRequest proto.InternalMessageInfo + +func (m *ListEventsRequest) GetFilter() *EventFilter { + if m != nil { + return m.Filter + } + return nil +} + +type ListEventsResponse struct { + Events []*v1alpha11.Event `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListEventsResponse) Reset() { *m = ListEventsResponse{} } +func (*ListEventsResponse) ProtoMessage() {} +func (*ListEventsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{9} +} +func (m *ListEventsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ListEventsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ListEventsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ListEventsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListEventsResponse.Merge(m, src) +} +func (m *ListEventsResponse) XXX_Size() int { + return m.Size() +} +func (m *ListEventsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ListEventsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ListEventsResponse proto.InternalMessageInfo + +func (m *ListEventsResponse) GetEvents() []*v1alpha11.Event { + if m != nil { + return m.Events + } + return nil +} + type ListBucketsRequest struct { Filter *BucketFilter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -440,7 +600,7 @@ type ListBucketsRequest struct { func (m *ListBucketsRequest) Reset() { *m = ListBucketsRequest{} } func (*ListBucketsRequest) ProtoMessage() {} func (*ListBucketsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{7} + return fileDescriptor_00212fb1f9d3bf1c, []int{10} } func (m *ListBucketsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -485,7 +645,7 @@ type ListBucketsResponse struct { func (m *ListBucketsResponse) Reset() { *m = ListBucketsResponse{} } func (*ListBucketsResponse) ProtoMessage() {} func (*ListBucketsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{8} + return fileDescriptor_00212fb1f9d3bf1c, []int{11} } func (m *ListBucketsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -530,7 +690,7 @@ type CreateBucketRequest struct { func (m *CreateBucketRequest) Reset() { *m = CreateBucketRequest{} } func (*CreateBucketRequest) ProtoMessage() {} func (*CreateBucketRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{9} + return fileDescriptor_00212fb1f9d3bf1c, []int{12} } func (m *CreateBucketRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -575,7 +735,7 @@ type CreateBucketResponse struct { func (m *CreateBucketResponse) Reset() { *m = CreateBucketResponse{} } func (*CreateBucketResponse) ProtoMessage() {} func (*CreateBucketResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{10} + return fileDescriptor_00212fb1f9d3bf1c, []int{13} } func (m *CreateBucketResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -620,7 +780,7 @@ type DeleteBucketRequest struct { func (m *DeleteBucketRequest) Reset() { *m = DeleteBucketRequest{} } func (*DeleteBucketRequest) ProtoMessage() {} func (*DeleteBucketRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{11} + return fileDescriptor_00212fb1f9d3bf1c, []int{14} } func (m *DeleteBucketRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -664,7 +824,7 @@ type DeleteBucketResponse struct { func (m *DeleteBucketResponse) Reset() { *m = DeleteBucketResponse{} } func (*DeleteBucketResponse) ProtoMessage() {} func (*DeleteBucketResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{12} + return fileDescriptor_00212fb1f9d3bf1c, []int{15} } func (m *DeleteBucketResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -701,7 +861,7 @@ type ListBucketClassesRequest struct { func (m *ListBucketClassesRequest) Reset() { *m = ListBucketClassesRequest{} } func (*ListBucketClassesRequest) ProtoMessage() {} func (*ListBucketClassesRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{13} + return fileDescriptor_00212fb1f9d3bf1c, []int{16} } func (m *ListBucketClassesRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -739,7 +899,7 @@ type ListBucketClassesResponse struct { func (m *ListBucketClassesResponse) Reset() { *m = ListBucketClassesResponse{} } func (*ListBucketClassesResponse) ProtoMessage() {} func (*ListBucketClassesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{14} + return fileDescriptor_00212fb1f9d3bf1c, []int{17} } func (m *ListBucketClassesResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -777,6 +937,8 @@ func (m *ListBucketClassesResponse) GetBucketClasses() []*BucketClass { func init() { proto.RegisterEnum("bucket.v1alpha1.BucketState", BucketState_name, BucketState_value) + proto.RegisterType((*EventFilter)(nil), "bucket.v1alpha1.EventFilter") + proto.RegisterMapType((map[string]string)(nil), "bucket.v1alpha1.EventFilter.LabelSelectorEntry") proto.RegisterType((*BucketFilter)(nil), "bucket.v1alpha1.BucketFilter") proto.RegisterMapType((map[string]string)(nil), "bucket.v1alpha1.BucketFilter.LabelSelectorEntry") proto.RegisterType((*BucketSpec)(nil), "bucket.v1alpha1.BucketSpec") @@ -786,6 +948,8 @@ func init() { proto.RegisterType((*BucketClass)(nil), "bucket.v1alpha1.BucketClass") proto.RegisterType((*BucketAccess)(nil), "bucket.v1alpha1.BucketAccess") proto.RegisterMapType((map[string][]byte)(nil), "bucket.v1alpha1.BucketAccess.SecretDataEntry") + proto.RegisterType((*ListEventsRequest)(nil), "bucket.v1alpha1.ListEventsRequest") + proto.RegisterType((*ListEventsResponse)(nil), "bucket.v1alpha1.ListEventsResponse") proto.RegisterType((*ListBucketsRequest)(nil), "bucket.v1alpha1.ListBucketsRequest") proto.RegisterType((*ListBucketsResponse)(nil), "bucket.v1alpha1.ListBucketsResponse") proto.RegisterType((*CreateBucketRequest)(nil), "bucket.v1alpha1.CreateBucketRequest") @@ -799,59 +963,68 @@ func init() { func init() { proto.RegisterFile("api.proto", fileDescriptor_00212fb1f9d3bf1c) } var fileDescriptor_00212fb1f9d3bf1c = []byte{ - // 822 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x55, 0xdf, 0x4e, 0x3b, 0x45, - 0x14, 0xee, 0xb6, 0xbf, 0x5f, 0x85, 0xd3, 0xd2, 0x5f, 0x1d, 0x1a, 0xa9, 0x0b, 0x36, 0x64, 0xd5, - 0x04, 0x49, 0xe8, 0x4a, 0x8d, 0x89, 0x98, 0x18, 0x6d, 0x4b, 0xc1, 0x86, 0x0a, 0x66, 0xaa, 0x92, - 0x90, 0x98, 0x3a, 0xbb, 0x1d, 0x60, 0x64, 0xdb, 0x5d, 0x77, 0xa6, 0x24, 0xdc, 0xf9, 0x08, 0xde, - 0xf9, 0x10, 0x3e, 0x81, 0x6f, 0xc0, 0xa5, 0x97, 0x5e, 0x4a, 0x7d, 0x11, 0xb3, 0x33, 0xd3, 0x65, - 0x4b, 0xff, 0x80, 0x77, 0x33, 0x67, 0xbe, 0xf3, 0x9d, 0x6f, 0xce, 0xf9, 0x76, 0x07, 0x56, 0x49, - 0xc0, 0xaa, 0x41, 0xe8, 0x0b, 0x1f, 0xbd, 0x71, 0x46, 0xee, 0x0d, 0x15, 0xd5, 0xdb, 0x7d, 0xe2, - 0x05, 0xd7, 0x64, 0xdf, 0xdc, 0xbb, 0x62, 0xe2, 0x7a, 0xe4, 0x54, 0x5d, 0x7f, 0x60, 0x5f, 0xf9, - 0x57, 0xbe, 0x2d, 0x71, 0xce, 0xe8, 0x52, 0xee, 0xe4, 0x46, 0xae, 0x54, 0xbe, 0x59, 0x4f, 0xc0, - 0x59, 0xe8, 0x0f, 0x5d, 0x3f, 0xa4, 0x7b, 0x7d, 0x7a, 0x1b, 0x6f, 0x6c, 0x16, 0x32, 0x9b, 0x04, - 0x8c, 0xdb, 0x03, 0x2a, 0x88, 0x3d, 0xa9, 0x63, 0xc7, 0x12, 0xac, 0x3f, 0x0d, 0xc8, 0x37, 0xa4, - 0x8a, 0x23, 0xe6, 0x09, 0x1a, 0xa2, 0x02, 0xa4, 0x59, 0xbf, 0x6c, 0x6c, 0x1b, 0x3b, 0xab, 0x38, - 0xcd, 0xfa, 0xe8, 0x1c, 0x0a, 0x1e, 0x71, 0xa8, 0xd7, 0xe3, 0xd4, 0xa3, 0xae, 0xf0, 0xc3, 0x72, - 0x7a, 0x3b, 0xb3, 0x93, 0xab, 0x7d, 0x5c, 0x7d, 0x22, 0xbe, 0x9a, 0xa4, 0xa9, 0x76, 0xa2, 0x9c, - 0xae, 0x4e, 0x69, 0x0d, 0x45, 0x78, 0x87, 0xd7, 0xbc, 0x64, 0xcc, 0xfc, 0x0a, 0xd0, 0x2c, 0x08, - 0x15, 0x21, 0x73, 0x43, 0xef, 0x74, 0xfd, 0x68, 0x89, 0x4a, 0xf0, 0xfa, 0x96, 0x78, 0x23, 0x5a, - 0x4e, 0xcb, 0x98, 0xda, 0x7c, 0x9e, 0xfe, 0xcc, 0xb0, 0x2c, 0x00, 0x55, 0xb3, 0x1b, 0x50, 0x37, - 0xc2, 0xb9, 0x1e, 0xe1, 0x7c, 0x82, 0x93, 0x1b, 0xeb, 0x6e, 0x72, 0xbd, 0xae, 0x20, 0x62, 0xc4, - 0x51, 0x0d, 0x5e, 0x73, 0x41, 0x04, 0x95, 0x15, 0x0a, 0xb5, 0xad, 0x05, 0xb7, 0x88, 0xd0, 0x14, - 0x2b, 0x28, 0xfa, 0x14, 0xb2, 0xc4, 0x75, 0xa9, 0xa6, 0xce, 0xd5, 0xde, 0x5b, 0x90, 0x54, 0x97, - 0x20, 0xac, 0xc1, 0xd6, 0x1f, 0x06, 0x64, 0xd5, 0x01, 0x3a, 0x80, 0x95, 0x68, 0x00, 0x7d, 0x22, - 0x88, 0x2c, 0x1c, 0x71, 0x44, 0x81, 0x47, 0x86, 0x33, 0xe7, 0x67, 0xea, 0x8a, 0x6f, 0x34, 0x08, - 0xc7, 0x70, 0x64, 0xc3, 0x2b, 0x1e, 0x50, 0x57, 0x97, 0xde, 0x5c, 0xa4, 0x37, 0xa0, 0x2e, 0x96, - 0xc0, 0x48, 0x2d, 0x97, 0x77, 0x2d, 0x67, 0x96, 0xaa, 0x55, 0x0d, 0xc1, 0x1a, 0x6c, 0x7d, 0x09, - 0x1b, 0x2a, 0xde, 0x8c, 0xfa, 0xd6, 0x24, 0x01, 0x71, 0x98, 0xc7, 0x04, 0xa3, 0x3c, 0x9a, 0x89, - 0x08, 0xb8, 0x14, 0x9e, 0xc1, 0xd1, 0x12, 0x21, 0x78, 0xc5, 0xfc, 0x40, 0xf5, 0x23, 0x83, 0xe5, - 0xda, 0xf2, 0x21, 0x97, 0x20, 0x88, 0x20, 0x43, 0x32, 0xa0, 0x7a, 0x92, 0x72, 0x8d, 0x3a, 0x90, - 0x77, 0x13, 0xc4, 0xfa, 0x4e, 0x3b, 0x0b, 0x04, 0xce, 0x08, 0xc1, 0x53, 0xd9, 0x09, 0xeb, 0xaa, - 0xc6, 0x23, 0x13, 0x56, 0xe8, 0xb0, 0x1f, 0xf8, 0x6c, 0x28, 0x74, 0xd9, 0x78, 0x8f, 0x4e, 0x21, - 0xc7, 0xa9, 0x1b, 0x52, 0xd1, 0x93, 0x43, 0x50, 0x1e, 0xde, 0x5b, 0x3a, 0xc8, 0x6a, 0x57, 0x26, - 0x1c, 0x12, 0x41, 0x94, 0x81, 0x81, 0xc7, 0x01, 0xf3, 0x0b, 0x78, 0xf3, 0xe4, 0xf8, 0x39, 0xeb, - 0xe6, 0x93, 0xd6, 0x3d, 0x01, 0xd4, 0x61, 0x5c, 0xa8, 0x72, 0x1c, 0xd3, 0x5f, 0x46, 0x94, 0x8b, - 0x68, 0x74, 0x97, 0xf2, 0xf3, 0x89, 0x4d, 0xb2, 0xec, 0x1b, 0xc3, 0x1a, 0x6c, 0x7d, 0x0d, 0xeb, - 0x53, 0x64, 0x3c, 0xf0, 0x87, 0x9c, 0xa2, 0x7d, 0x78, 0x4b, 0xa5, 0x47, 0xa3, 0x8b, 0xae, 0xbb, - 0xb1, 0x80, 0x0e, 0x4f, 0x70, 0xd6, 0x11, 0xac, 0x37, 0x43, 0x4a, 0x04, 0xd5, 0x07, 0x5a, 0x97, - 0x0d, 0x59, 0x85, 0xd0, 0xba, 0x16, 0x12, 0x69, 0x98, 0x75, 0x0c, 0xa5, 0x69, 0x1e, 0x2d, 0xe9, - 0x7f, 0x13, 0xd5, 0x60, 0xfd, 0x90, 0x7a, 0xf4, 0xa9, 0xa0, 0x4d, 0x58, 0x55, 0x80, 0x5e, 0xfc, - 0xaf, 0x5a, 0x51, 0x81, 0x76, 0xdf, 0x7a, 0x07, 0x4a, 0xd3, 0x39, 0xaa, 0xb8, 0x65, 0x42, 0xf9, - 0xb1, 0x4d, 0xd2, 0x5c, 0x74, 0xd2, 0x79, 0xeb, 0x27, 0x78, 0x77, 0xce, 0x99, 0x56, 0xdd, 0x84, - 0x82, 0xae, 0xe6, 0xaa, 0x13, 0xdd, 0xcf, 0xad, 0x65, 0xc6, 0xc5, 0x6b, 0x4e, 0x92, 0x6c, 0xb7, - 0x3d, 0xf9, 0x3c, 0xe4, 0xaf, 0x05, 0x21, 0x28, 0x34, 0xbe, 0x6f, 0x9e, 0xb4, 0xbe, 0xeb, 0x7d, - 0xdb, 0x3a, 0x3d, 0x6c, 0x9f, 0x1e, 0x17, 0x53, 0xa8, 0x04, 0x45, 0x1d, 0xab, 0xff, 0x50, 0x6f, - 0x77, 0xea, 0x8d, 0x4e, 0xab, 0x68, 0xa0, 0x22, 0xe4, 0x75, 0xb4, 0x85, 0xf1, 0x19, 0x2e, 0xa6, - 0x6b, 0xbf, 0x67, 0x60, 0x4d, 0xdf, 0x6d, 0x34, 0x14, 0x6c, 0x40, 0xd1, 0x05, 0xe4, 0x12, 0x0e, - 0x40, 0xef, 0xcf, 0x08, 0x9b, 0x35, 0x9b, 0xf9, 0xc1, 0x72, 0x90, 0x6e, 0x5a, 0x0a, 0xfd, 0x08, - 0xf9, 0xe4, 0x2c, 0xd1, 0x6c, 0xde, 0x1c, 0xcb, 0x98, 0x1f, 0x3e, 0x83, 0x4a, 0xd2, 0x27, 0xa7, - 0x35, 0x87, 0x7e, 0x8e, 0x01, 0xe6, 0xd0, 0xcf, 0x1d, 0x79, 0x0a, 0x79, 0xf0, 0xf6, 0xcc, 0x60, - 0xd1, 0x47, 0x4b, 0xae, 0x3e, 0x6d, 0x0c, 0x73, 0xf7, 0x25, 0xd0, 0x49, 0xb5, 0xc6, 0xf9, 0xfd, - 0x43, 0xc5, 0xf8, 0xfb, 0xa1, 0x92, 0xfa, 0x75, 0x5c, 0x31, 0xee, 0xc7, 0x15, 0xe3, 0xaf, 0x71, - 0xc5, 0xf8, 0x67, 0x5c, 0x31, 0x7e, 0xfb, 0xb7, 0x92, 0xba, 0x38, 0x78, 0xf9, 0x73, 0xad, 0x8a, - 0xc6, 0x0f, 0xb6, 0x93, 0x95, 0xaf, 0xf5, 0x27, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x5b, 0xdb, - 0xde, 0xcc, 0x3d, 0x08, 0x00, 0x00, + // 962 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xdf, 0x6e, 0x1b, 0xc5, + 0x17, 0xf6, 0xda, 0xa9, 0x7f, 0xc9, 0xb1, 0xe3, 0xfa, 0x37, 0x09, 0xd4, 0x6c, 0x53, 0x2b, 0x5a, + 0x8a, 0x64, 0x2a, 0xc5, 0x4b, 0x0c, 0x95, 0x28, 0x12, 0x02, 0xdb, 0x71, 0x8a, 0x55, 0x93, 0xa2, + 0x49, 0x68, 0xa4, 0x4a, 0xc8, 0x8c, 0xd7, 0x27, 0xe9, 0xd2, 0xb5, 0x77, 0xd9, 0x19, 0x47, 0xca, + 0x1d, 0x77, 0xdc, 0xf2, 0x1e, 0x3c, 0x01, 0x6f, 0xd0, 0x4b, 0x2e, 0xb9, 0xa4, 0xe1, 0x05, 0x78, + 0x04, 0xb4, 0x33, 0xe3, 0xcd, 0xfa, 0xcf, 0x3a, 0x41, 0xe2, 0x6e, 0xe6, 0xcc, 0x77, 0xbe, 0xf3, + 0x9d, 0xb3, 0xdf, 0x8c, 0x0d, 0x1b, 0x2c, 0x70, 0xeb, 0x41, 0xe8, 0x0b, 0x9f, 0xdc, 0x1d, 0x4c, + 0x9c, 0xd7, 0x28, 0xea, 0x17, 0xfb, 0xcc, 0x0b, 0x5e, 0xb1, 0x7d, 0x73, 0xef, 0xdc, 0x15, 0xaf, + 0x26, 0x83, 0xba, 0xe3, 0x8f, 0xec, 0x73, 0xff, 0xdc, 0xb7, 0x25, 0x6e, 0x30, 0x39, 0x93, 0x3b, + 0xb9, 0x91, 0x2b, 0x95, 0x6f, 0x36, 0x13, 0x70, 0x37, 0xf4, 0xc7, 0x8e, 0x1f, 0xe2, 0xde, 0x10, + 0x2f, 0xe2, 0x8d, 0xed, 0x86, 0xae, 0xcd, 0x02, 0x97, 0xdb, 0x23, 0x14, 0xcc, 0x9e, 0xd6, 0xb1, + 0x63, 0x09, 0x66, 0xeb, 0xf6, 0x14, 0x78, 0x81, 0x63, 0xb1, 0x84, 0xc3, 0xfa, 0x39, 0x0b, 0x85, + 0x4e, 0x74, 0x78, 0xe8, 0x7a, 0x02, 0x43, 0x52, 0x82, 0xac, 0x3b, 0xac, 0x18, 0xbb, 0x46, 0x6d, + 0x83, 0x66, 0xdd, 0x21, 0x79, 0x01, 0x25, 0x8f, 0x0d, 0xd0, 0xeb, 0x73, 0xf4, 0xd0, 0x11, 0x7e, + 0x58, 0xc9, 0xee, 0xe6, 0x6a, 0x85, 0x86, 0x5d, 0x9f, 0xeb, 0xbf, 0x9e, 0x60, 0xa9, 0xf7, 0xa2, + 0x94, 0x63, 0x9d, 0xd1, 0x19, 0x8b, 0xf0, 0x92, 0x6e, 0x7a, 0xc9, 0x18, 0xa9, 0x41, 0x59, 0x6a, + 0xe2, 0xfd, 0xb3, 0xd0, 0x1f, 0xf5, 0x85, 0x3b, 0xc2, 0x4a, 0x6e, 0xd7, 0xa8, 0xe5, 0x68, 0x49, + 0xc5, 0x0f, 0x43, 0x7f, 0x74, 0xe2, 0x8e, 0x90, 0x3c, 0x04, 0x1d, 0xe9, 0x0b, 0x5f, 0xe1, 0xd6, + 0x24, 0xae, 0xa8, 0xa2, 0x27, 0x7e, 0x84, 0x32, 0xbf, 0x04, 0xb2, 0x58, 0x94, 0x94, 0x21, 0xf7, + 0x1a, 0x2f, 0x75, 0x3b, 0xd1, 0x92, 0x6c, 0xc3, 0x9d, 0x0b, 0xe6, 0x4d, 0xb0, 0x92, 0x95, 0x31, + 0xb5, 0xf9, 0x2c, 0xfb, 0xa9, 0x61, 0xfd, 0x66, 0x40, 0xb1, 0x25, 0x7b, 0x4a, 0x19, 0xc5, 0x69, + 0xca, 0x28, 0x3e, 0x5a, 0x18, 0x45, 0x92, 0xe6, 0xe6, 0x59, 0xfc, 0x07, 0xda, 0x2d, 0x00, 0x55, + 0xf3, 0x38, 0x40, 0x27, 0xc2, 0x39, 0x1e, 0xe3, 0x7c, 0x8a, 0x93, 0x1b, 0xeb, 0x72, 0xda, 0xde, + 0xb1, 0x60, 0x62, 0xc2, 0x49, 0x03, 0xee, 0x70, 0xc1, 0x04, 0xca, 0x0a, 0xa5, 0xc6, 0x4e, 0x4a, + 0x17, 0x11, 0x1a, 0xa9, 0x82, 0x92, 0xc7, 0x90, 0x67, 0x8e, 0x83, 0x9a, 0xba, 0xd0, 0x78, 0x90, + 0x92, 0xd4, 0x94, 0x20, 0xaa, 0xc1, 0xd6, 0xaf, 0x06, 0xe4, 0xd5, 0x01, 0x79, 0x02, 0xeb, 0x91, + 0x9d, 0x87, 0x4c, 0x30, 0x59, 0x38, 0xe2, 0x88, 0x02, 0xd7, 0x0c, 0xcf, 0x07, 0x3f, 0xa0, 0x23, + 0xbe, 0xd6, 0x20, 0x1a, 0xc3, 0x89, 0x0d, 0x6b, 0x3c, 0x40, 0x47, 0x97, 0xbe, 0x9f, 0xa6, 0x37, + 0x40, 0x87, 0x4a, 0x60, 0xa4, 0x96, 0xcb, 0x5e, 0xa5, 0xb3, 0xd2, 0xd5, 0xaa, 0x81, 0x50, 0x0d, + 0xb6, 0xbe, 0x80, 0x7b, 0x2a, 0xde, 0x8e, 0xe6, 0xd6, 0x66, 0x01, 0x1b, 0xb8, 0x9e, 0x2b, 0x5c, + 0xe4, 0xd1, 0x37, 0x11, 0x01, 0x97, 0xc2, 0x73, 0x34, 0x5a, 0x12, 0x02, 0x6b, 0xae, 0x1f, 0xa8, + 0x79, 0xe4, 0xa8, 0x5c, 0x5b, 0x3e, 0x14, 0x12, 0x04, 0x11, 0x64, 0xcc, 0x46, 0xa8, 0xbf, 0xa4, + 0x5c, 0x93, 0x1e, 0x14, 0x9d, 0x04, 0xb1, 0xee, 0xa9, 0x96, 0x22, 0x70, 0x41, 0x08, 0x9d, 0xc9, + 0x4e, 0x58, 0x57, 0x0d, 0x9e, 0x98, 0xb0, 0x8e, 0xe3, 0x61, 0xe0, 0xbb, 0x63, 0xa1, 0xcb, 0xc6, + 0x7b, 0x72, 0x04, 0x05, 0x8e, 0x4e, 0x88, 0xa2, 0x2f, 0x3f, 0x82, 0xf2, 0xf0, 0xde, 0xca, 0x0f, + 0x59, 0x3f, 0x96, 0x09, 0x07, 0x4c, 0x30, 0x65, 0x60, 0xe0, 0x71, 0xc0, 0xfc, 0x1c, 0xee, 0xce, + 0x1d, 0xdf, 0x64, 0xdd, 0x62, 0xd2, 0xba, 0x5d, 0xf8, 0x7f, 0xcf, 0xe5, 0x42, 0xbe, 0x1e, 0x9c, + 0xe2, 0x8f, 0x13, 0xe4, 0x82, 0x7c, 0x02, 0xf9, 0x33, 0x79, 0x7b, 0xb4, 0x47, 0x76, 0x56, 0xbd, + 0x36, 0x54, 0x63, 0xad, 0x36, 0x90, 0x24, 0x15, 0x0f, 0xfc, 0x31, 0x47, 0xb2, 0x07, 0x79, 0xf5, + 0x52, 0x54, 0x0c, 0xd9, 0xea, 0x3b, 0x75, 0xb9, 0x9d, 0xa3, 0xa2, 0x1a, 0x64, 0x3d, 0x53, 0x24, + 0xaa, 0xfd, 0x58, 0xd0, 0xe3, 0x39, 0x41, 0x0f, 0x56, 0xde, 0xf9, 0x58, 0xd1, 0x57, 0xb0, 0x35, + 0x43, 0xa6, 0x25, 0xed, 0xc3, 0xff, 0x54, 0xfa, 0x54, 0xd3, 0xbd, 0x14, 0x3a, 0x3a, 0xc5, 0x59, + 0x87, 0xb0, 0xd5, 0x0e, 0x91, 0x09, 0xd4, 0x07, 0x5a, 0x97, 0x0d, 0x79, 0x85, 0xd0, 0xba, 0x52, + 0x89, 0x34, 0xcc, 0x7a, 0x0a, 0xdb, 0xb3, 0x3c, 0x5a, 0xd2, 0xbf, 0x26, 0x6a, 0xc0, 0xd6, 0x01, + 0x7a, 0x38, 0x2f, 0xe8, 0x3e, 0x6c, 0x28, 0x40, 0x3f, 0x7e, 0x3b, 0xd7, 0x55, 0xa0, 0x3b, 0xb4, + 0xde, 0x85, 0xed, 0xd9, 0x1c, 0x55, 0xdc, 0x32, 0xa1, 0x72, 0x3d, 0x26, 0x69, 0x76, 0x9c, 0x4e, + 0xde, 0xfa, 0x1e, 0xde, 0x5b, 0x72, 0xa6, 0x55, 0xb7, 0xa1, 0xa4, 0xab, 0x39, 0xea, 0x44, 0xcf, + 0x73, 0x67, 0xd5, 0x45, 0xa2, 0x9b, 0x83, 0x24, 0xd9, 0xa3, 0xee, 0xf4, 0xba, 0xca, 0xa7, 0x8e, + 0x10, 0x28, 0xb5, 0xbe, 0x6d, 0x3f, 0xeb, 0x9c, 0xf4, 0xbf, 0xe9, 0x1c, 0x1d, 0x74, 0x8f, 0x9e, + 0x96, 0x33, 0x64, 0x1b, 0xca, 0x3a, 0xd6, 0x7c, 0xd1, 0xec, 0xf6, 0x9a, 0xad, 0x5e, 0xa7, 0x6c, + 0x90, 0x32, 0x14, 0x75, 0xb4, 0x43, 0xe9, 0x73, 0x5a, 0xce, 0x36, 0xfe, 0xce, 0xc1, 0xa6, 0xee, + 0x6d, 0x32, 0x8e, 0x7e, 0xab, 0xc8, 0x29, 0xc0, 0xb5, 0x27, 0x89, 0xb5, 0xa0, 0x6b, 0xc1, 0xfb, + 0xe6, 0xfb, 0x2b, 0x31, 0x7a, 0x62, 0x19, 0xf2, 0x12, 0x0a, 0x09, 0x6b, 0x91, 0xe5, 0x59, 0xb3, + 0x2e, 0x36, 0x1f, 0xae, 0x06, 0xc5, 0xdc, 0xdf, 0x41, 0x31, 0x69, 0x12, 0xb2, 0x98, 0xb7, 0xc4, + 0x8b, 0xe6, 0x07, 0x37, 0xa0, 0x92, 0xf4, 0x49, 0x1b, 0x2c, 0xa1, 0x5f, 0xe2, 0xac, 0x25, 0xf4, + 0x4b, 0xbd, 0x94, 0x21, 0x9e, 0x7a, 0x51, 0x66, 0x1c, 0x43, 0x3e, 0x5c, 0xd1, 0xfa, 0xac, 0xe3, + 0xcc, 0x47, 0xb7, 0x81, 0x4e, 0xab, 0xb5, 0x4e, 0xdf, 0xbc, 0xad, 0x1a, 0x7f, 0xbc, 0xad, 0x66, + 0x7e, 0xba, 0xaa, 0x1a, 0x6f, 0xae, 0xaa, 0xc6, 0xef, 0x57, 0x55, 0xe3, 0xcf, 0xab, 0xaa, 0xf1, + 0xcb, 0x5f, 0xd5, 0xcc, 0xcb, 0x27, 0xb7, 0xff, 0x8b, 0xa6, 0x8a, 0xc6, 0xff, 0xd1, 0x06, 0x79, + 0xf9, 0x07, 0xed, 0xe3, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x5c, 0x7e, 0x76, 0x8e, 0x74, 0x0a, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -866,6 +1039,7 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type BucketRuntimeClient interface { + ListEvents(ctx context.Context, in *ListEventsRequest, opts ...grpc.CallOption) (*ListEventsResponse, error) ListBuckets(ctx context.Context, in *ListBucketsRequest, opts ...grpc.CallOption) (*ListBucketsResponse, error) CreateBucket(ctx context.Context, in *CreateBucketRequest, opts ...grpc.CallOption) (*CreateBucketResponse, error) DeleteBucket(ctx context.Context, in *DeleteBucketRequest, opts ...grpc.CallOption) (*DeleteBucketResponse, error) @@ -880,6 +1054,15 @@ func NewBucketRuntimeClient(cc *grpc.ClientConn) BucketRuntimeClient { return &bucketRuntimeClient{cc} } +func (c *bucketRuntimeClient) ListEvents(ctx context.Context, in *ListEventsRequest, opts ...grpc.CallOption) (*ListEventsResponse, error) { + out := new(ListEventsResponse) + err := c.cc.Invoke(ctx, "/bucket.v1alpha1.BucketRuntime/ListEvents", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *bucketRuntimeClient) ListBuckets(ctx context.Context, in *ListBucketsRequest, opts ...grpc.CallOption) (*ListBucketsResponse, error) { out := new(ListBucketsResponse) err := c.cc.Invoke(ctx, "/bucket.v1alpha1.BucketRuntime/ListBuckets", in, out, opts...) @@ -918,6 +1101,7 @@ func (c *bucketRuntimeClient) ListBucketClasses(ctx context.Context, in *ListBuc // BucketRuntimeServer is the server API for BucketRuntime service. type BucketRuntimeServer interface { + ListEvents(context.Context, *ListEventsRequest) (*ListEventsResponse, error) ListBuckets(context.Context, *ListBucketsRequest) (*ListBucketsResponse, error) CreateBucket(context.Context, *CreateBucketRequest) (*CreateBucketResponse, error) DeleteBucket(context.Context, *DeleteBucketRequest) (*DeleteBucketResponse, error) @@ -928,6 +1112,9 @@ type BucketRuntimeServer interface { type UnimplementedBucketRuntimeServer struct { } +func (*UnimplementedBucketRuntimeServer) ListEvents(ctx context.Context, req *ListEventsRequest) (*ListEventsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListEvents not implemented") +} func (*UnimplementedBucketRuntimeServer) ListBuckets(ctx context.Context, req *ListBucketsRequest) (*ListBucketsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListBuckets not implemented") } @@ -945,6 +1132,24 @@ func RegisterBucketRuntimeServer(s *grpc.Server, srv BucketRuntimeServer) { s.RegisterService(&_BucketRuntime_serviceDesc, srv) } +func _BucketRuntime_ListEvents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListEventsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BucketRuntimeServer).ListEvents(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/bucket.v1alpha1.BucketRuntime/ListEvents", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BucketRuntimeServer).ListEvents(ctx, req.(*ListEventsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _BucketRuntime_ListBuckets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListBucketsRequest) if err := dec(in); err != nil { @@ -1021,6 +1226,10 @@ var _BucketRuntime_serviceDesc = grpc.ServiceDesc{ ServiceName: "bucket.v1alpha1.BucketRuntime", HandlerType: (*BucketRuntimeServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "ListEvents", + Handler: _BucketRuntime_ListEvents_Handler, + }, { MethodName: "ListBuckets", Handler: _BucketRuntime_ListBuckets_Handler, @@ -1042,6 +1251,65 @@ var _BucketRuntime_serviceDesc = grpc.ServiceDesc{ Metadata: "api.proto", } +func (m *EventFilter) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventFilter) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventFilter) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.EventsToTime != 0 { + i = encodeVarintApi(dAtA, i, uint64(m.EventsToTime)) + i-- + dAtA[i] = 0x20 + } + if m.EventsFromTime != 0 { + i = encodeVarintApi(dAtA, i, uint64(m.EventsFromTime)) + i-- + dAtA[i] = 0x18 + } + if len(m.LabelSelector) > 0 { + for k := range m.LabelSelector { + v := m.LabelSelector[k] + baseI := i + i -= len(v) + copy(dAtA[i:], v) + i = encodeVarintApi(dAtA, i, uint64(len(v))) + i-- + dAtA[i] = 0x12 + i -= len(k) + copy(dAtA[i:], k) + i = encodeVarintApi(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = encodeVarintApi(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x12 + } + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = encodeVarintApi(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *BucketFilter) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1346,7 +1614,7 @@ func (m *BucketAccess) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *ListBucketsRequest) Marshal() (dAtA []byte, err error) { +func (m *ListEventsRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1356,12 +1624,12 @@ func (m *ListBucketsRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ListBucketsRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *ListEventsRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ListBucketsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ListEventsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1381,7 +1649,7 @@ func (m *ListBucketsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *ListBucketsResponse) Marshal() (dAtA []byte, err error) { +func (m *ListEventsResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1391,20 +1659,20 @@ func (m *ListBucketsResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ListBucketsResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *ListEventsResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *ListBucketsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ListEventsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Buckets) > 0 { - for iNdEx := len(m.Buckets) - 1; iNdEx >= 0; iNdEx-- { + if len(m.Events) > 0 { + for iNdEx := len(m.Events) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.Buckets[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Events[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1418,7 +1686,7 @@ func (m *ListBucketsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *CreateBucketRequest) Marshal() (dAtA []byte, err error) { +func (m *ListBucketsRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1428,19 +1696,19 @@ func (m *CreateBucketRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *CreateBucketRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *ListBucketsRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *CreateBucketRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ListBucketsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Bucket != nil { + if m.Filter != nil { { - size, err := m.Bucket.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Filter.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1453,7 +1721,7 @@ func (m *CreateBucketRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *CreateBucketResponse) Marshal() (dAtA []byte, err error) { +func (m *ListBucketsResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1463,32 +1731,34 @@ func (m *CreateBucketResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *CreateBucketResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *ListBucketsResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *CreateBucketResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *ListBucketsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Bucket != nil { - { - size, err := m.Bucket.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if len(m.Buckets) > 0 { + for iNdEx := len(m.Buckets) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Buckets[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintApi(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintApi(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa } - i-- - dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *DeleteBucketRequest) Marshal() (dAtA []byte, err error) { +func (m *CreateBucketRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -1498,19 +1768,89 @@ func (m *DeleteBucketRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *DeleteBucketRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *CreateBucketRequest) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *DeleteBucketRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *CreateBucketRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.BucketId) > 0 { - i -= len(m.BucketId) - copy(dAtA[i:], m.BucketId) + if m.Bucket != nil { + { + size, err := m.Bucket.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintApi(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *CreateBucketResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CreateBucketResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CreateBucketResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Bucket != nil { + { + size, err := m.Bucket.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintApi(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *DeleteBucketRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeleteBucketRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeleteBucketRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BucketId) > 0 { + i -= len(m.BucketId) + copy(dAtA[i:], m.BucketId) i = encodeVarintApi(dAtA, i, uint64(len(m.BucketId))) i-- dAtA[i] = 0xa @@ -1612,6 +1952,33 @@ func encodeVarintApi(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } +func (m *EventFilter) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + sovApi(uint64(l)) + } + if len(m.LabelSelector) > 0 { + for k, v := range m.LabelSelector { + _ = k + _ = v + mapEntrySize := 1 + len(k) + sovApi(uint64(len(k))) + 1 + len(v) + sovApi(uint64(len(v))) + n += mapEntrySize + 1 + sovApi(uint64(mapEntrySize)) + } + } + if m.EventsFromTime != 0 { + n += 1 + sovApi(uint64(m.EventsFromTime)) + } + if m.EventsToTime != 0 { + n += 1 + sovApi(uint64(m.EventsToTime)) + } + return n +} + func (m *BucketFilter) Size() (n int) { if m == nil { return 0 @@ -1740,6 +2107,34 @@ func (m *BucketAccess) Size() (n int) { return n } +func (m *ListEventsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Filter != nil { + l = m.Filter.Size() + n += 1 + l + sovApi(uint64(l)) + } + return n +} + +func (m *ListEventsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Events) > 0 { + for _, e := range m.Events { + l = e.Size() + n += 1 + l + sovApi(uint64(l)) + } + } + return n +} + func (m *ListBucketsRequest) Size() (n int) { if m == nil { return 0 @@ -1846,6 +2241,29 @@ func sovApi(x uint64) (n int) { func sozApi(x uint64) (n int) { return sovApi(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (this *EventFilter) String() string { + if this == nil { + return "nil" + } + keysForLabelSelector := make([]string, 0, len(this.LabelSelector)) + for k, _ := range this.LabelSelector { + keysForLabelSelector = append(keysForLabelSelector, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForLabelSelector) + mapStringForLabelSelector := "map[string]string{" + for _, k := range keysForLabelSelector { + mapStringForLabelSelector += fmt.Sprintf("%v: %v,", k, this.LabelSelector[k]) + } + mapStringForLabelSelector += "}" + s := strings.Join([]string{`&EventFilter{`, + `Id:` + fmt.Sprintf("%v", this.Id) + `,`, + `LabelSelector:` + mapStringForLabelSelector + `,`, + `EventsFromTime:` + fmt.Sprintf("%v", this.EventsFromTime) + `,`, + `EventsToTime:` + fmt.Sprintf("%v", this.EventsToTime) + `,`, + `}`, + }, "") + return s +} func (this *BucketFilter) String() string { if this == nil { return "nil" @@ -1943,6 +2361,31 @@ func (this *BucketAccess) String() string { }, "") return s } +func (this *ListEventsRequest) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ListEventsRequest{`, + `Filter:` + strings.Replace(this.Filter.String(), "EventFilter", "EventFilter", 1) + `,`, + `}`, + }, "") + return s +} +func (this *ListEventsResponse) String() string { + if this == nil { + return "nil" + } + repeatedStringForEvents := "[]*Event{" + for _, f := range this.Events { + repeatedStringForEvents += strings.Replace(fmt.Sprintf("%v", f), "Event", "v1alpha11.Event", 1) + "," + } + repeatedStringForEvents += "}" + s := strings.Join([]string{`&ListEventsResponse{`, + `Events:` + repeatedStringForEvents + `,`, + `}`, + }, "") + return s +} func (this *ListBucketsRequest) String() string { if this == nil { return "nil" @@ -2024,20 +2467,267 @@ func (this *ListBucketClassesResponse) String() string { for _, f := range this.BucketClasses { repeatedStringForBucketClasses += strings.Replace(f.String(), "BucketClass", "BucketClass", 1) + "," } - repeatedStringForBucketClasses += "}" - s := strings.Join([]string{`&ListBucketClassesResponse{`, - `BucketClasses:` + repeatedStringForBucketClasses + `,`, - `}`, - }, "") - return s -} -func valueToStringApi(v interface{}) string { - rv := reflect.ValueOf(v) - if rv.IsNil() { - return "nil" + repeatedStringForBucketClasses += "}" + s := strings.Join([]string{`&ListBucketClassesResponse{`, + `BucketClasses:` + repeatedStringForBucketClasses + `,`, + `}`, + }, "") + return s +} +func valueToStringApi(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *EventFilter) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventFilter: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventFilter: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthApi + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LabelSelector", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthApi + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.LabelSelector == nil { + m.LabelSelector = make(map[string]string) + } + var mapkey string + var mapvalue string + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthApi + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthApi + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapvalue |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapvalue := int(stringLenmapvalue) + if intStringLenmapvalue < 0 { + return ErrInvalidLengthApi + } + postStringIndexmapvalue := iNdEx + intStringLenmapvalue + if postStringIndexmapvalue < 0 { + return ErrInvalidLengthApi + } + if postStringIndexmapvalue > l { + return io.ErrUnexpectedEOF + } + mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue]) + iNdEx = postStringIndexmapvalue + } else { + iNdEx = entryPreIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.LabelSelector[mapkey] = mapvalue + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EventsFromTime", wireType) + } + m.EventsFromTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EventsFromTime |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EventsToTime", wireType) + } + m.EventsToTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EventsToTime |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF } - pv := reflect.Indirect(rv).Interface() - return fmt.Sprintf("*%v", pv) + return nil } func (m *BucketFilter) Unmarshal(dAtA []byte) error { l := len(dAtA) @@ -3009,6 +3699,176 @@ func (m *BucketAccess) Unmarshal(dAtA []byte) error { } return nil } +func (m *ListEventsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ListEventsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ListEventsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Filter", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthApi + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Filter == nil { + m.Filter = &EventFilter{} + } + if err := m.Filter.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ListEventsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ListEventsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ListEventsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Events", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthApi + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Events = append(m.Events, &v1alpha11.Event{}) + if err := m.Events[len(m.Events)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipApi(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ListBucketsRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/iri/apis/bucket/v1alpha1/api.proto b/iri/apis/bucket/v1alpha1/api.proto index 793f6f9ca..3ceb9f611 100644 --- a/iri/apis/bucket/v1alpha1/api.proto +++ b/iri/apis/bucket/v1alpha1/api.proto @@ -5,6 +5,7 @@ option go_package = "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; import "github.com/ironcore-dev/ironcore/iri/apis/meta/v1alpha1/api.proto"; +import "github.com/ironcore-dev/ironcore/iri/apis/event/v1alpha1/api.proto"; option (gogoproto.goproto_stringer_all) = false; option (gogoproto.stringer_all) = true; @@ -15,6 +16,7 @@ option (gogoproto.unmarshaler_all) = true; option (gogoproto.goproto_unrecognized_all) = false; service BucketRuntime { + rpc ListEvents(ListEventsRequest) returns (ListEventsResponse) {}; rpc ListBuckets(ListBucketsRequest) returns (ListBucketsResponse) {}; rpc CreateBucket(CreateBucketRequest) returns (CreateBucketResponse) {}; rpc DeleteBucket(DeleteBucketRequest) returns (DeleteBucketResponse) {}; @@ -22,6 +24,13 @@ service BucketRuntime { rpc ListBucketClasses(ListBucketClassesRequest) returns (ListBucketClassesResponse) {}; } +message EventFilter { + string id = 1; + map label_selector = 2; + int64 events_from_time = 3; + int64 events_to_time = 4; +} + message BucketFilter { string id = 1; map label_selector = 2; @@ -63,6 +72,14 @@ enum BucketState { BUCKET_ERROR = 2; } +message ListEventsRequest { + EventFilter filter = 1; +} + +message ListEventsResponse { + repeated event.v1alpha1.Event events = 1; +} + message ListBucketsRequest { BucketFilter filter = 1; } diff --git a/iri/apis/machine/v1alpha1/api.proto b/iri/apis/machine/v1alpha1/api.proto index 49021e9dd..ac22fc9f2 100644 --- a/iri/apis/machine/v1alpha1/api.proto +++ b/iri/apis/machine/v1alpha1/api.proto @@ -261,4 +261,4 @@ message ExecRequest { message ExecResponse { string url = 1; -} \ No newline at end of file +} diff --git a/iri/remote/bucket/runtime.go b/iri/remote/bucket/runtime.go index 9666ddcd8..4709d954a 100644 --- a/iri/remote/bucket/runtime.go +++ b/iri/remote/bucket/runtime.go @@ -30,6 +30,10 @@ func NewRemoteRuntime(endpoint string) (bucket.RuntimeService, error) { }, nil } +func (r *remoteRuntime) ListEvents(ctx context.Context, req *iri.ListEventsRequest) (*iri.ListEventsResponse, error) { + return r.client.ListEvents(ctx, req) +} + func (r *remoteRuntime) ListBuckets(ctx context.Context, request *iri.ListBucketsRequest) (*iri.ListBucketsResponse, error) { return r.client.ListBuckets(ctx, request) } diff --git a/iri/testing/bucket/fake.go b/iri/testing/bucket/fake.go new file mode 100644 index 000000000..5fa48c9c2 --- /dev/null +++ b/iri/testing/bucket/fake.go @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package bucket + +import ( + "context" + "sync" + "time" + + irievent "github.com/ironcore-dev/ironcore/iri/apis/event/v1alpha1" + "k8s.io/apimachinery/pkg/labels" + + "github.com/ironcore-dev/ironcore/broker/common/idgen" + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type FakeBucket struct { + iri.Bucket +} + +type FakeBucketClass struct { + iri.BucketClass +} + +type FakeEvent struct { + irievent.Event +} + +type FakeRuntimeService struct { + sync.Mutex + + idGen idgen.IDGen + + Buckets map[string]*FakeBucket + BucketClasses map[string]*FakeBucketClass + Events []*FakeEvent +} + +func NewFakeRuntimeService() *FakeRuntimeService { + return &FakeRuntimeService{ + idGen: idgen.Default, + + Buckets: make(map[string]*FakeBucket), + BucketClasses: make(map[string]*FakeBucketClass), + Events: []*FakeEvent{}, + } +} + +func (r *FakeRuntimeService) SetBuckets(buckets []*FakeBucket) { + r.Lock() + defer r.Unlock() + + r.Buckets = make(map[string]*FakeBucket) + for _, bucket := range buckets { + r.Buckets[bucket.Metadata.Id] = bucket + } +} + +func (r *FakeRuntimeService) SetBucketClasses(bucketClasses []*FakeBucketClass) { + r.Lock() + defer r.Unlock() + + r.BucketClasses = make(map[string]*FakeBucketClass) + for _, class := range bucketClasses { + r.BucketClasses[class.Name] = class + } +} + +func (r *FakeRuntimeService) SetEvents(events []*FakeEvent) { + r.Lock() + defer r.Unlock() + + r.Events = events +} + +func (r *FakeRuntimeService) ListEvents(ctx context.Context, req *iri.ListEventsRequest) (*iri.ListEventsResponse, error) { + r.Lock() + defer r.Unlock() + + var res []*irievent.Event + for _, e := range r.Events { + event := e.Event + res = append(res, &event) + } + + return &iri.ListEventsResponse{Events: res}, nil +} + +func (r *FakeRuntimeService) ListBuckets(ctx context.Context, req *iri.ListBucketsRequest) (*iri.ListBucketsResponse, error) { + r.Lock() + defer r.Unlock() + + filter := req.Filter + + var res []*iri.Bucket + for _, v := range r.Buckets { + if filter != nil { + if filter.Id != "" && filter.Id != v.Metadata.Id { + continue + } + if filter.LabelSelector != nil && !filterInLabels(filter.LabelSelector, v.Metadata.Labels) { + continue + } + } + + bucket := v.Bucket + res = append(res, &bucket) + } + return &iri.ListBucketsResponse{Buckets: res}, nil +} + +func (r *FakeRuntimeService) CreateBucket(ctx context.Context, req *iri.CreateBucketRequest) (*iri.CreateBucketResponse, error) { + r.Lock() + defer r.Unlock() + + bucket := *req.Bucket + bucket.Metadata.Id = r.idGen.Generate() + bucket.Metadata.CreatedAt = time.Now().UnixNano() + bucket.Status = &iri.BucketStatus{} + + r.Buckets[bucket.Metadata.Id] = &FakeBucket{ + Bucket: bucket, + } + + return &iri.CreateBucketResponse{ + Bucket: &bucket, + }, nil +} + +func (r *FakeRuntimeService) DeleteBucket(ctx context.Context, req *iri.DeleteBucketRequest) (*iri.DeleteBucketResponse, error) { + r.Lock() + defer r.Unlock() + + bucketID := req.BucketId + if _, ok := r.Buckets[bucketID]; !ok { + return nil, status.Errorf(codes.NotFound, "bucket %q not found", bucketID) + } + + delete(r.Buckets, bucketID) + return &iri.DeleteBucketResponse{}, nil +} + +func (r *FakeRuntimeService) ListBucketClasses(ctx context.Context, req *iri.ListBucketClassesRequest) (*iri.ListBucketClassesResponse, error) { + r.Lock() + defer r.Unlock() + + var res []*iri.BucketClass + for _, b := range r.BucketClasses { + bucketClass := b.BucketClass + res = append(res, &bucketClass) + } + return &iri.ListBucketClassesResponse{BucketClasses: res}, nil +} + +func filterInLabels(labelSelector, lbls map[string]string) bool { + return labels.SelectorFromSet(labelSelector).Matches(labels.Set(lbls)) +} diff --git a/irictl-bucket/cmd/irictl-bucket/irictlbucket/get/event/event.go b/irictl-bucket/cmd/irictl-bucket/irictlbucket/get/event/event.go new file mode 100644 index 000000000..ce90c6c4f --- /dev/null +++ b/irictl-bucket/cmd/irictl-bucket/irictlbucket/get/event/event.go @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package event + +import ( + "context" + "fmt" + "time" + + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + "github.com/ironcore-dev/ironcore/irictl-bucket/cmd/irictl-bucket/irictlbucket/common" + clicommon "github.com/ironcore-dev/ironcore/irictl/cmd" + "github.com/ironcore-dev/ironcore/irictl/renderer" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + ctrl "sigs.k8s.io/controller-runtime" +) + +type Options struct { + Labels map[string]string + Duration time.Duration +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringToStringVarP(&o.Labels, "labels", "l", o.Labels, "Labels to filter the events by.") + fs.DurationVarP(&o.Duration, "duration", "d", 60*time.Minute, "Duration to filter the events by.") +} + +func Command(streams clicommon.Streams, clientFactory common.ClientFactory) *cobra.Command { + var ( + opts Options + outputOpts = common.NewOutputOptions() + ) + + cmd := &cobra.Command{ + Use: "events", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + log := ctrl.LoggerFrom(ctx) + + client, cleanup, err := clientFactory.New() + if err != nil { + return err + } + defer func() { + if err := cleanup(); err != nil { + log.Error(err, "Error cleaning up") + } + }() + + render, err := outputOpts.Renderer("table") + if err != nil { + return err + } + + return Run(cmd.Context(), streams, client, render, opts) + }, + } + + outputOpts.AddFlags(cmd.Flags()) + opts.AddFlags(cmd.Flags()) + + return cmd +} + +func Run( + ctx context.Context, + streams clicommon.Streams, + client iri.BucketRuntimeClient, + render renderer.Renderer, + opts Options, +) error { + var filter *iri.EventFilter = &iri.EventFilter{ + LabelSelector: opts.Labels, + EventsFromTime: time.Now().Add(-1 * opts.Duration).Unix(), + EventsToTime: time.Now().Unix(), + } + + res, err := client.ListEvents(ctx, &iri.ListEventsRequest{Filter: filter}) + if err != nil { + return fmt.Errorf("error listing events: %w", err) + } + + return render.Render(res.Events, streams.Out) +} diff --git a/irictl-bucket/cmd/irictl-bucket/irictlbucket/get/get.go b/irictl-bucket/cmd/irictl-bucket/irictlbucket/get/get.go index 36d4c8012..9d4c993b2 100644 --- a/irictl-bucket/cmd/irictl-bucket/irictlbucket/get/get.go +++ b/irictl-bucket/cmd/irictl-bucket/irictlbucket/get/get.go @@ -7,6 +7,7 @@ import ( "github.com/ironcore-dev/ironcore/irictl-bucket/cmd/irictl-bucket/irictlbucket/common" "github.com/ironcore-dev/ironcore/irictl-bucket/cmd/irictl-bucket/irictlbucket/get/bucket" "github.com/ironcore-dev/ironcore/irictl-bucket/cmd/irictl-bucket/irictlbucket/get/bucketclass" + "github.com/ironcore-dev/ironcore/irictl-bucket/cmd/irictl-bucket/irictlbucket/get/event" irictlcmd "github.com/ironcore-dev/ironcore/irictl/cmd" "github.com/spf13/cobra" ) @@ -19,6 +20,7 @@ func Command(streams irictlcmd.Streams, clientFactory common.ClientFactory) *cob cmd.AddCommand( bucket.Command(streams, clientFactory), bucketclass.Command(streams, clientFactory), + event.Command(streams, clientFactory), ) return cmd diff --git a/irictl-bucket/tableconverters/event.go b/irictl-bucket/tableconverters/event.go new file mode 100644 index 000000000..ab15a77cf --- /dev/null +++ b/irictl-bucket/tableconverters/event.go @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package tableconverters + +import ( + iri "github.com/ironcore-dev/ironcore/iri/apis/event/v1alpha1" + "github.com/ironcore-dev/ironcore/irictl/api" + "github.com/ironcore-dev/ironcore/irictl/tableconverter" + bucketpoolletv1alpha1 "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/api/v1alpha1" +) + +const ( + RootBucketName = "downward-api.bucketpoollet.ironcore.dev/root-bucket-name" + RootBucketNamespace = "downward-api.bucketpoollet.ironcore.dev/root-bucket-namespace" +) + +var ( + eventHeaders = []api.Header{ + {Name: "InvolvedBucketName"}, + {Name: "Type"}, + {Name: "Reason"}, + {Name: "Message"}, + {Name: "RootBucketName"}, + {Name: "RootBucketNamespace"}, + } + + Events = tableconverter.Funcs[*iri.Event]{ + Headers: tableconverter.Headers(eventHeaders), + Rows: tableconverter.SingleRowFrom(func(event *iri.Event) (api.Row, error) { + return api.Row{ + event.Spec.GetInvolvedObjectMeta().Id, + event.Spec.Type, + event.Spec.Reason, + event.Spec.Message, + getRootBucketName(event.Spec.GetInvolvedObjectMeta().Labels), + getRootBucketNamespace(event.Spec.GetInvolvedObjectMeta().Labels), + }, nil + }), + } + + EventsSlice = tableconverter.SliceFuncs[*iri.Event](Events) +) + +func getRootBucketName(labels map[string]string) string { + var rootBucketName string + rootBucketName, ok := labels[RootBucketName] + if !ok { + return labels[bucketpoolletv1alpha1.BucketNameLabel] + } + return rootBucketName +} +func getRootBucketNamespace(labels map[string]string) string { + var rootBucketNamespace string + rootBucketNamespace, ok := labels[RootBucketNamespace] + if !ok { + return labels[bucketpoolletv1alpha1.BucketNamespaceLabel] + } + return rootBucketNamespace +} + +func init() { + RegistryBuilder.Register( + tableconverter.ToTagAndTypedAny[*iri.Event](Events), + tableconverter.ToTagAndTypedAny[[]*iri.Event](EventsSlice), + ) +} diff --git a/poollet/bucketpoollet/bem/bem.go b/poollet/bucketpoollet/bem/bem.go new file mode 100644 index 000000000..a14cf6a73 --- /dev/null +++ b/poollet/bucketpoollet/bem/bem.go @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package bem + +import ( + "context" + "fmt" + "time" + + "github.com/go-logr/logr" + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + "github.com/ironcore-dev/ironcore/iri/apis/bucket" + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/api/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +type BucketEventMapper struct { + manager.Runnable + record.EventRecorder + client.Client + + bucketRuntime bucket.RuntimeService + + relistPeriod time.Duration + lastFetched time.Time +} + +func (m *BucketEventMapper) relist(ctx context.Context, log logr.Logger) error { + log.V(1).Info("Relisting bucket cluster events") + toEventFilterTime := time.Now() + res, err := m.bucketRuntime.ListEvents(ctx, &iri.ListEventsRequest{ + Filter: &iri.EventFilter{EventsFromTime: m.lastFetched.Unix(), EventsToTime: toEventFilterTime.Unix()}, + }) + if err != nil { + return fmt.Errorf("error listing bucket cluster events: %w", err) + } + + m.lastFetched = toEventFilterTime + for _, bucketEvent := range res.Events { + if bucketEvent.Spec.InvolvedObjectMeta.Labels != nil { + involvedBucket := &storagev1alpha1.Bucket{ + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID(bucketEvent.Spec.InvolvedObjectMeta.Labels[v1alpha1.BucketUIDLabel]), + Name: bucketEvent.Spec.InvolvedObjectMeta.Labels[v1alpha1.BucketNameLabel], + Namespace: bucketEvent.Spec.InvolvedObjectMeta.Labels[v1alpha1.BucketNamespaceLabel], + }, + } + m.Eventf(involvedBucket, bucketEvent.Spec.Type, bucketEvent.Spec.Reason, bucketEvent.Spec.Message) + } + } + + return nil +} + +func (m *BucketEventMapper) Start(ctx context.Context) error { + log := ctrl.LoggerFrom(ctx).WithName("bem") + m.lastFetched = time.Now() + wait.UntilWithContext(ctx, func(ctx context.Context) { + if err := m.relist(ctx, log); err != nil { + log.Error(err, "Error relisting") + } + }, m.relistPeriod) + return nil +} + +type BucketEventMapperOptions struct { + RelistPeriod time.Duration +} + +func setBucketEventMapperOptionsDefaults(o *BucketEventMapperOptions) { + if o.RelistPeriod == 0 { + o.RelistPeriod = 1 * time.Minute + } +} + +func NewBucketEventMapper(client client.Client, runtime bucket.RuntimeService, recorder record.EventRecorder, opts BucketEventMapperOptions) *BucketEventMapper { + setBucketEventMapperOptionsDefaults(&opts) + return &BucketEventMapper{ + Client: client, + bucketRuntime: runtime, + relistPeriod: opts.RelistPeriod, + EventRecorder: recorder, + } +} diff --git a/poollet/bucketpoollet/bem/bem_suite_test.go b/poollet/bucketpoollet/bem/bem_suite_test.go new file mode 100644 index 000000000..de2335f59 --- /dev/null +++ b/poollet/bucketpoollet/bem/bem_suite_test.go @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package bem_test + +import ( + "testing" + "time" + + "github.com/ironcore-dev/controller-utils/buildutils" + "github.com/ironcore-dev/controller-utils/modutils" + corev1alpha1 "github.com/ironcore-dev/ironcore/api/core/v1alpha1" + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + utilsenvtest "github.com/ironcore-dev/ironcore/utils/envtest" + "github.com/ironcore-dev/ironcore/utils/envtest/apiserver" + "github.com/ironcore-dev/ironcore/utils/envtest/controllermanager" + "github.com/ironcore-dev/ironcore/utils/envtest/process" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var ( + cfg *rest.Config + testEnv *envtest.Environment + testEnvExt *utilsenvtest.EnvironmentExtensions + k8sClient client.Client +) + +const ( + eventuallyTimeout = 3 * time.Second + apiServiceTimeout = 5 * time.Minute + + controllerManagerService = "controller-manager" +) + +func TestControllers(t *testing.T) { + SetDefaultEventuallyTimeout(eventuallyTimeout) + + RegisterFailHandler(Fail) + RunSpecs(t, "Bucket event mapper Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + var err error + By("bootstrapping test environment") + testEnv = &envtest.Environment{} + testEnvExt = &utilsenvtest.EnvironmentExtensions{ + APIServiceDirectoryPaths: []string{ + modutils.Dir("github.com/ironcore-dev/ironcore", "config", "apiserver", "apiservice", "bases"), + }, + ErrorIfAPIServicePathIsMissing: true, + AdditionalServices: []utilsenvtest.AdditionalService{ + { + Name: controllerManagerService, + }, + }, + } + + cfg, err = utilsenvtest.StartWithExtensions(testEnv, testEnvExt) + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + DeferCleanup(utilsenvtest.StopWithExtensions, testEnv, testEnvExt) + + Expect(storagev1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) + + // Init package-level k8sClient + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + SetClient(k8sClient) + + apiSrv, err := apiserver.New(cfg, apiserver.Options{ + MainPath: "github.com/ironcore-dev/ironcore/cmd/ironcore-apiserver", + BuildOptions: []buildutils.BuildOption{buildutils.ModModeMod}, + ETCDServers: []string{testEnv.ControlPlane.Etcd.URL.String()}, + Host: testEnvExt.APIServiceInstallOptions.LocalServingHost, + Port: testEnvExt.APIServiceInstallOptions.LocalServingPort, + CertDir: testEnvExt.APIServiceInstallOptions.LocalServingCertDir, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(apiSrv.Start()).To(Succeed()) + DeferCleanup(apiSrv.Stop) + + Expect(utilsenvtest.WaitUntilAPIServicesReadyWithTimeout(apiServiceTimeout, testEnvExt, k8sClient, scheme.Scheme)).To(Succeed()) + + ctrlMgr, err := controllermanager.New(cfg, controllermanager.Options{ + Args: process.EmptyArgs().Set("controllers", "*"), + MainPath: "github.com/ironcore-dev/ironcore/cmd/ironcore-controller-manager", + BuildOptions: []buildutils.BuildOption{buildutils.ModModeMod}, + Host: testEnvExt.GetAdditionalServiceHost(controllerManagerService), + Port: testEnvExt.GetAdditionalServicePort(controllerManagerService), + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(ctrlMgr.Start()).To(Succeed()) + DeferCleanup(ctrlMgr.Stop) +}) + +func SetupTest() (*corev1.Namespace, *storagev1alpha1.BucketPool, *storagev1alpha1.BucketClass) { + var ( + ns = &corev1.Namespace{} + bp = &storagev1alpha1.BucketPool{} + bc = &storagev1alpha1.BucketClass{} + ) + + BeforeEach(func(ctx SpecContext) { + *ns = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-ns-", + }, + } + Expect(k8sClient.Create(ctx, ns)).To(Succeed(), "failed to create test namespace") + DeferCleanup(k8sClient.Delete, ns) + + *bp = storagev1alpha1.BucketPool{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-bp-", + }, + } + Expect(k8sClient.Create(ctx, bp)).To(Succeed(), "failed to create test bucket pool") + DeferCleanup(k8sClient.Delete, bp) + + *bc = storagev1alpha1.BucketClass{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-bc-", + }, + Capabilities: corev1alpha1.ResourceList{ + corev1alpha1.ResourceTPS: resource.MustParse("250Mi"), + corev1alpha1.ResourceIOPS: resource.MustParse("15000"), + }, + } + Expect(k8sClient.Create(ctx, bc)).To(Succeed(), "failed to create test bucket class") + DeferCleanup(k8sClient.Delete, bc) + }) + return ns, bp, bc +} diff --git a/poollet/bucketpoollet/bem/bem_test.go b/poollet/bucketpoollet/bem/bem_test.go new file mode 100644 index 000000000..775eaffed --- /dev/null +++ b/poollet/bucketpoollet/bem/bem_test.go @@ -0,0 +1,145 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package bem_test + +import ( + "context" + "fmt" + "time" + + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + irievent "github.com/ironcore-dev/ironcore/iri/apis/event/v1alpha1" + "github.com/ironcore-dev/ironcore/iri/apis/meta/v1alpha1" + fakebucket "github.com/ironcore-dev/ironcore/iri/testing/bucket" + "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/bcm" + "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/bem" + "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/controllers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + metricserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" +) + +var _ = Describe("BucketEventMapper", func() { + var srv = &fakebucket.FakeRuntimeService{} + ns, bp, bc := SetupTest() + + BeforeEach(func(ctx SpecContext) { + *srv = *fakebucket.NewFakeRuntimeService() + srv.SetBucketClasses([]*fakebucket.FakeBucketClass{ + { + BucketClass: iri.BucketClass{ + Name: bc.Name, + Capabilities: &iri.BucketClassCapabilities{ + Tps: 262144000, + Iops: 15000, + }, + }, + }, + }) + + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + Metrics: metricserver.Options{ + BindAddress: "0", + }, + }) + Expect(err).ToNot(HaveOccurred()) + + bucketClassMapper := bcm.NewGeneric(srv, bcm.GenericOptions{ + RelistPeriod: 2 * time.Second, + }) + Expect(k8sManager.Add(bucketClassMapper)).To(Succeed()) + + bucketEventMapper := bem.NewBucketEventMapper(k8sManager.GetClient(), srv, k8sManager.GetEventRecorderFor("test"), bem.BucketEventMapperOptions{ + RelistPeriod: 2 * time.Second, + }) + Expect(k8sManager.Add(bucketEventMapper)).To(Succeed()) + + Expect((&controllers.BucketReconciler{ + EventRecorder: &record.FakeRecorder{}, + Client: k8sManager.GetClient(), + BucketRuntime: srv, + BucketClassMapper: bucketClassMapper, + BucketPoolName: bp.Name, + }).SetupWithManager(k8sManager)).To(Succeed()) + + mgrCtx, cancel := context.WithCancel(context.Background()) + DeferCleanup(cancel) + + go func() { + defer GinkgoRecover() + Expect(k8sManager.Start(mgrCtx)).To(Succeed(), "failed to start manager") + }() + }) + + It("should get event list for bucket", func(ctx SpecContext) { + By("creating a bucket") + bucket := &storagev1alpha1.Bucket{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "bucket-", + }, + Spec: storagev1alpha1.BucketSpec{ + BucketClassRef: &corev1.LocalObjectReference{Name: bc.Name}, + BucketPoolRef: &corev1.LocalObjectReference{Name: bp.Name}, + }, + } + Expect(k8sClient.Create(ctx, bucket)).To(Succeed()) + + By("waiting for the runtime to report the bucket") + Eventually(srv).Should(SatisfyAll( + HaveField("Buckets", HaveLen(1)), + )) + _, iriBucket := GetSingleMapEntry(srv.Buckets) + By("setting an event for iri bucket") + eventList := []*fakebucket.FakeEvent{{ + Event: irievent.Event{ + Spec: &irievent.EventSpec{ + InvolvedObjectMeta: &v1alpha1.ObjectMetadata{ + Labels: iriBucket.Metadata.Labels, + }, + Reason: "testing", + Message: "this is test bucket event", + Type: "Normal", + EventTime: time.Now().Unix(), + }}, + }, + } + srv.SetEvents(eventList) + + By("validating event has been emitted for correct bucket") + bucketEventList := &corev1.EventList{} + selectorField := fields.Set{} + selectorField["involvedObject.name"] = bucket.GetName() + Eventually(func(g Gomega) []corev1.Event { + err := k8sClient.List(ctx, bucketEventList, + client.InNamespace(ns.Name), client.MatchingFieldsSelector{Selector: selectorField.AsSelector()}, + ) + g.Expect(err).NotTo(HaveOccurred()) + return bucketEventList.Items + }).Should(ContainElement(SatisfyAll( + HaveField("Reason", Equal("testing")), + HaveField("Message", Equal("this is test bucket event")), + HaveField("Type", Equal(corev1.EventTypeNormal)), + ))) + }) +}) + +func GetSingleMapEntry[K comparable, V any](m map[K]V) (K, V) { + if n := len(m); n != 1 { + Fail(fmt.Sprintf("Expected for map to have a single entry but got %d", n), 1) + } + for k, v := range m { + return k, v + } + panic("unreachable") +} diff --git a/poollet/bucketpoollet/cmd/bucketpoollet/app/app.go b/poollet/bucketpoollet/cmd/bucketpoollet/app/app.go index 6884eb506..8bb3fbcd2 100644 --- a/poollet/bucketpoollet/cmd/bucketpoollet/app/app.go +++ b/poollet/bucketpoollet/cmd/bucketpoollet/app/app.go @@ -17,6 +17,7 @@ import ( iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" iriremotebucket "github.com/ironcore-dev/ironcore/iri/remote/bucket" "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/bcm" + "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/bem" bucketpoolletconfig "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/client/config" "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/controllers" "github.com/ironcore-dev/ironcore/poollet/irievent" @@ -174,6 +175,11 @@ func Run(ctx context.Context, opts Options) error { return fmt.Errorf("error adding bucket class mapper: %w", err) } + bucketEventMapper := bem.NewBucketEventMapper(mgr.GetClient(), bucketRuntime, mgr.GetEventRecorderFor("bucket-cluster-events"), bem.BucketEventMapperOptions{}) + if err := mgr.Add(bucketEventMapper); err != nil { + return fmt.Errorf("error adding bucket event mapper: %w", err) + } + bucketEvents := irievent.NewGenerator(func(ctx context.Context) ([]*iri.Bucket, error) { res, err := bucketRuntime.ListBuckets(ctx, &iri.ListBucketsRequest{}) if err != nil { diff --git a/poollet/bucketpoollet/controllers/bucket_controller_test.go b/poollet/bucketpoollet/controllers/bucket_controller_test.go new file mode 100644 index 000000000..7fbd53089 --- /dev/null +++ b/poollet/bucketpoollet/controllers/bucket_controller_test.go @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package controllers_test + +import ( + "fmt" + + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + ironcoreclient "github.com/ironcore-dev/ironcore/utils/client" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" +) + +const ( + AccessKeyID = "AccessKeyID" + SecretAccessKey = "SecretAccessKey" +) + +var _ = Describe("BucketController", func() { + ns, bp, bc, srv := SetupTest() + + It("should create a bucket", func(ctx SpecContext) { + bucketEndpoint := "foo.com" + accessKey := "foo" + secretAccess := "bar" + + By("creating a bucket") + bucket := &storagev1alpha1.Bucket{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "bucket-", + }, + Spec: storagev1alpha1.BucketSpec{ + BucketClassRef: &corev1.LocalObjectReference{Name: bc.Name}, + BucketPoolRef: &corev1.LocalObjectReference{Name: bp.Name}, + }, + } + Expect(k8sClient.Create(ctx, bucket)).To(Succeed()) + DeferCleanup(expectBucketDeleted, bucket) + + By("waiting for the runtime to report the bucket") + Eventually(srv).Should(SatisfyAll( + HaveField("Buckets", HaveLen(1)), + )) + + _, iriBucket := GetSingleMapEntry(srv.Buckets) + Expect(iriBucket.Spec.Class).To(Equal(bc.Name)) + + iriBucket.Status.Access = &iri.BucketAccess{ + Endpoint: bucketEndpoint, + SecretData: map[string][]byte{ + AccessKeyID: []byte(accessKey), + SecretAccessKey: []byte(secretAccess), + }, + } + iriBucket.Status.State = iri.BucketState_BUCKET_AVAILABLE + + Expect(ironcoreclient.PatchAddReconcileAnnotation(ctx, k8sClient, bucket)).Should(Succeed()) + + Eventually(Object(bucket)).Should(SatisfyAll( + HaveField("Status.State", storagev1alpha1.BucketStateAvailable), + HaveField("Status.Access.SecretRef", Not(BeNil())), + HaveField("Status.Access.Endpoint", Equal(bucketEndpoint)), + )) + + accessSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: bucket.Status.Access.SecretRef.Name, + }} + Eventually(Object(accessSecret)).Should(SatisfyAll( + HaveField("Data", HaveKeyWithValue(AccessKeyID, []byte(accessKey))), + HaveField("Data", HaveKeyWithValue(SecretAccessKey, []byte(secretAccess))), + )) + + }) + +}) + +func GetSingleMapEntry[K comparable, V any](m map[K]V) (K, V) { + if n := len(m); n != 1 { + Fail(fmt.Sprintf("Expected for map to have a single entry but got %d", n), 1) + } + for k, v := range m { + return k, v + } + panic("unreachable") +} diff --git a/poollet/bucketpoollet/controllers/bucketpool_controller.go b/poollet/bucketpoollet/controllers/bucketpool_controller.go index 05fc38e22..f5c156065 100644 --- a/poollet/bucketpoollet/controllers/bucketpool_controller.go +++ b/poollet/bucketpoollet/controllers/bucketpool_controller.go @@ -57,7 +57,7 @@ func (r *BucketPoolReconciler) delete(ctx context.Context, log logr.Logger, buck func (r *BucketPoolReconciler) supportsBucketClass(ctx context.Context, log logr.Logger, bucketClass *storagev1alpha1.BucketClass) (bool, error) { iriCapabilities, err := getIRIBucketClassCapabilities(bucketClass) if err != nil { - return false, fmt.Errorf("error getting iri mahchine class capabilities: %w", err) + return false, fmt.Errorf("error getting iri bucket class capabilities: %w", err) } _, err = r.BucketClassMapper.GetBucketClassFor(ctx, bucketClass.Name, iriCapabilities) @@ -97,7 +97,7 @@ func (r *BucketPoolReconciler) reconcile(ctx context.Context, log logr.Logger, b base := bucketPool.DeepCopy() bucketPool.Status.AvailableBucketClasses = supported if err := r.Status().Patch(ctx, bucketPool, client.MergeFrom(base)); err != nil { - return ctrl.Result{}, fmt.Errorf("error patchign bucket pool status: %w", err) + return ctrl.Result{}, fmt.Errorf("error patching bucket pool status: %w", err) } log.V(1).Info("Reconciled") diff --git a/poollet/bucketpoollet/controllers/bucketpool_controller_test.go b/poollet/bucketpoollet/controllers/bucketpool_controller_test.go new file mode 100644 index 000000000..39269bc25 --- /dev/null +++ b/poollet/bucketpoollet/controllers/bucketpool_controller_test.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package controllers_test + +import ( + corev1alpha1 "github.com/ironcore-dev/ironcore/api/core/v1alpha1" + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + "github.com/ironcore-dev/ironcore/iri/testing/bucket" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" +) + +var _ = Describe("BucketPoolController", func() { + _, bucketPool, bucketClass, srv := SetupTest() + + It("should have the default bucket class in the pool", func(ctx SpecContext) { + By("checking if the default bucket classes are present") + Eventually(Object(bucketPool)).Should(SatisfyAll( + HaveField("Status.AvailableBucketClasses", ContainElements([]corev1.LocalObjectReference{ + { + Name: bucketClass.Name, + }, + }))), + ) + }) + + It("should add bucket classes to the pool", func(ctx SpecContext) { + By("creating a second bucket class") + testBucketClass := &storagev1alpha1.BucketClass{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-bc-1-", + }, + Capabilities: corev1alpha1.ResourceList{ + corev1alpha1.ResourceTPS: resource.MustParse("250Mi"), + corev1alpha1.ResourceIOPS: resource.MustParse("1000"), + }, + } + Expect(k8sClient.Create(ctx, testBucketClass)).To(Succeed(), "failed to create test bucket class") + DeferCleanup(k8sClient.Delete, testBucketClass) + + srv.SetBucketClasses([]*bucket.FakeBucketClass{ + { + BucketClass: iri.BucketClass{ + Name: bucketClass.Name, + Capabilities: &iri.BucketClassCapabilities{ + Tps: 262144000, + Iops: 15000, + }, + }, + }, + { + BucketClass: iri.BucketClass{ + Name: testBucketClass.Name, + Capabilities: &iri.BucketClassCapabilities{ + Tps: 262144000, + Iops: 1000, + }, + }, + }, + }) + + By("checking if the test bucket class is present") + Eventually(Object(bucketPool)).Should(SatisfyAll( + HaveField("Status.AvailableBucketClasses", ContainElements([]corev1.LocalObjectReference{ + {Name: bucketClass.Name}, + {Name: testBucketClass.Name}, + })), + )) + }) +}) diff --git a/poollet/bucketpoollet/controllers/controllers_suite_test.go b/poollet/bucketpoollet/controllers/controllers_suite_test.go new file mode 100644 index 000000000..f700e160b --- /dev/null +++ b/poollet/bucketpoollet/controllers/controllers_suite_test.go @@ -0,0 +1,242 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package controllers_test + +import ( + "context" + "testing" + "time" + + "github.com/ironcore-dev/controller-utils/buildutils" + "github.com/ironcore-dev/controller-utils/modutils" + corev1alpha1 "github.com/ironcore-dev/ironcore/api/core/v1alpha1" + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + storageclient "github.com/ironcore-dev/ironcore/internal/client/storage" + iri "github.com/ironcore-dev/ironcore/iri/apis/bucket/v1alpha1" + "github.com/ironcore-dev/ironcore/iri/testing/bucket" + "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/bcm" + "github.com/ironcore-dev/ironcore/poollet/bucketpoollet/controllers" + "github.com/ironcore-dev/ironcore/poollet/irievent" + utilsenvtest "github.com/ironcore-dev/ironcore/utils/envtest" + "github.com/ironcore-dev/ironcore/utils/envtest/apiserver" + "github.com/ironcore-dev/ironcore/utils/envtest/controllermanager" + "github.com/ironcore-dev/ironcore/utils/envtest/process" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + metricserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" +) + +var ( + cfg *rest.Config + testEnv *envtest.Environment + testEnvExt *utilsenvtest.EnvironmentExtensions + k8sClient client.Client +) + +const ( + eventuallyTimeout = 3 * time.Second + pollingInterval = 50 * time.Millisecond + consistentlyDuration = 1 * time.Second + apiServiceTimeout = 5 * time.Minute + + controllerManagerService = "controller-manager" +) + +func TestControllers(t *testing.T) { + SetDefaultConsistentlyPollingInterval(pollingInterval) + SetDefaultEventuallyPollingInterval(pollingInterval) + SetDefaultEventuallyTimeout(eventuallyTimeout) + SetDefaultConsistentlyDuration(consistentlyDuration) + + RegisterFailHandler(Fail) + RunSpecs(t, "Controllers Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + var err error + By("bootstrapping test environment") + testEnv = &envtest.Environment{} + testEnvExt = &utilsenvtest.EnvironmentExtensions{ + APIServiceDirectoryPaths: []string{ + modutils.Dir("github.com/ironcore-dev/ironcore", "config", "apiserver", "apiservice", "bases"), + }, + ErrorIfAPIServicePathIsMissing: true, + AdditionalServices: []utilsenvtest.AdditionalService{ + { + Name: controllerManagerService, + }, + }, + } + + cfg, err = utilsenvtest.StartWithExtensions(testEnv, testEnvExt) + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + DeferCleanup(utilsenvtest.StopWithExtensions, testEnv, testEnvExt) + + Expect(storagev1alpha1.AddToScheme(scheme.Scheme)).To(Succeed()) + + // Init package-level k8sClient + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + SetClient(k8sClient) + + apiSrv, err := apiserver.New(cfg, apiserver.Options{ + MainPath: "github.com/ironcore-dev/ironcore/cmd/ironcore-apiserver", + BuildOptions: []buildutils.BuildOption{buildutils.ModModeMod}, + ETCDServers: []string{testEnv.ControlPlane.Etcd.URL.String()}, + Host: testEnvExt.APIServiceInstallOptions.LocalServingHost, + Port: testEnvExt.APIServiceInstallOptions.LocalServingPort, + CertDir: testEnvExt.APIServiceInstallOptions.LocalServingCertDir, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(apiSrv.Start()).To(Succeed()) + DeferCleanup(apiSrv.Stop) + + Expect(utilsenvtest.WaitUntilAPIServicesReadyWithTimeout(apiServiceTimeout, testEnvExt, k8sClient, scheme.Scheme)).To(Succeed()) + + ctrlMgr, err := controllermanager.New(cfg, controllermanager.Options{ + Args: process.EmptyArgs().Set("controllers", "*"), + MainPath: "github.com/ironcore-dev/ironcore/cmd/ironcore-controller-manager", + BuildOptions: []buildutils.BuildOption{buildutils.ModModeMod}, + Host: testEnvExt.GetAdditionalServiceHost(controllerManagerService), + Port: testEnvExt.GetAdditionalServicePort(controllerManagerService), + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(ctrlMgr.Start()).To(Succeed()) + DeferCleanup(ctrlMgr.Stop) +}) + +func SetupTest() (*corev1.Namespace, *storagev1alpha1.BucketPool, *storagev1alpha1.BucketClass, *bucket.FakeRuntimeService) { + var ( + ns = &corev1.Namespace{} + bp = &storagev1alpha1.BucketPool{} + bc = &storagev1alpha1.BucketClass{} + srv = &bucket.FakeRuntimeService{} + ) + + BeforeEach(func(ctx SpecContext) { + *ns = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-ns-", + }, + } + Expect(k8sClient.Create(ctx, ns)).To(Succeed(), "failed to create test namespace") + DeferCleanup(k8sClient.Delete, ns) + + *bp = storagev1alpha1.BucketPool{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-bp-", + }, + } + Expect(k8sClient.Create(ctx, bp)).To(Succeed(), "failed to create test bucket pool") + DeferCleanup(k8sClient.Delete, bp) + + *bc = storagev1alpha1.BucketClass{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-bc-", + }, + Capabilities: corev1alpha1.ResourceList{ + corev1alpha1.ResourceTPS: resource.MustParse("250Mi"), + corev1alpha1.ResourceIOPS: resource.MustParse("15000"), + }, + } + Expect(k8sClient.Create(ctx, bc)).To(Succeed(), "failed to create test bucket class") + DeferCleanup(k8sClient.Delete, bc) + + *srv = *bucket.NewFakeRuntimeService() + srv.SetBucketClasses([]*bucket.FakeBucketClass{ + { + BucketClass: iri.BucketClass{ + Name: bc.Name, + Capabilities: &iri.BucketClassCapabilities{ + Tps: 262144000, + Iops: 15000, + }, + }, + }, + }) + DeferCleanup(srv.SetBucketClasses, []*bucket.FakeBucketClass{}) + + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + Metrics: metricserver.Options{ + BindAddress: "0", + }, + }) + Expect(err).ToNot(HaveOccurred()) + + indexer := k8sManager.GetFieldIndexer() + Expect(storageclient.SetupBucketSpecBucketPoolRefNameFieldIndexer(ctx, indexer)).To(Succeed()) + + bucketClassMapper := bcm.NewGeneric(srv, bcm.GenericOptions{ + RelistPeriod: 2 * time.Second, + }) + Expect(k8sManager.Add(bucketClassMapper)).To(Succeed()) + + mgrCtx, cancel := context.WithCancel(context.Background()) + DeferCleanup(cancel) + + Expect((&controllers.BucketReconciler{ + EventRecorder: &record.FakeRecorder{}, + Client: k8sManager.GetClient(), + Scheme: scheme.Scheme, + BucketRuntime: srv, + BucketClassMapper: bucketClassMapper, + BucketPoolName: bp.Name, + }).SetupWithManager(k8sManager)).To(Succeed()) + + bucketEvents := irievent.NewGenerator(func(ctx context.Context) ([]*iri.Bucket, error) { + res, err := srv.ListBuckets(ctx, &iri.ListBucketsRequest{}) + if err != nil { + return nil, err + } + return res.Buckets, nil + }, irievent.GeneratorOptions{}) + + Expect(k8sManager.Add(bucketEvents)).To(Succeed()) + + Expect((&controllers.BucketAnnotatorReconciler{ + Client: k8sManager.GetClient(), + BucketEvents: bucketEvents, + }).SetupWithManager(k8sManager)).To(Succeed()) + + Expect((&controllers.BucketPoolReconciler{ + Client: k8sManager.GetClient(), + BucketRuntime: srv, + BucketClassMapper: bucketClassMapper, + BucketPoolName: bp.Name, + }).SetupWithManager(k8sManager)).To(Succeed()) + + go func() { + defer GinkgoRecover() + Expect(k8sManager.Start(mgrCtx)).To(Succeed(), "failed to start manager") + }() + }) + + return ns, bp, bc, srv +} + +func expectBucketDeleted(ctx context.Context, bucket *storagev1alpha1.Bucket) { + Expect(k8sClient.Delete(ctx, bucket)).Should(Succeed()) + Eventually(Get(bucket)).Should(Satisfy(errors.IsNotFound)) +} diff --git a/poollet/machinepoollet/controllers/machinepool_controller.go b/poollet/machinepoollet/controllers/machinepool_controller.go index 4fe87f226..eef77329f 100644 --- a/poollet/machinepoollet/controllers/machinepool_controller.go +++ b/poollet/machinepoollet/controllers/machinepool_controller.go @@ -70,7 +70,7 @@ func (r *MachinePoolReconciler) delete(ctx context.Context, log logr.Logger, mac func (r *MachinePoolReconciler) supportsMachineClass(ctx context.Context, log logr.Logger, machineClass *computev1alpha1.MachineClass) (*iri.MachineClass, int64, error) { iriCapabilities, err := getIRIMachineClassCapabilities(machineClass) if err != nil { - return nil, 0, fmt.Errorf("error getting iri mahchine class capabilities: %w", err) + return nil, 0, fmt.Errorf("error getting iri machine class capabilities: %w", err) } class, quantity, err := r.MachineClassMapper.GetMachineClassFor(ctx, machineClass.Name, iriCapabilities) diff --git a/poollet/volumepoollet/controllers/volumepool_controller.go b/poollet/volumepoollet/controllers/volumepool_controller.go index 4c631de71..1e85715a5 100644 --- a/poollet/volumepoollet/controllers/volumepool_controller.go +++ b/poollet/volumepoollet/controllers/volumepool_controller.go @@ -64,7 +64,7 @@ func (r *VolumePoolReconciler) delete(ctx context.Context, log logr.Logger, volu func (r *VolumePoolReconciler) supportsVolumeClass(ctx context.Context, log logr.Logger, volumeClass *storagev1alpha1.VolumeClass) (*iri.VolumeClass, *resource.Quantity, error) { iriCapabilities, err := getIRIVolumeClassCapabilities(volumeClass) if err != nil { - return nil, nil, fmt.Errorf("error getting iri mahchine class capabilities: %w", err) + return nil, nil, fmt.Errorf("error getting iri volume class capabilities: %w", err) } class, quantity, err := r.VolumeClassMapper.GetVolumeClassFor(ctx, volumeClass.Name, iriCapabilities)