diff --git a/api/v1beta1/config_types.go b/api/v1beta1/config_types.go index c2021603..41a020dc 100644 --- a/api/v1beta1/config_types.go +++ b/api/v1beta1/config_types.go @@ -37,6 +37,8 @@ type Component struct { Source *UpdatingSource `json:"source,omitempty"` // +optional Dependencies []*Component `json:"dependencies,omitempty"` + // +optional + Schedules []string `json:"schedules,omitempty"` } // ComponentImage represents an image repository, tag and pattern which is a regex of tag diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 62e12c7d..b1eac506 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -365,6 +365,11 @@ func (in *Component) DeepCopyInto(out *Component) { } } } + if in.Schedules != nil { + in, out := &in.Schedules, &out.Schedules + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Component. diff --git a/config/crds/env.samsahai.io_configs.yaml b/config/crds/env.samsahai.io_configs.yaml index e131d814..569dec07 100644 --- a/config/crds/env.samsahai.io_configs.yaml +++ b/config/crds/env.samsahai.io_configs.yaml @@ -159,6 +159,10 @@ spec: type: string parent: type: string + schedules: + items: + type: string + type: array source: description: UpdatingSource represents source for checking desired version of components diff --git a/docs/docs.go b/docs/docs.go index d28e81ae..ee942310 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,6 +1,6 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2020-06-26 16:35:33.345858 +0700 +07 m=+0.159096294 +// 2020-07-09 14:36:35.052052 +0700 +07 m=+0.132228334 package docs @@ -723,7 +723,7 @@ var doc = `{ "type": "object", "properties": { "promotedBy": { - "description": "ActivePromotedBy represents a person who promoted the ActivePromotion\n+optional", + "description": "PromotedBy represents a person who promoted the ActivePromotion\n+optional", "type": "string" }, "skipTestRunner": { @@ -856,6 +856,13 @@ var doc = `{ "description": "+optional", "type": "string" }, + "schedules": { + "description": "+optional", + "type": "array", + "items": { + "type": "string" + } + }, "source": { "description": "+optional", "type": "string" @@ -1827,6 +1834,13 @@ var doc = `{ "description": "+optional", "type": "string" }, + "schedules": { + "description": "+optional", + "type": "array", + "items": { + "type": "string" + } + }, "source": { "description": "+optional", "type": "string" diff --git a/docs/swagger.json b/docs/swagger.json index 6d8850f4..79d45340 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -702,7 +702,7 @@ "type": "object", "properties": { "promotedBy": { - "description": "ActivePromotedBy represents a person who promoted the ActivePromotion\n+optional", + "description": "PromotedBy represents a person who promoted the ActivePromotion\n+optional", "type": "string" }, "skipTestRunner": { @@ -835,6 +835,13 @@ "description": "+optional", "type": "string" }, + "schedules": { + "description": "+optional", + "type": "array", + "items": { + "type": "string" + } + }, "source": { "description": "+optional", "type": "string" @@ -1806,6 +1813,13 @@ "description": "+optional", "type": "string" }, + "schedules": { + "description": "+optional", + "type": "array", + "items": { + "type": "string" + } + }, "source": { "description": "+optional", "type": "string" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 45624731..d6bda913 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -52,7 +52,7 @@ definitions: properties: promotedBy: description: |- - ActivePromotedBy represents a person who promoted the ActivePromotion + PromotedBy represents a person who promoted the ActivePromotion +optional type: string skipTestRunner: @@ -190,6 +190,11 @@ definitions: parent: description: +optional type: string + schedules: + description: +optional + items: + type: string + type: array source: description: +optional type: string @@ -961,6 +966,11 @@ definitions: parent: description: +optional type: string + schedules: + description: +optional + items: + type: string + type: array source: description: +optional type: string diff --git a/internal/config/controller.go b/internal/config/controller.go index e0823fdc..c6af38a2 100644 --- a/internal/config/controller.go +++ b/internal/config/controller.go @@ -2,10 +2,18 @@ package config import ( "context" + "fmt" + "strconv" + "strings" "time" "github.com/ghodss/yaml" + batchv1 "k8s.io/api/batch/v1" + batchv1beta1 "k8s.io/api/batch/v1beta1" + corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" cr "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -28,8 +36,9 @@ const ( ) type controller struct { - client client.Client - s2hCtrl internal.SamsahaiController + client client.Client + s2hCtrl internal.SamsahaiController + s2hConfig internal.SamsahaiConfig } type Option func(*controller) @@ -46,6 +55,12 @@ func WithS2hCtrl(s2hCtrl internal.SamsahaiController) Option { } } +func WithS2hConfig(s2hConfig internal.SamsahaiConfig) Option { + return func(c *controller) { + c.s2hConfig = s2hConfig + } +} + func New(mgr cr.Manager, options ...Option) internal.ConfigController { c := &controller{} @@ -99,10 +114,11 @@ func (c *controller) GetComponents(configName string) (map[string]*s2hv1beta1.Co // add to comps for _, dep := range comp.Dependencies { comps = append(comps, &s2hv1beta1.Component{ - Parent: comp.Name, - Name: dep.Name, - Image: dep.Image, - Source: dep.Source, + Parent: comp.Name, + Name: dep.Name, + Image: dep.Image, + Source: dep.Source, + Schedules: dep.Schedules, }) } } @@ -246,6 +262,125 @@ func GetEnvComponentValues(config *s2hv1beta1.ConfigSpec, compName string, envTy return baseValues, nil } +func (c *controller) CreateCronJob(cronJob batchv1beta1.CronJob) error { + if err := c.client.Create(context.TODO(), &cronJob); err != nil { + return err + } + return nil +} + +func (c *controller) DeleteCronJob(cronJob batchv1beta1.CronJob) error { + if err := c.client.Delete(context.TODO(), &cronJob); err != nil { + return err + } + return nil +} + +func (c *controller) GetCreatingCronJobs(namespace, teamName string, comp s2hv1beta1.Component, + cronJobList batchv1beta1.CronJobList) []batchv1beta1.CronJob { + creatingCronJobs := []batchv1beta1.CronJob{} + uniqueCreatingCronJobs := map[string]batchv1beta1.CronJob{} + cronJobCmd := fmt.Sprintf(`set -eux + +curl -X POST -k %v -d '{"component": %s, "team": %s, "repository": %s}' +`, c.s2hConfig.SamsahaiExternalURL, comp.Name, teamName, comp.Image.Repository) + + for i, schedule := range comp.Schedules { + isCronJobChanged := true + for _, cj := range cronJobList.Items { + if schedule == cj.Spec.Schedule { + argList := cj.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args + for _, arg := range argList { + if !strings.Contains(arg, cronJobCmd) { + isCronJobChanged = true + } else { + isCronJobChanged = false + } + } + } + } + if isCronJobChanged { + cronJobName := comp.Name + "-checker-" + strconv.Itoa(i) + cronJobLabel := internal.GetDefaultLabels(teamName) + cronJobLabel["component"] = comp.Name + cronJobDefaultArgs := []string{"/bin/sh", "-c", cronJobCmd} + cronJob := batchv1beta1.CronJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: cronJobName, + Namespace: namespace, + Labels: cronJobLabel, + }, + Spec: batchv1beta1.CronJobSpec{ + Schedule: schedule, + JobTemplate: batchv1beta1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "component-checker", + Image: "quay.io/samsahai/curl:latest", + Args: cronJobDefaultArgs, + }, + }, + RestartPolicy: "OnFailure", + }, + }, + }, + }, + }, + } + + if _, ok := uniqueCreatingCronJobs[schedule]; !ok { + uniqueCreatingCronJobs[schedule] = cronJob + } + } + } + + for _, v := range uniqueCreatingCronJobs { + creatingCronJobs = append(creatingCronJobs, v) + } + + return creatingCronJobs +} + +func (c *controller) GetDeletingCronJobs(teamName string, comp s2hv1beta1.Component, + cronJobList batchv1beta1.CronJobList) []batchv1beta1.CronJob { + deletingCronJobObjs := []batchv1beta1.CronJob{} + cronJobCmd := fmt.Sprintf(`set -eux + +curl -X POST -k %v -d '{"component": %s, "team": %s, "repository": %s}' +`, c.s2hConfig.SamsahaiExternalURL, comp.Name, teamName, comp.Image.Repository) + for _, cj := range cronJobList.Items { + isCronJobChanged := true + for _, schedule := range comp.Schedules { + if schedule == cj.Spec.Schedule { + argList := cj.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args + for _, arg := range argList { + if !strings.Contains(arg, cronJobCmd) { + isCronJobChanged = true + } else { + isCronJobChanged = false + } + } + } + } + if isCronJobChanged { + deletingCronJobObjs = append(deletingCronJobObjs, cj) + } + } + return deletingCronJobObjs +} + +func (c *controller) GetUpdateCronJobs(namespace, teamName string, comp *s2hv1beta1.Component, + cronjobList batchv1beta1.CronJobList) ([]batchv1beta1.CronJob, []batchv1beta1.CronJob) { + + creatingCronJobObjs := c.GetCreatingCronJobs(namespace, teamName, *comp, cronjobList) + deletingCronJobObjs := c.GetDeletingCronJobs(teamName, *comp, cronjobList) + + return creatingCronJobObjs, deletingCronJobObjs +} + // assignParent assigns Parent to SubComponent // only support 1 level of dependencies func (c *controller) assignParent(config *s2hv1beta1.ConfigSpec) { @@ -267,7 +402,7 @@ func (c *controller) getConfig(configName string) (*s2hv1beta1.Config, error) { } // ensureComponentChanged detects added or removed component -func (c *controller) ensureComponentChanged(teamName, namespace string) error { +func (c *controller) ensureConfigChanged(teamName, namespace string) error { comps, err := c.GetComponents(teamName) if err != nil { logger.Error(err, "cannot get components from configuration", @@ -291,20 +426,47 @@ func (c *controller) ensureComponentChanged(teamName, namespace string) error { return err } - c.notifyComponentChanged(teamName, namespace, comps) + if err := c.detectSchedulerChanged(comps, teamName, namespace); err != nil { + return err + } return nil } -func (c *controller) notifyComponentChanged(teamName, namespace string, comps map[string]*s2hv1beta1.Component) { - if c.s2hCtrl == nil { - logger.Debug("no s2h ctrl, skip notify changed") - } - - logger.Debug("start notifying components", "team", teamName, "namespace", namespace) +func (c *controller) detectSchedulerChanged(comps map[string]*s2hv1beta1.Component, teamName, namespace string) error { + ctx := context.TODO() for _, comp := range comps { - c.s2hCtrl.NotifyComponentChanged(comp.Name, comp.Image.Repository, teamName) + cronJobList := &batchv1beta1.CronJobList{} + componentLabel := labels.SelectorFromSet(map[string]string{"component": comp.Name}) + listOption := &client.ListOptions{Namespace: namespace, LabelSelector: componentLabel} + err := c.client.List(ctx, cronJobList, listOption) + if err != nil { + logger.Error(err, "cannot list cronJob ", "component", comp.Name) + return err + } + + creatingCronJobObjs, deletingCronJobObjs := c.GetUpdateCronJobs(namespace, teamName, comp, *cronJobList) + if len(deletingCronJobObjs) > 0 { + for _, obj := range deletingCronJobObjs { + err := c.DeleteCronJob(obj) + if err != nil && !k8serrors.IsNotFound(err) { + logger.Error(err, "cannot delete cronJob", "component", obj.Name) + return err + } + } + } + + if len(creatingCronJobObjs) > 0 { + for _, obj := range creatingCronJobObjs { + err := c.CreateCronJob(obj) + if err != nil && !k8serrors.IsAlreadyExists(err) { + logger.Error(err, "cannot create cronJob", "component", obj.Name) + return err + } + } + } } + return nil } func (c *controller) detectRemovedDesiredComponents(comps map[string]*s2hv1beta1.Component, namespace string) error { @@ -454,10 +616,10 @@ func (c *controller) Reconcile(req cr.Request) (cr.Result, error) { stagingNs := teamComp.Status.Namespace.Staging if stagingNs == "" { logger.Debug("no staging namespace to process", "team", req.Name) - return cr.Result{}, nil + return cr.Result{}, fmt.Errorf("staging namespace of team %s not found", req.Name) } - if err := c.ensureComponentChanged(req.Name, stagingNs); err != nil { + if err := c.ensureConfigChanged(req.Name, stagingNs); err != nil { return cr.Result{}, err } diff --git a/internal/config/controller_test.go b/internal/config/controller_test.go index 655ff1e4..94586578 100644 --- a/internal/config/controller_test.go +++ b/internal/config/controller_test.go @@ -1,16 +1,28 @@ -package config_test +package config import ( + "fmt" "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + batchv1 "k8s.io/api/batch/v1" + batchv1beta1 "k8s.io/api/batch/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" s2hv1beta1 "github.com/agoda-com/samsahai/api/v1beta1" - configctrl "github.com/agoda-com/samsahai/internal/config" + "github.com/agoda-com/samsahai/internal" "github.com/agoda-com/samsahai/internal/util/unittest" ) +const ( + AppName = "samsahai" + ContainerName = "component-checker" + ContainerImage = "quay.io/samsahai/curl:latest" + ContainerRestartPolicy = "OnFailure" +) + func TestConfig(t *testing.T) { unittest.InitGinkgo(t, "Config Controller") } @@ -49,7 +61,8 @@ var _ = Describe("Config Controller", func() { mockConfig := s2hv1beta1.ConfigSpec{ Envs: map[s2hv1beta1.EnvType]s2hv1beta1.ChartValuesURLs{ "staging": map[string][]string{ - redisCompName: {"https://raw.githubusercontent.com/agoda-com/samsahai/master/test/data/wordpress-redis/envs/staging/redis.yaml"}, + redisCompName: { + "https://raw.githubusercontent.com/agoda-com/samsahai/master/test/data/wordpress-redis/envs/staging/redis.yaml"}, }, }, Components: []*s2hv1beta1.Component{ @@ -61,7 +74,7 @@ var _ = Describe("Config Controller", func() { g := NewWithT(GinkgoT()) config := mockConfig - compValues, err := configctrl.GetEnvValues(&config, s2hv1beta1.EnvStaging) + compValues, err := GetEnvValues(&config, s2hv1beta1.EnvStaging) g.Expect(err).NotTo(HaveOccurred()) g.Expect(compValues).To(Equal(map[string]s2hv1beta1.ComponentValues{ redisCompName: { @@ -79,7 +92,7 @@ var _ = Describe("Config Controller", func() { g := NewWithT(GinkgoT()) config := mockConfig - compValues, err := configctrl.GetEnvComponentValues(&config, redisCompName, s2hv1beta1.EnvStaging) + compValues, err := GetEnvComponentValues(&config, redisCompName, s2hv1beta1.EnvStaging) g.Expect(err).NotTo(HaveOccurred()) g.Expect(compValues).To(Equal(s2hv1beta1.ComponentValues{ "master": map[string]interface{}{ @@ -91,3 +104,197 @@ var _ = Describe("Config Controller", func() { })) }) }) + +var _ = Describe("Updating Cronjob Controller", func() { + mockcontroller := controller{ + s2hConfig: internal.SamsahaiConfig{SamsahaiExternalURL: "http://localhost:8080"}, + } + teamTest := "teamTest" + namespaceTest := "namespaceTest" + compSource := s2hv1beta1.UpdatingSource("public-registry") + redisCompName := "redis" + redisSchedules := []string{"0 4 * * *", "0 5 * * *"} + + redisConfigComp := s2hv1beta1.Component{ + Name: redisCompName, + Chart: s2hv1beta1.ComponentChart{ + Repository: "https://kubernetes-charts.storage.googleapis.com", + Name: redisCompName, + }, + Image: s2hv1beta1.ComponentImage{ + Repository: "bitnami/redis", + Pattern: "5.*debian-9.*", + }, + Schedules: redisSchedules, + Source: &compSource, + Values: s2hv1beta1.ComponentValues{ + "image": map[string]interface{}{ + "repository": "bitnami/redis", + "pullPolicy": "IfNotPresent", + }, + "cluster": map[string]interface{}{ + "enabled": false, + }, + "usePassword": false, + "master": map[string]interface{}{ + "persistence": map[string]interface{}{ + "enabled": false, + }, + }, + }, + } + + mockCronjob := batchv1beta1.CronJobList{ + TypeMeta: metav1.TypeMeta{}, + ListMeta: metav1.ListMeta{}, + Items: []batchv1beta1.CronJob{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "Cronjob", + APIVersion: "batch/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: redisConfigComp.Name + "-checker-0", + Namespace: namespaceTest, + Labels: map[string]string{ + "app.kubernetes.io/managed-by": AppName, + "component": redisConfigComp.Name, + "samsahai.io/teamname": teamTest, + }, + }, + Spec: batchv1beta1.CronJobSpec{ + Schedule: "0 11 * * *", + JobTemplate: batchv1beta1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: ContainerName, + Image: ContainerImage, + Args: []string{"/bin/sh", "-c", fmt.Sprintf(`set -eux + +curl -X POST -k %s-d '{"component": %s ,"team": %s ,"repository": %s}' +`, mockcontroller.s2hConfig.SamsahaiExternalURL, "redis", "teamTest", "bitnami/redis")}, + }, + }, + RestartPolicy: ContainerRestartPolicy, + }, + }, + }, + }, + }, + }, + }, + } + + It("should create/delete cronjob correctly", func() { + g := NewWithT(GinkgoT()) + + expectedCronjob := []batchv1beta1.CronJob{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: redisConfigComp.Name + "-checker-0", + Namespace: namespaceTest, + Labels: map[string]string{ + "app.kubernetes.io/managed-by": AppName, + "samsahai.io/teamname": teamTest, + "component": redisConfigComp.Name, + }, + }, + Spec: batchv1beta1.CronJobSpec{ + Schedule: "0 4 * * *", + JobTemplate: batchv1beta1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: ContainerName, + Image: ContainerImage, + Args: []string{"/bin/sh", "-c", fmt.Sprintf(`set -eux + +curl -X POST -k %v -d '{"component": %s, "team": %s, "repository": %s}' +`, mockcontroller.s2hConfig.SamsahaiExternalURL, "redis", "teamTest", "bitnami/redis")}, + }, + }, + RestartPolicy: ContainerRestartPolicy, + }, + }, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: redisConfigComp.Name + "-checker-1", + Namespace: namespaceTest, + Labels: map[string]string{ + "app.kubernetes.io/managed-by": AppName, + "samsahai.io/teamname": teamTest, + "component": redisConfigComp.Name, + }, + }, + Spec: batchv1beta1.CronJobSpec{ + Schedule: "0 5 * * *", + JobTemplate: batchv1beta1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: ContainerName, + Image: ContainerImage, + Args: []string{"/bin/sh", "-c", fmt.Sprintf(`set -eux + +curl -X POST -k %v -d '{"component": %s, "team": %s, "repository": %s}' +`, mockcontroller.s2hConfig.SamsahaiExternalURL, "redis", "teamTest", "bitnami/redis")}, + }, + }, + RestartPolicy: ContainerRestartPolicy, + }, + }, + }, + }, + }, + }, + } + + c := controller{ + s2hConfig: internal.SamsahaiConfig{SamsahaiExternalURL: "http://localhost:8080"}, + } + creatingResult, deletingResult := c.GetUpdateCronJobs(namespaceTest, teamTest, &redisConfigComp, mockCronjob) + + g.Expect(creatingResult).To(HaveLen(len(expectedCronjob))) + g.Expect(creatingResult).To(ConsistOf(expectedCronjob)) + g.Expect(deletingResult).To(HaveLen(len(mockCronjob.Items))) + g.Expect(deletingResult).To(ConsistOf(mockCronjob.Items)) + }) + + It("should create/delete cronjob correctly when config have duplicate scheduler", func() { + g := NewWithT(GinkgoT()) + + redisConfigComp.Schedules = []string{"0 7 * * *", "0 7 * * *"} + + c := controller{} + _, deletingResult := c.GetUpdateCronJobs(namespaceTest, teamTest, &redisConfigComp, mockCronjob) + + g.Expect(deletingResult).To(HaveLen(len(mockCronjob.Items))) + g.Expect(deletingResult).To(ConsistOf(mockCronjob.Items)) + + }) + + It("should create cronjob/delete correctly when config have no scheduler", func() { + g := NewWithT(GinkgoT()) + + redisConfigComp.Schedules = []string{} + c := controller{} + creatingResult, deletingResult := c.GetUpdateCronJobs(namespaceTest, teamTest, &redisConfigComp, mockCronjob) + + g.Expect(len(creatingResult)).To(Equal(0)) + g.Expect(deletingResult).To(HaveLen(len(mockCronjob.Items))) + g.Expect(deletingResult).To(ConsistOf(mockCronjob.Items)) + + }) + +}) diff --git a/internal/samsahai/controller.go b/internal/samsahai/controller.go index 996f0804..04c9a974 100644 --- a/internal/samsahai/controller.go +++ b/internal/samsahai/controller.go @@ -126,7 +126,7 @@ func New( configs: configs, } - c.configCtrl = configctrl.New(mgr, configctrl.WithS2hCtrl(c)) + c.configCtrl = configctrl.New(mgr, configctrl.WithS2hCtrl(c), configctrl.WithS2hConfig(c.configs)) if mgr != nil { // create runtime client diff --git a/test/e2e/samsahai/ctrl.go b/test/e2e/samsahai/ctrl.go index 182cd4cb..d3962048 100644 --- a/test/e2e/samsahai/ctrl.go +++ b/test/e2e/samsahai/ctrl.go @@ -13,6 +13,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/tidwall/gjson" + batchv1beta1 "k8s.io/api/batch/v1beta1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -1736,6 +1737,76 @@ var _ = Describe("[e2e] Main controller", func() { }) Expect(err).NotTo(HaveOccurred(), "Notify component changed and promote the first active error") }, 20) + + It("should succesfully create cronjob", func(done Done) { + defer close(done) + setupSamsahai(true) + ctx := context.TODO() + + By("Creating Config that have Scheduler") + configRedis := mockConfigOnlyRedis + configRedis.Spec.Components[0].Schedules = []string{"0 4 * * *"} + Expect(client.Create(ctx, &configRedis)).To(BeNil()) + + By("Creating Team") + team := mockTeam + Expect(client.Create(ctx, &team)).To(BeNil()) + + By("Verifying namespace and config have been created") + err = wait.PollImmediate(verifyTime1s, verifyNSCreatedTimeout, func() (ok bool, err error) { + namespace := corev1.Namespace{} + if err := client.Get(ctx, types.NamespacedName{Name: stgNamespace}, &namespace); err != nil { + return false, nil + } + + config := s2hv1beta1.Config{} + err = client.Get(ctx, types.NamespacedName{Name: team.Name}, &config) + if err != nil { + return false, nil + } + + return true, nil + }) + Expect(err).NotTo(HaveOccurred(), "Verify namespace and config error") + + By("Verifying CronJob have been created") + err = wait.PollImmediate(verifyTime1s, verifyTime10s, func() (ok bool, err error) { + cronjobList := &batchv1beta1.CronJobList{} + cronjobLabel := labels.SelectorFromSet(map[string]string{"component": configRedis.Spec.Components[0].Name}) + listOption := &rclient.ListOptions{Namespace: stgNamespace, LabelSelector: cronjobLabel} + if err := client.List(ctx, cronjobList, listOption); err != nil { + return false, err + } + + if len(cronjobList.Items) == 0 || len(cronjobList.Items) != len(configRedis.Spec.Components[0].Schedules) { + return false, nil + } + return true, nil + }) + Expect(err).NotTo(HaveOccurred(), "Verify creating CronJob error") + + By("Updating Config that have no Scheduler") + configRedis = s2hv1beta1.Config{} + _ = client.Get(ctx, types.NamespacedName{Name: teamName}, &configRedis) + configRedis.Spec.Components[0].Schedules = []string{} + Expect(client.Update(ctx, &configRedis)).To(BeNil()) + + By("Verifying CronJob should be deleted") + err = wait.PollImmediate(verifyTime1s, verifyTime10s, func() (ok bool, err error) { + cronjobList := &batchv1beta1.CronJobList{} + cronjobLabel := labels.SelectorFromSet(map[string]string{"component": configRedis.Spec.Components[0].Name}) + listOption := &rclient.ListOptions{Namespace: stgNamespace, LabelSelector: cronjobLabel} + if err := client.List(ctx, cronjobList, listOption); err != nil { + return false, nil + } + + if len(cronjobList.Items) != 0 { + return false, nil + } + return true, nil + }) + Expect(err).NotTo(HaveOccurred(), "Config should be deleted") + }, 90) }) var (