Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DRPolicy: Add CEL and remove webhook #1175

Merged
merged 3 commits into from
Mar 14, 2024
Merged

Conversation

rakeshgm
Copy link
Member

Remove DRPolicy Webhook and add CEL immutability validation for DRPolicy

@@ -150,6 +150,9 @@ spec:
required:
- drClusters
type: object
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we add the same information twice - once in the kubebuilder comment, and once here. Can we have this only once? What happens if the rule and message are different in both places?

Also this looks really magical - can you link to the docs describing this special syntax?

Copy link
Member Author

@rakeshgm rakeshgm Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only add the comment in the API file and operator-sdk generates/updates this YAML when we run make. This is the general practice as far as I know.
check the link here: CEL Immutability

setupLog.Error(err, "unable to create webhook", "webhook", "DRPolicy")
os.Exit(1)
}

// setup webhook for drpc
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do the same for drpc?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that is the plan. I am planning to send different PRs for each CR, it's just that removal of webhook code is more than adding CEL so, I plan to take one CR at a time so that we don't break ramen. :)

@@ -12,5 +12,3 @@ spec:
namespace: system
name: webhook-service
path: /convert
conversionReviewVersions:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we even need this file now? Can this be removed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to keep it as it is part of the scaffolding.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this and config/crd/patches/cainjection_in_drpolicies.yaml needed though, I am not seeing the scaffolding requirement here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, this file was created when the ramen operator was scaffolded initially. I was just trying to revert the changes that we did when adding webhooks and this line was the only change. I tested now by deleting the file. It does not matter. we can safely delete the file I believe.

@@ -12,5 +12,3 @@ spec:
namespace: system
name: webhook-service
path: /convert
conversionReviewVersions:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to keep it as it is part of the scaffolding.

// DRPolicySpec defines the desired state of DRPolicy
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.schedulingInterval) || has(self.schedulingInterval)", message="schedulingInterval is required once set"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the pattern we need to use is Immutability upon object creation

For example with the current scheme a user would be allowed to set an empty value to begin with and then be allowed to modify it to a valid value, the immutability starts here and prevents further modification.

What we want is immutable post creation, so if the policy started with an empty value it should remain so.

Copy link
Member Author

@rakeshgm rakeshgm Mar 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the code with Immutability upon object creation pattern for schedulingInterval and drclusters only.

@rakeshgm rakeshgm changed the title Remove DRPolicy Webhook DRPolicy: Add CEL and remove webhook Mar 7, 2024
@rakeshgm rakeshgm force-pushed the rm-webhooks branch 3 times, most recently from 88de718 to f6f82d2 Compare March 11, 2024 14:37
@ShyamsundarR
Copy link
Member

Tested the DRPolicy PR out and here is what I see...

Initial DRPolicy created on a kind cluster extended with the CRD from the PR:

$ kubectl get drpolicy -A -o yaml
apiVersion: v1
items:
- apiVersion: ramendr.openshift.io/v1alpha1
  kind: DRPolicy
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"ramendr.openshift.io/v1alpha1","kind":"DRPolicy","metadata":{"annotations":{},"name":"drpolicy-sample"},"spec":{"drClusters":["east","west"],"schedulingInterval":"5m"}}
    creationTimestamp: "2024-03-12T22:19:00Z"
    generation: 1
    name: drpolicy-sample
    resourceVersion: "950"
    uid: e59bd15b-5178-476f-bbe7-de8af997831d
  spec:
    drClusters:
    - east
    - west
    schedulingInterval: 5m

Look at last-applied-configuration, does not have replicationClassSelector

Now changed the DRPolicy YAML to add a replicationClassSelector and applied it, and it was successful, and here is the get after that:

$ kubectl apply -f ./drp.yaml 
drpolicy.ramendr.openshift.io/drpolicy-sample configured

$ kubectl get drpolicy -A -o yaml
apiVersion: v1
items:
- apiVersion: ramendr.openshift.io/v1alpha1
  kind: DRPolicy
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"ramendr.openshift.io/v1alpha1","kind":"DRPolicy","metadata":{"annotations":{},"name":"drpolicy-sample"},"spec":{"drClusters":["east","west"],"replicationClassSelector":{"matchLabels":{"class":"ramen"}},"schedulingInterval":"5m"}}
    creationTimestamp: "2024-03-12T22:19:00Z"
    generation: 2
    name: drpolicy-sample
    resourceVersion: "967"
    uid: e59bd15b-5178-476f-bbe7-de8af997831d
  spec:
    drClusters:
    - east
    - west
    replicationClassSelector:
      matchLabels:
        class: ramen
    schedulingInterval: 5m
