Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions infra/feast-operator/api/v1alpha1/featurestore_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ type FeastInitOptions struct {

// FeastCronJob defines a CronJob to execute against a Feature Store deployment.
type FeastCronJob struct {
// Annotations to be added to the CronJob metadata.
Annotations map[string]string `json:"annotations,omitempty"`

// Specification of the desired behavior of a job.
JobSpec *JobSpec `json:"jobSpec,omitempty"`
ContainerConfigs *CronJobContainerConfigs `json:"containerConfigs,omitempty"`
Expand Down
7 changes: 7 additions & 0 deletions infra/feast-operator/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ spec:
description: FeastCronJob defines a CronJob to execute against a Feature
Store deployment.
properties:
annotations:
additionalProperties:
type: string
description: Annotations to be added to the CronJob metadata.
type: object
concurrencyPolicy:
description: Specifies how to treat concurrent executions of a
Job.
Expand Down Expand Up @@ -4063,6 +4068,11 @@ spec:
description: FeastCronJob defines a CronJob to execute against
a Feature Store deployment.
properties:
annotations:
additionalProperties:
type: string
description: Annotations to be added to the CronJob metadata.
type: object
concurrencyPolicy:
description: Specifies how to treat concurrent executions
of a Job.
Expand Down
10 changes: 10 additions & 0 deletions infra/feast-operator/dist/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ spec:
description: FeastCronJob defines a CronJob to execute against a Feature
Store deployment.
properties:
annotations:
additionalProperties:
type: string
description: Annotations to be added to the CronJob metadata.
type: object
concurrencyPolicy:
description: Specifies how to treat concurrent executions of a
Job.
Expand Down Expand Up @@ -4071,6 +4076,11 @@ spec:
description: FeastCronJob defines a CronJob to execute against
a Feature Store deployment.
properties:
annotations:
additionalProperties:
type: string
description: Annotations to be added to the CronJob metadata.
type: object
concurrencyPolicy:
description: Specifies how to treat concurrent executions
of a Job.
Expand Down
1 change: 1 addition & 0 deletions infra/feast-operator/docs/api/markdown/ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ _Appears in:_

| Field | Description |
| --- | --- |
| `annotations` _object (keys:string, values:string)_ | Annotations to be added to the CronJob metadata. |
| `jobSpec` _[JobSpec](#jobspec)_ | Specification of the desired behavior of a job. |
| `containerConfigs` _[CronJobContainerConfigs](#cronjobcontainerconfigs)_ | |
| `schedule` _string_ | The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ func (feast *FeastServices) initCronJob() *batchv1.CronJob {
func (feast *FeastServices) setCronJob(cronJob *batchv1.CronJob) error {
appliedCronJob := feast.Handler.FeatureStore.Status.Applied.CronJob
cronJob.Labels = feast.getFeastTypeLabels(CronJobFeastType)
if appliedCronJob.Annotations != nil {
cronJob.Annotations = make(map[string]string, len(appliedCronJob.Annotations))
for k, v := range appliedCronJob.Annotations {
cronJob.Annotations[k] = v
}
}
cronJob.Spec = batchv1.CronJobSpec{
Schedule: appliedCronJob.Schedule,
JobTemplate: batchv1.JobTemplateSpec{
Expand Down
101 changes: 101 additions & 0 deletions infra/feast-operator/test/api/featurestore_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,35 @@ func registryWithGRPCFalse(featureStore *feastdevv1alpha1.FeatureStore) *feastde
return fsCopy
}

func cronJobWithAnnotations(featureStore *feastdevv1alpha1.FeatureStore) *feastdevv1alpha1.FeatureStore {
fsCopy := featureStore.DeepCopy()
fsCopy.Spec.CronJob = &feastdevv1alpha1.FeastCronJob{
Annotations: map[string]string{
"test-annotation": "test-value",
"another-annotation": "another-value",
},
Schedule: "0 0 * * *",
}
return fsCopy
}

func cronJobWithEmptyAnnotations(featureStore *feastdevv1alpha1.FeatureStore) *feastdevv1alpha1.FeatureStore {
fsCopy := featureStore.DeepCopy()
fsCopy.Spec.CronJob = &feastdevv1alpha1.FeastCronJob{
Annotations: map[string]string{},
Schedule: "0 0 * * *",
}
return fsCopy
}

func cronJobWithoutAnnotations(featureStore *feastdevv1alpha1.FeatureStore) *feastdevv1alpha1.FeatureStore {
fsCopy := featureStore.DeepCopy()
fsCopy.Spec.CronJob = &feastdevv1alpha1.FeastCronJob{
Schedule: "0 0 * * *",
}
return fsCopy
}

func quotedSlice(stringSlice []string) string {
quotedSlice := make([]string, len(stringSlice))

Expand Down Expand Up @@ -645,4 +674,76 @@ var _ = Describe("FeatureStore API", func() {
})
})
})

Context("When creating a CronJob", func() {
ctx := context.Background()

BeforeEach(func() {
By("verifying the custom resource FeatureStore is not there")
resource := &feastdevv1alpha1.FeatureStore{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
Expect(err != nil && errors.IsNotFound(err)).To(BeTrue())
})
AfterEach(func() {
By("Cleaning up the test resource")
resource := &feastdevv1alpha1.FeatureStore{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
if err == nil {
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
}
err = k8sClient.Get(ctx, typeNamespacedName, resource)
Expect(err != nil && errors.IsNotFound(err)).To(BeTrue())
})

Context("with annotations", func() {
It("should succeed when annotations are provided", func() {
featurestore := createFeatureStore()
resource := cronJobWithAnnotations(featurestore)
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
})

It("should succeed when annotations are empty", func() {
featurestore := createFeatureStore()
resource := cronJobWithEmptyAnnotations(featurestore)
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
})

It("should succeed when annotations are not specified", func() {
featurestore := createFeatureStore()
resource := cronJobWithoutAnnotations(featurestore)
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
})

It("should apply the annotations correctly in the status", func() {
featurestore := createFeatureStore()
resource := cronJobWithAnnotations(featurestore)
services.ApplyDefaultsToStatus(resource)

Expect(resource.Status.Applied.CronJob).NotTo(BeNil())
Expect(resource.Status.Applied.CronJob.Annotations).NotTo(BeNil())
Expect(resource.Status.Applied.CronJob.Annotations).To(HaveLen(2))
Expect(resource.Status.Applied.CronJob.Annotations["test-annotation"]).To(Equal("test-value"))
Expect(resource.Status.Applied.CronJob.Annotations["another-annotation"]).To(Equal("another-value"))
})

It("should keep empty annotations in the status", func() {
featurestore := createFeatureStore()
resource := cronJobWithEmptyAnnotations(featurestore)
services.ApplyDefaultsToStatus(resource)

Expect(resource.Status.Applied.CronJob).NotTo(BeNil())
Expect(resource.Status.Applied.CronJob.Annotations).NotTo(BeNil())
Expect(resource.Status.Applied.CronJob.Annotations).To(BeEmpty())
})

It("should have nil annotations in status when not specified", func() {
featurestore := createFeatureStore()
resource := cronJobWithoutAnnotations(featurestore)
services.ApplyDefaultsToStatus(resource)

Expect(resource.Status.Applied.CronJob).NotTo(BeNil())
Expect(resource.Status.Applied.CronJob.Annotations).To(BeNil())
})
})
})
})
Loading