Skip to content

Commit

Permalink
feat: support validate policy of opsrequest
Browse files Browse the repository at this point in the history
  • Loading branch information
ian-hui authored and ian-hui committed Oct 22, 2024
1 parent edf8598 commit ae9ee0a
Show file tree
Hide file tree
Showing 11 changed files with 1,484 additions and 68 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/recheck-cla.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Recheck CLA

on:
issue_comment:
types: [created]

jobs:
recheck-cla:
name: Recheck CLA
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/recheck-cla')
runs-on: ubuntu-latest
steps:
- name: Get PR number
id: pr_number
run: echo "PR_NUMBER=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT

- name: Trigger CLA check
run: |
curl -X GET "https://cla-assistant.io/check/${{ github.repository_owner }}/${{ github.event.repository.name }}?pullRequest=${{ steps.pr_number.outputs.PR_NUMBER }}"
- name: Comment on PR
uses: actions/github-script@v6
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Recheck CLA triggered.'
})
16 changes: 10 additions & 6 deletions apis/operations/v1alpha1/opsrequest_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,12 +436,16 @@ func (r *OpsRequest) validateHorizontalScalingSpec(hScale HorizontalScaling, com
if err := validateHScaleOperation(scaleOut.ReplicaChanger, scaleOut.NewInstances, scaleOut.OfflineInstancesToOnline, false); err != nil {
return err
}
if len(scaleOut.OfflineInstancesToOnline) > 0 {
offlineInstanceSet := sets.New(compSpec.OfflineInstances...)
for _, offlineInsName := range scaleOut.OfflineInstancesToOnline {
if _, ok := offlineInstanceSet[offlineInsName]; !ok {
return fmt.Errorf(`cannot find the offline instance "%s" in component "%s" for scaleOut operation`, offlineInsName, hScale.ComponentName)
}
}
// instance cannot be both in OfflineInstancesToOnline and OnlineInstancesToOffline
if scaleIn != nil && scaleOut != nil {
offlineToOnlineSet := make(map[string]struct{})
for _, instance := range scaleIn.OnlineInstancesToOffline {
offlineToOnlineSet[instance] = struct{}{}
}
for _, instance := range scaleOut.OfflineInstancesToOnline {
if _, exists := offlineToOnlineSet[instance]; exists {
return fmt.Errorf(`instance "%s" cannot be both in "OfflineInstancesToOnline" and "OnlineInstancesToOffline"`, instance)
}
}
}
Expand Down

Large diffs are not rendered by default.

181 changes: 181 additions & 0 deletions controllers/dataprotection/backuppolicytemplate_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
Copyright (C) 2022-2024 ApeCloud Co., Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package dataprotection

import (
"context"
"fmt"
"reflect"

apierrors "k8s.io/apimachinery/pkg/api/errors"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"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/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
"github.com/apecloud/kubeblocks/pkg/controller/component"
intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
"github.com/apecloud/kubeblocks/pkg/dataprotection/utils/boolptr"
)

// +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=backuppolicytemplates,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=backuppolicytemplates/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=backuppolicytemplates/finalizers,verbs=update

type BackupPolicyTemplateReconciler struct {
client.Client
Scheme *k8sruntime.Scheme
Recorder record.EventRecorder
}

func (r *BackupPolicyTemplateReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
reqCtx := intctrlutil.RequestCtx{
Ctx: ctx,
Req: req,
Log: log.FromContext(ctx).WithValues("backupPolicyTemplate", req.NamespacedName),
Recorder: r.Recorder,
}

backupPolicyTemplate := &dpv1alpha1.BackupPolicyTemplate{}
if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, backupPolicyTemplate); err != nil {
return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "")
}
oldBPT := backupPolicyTemplate.DeepCopy()
if err := r.setComponentDefLabels(reqCtx, oldBPT, backupPolicyTemplate); err != nil {
return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "")
}
if err := r.validateAvailable(reqCtx, oldBPT, backupPolicyTemplate); err != nil {
return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "")
}
return intctrlutil.Reconciled()
}