kind: List
metadata:
  resourceVersion: ""

IOW, if we start wit a YAML that has no replicationClassSelector, and then add it later then it is allowed, which we should not allow.

Another test I did was to create a policy with empty list of DRClusters (as below) and it passed. This is a new test case though as older code also allowed this to pass through:

$ kubectl get drpolicy -A -o yaml
apiVersion: v1
items:
- apiVersion: ramendr.openshift.io/v1alpha1
  kind: DRPolicy
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"ramendr.openshift.io/v1alpha1","kind":"DRPolicy","metadata":{"annotations":{},"name":"drpolicy-sample"},"spec":{"drClusters":[],"replicationClassSelector":{"matchLabels":{"class":"ramen"}},"schedulingInterval":"5m"}}
    creationTimestamp: "2024-03-12T22:22:18Z"
    generation: 1
    name: drpolicy-sample
    resourceVersion: "1208"
    uid: 6755a4cb-cdfc-42f2-bb2e-e6a86fee267f
  spec:
    drClusters: []
    replicationClassSelector:
      matchLabels:
        class: ramen
    schedulingInterval: 5m
kind: List
metadata:
  resourceVersion: ""

Overall I think the CEL rules should be amended as below, such that we do not allow the above modifications:

diff --git a/api/v1alpha1/drpolicy_types.go b/api/v1alpha1/drpolicy_types.go
index c5fecea..ff7e80b 100644
--- a/api/v1alpha1/drpolicy_types.go
+++ b/api/v1alpha1/drpolicy_types.go
@@ -8,11 +8,7 @@ import (
 )
 
 // DRPolicySpec defines the desired state of DRPolicy
-// +kubebuilder:validation:XValidation:rule="!has(oldSelf.replicationClassSelector) || has(self.replicationClassSelector)", message="replicationClassSelector is required once set"
-// +kubebuilder:validation:XValidation:rule="!has(oldSelf.volumeSnapshotClassSelector) || has(self.volumeSnapshotClassSelector)", message="volumeSnapshotClassSelector is required once set"
 type DRPolicySpec struct {
-	// Important: Run "make" to regenerate code after modifying this file
-
 	// scheduling Interval for replicating Persistent Volume
 	// data to a peer cluster. Interval is typically in the
 	// form <num><m,h,d>. Here <num> is a number, 'm' means
@@ -25,19 +21,20 @@ type DRPolicySpec struct {
 	// Label selector to identify all the VolumeReplicationClasses.
 	// This selector is assumed to be the same for all subscriptions that
 	// need DR protection. It will be passed in to the VRG when it is created
-	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Required
 	// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="replicationClassSelector is immutable"
-	ReplicationClassSelector metav1.LabelSelector `json:"replicationClassSelector,omitempty"`
+	ReplicationClassSelector metav1.LabelSelector `json:"replicationClassSelector"`
 
 	// Label selector to identify all the VolumeSnapshotClasses.
 	// This selector is assumed to be the same for all subscriptions that
 	// need DR protection. It will be passed in to the VRG when it is created
-	// +kubebuilder:validation:Optional
+	// +kubebuilder:validation:Required
 	// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="volumeSnapshotClassSelector is immutable"
-	VolumeSnapshotClassSelector metav1.LabelSelector `json:"volumeSnapshotClassSelector,omitempty"`
+	VolumeSnapshotClassSelector metav1.LabelSelector `json:"volumeSnapshotClassSelector"`
 
 	// List of DRCluster resources that are governed by this policy
 	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:XValidation:rule="size(self) == 2", message="drClusters requires a list of 2 clusters"
 	// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="drClusters is immutable"
 	DRClusters []string `json:"drClusters"`
 }

For the tests, unfortunately as it uses a go struct for the DRPolicy, it is able to write out initial configuration with empty values, and so when we use YAML this is working differently.

I am not sure yet how to test with YAML and envtest, so that is unfortunate. We could test it in a kind cluster though and ensure that CEL rules are adhered to. I just used hack/setup-kind-cluster.sh to get a kind cluster, extended the CRD for DRPolicy and then tested with various YAMLs as above.

@rakeshgm
Copy link
Member Author

rakeshgm commented Mar 13, 2024

Thanks for the detail input. I have pushed the changes.

Signed-off-by: rakeshgm <[email protected]>
excluding lll on long comments helps in
kubebuilder validation rules to not be linted

Signed-off-by: rakeshgm <[email protected]>
Signed-off-by: rakeshgm <[email protected]>
@ShyamsundarR ShyamsundarR merged commit 16b07cf into RamenDR:main Mar 14, 2024
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants