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

webhook rules for sandbox shutdown #971

Merged
merged 46 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
adf92b3
implementation ready
LiboYu2 Oct 22, 2024
65d98ec
remove duplicate checking
LiboYu2 Oct 22, 2024
0ff79ad
no regression at this point
LiboYu2 Oct 22, 2024
96879d5
two create test cases added
LiboYu2 Oct 22, 2024
c43e823
add annotatated subcluster test
LiboYu2 Oct 22, 2024
2c690bc
made some logic change
LiboYu2 Oct 23, 2024
5f81aed
more tests need to be added
LiboYu2 Oct 23, 2024
56ea391
test case need conformation
LiboYu2 Oct 23, 2024
7dca0d4
rewording some tests
LiboYu2 Oct 23, 2024
df5724d
minor refactoring
LiboYu2 Oct 23, 2024
b7814e7
remove an edge test as confirmed by Roy
LiboYu2 Oct 23, 2024
7511fa3
fix a bug
LiboYu2 Oct 23, 2024
fbd7d10
minor refactoring
LiboYu2 Oct 23, 2024
2f29519
fix a bug found during manual testing
LiboYu2 Oct 24, 2024
766c31b
fix bugs found
LiboYu2 Oct 25, 2024
754635d
not allow users to add drivenbyshutdown annotation
LiboYu2 Oct 25, 2024
7d6a6a3
fix a typo
LiboYu2 Oct 25, 2024
c9c6826
not allowed to unset scluter shutdown when sbox shutdown
LiboYu2 Oct 25, 2024
6348299
add new unsandbox test case
LiboYu2 Oct 25, 2024
d398cf3
distinguish unsandbox/terminate
LiboYu2 Oct 26, 2024
7be1995
remove newly added and uncommented previous implementation
LiboYu2 Oct 28, 2024
0c4d152
update annotated test cases
LiboYu2 Oct 28, 2024
c5df20c
fix lint errors
LiboYu2 Oct 28, 2024
a2fa915
make changes per code review
LiboYu2 Oct 30, 2024
86305d2
new rule and test cases added
LiboYu2 Oct 30, 2024
c573373
added two more cases per code review
LiboYu2 Oct 31, 2024
3b1ed31
moved new functions to validateImmutableFields. fixed 3 broken test c…
LiboYu2 Oct 31, 2024
d61031c
use index map from status intead of old vdb
LiboYu2 Oct 31, 2024
136a6c0
Update api/v1/verticadb_webhook.go
LiboYu2 Oct 31, 2024
f7b12e0
Update api/v1/verticadb_webhook.go
LiboYu2 Oct 31, 2024
1eae0a2
fix build errors
LiboYu2 Oct 31, 2024
24c1533
Update api/v1/verticadb_webhook.go
LiboYu2 Nov 1, 2024
bbfef12
Update api/v1/verticadb_webhook.go
LiboYu2 Nov 4, 2024
a554cda
Update api/v1/verticadb_webhook.go
LiboYu2 Nov 4, 2024
801ecf6
make changed per code review
LiboYu2 Nov 4, 2024
5a3f913
Merge branch 'VER-97368-webhook-rules' of github.com:vertica/vertica-…
LiboYu2 Nov 4, 2024
6a69ce8
address code review comments
LiboYu2 Nov 6, 2024
f0d1e86
add new test cases for new unsandbox scenario
LiboYu2 Nov 6, 2024
8bfe4a1
optimize a little bit per code review
LiboYu2 Nov 6, 2024
4d4bea3
fix lint error, duplicate err msg, logic change
LiboYu2 Nov 7, 2024
901a0e1
put back pieces removed by mistake
LiboYu2 Nov 7, 2024
b88741b
updated unsandbox definition per code review
LiboYu2 Nov 8, 2024
a2715ed
new rule to avoid user changing subcluster shutdown
LiboYu2 Nov 8, 2024
ae518fb
remove useless comment
LiboYu2 Nov 8, 2024
ec040a6
update method description
LiboYu2 Nov 8, 2024
ae0a3c7
changed if statement
LiboYu2 Nov 8, 2024
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
30 changes: 30 additions & 0 deletions api/v1/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,36 @@ func (v *VerticaDB) GenSubclusterSandboxStatusMap() map[string]string {
return scSbMap
}

