303 lines
11 KiB
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}
|
|
}
|