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 13, 2022
1 parent e18eff0 commit ba4e6af
Show file tree
Hide file tree
Showing 14 changed files with 608 additions and 6 deletions.
41 changes: 41 additions & 0 deletions apis/compute/validation/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 (
"reflect"

"github.com/onmetal/onmetal-api/equality"
apivalidation "k8s.io/apimachinery/pkg/api/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
)

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
}
102 changes: 102 additions & 0 deletions apis/compute/validation/machine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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"
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))
}

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))
}
}

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

for i, vol := range machineSpec.Volumes {
for _, msg := range apivalidation.NameIsDNSLabel(vol.Name, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("volume").Index(i).Child("name"), vol.Name, msg))
}
if vol.VolumeClaimRef != nil && vol.VolumeClaimRef.Name != "" {
for _, msg := range apivalidation.NameIsDNSLabel(vol.VolumeClaimRef.Name, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("volume").Index(i).Child("volumeClaimRef").Child("name"), vol.VolumeClaimRef.Name, msg))
}
}
}

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

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

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

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
}
134 changes: 134 additions & 0 deletions apis/compute/validation/machine_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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 (
commonv1alpha1 "github.com/onmetal/onmetal-api/apis/common/v1alpha1"
"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")),
),
Entry("invalid volume name",
&compute.Machine{
Spec: compute.MachineSpec{
Volumes: []compute.Volume{{Name: "foo*"}},
},
},
ContainElement(InvalidField("spec.volume[0].name")),
),
Entry("invalid volumeClaimRef name",
&compute.Machine{
Spec: compute.MachineSpec{
Volumes: []compute.Volume{
{Name: "foo", VolumeSource: compute.VolumeSource{
VolumeClaimRef: &corev1.LocalObjectReference{Name: "foo*"}}
},
},
},
},
ContainElement(InvalidField("spec.volume[0].volumeClaimRef.name")),
),
Entry("invalid ignition ref name",
&compute.Machine{
Spec: compute.MachineSpec{
IgnitionRef: &commonv1alpha1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "foo*",
},
},
},
},
ContainElement(InvalidField("spec.ignitionRef.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 ba4e6af

Please sign in to comment.