polardbxoperator/pkg/webhook/polardbxcluster/validator.go

835 lines
24 KiB
Go

/*
Copyright 2021 Alibaba Group Holding Limited.
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 polardbxcluster
import (
"bytes"
"context"
"fmt"
"strconv"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/validation/field"
polardbxv1 "github.com/alibaba/polardbx-operator/api/v1"
polardbxv1common "github.com/alibaba/polardbx-operator/api/v1/common"
polardbxv1polardbx "github.com/alibaba/polardbx-operator/api/v1/polardbx"
polardbxv1xstore "github.com/alibaba/polardbx-operator/api/v1/xstore"
iniutil "github.com/alibaba/polardbx-operator/pkg/util/ini"
"github.com/alibaba/polardbx-operator/pkg/webhook/extension"
)
func validateImagePullPolicy(fldPath *field.Path, policy corev1.PullPolicy) *field.Error {
switch policy {
case "", corev1.PullIfNotPresent, corev1.PullAlways, corev1.PullNever:
return nil
default:
return field.NotSupported(fldPath, policy, []string{
"", string(corev1.PullIfNotPresent), string(corev1.PullAlways), string(corev1.PullNever),
})
}
}
func validateResources(ctx context.Context, fldPath *field.Path, resources *corev1.ResourceRequirements) field.ErrorList {
var errList field.ErrorList
// If no limits and requests are set, there's a default value for the resource in CRD.
if resources.Limits == nil && resources.Requests == nil {
return nil
}
if resources.Limits == nil {
errList = append(errList, field.Required(fldPath.Child("limits"), "limits is required"))
}
cpuLimits, cpuLimitsExists := resources.Limits[corev1.ResourceCPU]
if !cpuLimitsExists {
errList = append(errList, field.Required(fldPath.Child("limits", "cpu"), "limits.cpu is required"))
}
memoryLimits, memoryLimitsExists := resources.Limits[corev1.ResourceMemory]
if !memoryLimitsExists {
errList = append(errList, field.Required(fldPath.Child("limits", "memory"), "limits.memory is required"))
}
if resources.Requests != nil {
if cpuLimitsExists {
if cpuRequests, ok := resources.Requests[corev1.ResourceCPU]; ok {
if cpuRequests.MilliValue() > cpuLimits.MilliValue() {
errList = append(errList, field.Invalid(fldPath.Child("requests", "cpu"),
cpuRequests.String(), "must be less than limits: "+cpuLimits.String()))
}
}
}
if memoryLimitsExists {
if memoryRequests, ok := resources.Requests[corev1.ResourceMemory]; ok {
if memoryRequests.Value() > memoryLimits.Value() {
errList = append(errList, field.Invalid(fldPath.Child("requests", "memory"),
memoryRequests.String(), "must be less than limits: "+memoryLimits.String()))
}
}
}
}
return errList
}
func validateExtendResources(ctx context.Context, fldPath *field.Path, resources *polardbxv1common.ExtendedResourceRequirements) field.ErrorList {
return validateResources(ctx, fldPath, &resources.ResourceRequirements)
}
type PolarDBXClusterV1Validator struct {
configLoader func() *ValidatorConfig
}
func (v *PolarDBXClusterV1Validator) validateSecurity(ctx context.Context, security *polardbxv1polardbx.Security) field.ErrorList {
var errList field.ErrorList
if security == nil {
return errList
}
if security.EncodeKey != nil {
if security.EncodeKey.Name == "" {
errList = append(errList, field.NotFound(
field.NewPath("spec", "security", "encodeKey", "name"), ""),
)
}
if security.EncodeKey.Key == "" {
errList = append(errList, field.NotFound(
field.NewPath("spec", "security", "encodeKey", "key"), ""),
)
}
}
return errList
}
func (v *PolarDBXClusterV1Validator) validateNodeSelector(fieldPath *field.Path, ns *corev1.NodeSelector) *field.Error {
return nil
}
func (v *PolarDBXClusterV1Validator) validateStatelessTopologyRuleItems(ctx context.Context, fieldPath *field.Path, items []polardbxv1polardbx.StatelessTopologyRuleItem, validSelectors map[string]int) field.ErrorList {
var errList field.ErrorList
names := make(map[string]int)
for index, item := range items {
_, ok := names[item.Name]
if ok {
errList = append(errList, field.Duplicate(
fieldPath.Index(index).Child("name"),
item.Name),
)
} else {
names[item.Name] = index
}
// Defer validation on replicas.
ns := item.NodeSelector
if ns != nil {
if ns.Reference != "" {
_, found := validSelectors[ns.Reference]
if !found {
errList = append(errList, field.Invalid(
fieldPath.Index(index).Child("selector", "reference"),
ns.Reference,
"invalid selector, not predefined",
))
}
}
}
}
return errList
}
func (v *PolarDBXClusterV1Validator) validateXStoreTopologyRule(ctx context.Context, fieldPath *field.Path, rule *polardbxv1polardbx.XStoreTopologyRule, validSelectors map[string]int) field.ErrorList {
var errList field.ErrorList
if rule == nil {
return errList
}
if rule.Rolling != nil && len(rule.NodeSets) > 0 {
errList = append(errList, field.Invalid(fieldPath,
rule,
"rolling and nodeSets can not be both defined"))
return errList
}
if rule.Rolling != nil {
if rule.Rolling.Replicas%2 == 0 {
errList = append(errList, field.Invalid(
fieldPath.Child("rolling", "replicas"),
rule.Rolling.Replicas,
"must be odd"))
}
ns := rule.Rolling.NodeSelector
if ns != nil {
if ns.Reference != "" {
_, found := validSelectors[ns.Reference]
if !found {
errList = append(errList, field.Invalid(
fieldPath.Child("rolling", "selector", "reference"),
ns.Reference,
"invalid selector, not predefined",
))
}
}
}
}
if len(rule.NodeSets) > 0 {
nodeSetNames := make(map[string]int)
for index, nodeSet := range rule.NodeSets {
if nodeSet.Name == "" {
errList = append(errList, field.NotFound(
fieldPath.Child("nodeSets").Index(index).Child("name"),
"",
))
} else {
_, found := nodeSetNames[nodeSet.Name]
if found {
errList = append(errList, field.Duplicate(
fieldPath.Child("nodeSets").Index(index).Child("name"),
nodeSet.Name))
} else {
nodeSetNames[nodeSet.Name] = index
}
}
switch nodeSet.Role {
case polardbxv1xstore.RoleCandidate,
polardbxv1xstore.RoleVoter,
polardbxv1xstore.RoleLearner:
// break
default:
errList = append(errList, field.NotSupported(
fieldPath.Child("nodeSets").Index(index).Child("role"),
nodeSet.Role,
[]string{
string(polardbxv1xstore.RoleCandidate),
string(polardbxv1xstore.RoleVoter),
string(polardbxv1xstore.RoleLearner),
}),
)
}
ns := nodeSet.NodeSelector
if ns != nil {
if ns.Reference != "" {
_, found := validSelectors[ns.Reference]
if !found {
errList = append(errList, field.Invalid(
fieldPath.Index(index).Child("selector", "reference"),
ns.Reference,
"invalid selector, not predefined",
))
}
}
}
}
}
return errList
}
func (v *PolarDBXClusterV1Validator) validateTopologyRules(ctx context.Context, rules *polardbxv1polardbx.TopologyRules) field.ErrorList {
var errList field.ErrorList
fieldPath := field.NewPath("spec", "topology", "rules")
validSelectors := make(map[string]int)
for index, selector := range rules.Selectors {
if selector.Name == "" {
errList = append(errList, field.NotFound(
fieldPath.
Child("selectors").
Index(index).
Child("name"),
""),
)
} else {
_, ok := validSelectors[selector.Name]
if ok {
errList = append(errList, field.Duplicate(
fieldPath.
Child("selectors").
Index(index).
Child("name"),
selector.Name),
)
} else {
validSelectors[selector.Name] = index
}
}
err := v.validateNodeSelector(fieldPath.
Child("selectors").
Index(index).
Child("nodeSelector"), &selector.NodeSelector)
if err != nil {
errList = append(errList, err)
}
}
errList = append(errList, v.validateStatelessTopologyRuleItems(ctx,
fieldPath.Child("components", "cn"), rules.Components.CN, validSelectors)...)
errList = append(errList, v.validateStatelessTopologyRuleItems(ctx,
fieldPath.Child("components", "cdc"), rules.Components.CDC, validSelectors)...)
errList = append(errList, v.validateStatelessTopologyRuleItems(ctx,
fieldPath.Child("components", "columnar"), rules.Components.Columnar, validSelectors)...)
errList = append(errList, v.validateXStoreTopologyRule(ctx,
fieldPath.Child("components", "gms"), rules.Components.GMS, validSelectors)...)
errList = append(errList, v.validateXStoreTopologyRule(ctx,
fieldPath.Child("components", "dn"), rules.Components.DN, validSelectors)...)
return errList
}
func (v *PolarDBXClusterV1Validator) validateXStoreTemplate(ctx context.Context, fldPath *field.Path, template *polardbxv1polardbx.XStoreTemplate) field.ErrorList {
if template == nil {
return nil
}
var errList field.ErrorList
switch template.ServiceType {
case corev1.ServiceTypeClusterIP,
corev1.ServiceTypeNodePort,
corev1.ServiceTypeLoadBalancer,
corev1.ServiceTypeExternalName: // break
default:
errList = append(errList, field.NotSupported(
fldPath.Child("serviceType"),
template.ServiceType,
[]string{
string(corev1.ServiceTypeClusterIP),
string(corev1.ServiceTypeNodePort),
string(corev1.ServiceTypeLoadBalancer),
string(corev1.ServiceTypeExternalName),
}))
}
if err := validateImagePullPolicy(fldPath.Child("imagePullPolicy"), template.ImagePullPolicy); err != nil {
errList = append(errList, err)
}
errList = append(errList, validateExtendResources(ctx, fldPath.Child("resources"), &template.Resources)...)
return errList
}
func (v *PolarDBXClusterV1Validator) validateCNTemplate(ctx context.Context, fldPath *field.Path, template *polardbxv1polardbx.CNTemplate) field.ErrorList {
var errList field.ErrorList
errList = append(errList, validateResources(ctx, fldPath.Child("resources"), &template.Resources)...)
if err := validateImagePullPolicy(fldPath.Child("imagePullPolicy"), template.ImagePullPolicy); err != nil {
errList = append(errList, err)
}
return errList
}
func (v *PolarDBXClusterV1Validator) validateCDCTemplate(ctx context.Context, fldPath *field.Path, template *polardbxv1polardbx.CDCTemplate) field.ErrorList {
var errList field.ErrorList
errList = append(errList, validateResources(ctx, fldPath.Child("resources"), &template.Resources)...)
if err := validateImagePullPolicy(fldPath.Child("imagePullPolicy"), template.ImagePullPolicy); err != nil {
errList = append(errList, err)
}
return errList
}
func (v *PolarDBXClusterV1Validator) validateColumnarTemplate(ctx context.Context, fldPath *field.Path, template *polardbxv1polardbx.ColumnarTemplate) field.ErrorList {
var errList field.ErrorList
errList = append(errList, validateResources(ctx, fldPath.Child("resources"), &template.Resources)...)
if err := validateImagePullPolicy(fldPath.Child("imagePullPolicy"), template.ImagePullPolicy); err != nil {
errList = append(errList, err)
}
return errList
}
func (v *PolarDBXClusterV1Validator) validateTopologyNodes(ctx context.Context, nodes *polardbxv1polardbx.TopologyNodes) field.ErrorList {
var errList field.ErrorList
fldPath := field.NewPath("spec", "topology", "nodes")
errList = append(errList, v.validateXStoreTemplate(ctx,
fldPath.Child("gms", "template"),
nodes.GMS.Template)...)
errList = append(errList, v.validateXStoreTemplate(ctx,
fldPath.Child("dn", "template"),
&nodes.DN.Template)...)
errList = append(errList, v.validateCNTemplate(ctx,
fldPath.Child("cn", "template"),
&nodes.CN.Template)...)
if nodes.CDC != nil {
errList = append(errList, v.validateCDCTemplate(ctx,
fldPath.Child("cdc", "template"),
&nodes.CDC.Template)...)
}
if nodes.Columnar != nil {
errList = append(errList, v.validateColumnarTemplate(ctx,
fldPath.Child("columnar", "template"),
&nodes.Columnar.Template)...)
}
return errList
}
func getRuleReplicas(total int, replicas *intstr.IntOrString) (int, error) {
if replicas.Type == intstr.Int {
val := replicas.IntValue()
return val, nil
} else {
s := replicas.StrVal
if strings.HasSuffix(s, "%") || strings.HasSuffix(s, "%+") {
var percentageStr string
roundUp := false
if s[len(s)-1] == '+' {
percentageStr = s[:len(s)-2]
roundUp = true
} else {
percentageStr = s[:len(s)-1]
}
percentage, err := strconv.Atoi(strings.TrimSpace(percentageStr))
if err != nil {
return 0, fmt.Errorf("invalid replicas: not a percentage, %w", err)
}
if percentage >= 100 {
return 0, fmt.Errorf("invalid replicas: not a valid percentage, should be less than 1")
}
if roundUp {
return (total*percentage + 99) / 100, nil
} else {
return total * percentage / 100, nil
}
} else if strings.Contains(s, "/") {
split := strings.SplitN(s, "/", 2)
if len(split) < 2 {
return 0, fmt.Errorf("invalid replicas: not a fraction")
}
a, err := strconv.Atoi(strings.TrimSpace(split[0]))
if err != nil {
return 0, fmt.Errorf("invalid replicas: not a fraction, %w", err)
}
b, err := strconv.Atoi(strings.TrimSpace(split[1]))
if err != nil {
return 0, fmt.Errorf("invalid replicas: not a fraction, %w", err)
}
if a < 0 {
return 0, fmt.Errorf("invalid replicas: not a valid fraction, numerator should be non-negative integer")
}
if b <= 0 {
return 0, fmt.Errorf("invalid replicas: not a valid fraction, denominator should be positive integer")
}
if a >= b {
return 0, fmt.Errorf("invalid replicas: not a valid fraction, should be less than 1")
}
return total * a / b, nil
} else {
val, err := strconv.Atoi(replicas.StrVal)
if err != nil {
return 0, fmt.Errorf("invalid replicas: %w", err)
}
return val, nil
}
}
}
func (v *PolarDBXClusterV1Validator) validateReplicasInRule(fldPath *field.Path, replicas *intstr.IntOrString, total int) (int, *field.Error) {
r, err := getRuleReplicas(total, replicas)
if err != nil {
return 0, field.Invalid(fldPath, replicas, err.Error())
}
return r, nil
}
func (v *PolarDBXClusterV1Validator) validateReplicasOnStatelessComponent(ctx context.Context, rules []polardbxv1polardbx.StatelessTopologyRuleItem, fldPath *field.Path, replicas int) field.ErrorList {
var errList field.ErrorList
sum := 0
emptyReplicas := 0
for i, rule := range rules {
rFldPath := fldPath.Index(i).Child("replicas")
if rule.Replicas == nil {
emptyReplicas++
if emptyReplicas > 1 {
errList = append(errList, field.Forbidden(rFldPath, "multiple nil replicas found, only one is allowed"))
}
continue
} else {
r, err := v.validateReplicasInRule(rFldPath, rule.Replicas, replicas)
if err != nil {
errList = append(errList, err)
}
sum += r
}
}
// It's allowed to be less or equal.
if sum > replicas {
errList = append(errList, field.Forbidden(fldPath,
fmt.Sprintf("invalid rules, sum of replicas %d is larger than declared %d", sum, replicas)))
}
return errList
}
func (v *PolarDBXClusterV1Validator) validateReplicas(ctx context.Context, topology *polardbxv1polardbx.Topology) field.ErrorList {
var errList field.ErrorList
cnRules := topology.Rules.Components.CN
cnNodes := topology.Nodes.CN
// Skip if CN nodes is nil.
errList = append(errList, v.validateReplicasOnStatelessComponent(ctx, cnRules,
field.NewPath("spec", "topology", "rules", "cn"), int(*cnNodes.Replicas))...)
cdcRules := topology.Rules.Components.CDC
cdcNodes := topology.Nodes.CDC
// Skip if CDC nodes is nil.
if cdcNodes != nil {
errList = append(errList, v.validateReplicasOnStatelessComponent(ctx, cdcRules,
field.NewPath("spec", "topology", "rules", "cdc"), int(cdcNodes.Replicas+cdcNodes.XReplicas))...)
}
columnarRules := topology.Rules.Components.Columnar
columnarNodes := topology.Nodes.Columnar
// Skip if Columnar nodes is nil.
if columnarNodes != nil {
errList = append(errList, v.validateReplicasOnStatelessComponent(ctx, columnarRules,
field.NewPath("spec", "topology", "rules", "columnar"), int(columnarNodes.Replicas))...)
}
return errList
}
func (v *PolarDBXClusterV1Validator) validateTopology(ctx context.Context, topology *polardbxv1polardbx.Topology) field.ErrorList {
var errList field.ErrorList
errList = append(errList, v.validateTopologyRules(ctx, &topology.Rules)...)
errList = append(errList, v.validateTopologyNodes(ctx, &topology.Nodes)...)
errList = append(errList, v.validateReplicas(ctx, topology)...)
return errList
}
func (v *PolarDBXClusterV1Validator) validatePrivileges(ctx context.Context, privileges []polardbxv1polardbx.PrivilegeItem) field.ErrorList {
var errList field.ErrorList
usernames := make(map[string]int)
fieldPath := field.NewPath("spec", "privileges")
for index, priv := range privileges {
if priv.Username == "" {
errList = append(errList, field.NotFound(
fieldPath.Index(index).Child("username"),
""),
)
} else {
_, ok := usernames[priv.Username]
if ok {
errList = append(errList, field.Duplicate(
fieldPath.Index(index).Child("username"),
priv.Username),
)
} else {
usernames[priv.Username] = index
}
}
switch priv.Type {
case polardbxv1polardbx.ReadWrite, polardbxv1polardbx.ReadOnly,
polardbxv1polardbx.DDLOnly, polardbxv1polardbx.DMLOnly,
polardbxv1polardbx.Super, "":
// break
default:
errList = append(errList, field.NotSupported(
fieldPath.Index(index).Child("type"),
priv.Type,
[]string{
string(polardbxv1polardbx.ReadWrite),
string(polardbxv1polardbx.ReadOnly),
string(polardbxv1polardbx.DDLOnly),
string(polardbxv1polardbx.DMLOnly),
string(polardbxv1polardbx.Super),
}),
)
}
}
return errList
}
func (v *PolarDBXClusterV1Validator) validateConfig(ctx context.Context, config *polardbxv1polardbx.Config) field.ErrorList {
var errList field.ErrorList
fieldPath := field.NewPath("spec", "config")
// Check DN's overwrite.
if config.DN.MycnfOverwrite != "" {
_, err := iniutil.ParseMyCnfOverlayFile(bytes.NewBufferString(config.DN.MycnfOverwrite))
if err != nil {
errList = append(errList, field.Invalid(
fieldPath.Child("config", "dn", "mycnfOverwrite"),
config.DN.MycnfOverwrite,
"invalid format, parse error: "+err.Error()),
)
}
}
return errList
}
func (v *PolarDBXClusterV1Validator) validate(ctx context.Context, polardbx *polardbxv1.PolarDBXCluster) error {
spec := &polardbx.Spec
errList := field.ErrorList{}
errList = append(errList, v.validateTopology(ctx, &polardbx.Spec.Topology)...)
errList = append(errList, v.validateConfig(ctx, &polardbx.Spec.Config)...)
errList = append(errList, v.validatePrivileges(ctx, polardbx.Spec.Privileges)...)
errList = append(errList, v.validateSecurity(ctx, polardbx.Spec.Security)...)
if spec.Readonly {
if spec.PrimaryCluster == "" {
errList = append(errList, field.Invalid(
field.NewPath("spec", "primaryCluster"),
spec.PrimaryCluster,
fmt.Sprintf(`primaryCluster cannot be empty for readonly pxc`),
))
}
}
switch spec.ProtocolVersion.String() {
case "5", "5.7", "8", "8.0": // break
default:
errList = append(errList, field.NotSupported(
field.NewPath("spec", "protocolVersion"),
spec.ProtocolVersion,
[]string{
"5", "5.7", "8", "8.0",
}),
)
}
staticConfig := spec.Config.CN.Static
if staticConfig != nil {
switch staticConfig.RPCProtocolVersion.String() {
case "1", "2", "":
default:
errList = append(errList, field.NotSupported(
field.NewPath("spec", "RPCProtocolVersion"),
spec.Config.CN.Static.RPCProtocolVersion,
[]string{
"1", "2",
}),
)
}
}
switch spec.ServiceType {
case corev1.ServiceTypeClusterIP,
corev1.ServiceTypeNodePort,
corev1.ServiceTypeLoadBalancer,
corev1.ServiceTypeExternalName: // break
default:
errList = append(errList, field.NotSupported(
field.NewPath("spec", "serviceType"),
spec.ServiceType,
[]string{
string(corev1.ServiceTypeClusterIP),
string(corev1.ServiceTypeNodePort),
string(corev1.ServiceTypeLoadBalancer),
string(corev1.ServiceTypeExternalName),
}),
)
}
switch spec.UpgradeStrategy {
case polardbxv1polardbx.RecreateUpgradeStrategy,
polardbxv1polardbx.RollingUpgradeStrategy: // break
default:
errList = append(errList, field.NotSupported(
field.NewPath("spec", "upgradeStrategy"),
spec.UpgradeStrategy,
[]string{
string(polardbxv1polardbx.RecreateUpgradeStrategy),
string(polardbxv1polardbx.RollingUpgradeStrategy),
}))
}
if len(errList) > 0 {
return apierrors.NewInvalid(
polardbx.GroupVersionKind().GroupKind(),
polardbx.Name,
errList)
}
return nil
}
func (v *PolarDBXClusterV1Validator) ValidateCreate(ctx context.Context, obj runtime.Object) error {
return v.validate(ctx, obj.(*polardbxv1.PolarDBXCluster))
}
func (v *PolarDBXClusterV1Validator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error {
old, new := oldObj.(*polardbxv1.PolarDBXCluster), newObj.(*polardbxv1.PolarDBXCluster)
gvk := old.GroupVersionKind()
// Validate the immutable fields, such as storage engine.
oldSpec, newSpec := &old.Spec, &new.Spec
if oldSpec.ShareGMS != newSpec.ShareGMS {
return apierrors.NewForbidden(
schema.GroupResource{
Group: gvk.Group,
Resource: gvk.Kind,
},
new.Name,
field.Forbidden(field.NewPath("spec").Child("shareGMS"), "field is immutable"),
)
}
if oldSpec.Readonly != newSpec.Readonly {
return apierrors.NewForbidden(
schema.GroupResource{
Group: gvk.Group,
Resource: gvk.Kind,
},
new.Name,
field.Forbidden(field.NewPath("spec").Child("readonly"), "field is immutable"),
)
}
if oldSpec.PrimaryCluster != newSpec.PrimaryCluster {
return apierrors.NewForbidden(
schema.GroupResource{
Group: gvk.Group,
Resource: gvk.Kind,
},
new.Name,
field.Forbidden(field.NewPath("spec").Child("primaryCluster"), "field is immutable"),
)
}
oldStorageEngine := oldSpec.Topology.Nodes.DN.Template.Engine
newStorageEngine := newSpec.Topology.Nodes.DN.Template.Engine
if oldStorageEngine != newStorageEngine {
return apierrors.NewForbidden(
schema.GroupResource{
Group: gvk.Group,
Resource: gvk.Kind,
},
new.Name,
field.Forbidden(field.NewPath("spec").
Child("topology").
Child("nodes").
Child("dn").
Child("template").
Child("engine"),
"storage engine can not be changed"),
)
}
oldGmsStorageEngine := oldStorageEngine
if oldSpec.Topology.Nodes.GMS.Template != nil {
oldGmsStorageEngine = oldSpec.Topology.Nodes.GMS.Template.Engine
}
newGmsStorageEngine := newStorageEngine
if newSpec.Topology.Nodes.GMS.Template != nil {
newGmsStorageEngine = newSpec.Topology.Nodes.GMS.Template.Engine
}
if oldGmsStorageEngine != newGmsStorageEngine {
return apierrors.NewForbidden(
schema.GroupResource{
Group: gvk.Group,
Resource: gvk.Kind,
},
new.Name,
field.Forbidden(field.NewPath("spec").
Child("topology").
Child("nodes").
Child("gms").
Child("template").
Child("engine"),
"storage engine can not be changed"),
)
}
if !equality.Semantic.DeepEqual(oldSpec.Security, newSpec.Security) {
return apierrors.NewForbidden(
schema.GroupResource{
Group: gvk.Group,
Resource: gvk.Kind,
},
new.Name,
field.Forbidden(
field.NewPath("spec", "security"),
"security is immutable",
),
)
}
if !equality.Semantic.DeepEqual(oldSpec.InitReadonly, newSpec.InitReadonly) {
return apierrors.NewForbidden(
schema.GroupResource{
Group: gvk.Group,
Resource: gvk.Kind,
},
new.Name,
field.Forbidden(
field.NewPath("spec", "initReadonly"),
"initReadonly is immutable",
),
)
}
if !equality.Semantic.DeepEqual(oldSpec.Config.CN.ColdDataFileStorage, newSpec.Config.CN.ColdDataFileStorage) {
return apierrors.NewForbidden(
schema.GroupResource{
Group: gvk.Group,
Resource: gvk.Kind,
},
new.Name,
field.Forbidden(
field.NewPath("spec", "config", "cn", "coldDataFileStorage"),
"coldDataFileStorage is immutable",
),
)
}
// Validate the new object at last.
return v.validate(ctx, new)
}
func (v *PolarDBXClusterV1Validator) ValidateDelete(ctx context.Context, obj runtime.Object) error {
return nil
}
func NewPolarDBXClusterV1Validator(configLoader func() *ValidatorConfig) extension.CustomValidator {
return &PolarDBXClusterV1Validator{
configLoader: configLoader,
}
}