Skip to content

Commit

Permalink
Implement compute validation
Browse files Browse the repository at this point in the history
Implement validation for the following `compute` types:

- `Machine`
- `MachineClass`
- `MachinePool`

Adjust tests to fit the new validation.
  • Loading branch information
afritzler committed Apr 12, 2022
1 parent e18eff0 commit 352d483
Show file tree
Hide file tree
Showing 14 changed files with 570 additions and 13 deletions.
40 changes: 40 additions & 0 deletions apis/compute/validation/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2022 by the OnMetal authors.
*
* 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 validation

import (
"k8s.io/apimachinery/pkg/api/equality"
apivalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"reflect"
)

func ValidateImmutable(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if !equality.Semantic.DeepEqual(oldVal, newVal) {
allErrs = append(allErrs, field.Invalid(fldPath, newVal, apivalidation.FieldImmutableErrorMsg))
}
return allErrs
}

func ValidateSetOnceField(newVal, oldVal interface{}, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if !reflect.ValueOf(oldVal).IsZero() {
allErrs = append(allErrs, apivalidation.ValidateImmutableField(newVal, oldVal, fldPath)...)
}
return allErrs
}
92 changes: 92 additions & 0 deletions apis/compute/validation/machine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2022 by the OnMetal authors.
*
* 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 validation

import (
"github.com/onmetal/onmetal-api/apis/compute"
apiequality "github.com/onmetal/onmetal-api/equality"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/validation"
apivalidation "k8s.io/apimachinery/pkg/api/validation"
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
)

// ValidateMachine validates a Machine object.
func ValidateMachine(machine *compute.Machine) field.ErrorList {
var allErrs field.ErrorList

allErrs = append(allErrs, apivalidation.ValidateObjectMetaAccessor(machine, true, apivalidation.NameIsDNSLabel, field.NewPath("metadata"))...)
allErrs = append(allErrs, validateMachineSpec(&machine.Spec, field.NewPath("spec"))...)

return allErrs
}

// ValidateMachineUpdate validates a Machine object before an update.
func ValidateMachineUpdate(newMachine, oldMachine *compute.Machine) field.ErrorList {
var allErrs field.ErrorList

allErrs = append(allErrs, apivalidation.ValidateObjectMetaAccessorUpdate(newMachine, oldMachine, field.NewPath("metadata"))...)
allErrs = append(allErrs, validateMachineSpecUpdate(&newMachine.Spec, &oldMachine.Spec, newMachine.DeletionTimestamp != nil, field.NewPath("spec"))...)
allErrs = append(allErrs, ValidateMachine(newMachine)...)

return allErrs
}

// validateMachineSpec validates the spec of a Machine object.
func validateMachineSpec(machineSpec *compute.MachineSpec, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList

if machineSpec.MachineClassRef == (corev1.LocalObjectReference{}) {
allErrs = append(allErrs, field.Required(fldPath.Child("machineClassRef"), "must specify a machine class ref"))
}

for _, msg := range apivalidation.NameIsDNSLabel(machineSpec.MachineClassRef.Name, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("machineClassRef").Child("name"), machineSpec.MachineClassRef.Name, msg))
}

imagePath := fldPath.Child("image")
if machineSpec.Image == "" {
allErrs = append(allErrs, field.Required(imagePath, "must specify an image"))
}

allErrs = append(allErrs, metav1validation.ValidateLabels(machineSpec.MachinePoolSelector, fldPath.Child("machinePoolSelector"))...)

if machineSpec.MachinePoolRef.Name != "" {
for _, msg := range apivalidation.NameIsDNSLabel(machineSpec.MachinePoolRef.Name, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("machinePoolRef").Child("name"), machineSpec.MachinePoolRef.Name, msg))
}
}

return allErrs
}

// validateMachineSpecUpdate validates the spec of a Machine object before an update.
func validateMachineSpecUpdate(new, old *compute.MachineSpec, deletionTimestampSet bool, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList

if deletionTimestampSet && !apiequality.Semantic.DeepEqual(new, old) {
allErrs = append(allErrs, validation.ValidateImmutableField(new, old, fldPath)...)
return allErrs
}

allErrs = append(allErrs, validation.ValidateImmutableField(new.Image, old.Image, fldPath.Child("image"))...)
allErrs = append(allErrs, apivalidation.ValidateImmutableField(new.MachineClassRef, old.MachineClassRef, fldPath.Child("machineClassRef"))...)
allErrs = append(allErrs, ValidateSetOnceField(new.MachinePoolRef, old.MachinePoolRef, fldPath.Child("machinePoolRef"))...)

return allErrs
}
101 changes: 101 additions & 0 deletions apis/compute/validation/machine_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright (c) 2022 by the OnMetal authors.
*
* 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 validation