func (r *BackupPolicyTemplateReconciler) setComponentDefLabels(reqCtx intctrlutil.RequestCtx, oldBPT, bpt *dpv1alpha1.BackupPolicyTemplate) error {
compDefList := &appsv1.ComponentDefinitionList{}
if err := r.Client.List(reqCtx.Ctx, compDefList); err != nil {
return err
}
if bpt.Labels == nil {
bpt.Labels = map[string]string{}
}
for _, item := range compDefList.Items {
for _, compDef := range bpt.Spec.CompDefs {
// set componentDef labels
if component.PrefixOrRegexMatched(item.Name, compDef) {
bpt.Labels[item.Name] = item.Name
}
}
}
if !reflect.DeepEqual(oldBPT.Labels, bpt.Labels) {
return r.Client.Update(reqCtx.Ctx, bpt)
}
return nil
}

func (r *BackupPolicyTemplateReconciler) validateAvailable(reqCtx intctrlutil.RequestCtx, oldBPT, bpt *dpv1alpha1.BackupPolicyTemplate) error {
message := ""
backupMethodMap := map[string]sets.Empty{}
actionSetNotFound := false
// validate the referred actionSetName of the backupMethod
for _, v := range bpt.Spec.BackupMethods {
backupMethodMap[v.Name] = sets.Empty{}
if boolptr.IsSetToFalse(v.SnapshotVolumes) && v.ActionSetName == "" {
message += fmt.Sprintf(`backupMethod "%s" is missing an ActionSet name;`, v.Name)
continue
}
if v.ActionSetName == "" {
continue
}
actionSet := &dpv1alpha1.ActionSet{}
if err := r.Client.Get(reqCtx.Ctx, client.ObjectKey{Name: v.ActionSetName}, actionSet); err != nil {
if apierrors.IsNotFound(err) {
message += fmt.Sprintf(`ActionSet "%s" not found;`, v.ActionSetName)
actionSetNotFound = true
continue
}
return err
}
}
// validate the schedules
for _, v := range bpt.Spec.Schedules {
if _, ok := backupMethodMap[v.BackupMethod]; !ok {
message += fmt.Sprintf(`backupMethod "%s" not found in the spec.backupMethods;`, v.BackupMethod)
}
}
bpt.Status.ObservedGeneration = bpt.Generation
bpt.Status.Message = message
if len(message) > 0 {
bpt.Status.Phase = dpv1alpha1.UnavailablePhase
} else {
bpt.Status.Phase = dpv1alpha1.AvailablePhase
}
if !reflect.DeepEqual(oldBPT.Status, bpt.Status) {
if err := r.Client.Status().Patch(reqCtx.Ctx, bpt, client.MergeFrom(oldBPT)); err != nil {
return err
}
}
if actionSetNotFound {
return fmt.Errorf("some ActionSets not found")
}
return nil
}

func (r *BackupPolicyTemplateReconciler) isCompatibleWith(compDef appsv1.ComponentDefinition, bpt *dpv1alpha1.BackupPolicyTemplate) bool {
for _, compDefRegex := range bpt.Spec.CompDefs {
if component.PrefixOrRegexMatched(compDef.Name, compDefRegex) {
return true
}
}
return false
}

func (r *BackupPolicyTemplateReconciler) compatibleBackupPolicyTemplate(ctx context.Context, obj client.Object) []reconcile.Request {
compDef, ok := obj.(*appsv1.ComponentDefinition)
if !ok {
return nil
}
bpts := &dpv1alpha1.BackupPolicyTemplateList{}
if err := r.Client.List(ctx, bpts); err != nil {
return nil
}
requests := make([]reconcile.Request, 0)
for i := range bpts.Items {
if r.isCompatibleWith(*compDef, &bpts.Items[i]) {
requests = append(requests, reconcile.Request{
NamespacedName: types.NamespacedName{
Name: bpts.Items[i].Name,
},
})
}
}
return requests
}

// SetupWithManager sets up the controller with the Manager.
func (r *BackupPolicyTemplateReconciler) SetupWithManager(mgr ctrl.Manager) error {
return intctrlutil.NewNamespacedControllerManagedBy(mgr).
For(&dpv1alpha1.BackupPolicyTemplate{}).
Watches(&appsv1.ComponentDefinition{}, handler.EnqueueRequestsFromMapFunc(r.compatibleBackupPolicyTemplate)).
Complete(r)
}
Loading

0 comments on commit ae9ee0a

Please sign in to comment.