mirror of
https://github.com/mariadb-operator/mariadb-operator.git
synced 2025-07-31 05:05:09 +00:00
MaxScale webhook validation
This commit is contained in:
@ -578,8 +578,8 @@ var _ = Describe("MariaDB webhook", func() {
|
||||
BeforeAll(func() {
|
||||
mariadb := MariaDB{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mariadb-update-webhook",
|
||||
Namespace: testNamespace,
|
||||
Name: key.Name,
|
||||
Namespace: key.Namespace,
|
||||
},
|
||||
Spec: MariaDBSpec{
|
||||
Image: "mariadb:11.3.3",
|
||||
|
@ -2,6 +2,7 @@ package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
@ -23,7 +24,7 @@ var _ webhook.Validator = &MaxScale{}
|
||||
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
|
||||
func (r *MaxScale) ValidateCreate() (admission.Warnings, error) {
|
||||
maxscaleLogger.V(1).Info("Validate create", "name", r.Name)
|
||||
return nil, nil
|
||||
return nil, r.validate()
|
||||
}
|
||||
|
||||
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
|
||||
@ -33,10 +34,84 @@ func (r *MaxScale) ValidateUpdate(old runtime.Object) (admission.Warnings, error
|
||||
if err := inmutableWebhook.ValidateUpdate(r, oldMaxScale); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
return nil, r.validate()
|
||||
}
|
||||
|
||||
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
|
||||
func (r *MaxScale) ValidateDelete() (admission.Warnings, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *MaxScale) validate() error {
|
||||
validateFns := []func() error{
|
||||
r.validateServers,
|
||||
r.validateServices,
|
||||
r.validatePodDisruptionBudget,
|
||||
}
|
||||
for _, fn := range validateFns {
|
||||
if err := fn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MaxScale) validateServers() error {
|
||||
idx := r.ServerIndex()
|
||||
if len(idx) != len(r.Spec.Servers) {
|
||||
return field.Invalid(
|
||||
field.NewPath("spec").Child("servers"),
|
||||
r.Spec.Servers,
|
||||
"server names must be unique",
|
||||
)
|
||||
}
|
||||
addresses := make(map[string]struct{})
|
||||
for _, srv := range r.Spec.Servers {
|
||||
addresses[srv.Address] = struct{}{}
|
||||
}
|
||||
if len(addresses) != len(r.Spec.Servers) {
|
||||
return field.Invalid(
|
||||
field.NewPath("spec").Child("servers"),
|
||||
r.Spec.Servers,
|
||||
"server addresses must be unique",
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MaxScale) validateServices() error {
|
||||
idx := r.ServiceIndex()
|
||||
if len(idx) != len(r.Spec.Services) {
|
||||
return field.Invalid(
|
||||
field.NewPath("spec").Child("services"),
|
||||
r.Spec.Services,
|
||||
"service names must be unique",
|
||||
)
|
||||
}
|
||||
ports := make(map[int]struct{})
|
||||
for _, svc := range r.Spec.Services {
|
||||
ports[int(svc.Listener.Port)] = struct{}{}
|
||||
}
|
||||
if len(ports) != len(r.Spec.Services) {
|
||||
return field.Invalid(
|
||||
field.NewPath("spec").Child("services"),
|
||||
r.Spec.Services,
|
||||
"service listener ports must be unique",
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MaxScale) validatePodDisruptionBudget() error {
|
||||
if r.Spec.PodDisruptionBudget == nil {
|
||||
return nil
|
||||
}
|
||||
if err := r.Spec.PodDisruptionBudget.Validate(); err != nil {
|
||||
return field.Invalid(
|
||||
field.NewPath("spec").Child("podDisruptionBudget"),
|
||||
r.Spec.PodDisruptionBudget,
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
415
api/v1alpha1/maxscale_webhook_test.go
Normal file
415
api/v1alpha1/maxscale_webhook_test.go
Normal file
@ -0,0 +1,415 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
var _ = Describe("MaxScale webhook", func() {
|
||||
Context("When creating a MaxScale", func() {
|
||||
meta := metav1.ObjectMeta{
|
||||
Name: "maxscale-create-webhook",
|
||||
Namespace: testNamespace,
|
||||
}
|
||||
DescribeTable(
|
||||
"Should validate",
|
||||
func(mxs *MaxScale, wantErr bool) {
|
||||
_ = k8sClient.Delete(testCtx, mxs)
|
||||
err := k8sClient.Create(testCtx, mxs)
|
||||
if wantErr {
|
||||
Expect(err).To(HaveOccurred())
|
||||
} else {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
},
|
||||
Entry(
|
||||
"Invalid server names",
|
||||
&MaxScale{
|
||||
ObjectMeta: meta,
|
||||
Spec: MaxScaleSpec{
|
||||
Servers: []MaxScaleServer{
|
||||
{
|
||||
Name: "mariadb-0",
|
||||
Address: "mariadb-repl-0.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
Name: "mariadb-0",
|
||||
Address: "mariadb-repl-1.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
Services: []MaxScaleService{
|
||||
{
|
||||
Name: "rw-router",
|
||||
Router: ServiceRouterReadWriteSplit,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3306,
|
||||
},
|
||||
},
|
||||
},
|
||||
Monitor: MaxScaleMonitor{
|
||||
Module: MonitorModuleMariadb,
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
Entry(
|
||||
"Invalid server addresses",
|
||||
&MaxScale{
|
||||
ObjectMeta: meta,
|
||||
Spec: MaxScaleSpec{
|
||||
Servers: []MaxScaleServer{
|
||||
{
|
||||
Name: "mariadb-0",
|
||||
Address: "mariadb-repl-0.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
Name: "mariadb-1",
|
||||
Address: "mariadb-repl-0.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
Services: []MaxScaleService{
|
||||
{
|
||||
Name: "rw-router",
|
||||
Router: ServiceRouterReadWriteSplit,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3306,
|
||||
},
|
||||
},
|
||||
},
|
||||
Monitor: MaxScaleMonitor{
|
||||
Module: MonitorModuleMariadb,
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
Entry(
|
||||
"Invalid service names",
|
||||
&MaxScale{
|
||||
ObjectMeta: meta,
|
||||
Spec: MaxScaleSpec{
|
||||
Servers: []MaxScaleServer{
|
||||
{
|
||||
Name: "mariadb-0",
|
||||
Address: "mariadb-repl-0.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
Services: []MaxScaleService{
|
||||
{
|
||||
Name: "rw-router",
|
||||
Router: ServiceRouterReadWriteSplit,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3306,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "rw-router",
|
||||
Router: ServiceRouterReadConnRoute,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3307,
|
||||
},
|
||||
},
|
||||
},
|
||||
Monitor: MaxScaleMonitor{
|
||||
Module: MonitorModuleMariadb,
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
Entry(
|
||||
"Invalid service ports",
|
||||
&MaxScale{
|
||||
ObjectMeta: meta,
|
||||
Spec: MaxScaleSpec{
|
||||
Servers: []MaxScaleServer{
|
||||
{
|
||||
Name: "mariadb-0",
|
||||
Address: "mariadb-repl-0.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
Services: []MaxScaleService{
|
||||
{
|
||||
Name: "rw-router",
|
||||
Router: ServiceRouterReadWriteSplit,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3306,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "conn-router",
|
||||
Router: ServiceRouterReadConnRoute,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3306,
|
||||
},
|
||||
},
|
||||
},
|
||||
Monitor: MaxScaleMonitor{
|
||||
Module: MonitorModuleMariadb,
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
Entry(
|
||||
"Invalid PodDisruptionBudget",
|
||||
&MaxScale{
|
||||
ObjectMeta: meta,
|
||||
Spec: MaxScaleSpec{
|
||||
Servers: []MaxScaleServer{
|
||||
{
|
||||
Name: "mariadb-0",
|
||||
Address: "mariadb-repl-0.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
Services: []MaxScaleService{
|
||||
{
|
||||
Name: "rw-router",
|
||||
Router: ServiceRouterReadWriteSplit,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3306,
|
||||
},
|
||||
},
|
||||
},
|
||||
Monitor: MaxScaleMonitor{
|
||||
Module: MonitorModuleMariadb,
|
||||
},
|
||||
PodDisruptionBudget: &PodDisruptionBudget{
|
||||
MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromString("50%"); return &i }(),
|
||||
MinAvailable: func() *intstr.IntOrString { i := intstr.FromString("50%"); return &i }(),
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
),
|
||||
Entry(
|
||||
"Valid",
|
||||
&MaxScale{
|
||||
ObjectMeta: meta,
|
||||
Spec: MaxScaleSpec{
|
||||
Servers: []MaxScaleServer{
|
||||
{
|
||||
Name: "mariadb-0",
|
||||
Address: "mariadb-repl-0.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
Services: []MaxScaleService{
|
||||
{
|
||||
Name: "rw-router",
|
||||
Router: ServiceRouterReadWriteSplit,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3306,
|
||||
},
|
||||
},
|
||||
},
|
||||
Monitor: MaxScaleMonitor{
|
||||
Module: MonitorModuleMariadb,
|
||||
},
|
||||
PodDisruptionBudget: &PodDisruptionBudget{
|
||||
MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromString("50%"); return &i }(),
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
Context("When updating a MaxScale", Ordered, func() {
|
||||
key := types.NamespacedName{
|
||||
Name: "maxscale-update-webhook",
|
||||
Namespace: testNamespace,
|
||||
}
|
||||
BeforeAll(func() {
|
||||
mxs := MaxScale{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: key.Name,
|
||||
Namespace: key.Namespace,
|
||||
},
|
||||
Spec: MaxScaleSpec{
|
||||
Servers: []MaxScaleServer{
|
||||
{
|
||||
Name: "mariadb-0",
|
||||
Address: "mariadb-repl-0.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
Name: "mariadb-1",
|
||||
Address: "mariadb-repl-1.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
{
|
||||
Name: "mariadb-2",
|
||||
Address: "mariadb-repl-2.mariadb-repl-internal.default.svc.cluster.local",
|
||||
},
|
||||
},
|
||||
Services: []MaxScaleService{
|
||||
{
|
||||
Name: "rw-router",
|
||||
Router: ServiceRouterReadWriteSplit,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3306,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "rconn-master-router",
|
||||
Router: ServiceRouterReadConnRoute,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3307,
|
||||
Params: map[string]string{
|
||||
"router_options": "master",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "rconn-slave-router",
|
||||
Router: ServiceRouterReadConnRoute,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3308,
|
||||
Params: map[string]string{
|
||||
"router_options": "slave",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Monitor: MaxScaleMonitor{
|
||||
Module: MonitorModuleMariadb,
|
||||
},
|
||||
Admin: MaxScaleAdmin{
|
||||
Port: 8989,
|
||||
},
|
||||
KubernetesService: &ServiceTemplate{
|
||||
Type: corev1.ServiceTypeLoadBalancer,
|
||||
Annotations: map[string]string{
|
||||
"metallb.universe.tf/loadBalancerIPs": "172.18.0.214",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(testCtx, &mxs)).To(Succeed())
|
||||
})
|
||||
DescribeTable(
|
||||
"Should validate",
|
||||
func(patchFn func(mxs *MaxScale), wantErr bool) {
|
||||
var mxs MaxScale
|
||||
Expect(k8sClient.Get(testCtx, key, &mxs)).To(Succeed())
|
||||
|
||||
patch := client.MergeFrom(mxs.DeepCopy())
|
||||
patchFn(&mxs)
|
||||
|
||||
err := k8sClient.Patch(testCtx, &mxs, patch)
|
||||
if wantErr {
|
||||
Expect(err).To(HaveOccurred())
|
||||
} else {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
},
|
||||
Entry(
|
||||
"Updating Image",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Image = "mariadb/maxscale:23.07"
|
||||
},
|
||||
false,
|
||||
),
|
||||
Entry(
|
||||
"Adding Server",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Servers = append(mxs.Spec.Servers, MaxScaleServer{
|
||||
Name: "mariadb-3",
|
||||
Address: "mariadb-repl-3.mariadb-repl-internal.default.svc.cluster.local",
|
||||
})
|
||||
},
|
||||
false,
|
||||
),
|
||||
Entry(
|
||||
"Updating Server",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Servers[0].Name = "mariadb-0-test"
|
||||
},
|
||||
false,
|
||||
),
|
||||
Entry(
|
||||
"Adding Service",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Services = append(mxs.Spec.Services, MaxScaleService{
|
||||
Name: "rconn-router",
|
||||
Router: ServiceRouterReadConnRoute,
|
||||
Listener: MaxScaleListener{
|
||||
Port: 3309,
|
||||
}},
|
||||
)
|
||||
},
|
||||
false,
|
||||
),
|
||||
Entry(
|
||||
"Updating Service",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Services[0].Listener.Port = 1111
|
||||
},
|
||||
true,
|
||||
),
|
||||
Entry(
|
||||
"Updating Monitor interval",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Monitor.Interval = metav1.Duration{Duration: 1 * time.Second}
|
||||
},
|
||||
false,
|
||||
),
|
||||
Entry(
|
||||
"Updating Monitor module",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Monitor.Module = MonitorModuleGalera
|
||||
},
|
||||
true,
|
||||
),
|
||||
Entry(
|
||||
"Updating Admin",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Admin.Port = 9090
|
||||
},
|
||||
true,
|
||||
),
|
||||
Entry(
|
||||
"Updating Config",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Config.Params = map[string]string{
|
||||
"foo": "bar",
|
||||
}
|
||||
},
|
||||
true,
|
||||
),
|
||||
Entry(
|
||||
"Updating Auth",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Auth.AdminUsername = "foo"
|
||||
},
|
||||
true,
|
||||
),
|
||||
Entry(
|
||||
"Updating Replicas",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Replicas = 3
|
||||
},
|
||||
false,
|
||||
),
|
||||
Entry(
|
||||
"Updating Resources",
|
||||
func(mxs *MaxScale) {
|
||||
mxs.Spec.Resources = &corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
"cpu": resource.MustParse("200m"),
|
||||
},
|
||||
}
|
||||
},
|
||||
false,
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
@ -26,7 +26,7 @@ release: goreleaser ## Test release locally.
|
||||
##@ Run
|
||||
|
||||
WATCH_NAMESPACE ?= ""
|
||||
RUN_FLAGS ?= --log-dev --log-maxscale --log-level=info --log-time-encoder=iso8601
|
||||
RUN_FLAGS ?= --log-dev --log-level=info --log-time-encoder=iso8601
|
||||
|
||||
RUN_ENV ?= RELATED_IMAGE_MARIADB=$(RELATED_IMAGE_MARIADB) RELATED_IMAGE_MAXSCALE=$(RELATED_IMAGE_MAXSCALE) RELATED_IMAGE_EXPORTER=$(RELATED_IMAGE_EXPORTER) MARIADB_OPERATOR_IMAGE=$(IMG) \
|
||||
MARIADB_OPERATOR_NAME=$(MARIADB_OPERATOR_NAME) MARIADB_OPERATOR_NAMESPACE=$(MARIADB_OPERATOR_NAMESPACE) MARIADB_OPERATOR_SA_PATH=$(MARIADB_OPERATOR_SA_PATH) \
|
||||
|
Reference in New Issue
Block a user