import (
"github.com/onmetal/onmetal-api/apis/compute"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/types"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = Describe("Machine", func() {
DescribeTable("ValidateMachine",
func(machine *compute.Machine, match types.GomegaMatcher) {
errList := ValidateMachine(machine)
Expect(errList).To(match)
},
Entry("missing name",
&compute.Machine{},
ContainElement(RequiredField("metadata.name")),
),
Entry("missing namespace",
&compute.Machine{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
ContainElement(RequiredField("metadata.namespace")),
),
Entry("bad name",
&compute.Machine{ObjectMeta: metav1.ObjectMeta{Name: "foo*"}},
ContainElement(InvalidField("metadata.name")),
),
Entry("no machine class ref",
&compute.Machine{},
ContainElement(RequiredField("spec.machineClassRef")),
),
Entry("invalid machine class ref name",
&compute.Machine{
Spec: compute.MachineSpec{
MachineClassRef: corev1.LocalObjectReference{Name: "foo*"},
},
},
ContainElement(InvalidField("spec.machineClassRef.name")),
),
)

DescribeTable("ValidateMachineUpdate",
func(newMachine, oldMachine *compute.Machine, match types.GomegaMatcher) {
errList := ValidateMachineUpdate(newMachine, oldMachine)
Expect(errList).To(match)
},
Entry("immutable machineClassRef",
&compute.Machine{
Spec: compute.MachineSpec{
MachineClassRef: corev1.LocalObjectReference{Name: "foo"},
},
},
&compute.Machine{
Spec: compute.MachineSpec{
MachineClassRef: corev1.LocalObjectReference{Name: "bar"},
},
},
ContainElement(ImmutableField("spec.machineClassRef")),
),
Entry("immutable machinePoolRef if set",
&compute.Machine{
Spec: compute.MachineSpec{
MachinePoolRef: corev1.LocalObjectReference{Name: "foo"},
},
},
&compute.Machine{
Spec: compute.MachineSpec{
MachinePoolRef: corev1.LocalObjectReference{Name: "bar"},
},
},
ContainElement(ImmutableField("spec.machinePoolRef")),
),
Entry("mutable machinePoolRef if not set",
&compute.Machine{
Spec: compute.MachineSpec{
MachinePoolRef: corev1.LocalObjectReference{Name: "foo"},
},
},
&compute.Machine{},
Not(ContainElement(ImmutableField("spec.machinePoolRef"))),
),
)
})
43 changes: 43 additions & 0 deletions apis/compute/validation/machineclass.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2022 by the OnMetal authors.
*
* 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 validation

import (
"github.com/onmetal/onmetal-api/apis/compute"
apivalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
)

// ValidateMachineClass validates a MachineClass object.
func ValidateMachineClass(machineClass *compute.MachineClass) field.ErrorList {
var allErrs field.ErrorList

allErrs = append(allErrs, apivalidation.ValidateObjectMetaAccessor(machineClass, false, apivalidation.NameIsDNSLabel, field.NewPath("metadata"))...)

return allErrs
}

// ValidateMachineClassUpdate validates a MachineClass object before an update.
func ValidateMachineClassUpdate(newMachineClass, oldMachineClass *compute.MachineClass) field.ErrorList {
var allErrs field.ErrorList

allErrs = append(allErrs, apivalidation.ValidateObjectMetaAccessorUpdate(newMachineClass, oldMachineClass, field.NewPath("metadata"))...)
allErrs = append(allErrs, ValidateImmutable(newMachineClass.Capabilities, oldMachineClass.Capabilities, field.NewPath("capabilities"))...)
allErrs = append(allErrs, ValidateMachineClass(newMachineClass)...)

return allErrs
}
62 changes: 62 additions & 0 deletions apis/compute/validation/machineclass_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2022 by the OnMetal authors.
*
* 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 validation

import (
"github.com/onmetal/onmetal-api/apis/compute"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/types"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = Describe("MachineClass", func() {
DescribeTable("ValidateMachineClass",
func(machineClass *compute.MachineClass, match types.GomegaMatcher) {
errList := ValidateMachineClass(machineClass)
Expect(errList).To(match)
},
Entry("missing name",
&compute.MachineClass{},
ContainElement(RequiredField("metadata.name")),
),
Entry("bad name",
&compute.MachineClass{ObjectMeta: metav1.ObjectMeta{Name: "foo*"}},
ContainElement(InvalidField("metadata.name")),
),
)

DescribeTable("ValidateMachineClassUpdate",
func(newMachineClass, oldMachineClass *compute.MachineClass, match types.GomegaMatcher) {
errList := ValidateMachineClassUpdate(newMachineClass, oldMachineClass)
Expect(errList).To(match)
},
Entry("immutable capabilities",
&compute.MachineClass{
Capabilities: map[corev1.ResourceName]resource.Quantity{
"ram": resource.MustParse("1Gi"),
},
},
&compute.MachineClass{
Capabilities: map[corev1.ResourceName]resource.Quantity{},
},
ContainElement(ImmutableField("capabilities")),
),
)
})
Loading

0 comments on commit 352d483

Please sign in to comment.