Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement compute validation #346

Merged
merged 1 commit into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
afritzler marked this conversation as resolved.
Show resolved Hide resolved
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 {
afritzler marked this conversation as resolved.
Show resolved Hide resolved
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))
}
afritzler marked this conversation as resolved.
Show resolved Hide resolved

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