polardbxoperator/pkg/webhook/parameter/validator.go

303 lines
11 KiB
Go

package parameter
import (
"context"
"fmt"
"math/big"
"regexp"
"strconv"
"strings"
"github.com/alibaba/polardbx-operator/pkg/util/gms"
"github.com/alibaba/polardbx-operator/pkg/operator/v1/xstore/convention"
polardbxmeta "github.com/alibaba/polardbx-operator/pkg/operator/v1/polardbx/meta"
"github.com/alibaba/polardbx-operator/pkg/webhook/extension"
"github.com/go-logr/logr"
"github.com/itchyny/timefmt-go"
"sigs.k8s.io/controller-runtime/pkg/client"
polardbxv1 "github.com/alibaba/polardbx-operator/api/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
)
type Validator struct {
client.Reader
logr.Logger
}
func (v *Validator) validateObject(ctx context.Context, obj *polardbxv1.PolarDBXParameter, polardbxcluster *polardbxv1.PolarDBXCluster) field.ErrorList {
var errList field.ErrorList
polarDBXParameterList := polardbxv1.PolarDBXParameterList{}
err := v.List(context.Background(), &polarDBXParameterList,
client.InNamespace(obj.Namespace),
client.MatchingLabels(convention.GetParameterLabel()),
)
if err != nil {
errList = append(errList, field.Required(field.NewPath("spec", "templateName"), "parameter template is different from polardbxcluster's template."))
return errList
}
for _, p := range polarDBXParameterList.Items {
if p.Name != obj.Name && p.Labels[polardbxmeta.LabelName] == obj.ClusterName && p.Spec.TemplateName == obj.Spec.TemplateName {
errList = append(errList, field.Required(field.NewPath("spec", "templateName"), "same cluster and parameter template only have one dynamic parameter."))
}
}
return errList
}
func checkTime(time string, paramName string) field.ErrorList {
var errList field.ErrorList
t, err := timefmt.Parse(time, "%H:%M")
if err != nil {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), fmt.Sprintf("Invalid value, Out of Hour Range, paramName %s", paramName)))
} else {
str := timefmt.Format(t, "%H:%M")
if str != time {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), fmt.Sprintf("Invalid value, Out of Hour Range, paramName %s", paramName)))
}
}
return errList
}
func checkHelper(param polardbxv1.Params, nodeRole string,
templateParams map[string]polardbxv1.TemplateParams, polardbxcluster *polardbxv1.PolarDBXCluster) field.ErrorList {
var errList field.ErrorList
allMatch := ".*"
// check parameters unit
switch templateParams[param.Name].Unit {
case polardbxv1.UnitInt:
optional := templateParams[param.Name].Optional
value := new(big.Int)
var ok bool
if param.Value[0] == '{' {
// check if value need formula compute
valueInt64, err := formulaComputing(param.Value, polardbxcluster)
if err != nil {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), err.Error()))
}
value = new(big.Int).SetInt64(valueInt64)
} else {
if strings.ContainsAny(optional, "|") && optional != allMatch {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), fmt.Sprintf("Invalid unit type, Need Unit INT, paramName %s", param.Name)))
}
value, ok = value.SetString(param.Value, 10)
if !ok {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), fmt.Sprintf("Invalid unit type, Need Unit INT, paramName %s", param.Name)))
}
}
// check value whether divisible by divisibility factor
modulus := new(big.Int)
new(big.Int).DivMod(value, new(big.Int).SetInt64(templateParams[param.Name].DivisibilityFactor), modulus)
if modulus.Cmp(new(big.Int).SetInt64(0)) != 0 {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), fmt.Sprintf("Invalid value, Can Not Divisible By Divisibility Factor, paramName %s", param.Name)))
}
if optional != allMatch {
// check value is in the range of optional by regex
re := regexp.MustCompile(`(-?\d+) ?- ?(-?\d+)`)
match := re.FindStringSubmatch(optional[1 : len(optional)-1])
num1, num2 := new(big.Int), new(big.Int)
num1, _ = num1.SetString(match[1], 10)
num2, _ = num2.SetString(match[2], 10)
if value.Cmp(num1) == -1 || value.Cmp(num2) == 1 {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), fmt.Sprintf("Invalid value, Out of Range of Optional, paramName %s", param.Name)))
}
}
case polardbxv1.UnitDouble:
optional := templateParams[param.Name].Optional
if strings.ContainsAny(optional, "|") && optional != allMatch {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), fmt.Sprintf("Invalid unit type, Need Unit DOUBLE, paramName %s", param.Name)))
}
value, _ := strconv.ParseFloat(param.Value, 64)
if optional != allMatch {
// check value is in the range of optional by regex
re := regexp.MustCompile(`(-?\d+.?\d+) ?- ?(-?\d+.?\d+)`)
match := re.FindStringSubmatch(optional[1 : len(optional)-1])
var num1, num2 float64
num1, _ = strconv.ParseFloat(match[1], 64)
num2, _ = strconv.ParseFloat(match[2], 64)
if value < num1 || value > num2 {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), fmt.Sprintf("Invalid value, Out of Range of Optional, paramName %s", param.Name)))
}
}
case polardbxv1.UnitString:
// special parameter sql_mode
if param.Name == "sql_mode" {
break
}
optional := templateParams[param.Name].Optional
if !strings.ContainsAny(templateParams[param.Name].Optional, "|") {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), fmt.Sprintf("Invalid unit type, Need Unit STRING, paramName %s", param.Name)))
}
if optional != allMatch {
// check value is in the range of optional
splits := strings.Split(optional[1:len(optional)-1], "|")
exists := false
for _, split := range splits {
if split == param.Value {
exists = true
break
}
}
if !exists {
errList = append(errList, field.Required(field.NewPath("spec", "parameterTemplate"), fmt.Sprintf("Invalid value, Out of Range of Optional, paramName %s", param.Name)))
}
}
case polardbxv1.UnitTZ:
if param.Value != "SYSTEM" {
errList = append(errList, checkTime(param.Value, param.Name)...)
}
case polardbxv1.UnitHOUR_RANGE:
times := strings.Split(param.Value, "-")
errList = append(errList, checkTime(times[0], param.Name)...)
errList = append(errList, checkTime(times[1], param.Name)...)
}
return errList
}
func (v *Validator) validateParameters(ctx context.Context, obj *polardbxv1.PolarDBXParameter,
template *polardbxv1.PolarDBXParameterTemplate, polardbxcluster *polardbxv1.PolarDBXCluster) field.ErrorList {
var errList field.ErrorList
templateParams := make(map[string]map[string]polardbxv1.TemplateParams)
templateParams[polardbxmeta.RoleCN] = make(map[string]polardbxv1.TemplateParams)
templateParams[polardbxmeta.RoleDN] = make(map[string]polardbxv1.TemplateParams)
templateParams[polardbxmeta.RoleGMS] = make(map[string]polardbxv1.TemplateParams)
for _, param := range template.Spec.NodeType.CN.ParamList {
templateParams[polardbxmeta.RoleCN][param.Name] = param
}
for _, param := range template.Spec.NodeType.DN.ParamList {
templateParams[polardbxmeta.RoleDN][param.Name] = param
}
if len(template.Spec.NodeType.GMS.ParamList) != 0 {
for _, param := range template.Spec.NodeType.GMS.ParamList {
templateParams[polardbxmeta.RoleGMS][param.Name] = param
}
} else {
for _, param := range template.Spec.NodeType.DN.ParamList {
templateParams[polardbxmeta.RoleGMS][param.Name] = param
}
}
for _, param := range obj.Spec.NodeType.CN.ParamList {
errList = append(errList, checkHelper(param, polardbxmeta.RoleCN, templateParams[polardbxmeta.RoleCN], polardbxcluster)...)
}
for _, param := range obj.Spec.NodeType.DN.ParamList {
errList = append(errList, checkHelper(param, polardbxmeta.RoleDN, templateParams[polardbxmeta.RoleDN], polardbxcluster)...)
}
gmsParamList := gms.GetGmsParamList(obj)
for _, param := range gmsParamList {
errList = append(errList, checkHelper(param, polardbxmeta.RoleGMS, templateParams[polardbxmeta.RoleGMS], polardbxcluster)...)
}
return errList
}
func (v *Validator) validate(ctx context.Context, obj *polardbxv1.PolarDBXParameter) error {
var errList field.ErrorList
if len(obj.Spec.TemplateName) == 0 {
errList = append(errList, field.Required(field.NewPath("spec", "templateName"), "template name must be specified"))
return apierrors.NewInvalid(obj.GroupVersionKind().GroupKind(), obj.Name, errList)
}
// get parameter template
parameterTemplate := &polardbxv1.PolarDBXParameterTemplate{}
err := v.Get(context.Background(), client.ObjectKey{
Namespace: obj.Namespace,
Name: obj.Spec.TemplateName,
}, parameterTemplate)
if err != nil {
if apierrors.IsNotFound(err) {
v.Logger.Info("Specified parameter template not exists.", "parameter template", obj.Spec.TemplateName)
} else {
v.Logger.Error(err, "Failed to get parameter template.")
}
return err
}
// sync spec snapshot
obj.Status.ParameterSpecSnapshot = obj.Spec.DeepCopy()
if len(obj.Spec.ClusterName) == 0 {
errList = append(errList, field.Required(field.NewPath("spec", "clusterName"), "cluster name must be specified"))
return apierrors.NewInvalid(obj.GroupVersionKind().GroupKind(), obj.Name, errList)
}
// get polardbx cluster
polardbxcluster := &polardbxv1.PolarDBXCluster{}
err = v.Get(context.Background(), client.ObjectKey{
Namespace: obj.Namespace,
Name: obj.Spec.ClusterName,
}, polardbxcluster)
if err != nil {
if apierrors.IsNotFound(err) {
v.Logger.Info("Specified polardbx cluster not exists.", "polardbxcluster", obj.Spec.TemplateName)
} else {
v.Logger.Error(err, "Failed to get polardbx cluster.")
}
return err
}
errList = append(errList, v.validateObject(ctx, obj, polardbxcluster)...)
errList = append(errList, v.validateParameters(ctx, obj, parameterTemplate, polardbxcluster)...)
if len(errList) > 0 {
return apierrors.NewInvalid(obj.GroupVersionKind().GroupKind(), obj.Name, errList)
}
return nil
}
func (v *Validator) ValidateCreate(ctx context.Context, obj runtime.Object) error {
v.Logger.Info("Validate parameter template creation.")
return v.validate(ctx, obj.(*polardbxv1.PolarDBXParameter))
}
func (v *Validator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error {
newParameter := newObj.(*polardbxv1.PolarDBXParameter)
if err := v.validate(ctx, newParameter); err != nil {
return err
}
return nil
}
func (v *Validator) ValidateDelete(ctx context.Context, obj runtime.Object) error {
return nil
}
func NewParameterValidator(r client.Reader, logger logr.Logger) extension.CustomValidator {
return &Validator{Reader: r, Logger: logger}
}