// GenStatusSandboxMap() returns a map from status. The key is sandbox name and value is the sandbox pointer
func (v *VerticaDB) GenStatusSandboxMap() map[string]*SandboxStatus {
statusSboxMap := make(map[string]*SandboxStatus)
for i := range v.Status.Sandboxes {
sBox := &v.Status.Sandboxes[i]
statusSboxMap[sBox.Name] = sBox
}
return statusSboxMap
}

// GenStatusSandboxMap() returns a map from status. The key is subcluster name and value is the subcluster pointer
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor: update GenStatusSandboxMap to GenStatusSubclusterMap

func (v *VerticaDB) GenStatusSubclusterMap() map[string]*SubclusterStatus {
statusSclusterMap := make(map[string]*SubclusterStatus)
for i := range v.Status.Subclusters {
sCluster := &v.Status.Subclusters[i]
statusSclusterMap[sCluster.Name] = sCluster
}
return statusSclusterMap
}

// GenStatusSClusterIndexMap will organize all of the subclusters into a map so we
// can quickly find its index in the status.subclusters[] array.
func (v *VerticaDB) GenStatusSClusterIndexMap() map[string]int {
m := make(map[string]int)
for i := range v.Status.Subclusters {
m[v.Status.Subclusters[i].Name] = i
}
return m
}

// GenSandboxSubclusterMapForUnsandbox will compare sandbox status and spec
// for finding subclusters that need to be unsandboxed, this function returns a map
// with sandbox name as the key and its subclusters (need to be unsandboxed) as the value
Expand Down
223 changes: 219 additions & 4 deletions api/v1/verticadb_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const (
S3Prefix = "s3://"
GCloudPrefix = "gs://"
AzurePrefix = "azb://"
trueString = "true"
)

