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

Add validation for machine network interface #1217

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
71 changes: 71 additions & 0 deletions internal/apis/compute/validation/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/ironcore-dev/ironcore/internal/admission/plugin/machinevolumedevices/device"
ironcorevalidation "github.com/ironcore-dev/ironcore/internal/api/validation"
"github.com/ironcore-dev/ironcore/internal/apis/compute"
"github.com/ironcore-dev/ironcore/internal/apis/networking"
networkvalidation "github.com/ironcore-dev/ironcore/internal/apis/networking/validation"
"github.com/ironcore-dev/ironcore/internal/apis/storage"
storagevalidation "github.com/ironcore-dev/ironcore/internal/apis/storage/validation"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -98,6 +100,75 @@ func validateMachineSpec(machineSpec *compute.MachineSpec, fldPath *field.Path)

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

seenNwiNames := sets.NewString()
for i, nwi := range machineSpec.NetworkInterfaces {
if seenNwiNames.Has(nwi.Name) {
allErrs = append(allErrs, field.Duplicate(fldPath.Child("networkInterface").Index(i).Child("name"), nwi.Name))
} else {
seenNwiNames.Insert(nwi.Name)
}
allErrs = append(allErrs, validateNetworkInterface(&nwi, fldPath.Child("networkInterface").Index(i))...)
}

return allErrs
}

func validateNetworkInterface(networkInterface *compute.NetworkInterface, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList

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

allErrs = append(allErrs, validateNetworkInterfaceSource(&networkInterface.NetworkInterfaceSource, fldPath)...)

return allErrs
}

func validateNetworkInterfaceSource(source *compute.NetworkInterfaceSource, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
var numDefs int
if source.NetworkInterfaceRef != nil {
numDefs++
for _, msg := range apivalidation.NameIsDNSLabel(source.NetworkInterfaceRef.Name, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("networkInterfaceRef").Child("name"), source.NetworkInterfaceRef.Name, msg))
}
}
if source.Ephemeral != nil {
if numDefs > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeral"), "must only specify one networkInterface source"))
} else {
numDefs++
allErrs = append(allErrs, validateEphemeralNetworkInterface(source.Ephemeral, fldPath.Child("ephemeral"))...)
}
}
if numDefs == 0 {
allErrs = append(allErrs, field.Invalid(fldPath, source, "must specify at least one networkInterface source"))
}
return allErrs
}

func validateEphemeralNetworkInterface(source *compute.EphemeralNetworkInterfaceSource, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList

if source.NetworkInterfaceTemplate == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("NetworkInterfaceTemplate"), "must specify networkInterface template "))
} else {
allErrs = append(allErrs, validateNetworkInterfaceTemplateSpecForMachine(source.NetworkInterfaceTemplate, fldPath.Child("networkInterfaceTemplate"))...)
}

return allErrs
}

func validateNetworkInterfaceTemplateSpecForMachine(template *networking.NetworkInterfaceTemplateSpec, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList

if template == nil {
allErrs = append(allErrs, field.Required(fldPath, ""))
} else {
allErrs = append(allErrs, networkvalidation.ValidateNetworkInterfaceSpec(&template.Spec, &template.ObjectMeta, fldPath)...)
}

return allErrs
}

