Skip to content

Commit

Permalink
Merge pull request #73 from vshn/improve-validation
Browse files Browse the repository at this point in the history
improve version validation on DBaaS
  • Loading branch information
zugao authored Dec 20, 2022
2 parents b15136a + e879838 commit 5c73631
Show file tree
Hide file tree
Showing 16 changed files with 403 additions and 129 deletions.
1 change: 1 addition & 0 deletions generate_sample.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ func newKafkaSample() *exoscalev1.Kafka {
},
IPFilter: exoscalev1.IPFilter{"0.0.0.0/0"},
},
Version: "3.2",
KafkaSettings: runtime.RawExtension{Raw: []byte(`{"connections_max_idle_ms": 60000}`)},
},
},
Expand Down
1 change: 1 addition & 0 deletions operator/kafkacontroller/observe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ func sampleKafka(name string) exoscalev1.Kafka {
Name: "foo",
},
}
instance.Spec.ForProvider.Version = "3.2"
instance.Spec.ForProvider.Size.Plan = "businesss-8"
instance.Spec.ForProvider.IPFilter = []string{
"0.0.0.0/0",
Expand Down
65 changes: 56 additions & 9 deletions operator/kafkacontroller/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,93 @@ package kafkacontroller
import (
"context"
"fmt"
"github.com/exoscale/egoscale/v2/oapi"
"strings"

exoscalesdk "github.com/exoscale/egoscale/v2"
exoscalev1 "github.com/vshn/provider-exoscale/apis/exoscale/v1"
"github.com/vshn/provider-exoscale/operator/pipelineutil"
"github.com/vshn/provider-exoscale/operator/webhook"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
)

const serviceType = "kafka"

// SetupWebhook adds a webhook for kafka resources.
func SetupWebhook(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(&exoscalev1.Kafka{}).
WithValidator(&Validator{
log: mgr.GetLogger().WithName("webhook").WithName(strings.ToLower(exoscalev1.KafkaKind)),
log: mgr.GetLogger().WithName("webhook").WithName(strings.ToLower(exoscalev1.KafkaKind)),
kube: mgr.GetClient(),
}).
Complete()
}

// Validator validates kafka admission requests.
type Validator struct {
log logr.Logger
log logr.Logger
kube client.Client
}

// ValidateCreate validates the spec of a created kafka resource.
func (v *Validator) ValidateCreate(_ context.Context, obj runtime.Object) error {
func (v *Validator) ValidateCreate(ctx context.Context, obj runtime.Object) error {
instance := obj.(*exoscalev1.Kafka)
v.log.V(1).Info("get kafka available versions")
exo, err := pipelineutil.OpenExoscaleClient(ctx, v.kube, instance.GetProviderConfigName(), exoscalesdk.ClientOptWithAPIEndpoint(fmt.Sprintf("https://api-%s.exoscale.com", instance.Spec.ForProvider.Zone)))
if err != nil {
return fmt.Errorf("open exoscale client failed: %w", err)
}
return v.validateCreateWithExoClient(ctx, obj, exo.Exoscale)
}

func (v *Validator) validateCreateWithExoClient(ctx context.Context, obj runtime.Object, exo oapi.ClientWithResponsesInterface) error {
instance, ok := obj.(*exoscalev1.Kafka)
if !ok {
return fmt.Errorf("invalid managed resource type %T for kafka webhook", obj)
}
v.log.V(2).WithValues("instance", instance).Info("validate create")

availableVersions, err := v.getAvailableVersions(ctx, exo)
if err != nil {
return err
}

err = v.validateVersion(ctx, obj, *availableVersions)
if err != nil {
return err
}

return validateSpec(instance.Spec.ForProvider)
}

func (v *Validator) getAvailableVersions(ctx context.Context, exo oapi.ClientWithResponsesInterface) (*[]string, error) {
// get kafka available versions
resp, err := exo.GetDbaasServiceTypeWithResponse(ctx, serviceType)
if err != nil {
return nil, fmt.Errorf("get DBaaS service type failed: %w", err)
}

v.log.V(1).Info("DBaaS service type", "body", string(resp.Body))

serviceType := *resp.JSON200
if serviceType.AvailableVersions == nil {
return nil, fmt.Errorf("kafka available versions not found")
}
return serviceType.AvailableVersions, nil
}

func (v *Validator) validateVersion(_ context.Context, obj runtime.Object, availableVersions []string) error {
instance := obj.(*exoscalev1.Kafka)

v.log.V(1).Info("validate version")
return webhook.ValidateVersions(instance.Spec.ForProvider.Version, availableVersions)
}

// ValidateUpdate validates the spec of an updated kafka resource and checks that no immutable field has been modified.
func (v *Validator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) error {
newInstance, ok := newObj.(*exoscalev1.Kafka)
Expand All @@ -47,7 +98,7 @@ func (v *Validator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Obj
}
oldInstance, ok := oldObj.(*exoscalev1.Kafka)
if !ok {
return fmt.Errorf("invalid managed resource type %T for kafka webhook", newObj)
return fmt.Errorf("invalid managed resource type %T for kafka webhook", oldObj)
}
v.log.V(2).WithValues("old", oldInstance, "new", newInstance).Info("VALIDATE update")

Expand Down Expand Up @@ -111,13 +162,9 @@ func compareVersion(oldInst, newInst exoscalev1.Kafka) error {
if oldInst.Spec.ForProvider.Version == newInst.Spec.ForProvider.Version {
return nil
}
if newInst.Spec.ForProvider.Version == "" {
// Setting version to empyt string should always be fine
return nil
}
if oldInst.Spec.ForProvider.Version == "" {
// Fall back to reported version if no version was set before
oldInst.Spec.ForProvider.Version = oldInst.Status.AtProvider.Version
}
return webhook.ValidateVersion(oldInst.Status.AtProvider.Version, oldInst.Spec.ForProvider.Version, newInst.Spec.ForProvider.Version)
return webhook.ValidateUpdateVersion(oldInst.Status.AtProvider.Version, oldInst.Spec.ForProvider.Version, newInst.Spec.ForProvider.Version)
}
28 changes: 24 additions & 4 deletions operator/kafkacontroller/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package kafkacontroller

import (
"context"
"github.com/exoscale/egoscale/v2/oapi"
"github.com/stretchr/testify/mock"
"github.com/vshn/provider-exoscale/internal/operatortest"
"testing"

"github.com/go-logr/logr"
Expand All @@ -15,26 +18,34 @@ func TestWebhook_Create(t *testing.T) {
log: logr.Discard(),
}

exoMock := &operatortest.ClientWithResponsesInterface{}
mockAvailableVersionsCall(exoMock)
base := sampleKafka("foo")

t.Run("valid", func(t *testing.T) {
err := v.ValidateCreate(ctx, &base)
err := v.validateCreateWithExoClient(ctx, &base, exoMock)
assert.NoError(t, err)
})
t.Run("invalid empty", func(t *testing.T) {
err := v.ValidateCreate(ctx, &exoscalev1.Kafka{})
err := v.validateCreateWithExoClient(ctx, &exoscalev1.Kafka{}, exoMock)
assert.Error(t, err)
})
t.Run("invalid no ipfilter", func(t *testing.T) {
inst := base
inst.Spec.ForProvider.IPFilter = nil
err := v.ValidateCreate(ctx, &inst)
err := v.validateCreateWithExoClient(ctx, &inst, exoMock)
assert.Error(t, err)
})
t.Run("invalid no time", func(t *testing.T) {
inst := base
inst.Spec.ForProvider.Maintenance.TimeOfDay = ""
err := v.ValidateCreate(ctx, &inst)
err := v.validateCreateWithExoClient(ctx, &inst, exoMock)
assert.Error(t, err)
})
t.Run("invalid version", func(t *testing.T) {
inst := base
inst.Spec.ForProvider.Version = "non-valid-version"
err := v.validateCreateWithExoClient(ctx, &inst, exoMock)
assert.Error(t, err)
})
}
Expand Down Expand Up @@ -91,3 +102,12 @@ func TestWebhook_Update(t *testing.T) {
})

}

func mockAvailableVersionsCall(m *operatortest.ClientWithResponsesInterface) {
m.On("GetDbaasServiceTypeWithResponse", mock.Anything, mock.Anything).
Return(&oapi.GetDbaasServiceTypeResponse{
JSON200: &oapi.DbaasServiceType{
AvailableVersions: &[]string{"3.2", "4.3"},
},
}, nil)
}
3 changes: 2 additions & 1 deletion operator/mysqlcontroller/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ func SetupWebhook(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(&exoscalev1.MySQL{}).
WithValidator(&Validator{
log: mgr.GetLogger().WithName("webhook").WithName(strings.ToLower(exoscalev1.MySQLKind)),
log: mgr.GetLogger().WithName("webhook").WithName(strings.ToLower(exoscalev1.MySQLKind)),
kube: mgr.GetClient(),
}).
Complete()
}
Loading

0 comments on commit 5c73631

Please sign in to comment.