// hdfsPrefixes are prefixes for an HDFS path.
Expand Down Expand Up @@ -105,7 +106,7 @@ func (v *VerticaDB) ValidateCreate() error {
verticadblog.Info("validate create", "name", v.Name, "GroupVersion", GroupVersion)

allErrs := v.validateVerticaDBSpec()
if allErrs == nil {
if len(allErrs) == 0 {
return nil
}
return apierrors.NewInvalid(schema.GroupKind{Group: Group, Kind: VerticaDBKind}, v.Name, allErrs)
Expand All @@ -116,7 +117,8 @@ func (v *VerticaDB) ValidateUpdate(old runtime.Object) error {
verticadblog.Info("validate update", "name", v.Name, "GroupVersion", GroupVersion)

allErrs := append(v.validateImmutableFields(old), v.validateVerticaDBSpec()...)
if allErrs == nil {

if len(allErrs) == 0 {
return nil
}
return apierrors.NewInvalid(schema.GroupKind{Group: Group, Kind: VerticaDBKind}, v.Name, allErrs)
Expand Down Expand Up @@ -148,6 +150,11 @@ func (v *VerticaDB) validateImmutableFields(old runtime.Object) field.ErrorList
allErrs = v.checkImmutableStsName(oldObj, allErrs)
allErrs = v.checkValidSubclusterTypeTransition(oldObj, allErrs)
allErrs = v.checkSandboxesDuringUpgrade(oldObj, allErrs)
allErrs = v.validateShutdownSandboxImage(old, allErrs)
allErrs = v.validateTerminatingSandboxes(old, allErrs)
allErrs = v.validateUnsandboxShutdownConditions(old, allErrs)
allErrs = v.validateAnnotatedSubclustersInShutdownSandbox(old, allErrs)
allErrs = v.validateNewSBoxOrSClusterShutdownUnset(allErrs)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's keep the same function structure and call these check... like checkShutdownSandboxImage

return allErrs
}

Expand Down Expand Up @@ -1591,6 +1598,23 @@ func (v *VerticaDB) findPersistScsInSandbox(oldObj *VerticaDB) map[string]int {
return persistScsWithSbIndex
}

// findPersistSandboxes returns a slice of sandbox names that are in both new and old vdb.
func (v *VerticaDB) findPersistSandboxes(oldObj *VerticaDB) []string {
oldSandboxes := oldObj.Spec.Sandboxes
newSandboxes := v.Spec.Sandboxes
oldSandboxMap := make(map[string]int)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor: this map should be a map[string]any or map[string]bool.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This needs to be addressed.

for _, oldSandbox := range oldSandboxes {
oldSandboxMap[oldSandbox.Name] = 1
}
persistSandboxes := make([]string, 0)
for _, newSandbox := range newSandboxes {
if _, ok := oldSandboxMap[newSandbox.Name]; ok {
persistSandboxes = append(persistSandboxes, newSandbox.Name)
}
}
return persistSandboxes
}

// checkImmutableSubclusterInSandbox ensures we do not scale and remove any subcluster that is in a sandbox
func (v *VerticaDB) checkImmutableSubclusterInSandbox(oldObj *VerticaDB, allErrs field.ErrorList) field.ErrorList {
// if either old vdb or new vdb does not have any sandboxes, skip this check
Expand Down Expand Up @@ -1668,13 +1692,15 @@ func (v *VerticaDB) checkSandboxPrimary(allErrs field.ErrorList, oldObj *Vertica
oldSbMap := oldObj.GenSandboxMap()
newSbMap := v.GenSandboxMap()
for oldScName, oldSbName := range oldScInSandbox {
newSbName, newFound := newScInSandbox[oldScName]
sc := oldScMap[oldScName]

// sandbox is removed
if _, sandboxExist := newSbMap[oldSbName]; !sandboxExist {
continue
}

newSbName, newFound := newScInSandbox[oldScName]
// old sandbox still exits
if !newFound && sc.Type == SandboxPrimarySubcluster {
i := oldScIndexMap[oldScName]
err := field.Invalid(path.Index(i),
Expand All @@ -1683,11 +1709,12 @@ func (v *VerticaDB) checkSandboxPrimary(allErrs field.ErrorList, oldObj *Vertica
allErrs = append(allErrs, err)
continue
}

// old sandbox exists and subcluster either exists (primary or nonprimary) or removed (nonprimary)
// Remaining check is concerned with subclusters moving between sandboxes.
if oldSbName == newSbName {
continue
}
// old sandbox name does not match new sandbox name
if sc.Type == SandboxPrimarySubcluster {
i := oldSbIndexMap[oldSbName]
p := field.NewPath("spec").Child("sandboxes")
Expand All @@ -1701,6 +1728,194 @@ func (v *VerticaDB) checkSandboxPrimary(allErrs field.ErrorList, oldObj *Vertica
return allErrs
}

// ensures a new subcluster or sandbox to be added to a vdb has Shutdown field set to false
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// ensures a new subcluster or sandbox to be added to a vdb has Shutdown field set to false
// validateNewSBoxOrSClusterShutdownUnset ensures a new subcluster or sandbox to be added to a vdb has Shutdown field set to false

Follow the same forma for other functions

func (v *VerticaDB) validateNewSBoxOrSClusterShutdownUnset(allErrs field.ErrorList) field.ErrorList {
statusSClusterMap := v.GenStatusSubclusterMap()
statusSBoxMap := v.GenStatusSandboxMap()
for i := range v.Spec.Sandboxes {
newSBox := v.Spec.Sandboxes[i]
if _, foundInStatus := statusSBoxMap[newSBox.Name]; !foundInStatus && newSBox.Shutdown {
p := field.NewPath("spec").Child("sandboxes").Index(i)
err := field.Invalid(p,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
err := field.Invalid(p,
err := field.Invalid(p.Child("shutdown"),

newSBox.Shutdown,
fmt.Sprintf("shutdown must be false when adding sandbox %q",
newSBox.Name))
allErrs = append(allErrs, err)
}
}
for i := range v.Spec.Subclusters {
newSCluster := &v.Spec.Subclusters[i]
if _, foundInStatus := statusSClusterMap[newSCluster.Name]; !foundInStatus && newSCluster.Shutdown {
p := field.NewPath("spec").Child("subclusters").Index(i)
err := field.Invalid(p,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
err := field.Invalid(p,
err := field.Invalid(p.child("shutdown"),

newSCluster.Shutdown,
fmt.Sprintf("shutdown must be false when adding subcluster %q",
newSCluster.Name))
allErrs = append(allErrs, err)
}
}
return allErrs
}

// return a map whose key is the pointer of a subcluster that is to be unsandboxed. The value is the pointer to a sandbox
// where the subcluster lives in
func (v *VerticaDB) findSclustersToUnsandbox(old runtime.Object) map[*Subcluster]*Sandbox {
oldObj := old.(*VerticaDB)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not just past this as argument?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Then the casting will be done in the caller of this function. It makes no difference.

oldSubclusterInSandbox := oldObj.GenSubclusterSandboxMap()
newSuclusterInSandbox := v.GenSubclusterSandboxMap()
oldSubclusterMap := oldObj.GenSubclusterMap()
oldSandboxMap := oldObj.GenSandboxMap()
newSubclusterMap := v.GenSubclusterMap()
sclusterSboxMap := make(map[*Subcluster]*Sandbox)
for oldSubclusterName, oldSandboxName := range oldSubclusterInSandbox {
_, oldSubclusterInNewSandboxes := newSuclusterInSandbox[oldSubclusterName]
Copy link
Collaborator

Choose a reason for hiding this comment

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

You need to check if the old sandbox name and new sandbox name are the same. If we move a subcluster from a sandbox to another in new vdb, that subcluster is still in new vdb, but it will be unsandboxed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I will make code change to cover that scenario and set up a new test case.

_, oldSubclusterInPersist := newSubclusterMap[oldSubclusterName]
// for unsandboxing, check shutdown field of the subcluster and sandbox
if !oldSubclusterInNewSandboxes && oldSubclusterInPersist {
oldSubcluster := oldSubclusterMap[oldSubclusterName]
oldSandbox := oldSandboxMap[oldSandboxName]
sclusterSboxMap[oldSubcluster] = oldSandbox
}
}
return sclusterSboxMap
}

// validateUnsandboxShutdownConditions ensures we will not unsandbox a subcluster that has shutdown set to true, or a subcluster
// in a sandbox where the shutdown field for the sandbox is set to true
func (v *VerticaDB) validateUnsandboxShutdownConditions(old runtime.Object, allErrs field.ErrorList) field.ErrorList {
statusSClusterIndexMap := v.GenStatusSClusterIndexMap()
sclustersToUnsandbox := v.findSclustersToUnsandbox(old)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
sclustersToUnsandbox := v.findSclustersToUnsandbox(old)
subclustersToUnsandbox := v.findSclustersToUnsandbox(old)

for oldSubcluster, oldSandbox := range sclustersToUnsandbox {
oldSubclusterIndex := statusSClusterIndexMap[oldSubcluster.Name]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Always make sure the subcluster is in the status. If it is not(very unlikely), skip it.

if oldSubcluster.Shutdown || v.Status.Subclusters[oldSubclusterIndex].Shutdown {
p := field.NewPath("spec").Child("subclusters")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
p := field.NewPath("spec").Child("subclusters")
p := field.NewPath("spec").Child("subclusters").Index(...)

err := field.Invalid(p,
oldSubcluster.Name,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
err := field.Invalid(p,
oldSubcluster.Name,
err := field.Invalid(p.Child("shutdown"),
oldSubcluster.Shutdown,

fmt.Sprintf("cannot unsandbox subcluster %q that has Shutdown field set to true",
oldSubcluster.Name))
allErrs = append(allErrs, err)
continue
}
if oldSandbox.Shutdown {
Copy link
Collaborator

Choose a reason for hiding this comment

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

For one sandbox, we probably reach here multiple times so we probably return duplicate errors.

p := field.NewPath("spec").Child("sandboxes")
err := field.Invalid(p,
oldSubcluster.Name,
fmt.Sprintf("cannot unsandbox subcluster %q in sandbox %q that has Shutdown field set to true",
oldSubcluster.Name, oldSandbox.Name))
allErrs = append(allErrs, err)
continue
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
allErrs = append(allErrs, err)
continue
}
if oldSandbox.Shutdown {
p := field.NewPath("spec").Child("sandboxes")
err := field.Invalid(p,
oldSubcluster.Name,
fmt.Sprintf("cannot unsandbox subcluster %q in sandbox %q that has Shutdown field set to true",
oldSubcluster.Name, oldSandbox.Name))
allErrs = append(allErrs, err)
continue
}
allErrs = append(allErrs, err)
}
if oldSandbox.Shutdown {
p := field.NewPath("spec").Child("sandboxes").Index(...)
err := field.Invalid(p.Child("shutdown"),
oldSandbox.Name,
fmt.Sprintf("cannot unsandbox subcluster %q in sandbox %q that has shutdown field set to true",
oldSubcluster.Name, oldSandbox.Name))
allErrs = append(allErrs, err)
}

}
return allErrs
}

// ensure: when a sandbox is to be shutdown, if a subcluster in it has annotation vertica.com/shutdown-driven-by-sandbox set to true, the
// subcluster's shutdown field should not be updated
func (v *VerticaDB) validateAnnotatedSubclustersInShutdownSandbox(old runtime.Object, allErrs field.ErrorList) field.ErrorList {
oldObj := old.(*VerticaDB)
persistSandboxes := v.findPersistSandboxes(oldObj)
newSandboxMap := v.GenSandboxMap()
oldSandboxMap := oldObj.GenSandboxMap()
newSubclusterIndexMap := v.GenSubclusterIndexMap()
newSubclusterMap := v.GenSubclusterMap()
oldSubclusterMap := oldObj.GenSubclusterMap()
for _, sandboxName := range persistSandboxes {
newSandbox := newSandboxMap[sandboxName]
oldSandbox := oldSandboxMap[sandboxName]
if newSandbox.Shutdown {
for _, subclusterName := range oldSandbox.Subclusters {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we checking old sandbox's subclusters? Doesn't this rule apply on the subclusters in new sandbox?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is from the JIRA: " If a sandbox has shutdown(spec) true and any of its subclusters has the annotation "vertica.com/shutdown-driven-by-sandbox" set to true in the old vdb, we should prevent the user from updating that subcluster's shutdown field. "
Here we check the subclusters that exist in both new and old vdbs.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Then in line 1836, we should check oldSandbox.Shutdown.

Copy link
Collaborator

Choose a reason for hiding this comment

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

No we should check newSandbox. Here is the scenario this covers: sandbox sand1 has a subcluster sc1. you set sand1.shutdown to true, the operator will set sc1.shutdown to true and add the annotation to sc1. So now sand.shutdown = true, sc1.shutdown. true and sc1 has "vertica.com/shutdown-driven-by-sandbox". We do not want a user to come and change sc1.shutdown because that annotation is a proof that the sandbox drives the shutdown.

This is relevant only if sand1.shutdown is still true(newvdb)

oldSubcluster := oldSubclusterMap[subclusterName.Name]
if vmeta.GetShutdownDrivenBySandbox(oldSubcluster.Annotations) {
newSubcluster, oldSclusterPersist := newSubclusterMap[subclusterName.Name]
if oldSclusterPersist && oldSubcluster.Shutdown != newSubcluster.Shutdown {
index := newSubclusterIndexMap[subclusterName.Name]
p := field.NewPath("spec").Child("subclusters")
err := field.Invalid(p.Index(index),
subclusterName.Name,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
err := field.Invalid(p.Index(index),
subclusterName.Name,
err := field.Invalid(p.Index(index).Child("shutdown"),
newSubcluster.Shutdown,

fmt.Sprintf("cannot change Shutdown field for subcluster %q that has annotation \"vertica.com/shutdown-driven-by-sandbox\" set to true",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
fmt.Sprintf("cannot change Shutdown field for subcluster %q that has annotation \"vertica.com/shutdown-driven-by-sandbox\" set to true",
fmt.Sprintf("cannot change shutdown field for subcluster %q whose shutdown is driven by sandbox %q",

subclusterName.Name))
allErrs = append(allErrs, err)
}
}
}
}
}
return allErrs
}

// ensures a sandbox's image is not updated after the sandbox or any of its subclusters are shut down
func (v *VerticaDB) validateShutdownSandboxImage(old runtime.Object, allErrs field.ErrorList) field.ErrorList {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add a comment for this function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

"Why don't we allow shutdown in db creation?"

That rule will be changed to the following per Roy's comment:
when a subcluster or sandbox is added to the vdb for the 1st time (not in status yet) they all must have shutdown field set to false

I will implement this new rule.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, and this is because we want to keep things simple, the users must first create the subcluster/sandbox before they can shut it down.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We should have one more rule: if the sandbox is shut down or partial shut down, we cannot add or remove any subclusters in it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The remove scenario is covered by unsandbox. We can discuss with Roy about the add scenario.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

FYI:
I did some manual testing. When I tried to add a new subcluster to a shutdown sandbox. Webhook did not complain. The spec was updated but nothing happened. It seems that is not supported and we may need a new rule.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The proposed new rule can be found in another webhook JIRA VER-97254

oldObj := old.(*VerticaDB)
persistSandboxes := v.findPersistSandboxes(oldObj)
oldSandboxMap := oldObj.GenSandboxMap()
newSandboxMap := v.GenSandboxMap()
newSandboxIndexMap := v.GenSandboxIndexMap()
newSubclusterMap := v.GenSubclusterMap()
statusSClusterIndexMap := v.GenStatusSClusterIndexMap()
for _, sandboxName := range persistSandboxes {
oldSandbox := oldSandboxMap[sandboxName]
newSandbox := newSandboxMap[sandboxName]
if oldSandbox.Image != newSandbox.Image {
newSandboxIndex := newSandboxIndexMap[sandboxName]
errMsg := v.checkSboxForShutdown(newSandbox, newSubclusterMap, statusSClusterIndexMap)
if errMsg != "" {
p := field.NewPath("spec").Child("sandboxes").Index(newSandboxIndex)
err := field.Invalid(p.Child("image"),
sandboxName,
LiboYu2 marked this conversation as resolved.
Show resolved Hide resolved
fmt.Sprintf("cannot change the image for sandbox %q because %q",
sandboxName, errMsg))
allErrs = append(allErrs, err)
}
}
}
return allErrs
}

// ensure the sandbox to terminate is not shut down
func (v *VerticaDB) validateTerminatingSandboxes(old runtime.Object, allErrs field.ErrorList) field.ErrorList {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add a comment for this function.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can simplify this rule to "we cannot remove any subclusters that are shut down". Preventing removing the sandbox should already be covered in validateUnsandboxShutdownConditions.

We should remove this rule in future since a stopped subcluster/sandbox should be able to removed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is from the JIRA requirement: "terminate means removing the sandbox in spec.sandboxes[] and its subclusters in spec.subclusters[] at the same time."
There is another rule that does not allow unsandboxing a subcluster that is shut down. The two scenarios require different handling logics.

oldObj := old.(*VerticaDB)
oldSandboxMap := oldObj.GenSandboxMap()
newSandboxMap := v.GenSandboxMap()
sandboxesToBeRemoved := vutil.MapKeyDiff(oldSandboxMap, newSandboxMap)
newSubclusterMap := v.GenSubclusterMap()
for _, sandboxToBeRemoved := range sandboxesToBeRemoved {
numOfSclustersInSbox := len(oldSandboxMap[sandboxToBeRemoved].Subclusters)
sclustersToTerminate := numOfSclustersInSbox
for _, subcluster := range oldSandboxMap[sandboxToBeRemoved].Subclusters {
if _, ok := newSubclusterMap[subcluster.Name]; ok {
sclustersToTerminate--
}
}
// sandboxToBeRemoved and all its subclusters are not found in new spec
if sclustersToTerminate == numOfSclustersInSbox {
if oldSandboxMap[sandboxToBeRemoved].Shutdown {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This check should be at the beginning of the loop so you can skip to the next sandbox if the current sandbox has shutdown true.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This if statement compares the number of subclusters to be removed with the number of subclusters in the sandbox.
If they equal, that means the sandbox will be terminated.
There is another for loop on the top of this if statement. It is used to calculate "sclustersToTerminate" used in the if statement.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Again, if oldSandboxMap[sandboxToBeRemoved].Shutdown is false then there is no point in running all this code. Check oldSandboxMap[sandboxToBeRemoved].Shutdown at the beginning of the loop and if false skip and go to next sandbox. Something like:

for _, sandboxToBeRemoved := range sandboxesToBeRemoved {
       if !oldSandboxMap[sandboxToBeRemoved].Shutdown {
              continue
       }

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That is a good point. I will check oldSandboxMap[sandboxToBeRemoved].Shutdown earlier. If it is false, let it skip the rest of the current loop.

p := field.NewPath("spec").Child("sandboxes")
err := field.Invalid(p,
sandboxToBeRemoved,
fmt.Sprintf("cannot terminate sandbox %q that has Shutdown field set to true",
sandboxToBeRemoved))
allErrs = append(allErrs, err)
} // else looks good
} // else is expected
}
return allErrs
}

// checkSboxForShutdown checks if sbox or its scluster has shutdown field set to true in spec/status
Copy link
Collaborator

Choose a reason for hiding this comment

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

Enrich the comment to explain the returned value.

func (v *VerticaDB) checkSboxForShutdown(newSandbox *Sandbox, newSClusterMap map[string]*Subcluster, statusSClusterIndexMap map[string]int) string {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
func (v *VerticaDB) checkSboxForShutdown(newSandbox *Sandbox, newSClusterMap map[string]*Subcluster, statusSClusterIndexMap map[string]int) string {
func (v *VerticaDB) checkSboxForShutdown(newSandbox *Sandbox, newSClusterMap map[string]*Subcluster, statusSClusterIndexMap map[string]int) []string {
errMsgs := []string{}

We still want to return all the error msgs. We just did not want to return one batch for spec and another for status.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Now I know what you want to achieve. I withdraw my previous comments and will make the change.

if newSandbox.Shutdown {
return fmt.Sprintf("Shutdown field of sandbox %q is set to true", newSandbox.Name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return fmt.Sprintf("Shutdown field of sandbox %q is set to true", newSandbox.Name)
errMsgs = append(errMsgs, fmt.Sprintf("Shutdown field of sandbox %q is set to true", newSandbox.Name))

Copy link
Collaborator Author

@LiboYu2 LiboYu2 Nov 4, 2024

Choose a reason for hiding this comment

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

will make the change.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, but it can happen that the sandbox has shutdown true and also all its subclusters have shutdown true. In that case we want to report all the error msgs so the user is aware.

}

for _, subcluster := range newSandbox.Subclusters {
i, foundInStatus := statusSClusterIndexMap[subcluster.Name]
if foundInStatus && (newSClusterMap[subcluster.Name].Shutdown || v.Status.Subclusters[i].Shutdown) {
LiboYu2 marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Sprintf("Subcluster %q is marked for shut down or has already been shut down", subcluster.Name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return fmt.Sprintf("Subcluster %q is marked for shut down or has already been shut down", subcluster.Name)
errMsgs = append(errMsgs, fmt.Sprintf("Subcluster %q is marked for shut down or has already been shut down", subcluster.Name))

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

same comment as the above one.

}
}
return ""
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return ""
return errMsgs

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

same comment as the above one. When it returns empty string, it means no error.

}

// checkImmutableStsName ensures the statefulset name of the subcluster stays constant
func (v *VerticaDB) checkImmutableStsName(oldObj *VerticaDB, allErrs field.ErrorList) field.ErrorList {
// We have an annotation to control the sts name. We are not going to allow
Expand Down
Loading
Loading