Expand Down
246 changes: 246 additions & 0 deletions internal/apis/compute/validation/machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package validation
import (
commonv1alpha1 "github.com/ironcore-dev/ironcore/api/common/v1alpha1"
"github.com/ironcore-dev/ironcore/internal/apis/compute"
"github.com/ironcore-dev/ironcore/internal/apis/ipam"
"github.com/ironcore-dev/ironcore/internal/apis/networking"
. "github.com/ironcore-dev/ironcore/internal/testutils/validation"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -188,6 +190,250 @@ var _ = Describe("Machine", func() {
),
)

DescribeTable("ValidateMachineNetworkInterface",
func(machine *compute.Machine, match types.GomegaMatcher) {
errList := ValidateMachine(machine)
Expect(errList).To(match)
},
Entry("invalid networkInterface name",
&compute.Machine{
Spec: compute.MachineSpec{
NetworkInterfaces: []compute.NetworkInterface{{Name: "bar*"}},
},
},
ContainElement(InvalidField("spec.networkInterface[0].name")),
),
Entry("duplicate networkInterface name",
&compute.Machine{
Spec: compute.MachineSpec{
NetworkInterfaces: []compute.NetworkInterface{
{Name: "foo"},
{Name: "foo"},
},
},
},
ContainElement(DuplicateField("spec.networkInterface[1].name")),
),
Entry("invalid networkInterfaceRef name",
&compute.Machine{
Spec: compute.MachineSpec{
NetworkInterfaces: []compute.NetworkInterface{
{
Name: "bar",
NetworkInterfaceSource: compute.NetworkInterfaceSource{
NetworkInterfaceRef: &corev1.LocalObjectReference{Name: "foo*"},
},
},
},
},
},
ContainElement(InvalidField("spec.networkInterface[0].networkInterfaceRef.name")),
),
Entry("invalid networkInterface prefix length for IPv4 IPSource",
&compute.Machine{
Spec: compute.MachineSpec{
NetworkInterfaces: []compute.NetworkInterface{
{
Name: "foo",
NetworkInterfaceSource: compute.NetworkInterfaceSource{
Ephemeral: &compute.EphemeralNetworkInterfaceSource{
NetworkInterfaceTemplate: &networking.NetworkInterfaceTemplateSpec{
Spec: networking.NetworkInterfaceSpec{
NetworkRef: corev1.LocalObjectReference{Name: "bar"},
IPs: []networking.IPSource{{
Ephemeral: &networking.EphemeralPrefixSource{
PrefixTemplate: &ipam.PrefixTemplateSpec{
Spec: ipam.PrefixSpec{
IPFamily: corev1.IPv4Protocol,
Prefix: commonv1alpha1.MustParseNewIPPrefix("10.0.0.0/24"),
},
}},
}},
},
},
},
},
},
},
},
},
ContainElement(ForbiddenField("spec.networkInterface[0].ephemeral.networkInterfaceTemplate.ips[0].ephemeral.prefixTemplate.spec.prefix")),
),
Entry("invalid ephemral networkInterface prefix length for IPv6 IPSource",
&compute.Machine{
Spec: compute.MachineSpec{
NetworkInterfaces: []compute.NetworkInterface{
{
Name: "foo",
NetworkInterfaceSource: compute.NetworkInterfaceSource{
Ephemeral: &compute.EphemeralNetworkInterfaceSource{
NetworkInterfaceTemplate: &networking.NetworkInterfaceTemplateSpec{
Spec: networking.NetworkInterfaceSpec{
NetworkRef: corev1.LocalObjectReference{Name: "bar"},
IPs: []networking.IPSource{{
Ephemeral: &networking.EphemeralPrefixSource{
PrefixTemplate: &ipam.PrefixTemplateSpec{
Spec: ipam.PrefixSpec{
IPFamily: corev1.IPv6Protocol,
Prefix: commonv1alpha1.MustParseNewIPPrefix("2001:db8::/64"),
},
}},
}},
},
},
},
},
},
},
},
},
ContainElement(ForbiddenField("spec.networkInterface[0].ephemeral.networkInterfaceTemplate.ips[0].ephemeral.prefixTemplate.spec.prefix")),
),
Entry("invalid networkInterface prefix length derived from parent prefix for IPv4 IPSource",
&compute.Machine{
Spec: compute.MachineSpec{
NetworkInterfaces: []compute.NetworkInterface{
{
Name: "foo",
NetworkInterfaceSource: compute.NetworkInterfaceSource{
Ephemeral: &compute.EphemeralNetworkInterfaceSource{
NetworkInterfaceTemplate: &networking.NetworkInterfaceTemplateSpec{
Spec: networking.NetworkInterfaceSpec{
NetworkRef: corev1.LocalObjectReference{Name: "bar"},
IPFamilies: []corev1.IPFamily{"IPv4"},
IPs: []networking.IPSource{{
Ephemeral: &networking.EphemeralPrefixSource{
PrefixTemplate: &ipam.PrefixTemplateSpec{
Spec: ipam.PrefixSpec{
IPFamily: corev1.IPv4Protocol,
ParentSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"foo": "bar"},
},
PrefixLength: 40,
},
}},
}},
},
},
},
},
},
},
},
},
ContainElement(ForbiddenField("spec.networkInterface[0].ephemeral.networkInterfaceTemplate.ips[0].ephemeral.prefixTemplate.spec.prefixLength")),
),
Entry("invalid ephemral networkInterface prefix length for IPv6 IPSource",
&compute.Machine{
Spec: compute.MachineSpec{
NetworkInterfaces: []compute.NetworkInterface{
{
Name: "foo",
NetworkInterfaceSource: compute.NetworkInterfaceSource{
Ephemeral: &compute.EphemeralNetworkInterfaceSource{
NetworkInterfaceTemplate: &networking.NetworkInterfaceTemplateSpec{
Spec: networking.NetworkInterfaceSpec{
NetworkRef: corev1.LocalObjectReference{Name: "bar"},
IPFamilies: []corev1.IPFamily{"IPv6"},
IPs: []networking.IPSource{{
Ephemeral: &networking.EphemeralPrefixSource{
PrefixTemplate: &ipam.PrefixTemplateSpec{
Spec: ipam.PrefixSpec{
IPFamily: corev1.IPv6Protocol,
ParentSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"foo": "bar"},
},
PrefixLength: 132,
},
}},
}},
},
},
},
},
},
},
},
},
ContainElement(ForbiddenField("spec.networkInterface[0].ephemeral.networkInterfaceTemplate.ips[0].ephemeral.prefixTemplate.spec.prefixLength")),
),
)

DescribeTable("ValidateMachineNetworkInterface prefix length derived from parent prefix",
func(machine *compute.Machine, match types.GomegaMatcher) {
errList := ValidateMachine(machine)
Expect(errList).To(match)
},
Entry("valid networkInterface prefix length derived from parent prefix for IPv4 IPSource",
&compute.Machine{
Spec: compute.MachineSpec{
NetworkInterfaces: []compute.NetworkInterface{
{
Name: "foo",
NetworkInterfaceSource: compute.NetworkInterfaceSource{
Ephemeral: &compute.EphemeralNetworkInterfaceSource{
NetworkInterfaceTemplate: &networking.NetworkInterfaceTemplateSpec{
Spec: networking.NetworkInterfaceSpec{
NetworkRef: corev1.LocalObjectReference{Name: "bar"},
IPFamilies: []corev1.IPFamily{"IPv4"},
IPs: []networking.IPSource{{
Ephemeral: &networking.EphemeralPrefixSource{
PrefixTemplate: &ipam.PrefixTemplateSpec{
Spec: ipam.PrefixSpec{
IPFamily: corev1.IPv4Protocol,
ParentRef: &corev1.LocalObjectReference{
Name: "root",
},
PrefixLength: 32,
},
}},
}},
},
},
},
},
},
},
},
},
Not(ContainElement(ForbiddenField("spec.networkInterface[0].ephemeral.networkInterfaceTemplate.ips[0].ephemeral.prefixTemplate.spec.prefixLength"))),
),
Entry("valid ephemral networkInterface prefix length derived from parent prefix for IPv6 IPSource",
&compute.Machine{
Spec: compute.MachineSpec{
NetworkInterfaces: []compute.NetworkInterface{
{
Name: "foo",
NetworkInterfaceSource: compute.NetworkInterfaceSource{
Ephemeral: &compute.EphemeralNetworkInterfaceSource{
NetworkInterfaceTemplate: &networking.NetworkInterfaceTemplateSpec{
Spec: networking.NetworkInterfaceSpec{
NetworkRef: corev1.LocalObjectReference{Name: "bar"},
IPFamilies: []corev1.IPFamily{"IPv6"},
IPs: []networking.IPSource{{
Ephemeral: &networking.EphemeralPrefixSource{
PrefixTemplate: &ipam.PrefixTemplateSpec{
Spec: ipam.PrefixSpec{
IPFamily: corev1.IPv6Protocol,
ParentRef: &corev1.LocalObjectReference{
Name: "root",
},
PrefixLength: 128,
},
}},
}},
},
},
},
},
},
},
},
},
Not(ContainElement(ForbiddenField("spec.networkInterface[0].ephemeral.networkInterfaceTemplate.ips[0].ephemeral.prefixTemplate.spec.prefixLength"))),
),
)

DescribeTable("ValidateMachineUpdate",
func(newMachine, oldMachine *compute.Machine, match types.GomegaMatcher) {
errList := ValidateMachineUpdate(newMachine, oldMachine)
Expand Down
4 changes: 2 additions & 2 deletions internal/apis/networking/validation/networkinterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func ValidateNetworkInterface(networkInterface *networking.NetworkInterface) fie
var allErrs field.ErrorList

allErrs = append(allErrs, apivalidation.ValidateObjectMetaAccessor(networkInterface, true, apivalidation.NameIsDNSLabel, field.NewPath("metadata"))...)
allErrs = append(allErrs, validateNetworkInterfaceSpec(&networkInterface.Spec, &networkInterface.ObjectMeta, field.NewPath("spec"))...)
allErrs = append(allErrs, ValidateNetworkInterfaceSpec(&networkInterface.Spec, &networkInterface.ObjectMeta, field.NewPath("spec"))...)

return allErrs
}
Expand All @@ -37,7 +37,7 @@ func ValidateNetworkInterfaceUpdate(newNetworkInterface, oldNetworkInterface *ne
return allErrs
}

func validateNetworkInterfaceSpec(spec *networking.NetworkInterfaceSpec, nicMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
func ValidateNetworkInterfaceSpec(spec *networking.NetworkInterfaceSpec, nicMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList

if spec.NetworkRef == (corev1.LocalObjectReference{}) {
Expand Down
Loading