Skip to content

Commit

Permalink
Merge pull request kubevirt#11511 from EdDev/admitters-net-move
Browse files Browse the repository at this point in the history
virt-api, admitter: Refactor & move network validation admitters to the network package
  • Loading branch information
kubevirt-bot authored Mar 27, 2024
2 parents 4364114 + 9d3867f commit b1d9476
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 85 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ lint:
hack/dockerized "golangci-lint run --timeout 20m --verbose \
pkg/instancetype/... \
pkg/libvmi/... \
pkg/network/admitter/... \
pkg/network/namescheme/... \
pkg/network/domainspec/... \
pkg/network/sriov/... \
Expand Down
12 changes: 10 additions & 2 deletions pkg/network/admitter/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
srcs = ["admit.go"],
srcs = [
"admit.go",
"slirp.go",
],
importpath = "kubevirt.io/kubevirt/pkg/network/admitter",
visibility = ["//visibility:public"],
deps = [
Expand All @@ -15,11 +18,16 @@ go_library(

go_test(
name = "go_default_test",
srcs = ["admit_test.go"],
srcs = [
"admit_suite_test.go",
"admit_test.go",
"slirp_test.go",
],
deps = [
":go_default_library",
"//staging/src/kubevirt.io/api/core/v1:go_default_library",
"//staging/src/kubevirt.io/client-go/api:go_default_library",
"//staging/src/kubevirt.io/client-go/testutils:go_default_library",
"//vendor/github.com/onsi/ginkgo/v2:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
Expand Down
21 changes: 21 additions & 0 deletions pkg/network/admitter/admit.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,24 @@ func hasInterfaceBindingMethod(iface v1.Interface) bool {
iface.InterfaceBindingMethod.Macvtap != nil ||
iface.InterfaceBindingMethod.Passt != nil
}

func ValidateSinglePodNetwork(field *k8sfield.Path, spec *v1.VirtualMachineInstanceSpec) []metav1.StatusCause {
if countPodNetworks(spec.Networks) > 1 {
return []metav1.StatusCause{{
Type: metav1.CauseTypeFieldValueDuplicate,
Message: fmt.Sprintf("more than one interface is connected to a pod network in %s", field.Child("interfaces").String()),
Field: field.Child("interfaces").String(),
}}
}
return nil
}

func countPodNetworks(networks []v1.Network) int {
count := 0
for _, net := range networks {
if net.Pod != nil {
count++
}
}
return count
}
30 changes: 30 additions & 0 deletions pkg/network/admitter/admit_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* This file is part of the KubeVirt project
*
* 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.
*
* Copyright 2024 Red Hat, Inc.
*
*/

package admitter_test

import (
"testing"

"kubevirt.io/client-go/testutils"
)

func TestAdmitter(t *testing.T) {
testutils.KubeVirtTestSuiteSetup(t)
}
13 changes: 13 additions & 0 deletions pkg/network/admitter/admit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,17 @@ var _ = Describe("Validating VMI network spec", func() {
}}
Expect(admitter.ValidateInterfaceBinding(k8sfield.NewPath("fake"), &vm.Spec)).To(BeEmpty())
})

It("support only a single pod network", func() {
const net1Name = "default"
const net2Name = "default2"
vmi := v1.VirtualMachineInstance{}
vmi.Spec.Networks = []v1.Network{
{Name: net1Name, NetworkSource: v1.NetworkSource{Pod: &v1.PodNetwork{}}},
{Name: net2Name, NetworkSource: v1.NetworkSource{Pod: &v1.PodNetwork{}}},
}
causes := admitter.ValidateSinglePodNetwork(k8sfield.NewPath("fake"), &vmi.Spec)
Expect(causes).To(HaveLen(1))
Expect(causes[0].Message).To(Equal("more than one interface is connected to a pod network in fake.interfaces"))
})
})
63 changes: 63 additions & 0 deletions pkg/network/admitter/slirp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* This file is part of the KubeVirt project
*
* 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.
*
* Copyright 2024 Red Hat, Inc.
*
*/

package admitter

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sfield "k8s.io/apimachinery/pkg/util/validation/field"

"kubevirt.io/kubevirt/pkg/network/vmispec"

v1 "kubevirt.io/api/core/v1"
)

type slirpClusterConfigChecker interface {
IsSlirpInterfaceEnabled() bool
}

func ValidateSlirpBinding(
field *k8sfield.Path,
spec *v1.VirtualMachineInstanceSpec,
configChecker slirpClusterConfigChecker) (causes []metav1.StatusCause) {
for idx, ifaceSpec := range spec.Domain.Devices.Interfaces {
if ifaceSpec.Slirp == nil {
continue
}
net := vmispec.LookupNetworkByName(spec.Networks, ifaceSpec.Name)
if net == nil {
continue
}

if net.Pod == nil {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: "Slirp interface only implemented with pod network",
Field: field.Child("domain", "devices", "interfaces").Index(idx).Child("name").String(),
})
} else if !configChecker.IsSlirpInterfaceEnabled() {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: "Slirp interface is not enabled in kubevirt-config",
Field: field.Child("domain", "devices", "interfaces").Index(idx).Child("name").String(),
})
}
}
return causes
}
96 changes: 96 additions & 0 deletions pkg/network/admitter/slirp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* This file is part of the KubeVirt project
*
* 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.
*
* Copyright 2024 Red Hat, Inc.
*
*/

package admitter_test

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

k8sfield "k8s.io/apimachinery/pkg/util/validation/field"

v1 "kubevirt.io/api/core/v1"

"kubevirt.io/kubevirt/pkg/network/admitter"
)

var _ = Describe("Validate interface with SLIRP binding", func() {
It("should be rejected if not enabled in the Kubevirt CR", func() {
vmi := v1.VirtualMachineInstance{}
vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{{
Name: "default",
InterfaceBindingMethod: v1.InterfaceBindingMethod{
Slirp: &v1.InterfaceSlirp{},
},
}}
vmi.Spec.Networks = []v1.Network{{
Name: "default",
NetworkSource: v1.NetworkSource{Pod: &v1.PodNetwork{}},
}}

config := stubSlirpClusterConfigChecker{}
causes := admitter.ValidateSlirpBinding(k8sfield.NewPath("fake"), &vmi.Spec, config)
Expect(causes).To(HaveLen(1))
Expect(causes[0].Message).To(Equal("Slirp interface is not enabled in kubevirt-config"))
})

It("should be rejected without a pod network", func() {
vmi := v1.VirtualMachineInstance{}
vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{{
Name: "default",
InterfaceBindingMethod: v1.InterfaceBindingMethod{
Slirp: &v1.InterfaceSlirp{},
},
}}
vmi.Spec.Networks = []v1.Network{{
Name: "default",
NetworkSource: v1.NetworkSource{Multus: &v1.MultusNetwork{}},
}}

config := stubSlirpClusterConfigChecker{enabled: true}
causes := admitter.ValidateSlirpBinding(k8sfield.NewPath("fake"), &vmi.Spec, config)
Expect(causes).To(HaveLen(1))
Expect(causes[0].Message).To(Equal("Slirp interface only implemented with pod network"))
})

It("should be accepted with a pod network when SLIRP is enabled in the Kubevirt CR", func() {
vmi := v1.VirtualMachineInstance{}
vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{{
Name: "default",
InterfaceBindingMethod: v1.InterfaceBindingMethod{
Slirp: &v1.InterfaceSlirp{},
},
}}
vmi.Spec.Networks = []v1.Network{{
Name: "default",
NetworkSource: v1.NetworkSource{Pod: &v1.PodNetwork{}},
}}

config := stubSlirpClusterConfigChecker{enabled: true}
Expect(admitter.ValidateSlirpBinding(k8sfield.NewPath("fake"), &vmi.Spec, config)).To(BeEmpty())
})
})

type stubSlirpClusterConfigChecker struct {
enabled bool
}

func (s stubSlirpClusterConfigChecker) IsSlirpInterfaceEnabled() bool {
return s.enabled
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,7 @@ func ValidateVirtualMachineInstanceSpec(field *k8sfield.Path, spec *v1.VirtualMa
causes = append(causes, validateSpecTopologySpreadConstraints(field, spec)...)
causes = append(causes, validateArchitecture(field, spec, config)...)

moreThanOnePodInterface := getNumberOfPodInterfaces(spec) > 1
if moreThanOnePodInterface {
return appendStatusCauseForMoreThanOnePodInterface(field, causes)
}
causes = append(causes, netadmitter.ValidateSinglePodNetwork(field, spec)...)

bootOrderMap, newCauses := validateBootOrder(field, spec, volumeNameMap)
causes = append(causes, newCauses...)
Expand All @@ -184,6 +181,7 @@ func ValidateVirtualMachineInstanceSpec(field *k8sfield.Path, spec *v1.VirtualMa
causes = appendStatusCauseForPodNetworkDefinedWithMultusDefaultNetworkDefined(field, causes)
}

causes = append(causes, netadmitter.ValidateSlirpBinding(field, spec, config)...)
networkInterfaceMap, newCauses, done := validateNetworksMatchInterfaces(field, spec, config, networkNameMap, bootOrderMap)
causes = append(causes, newCauses...)
if done {
Expand Down Expand Up @@ -315,10 +313,6 @@ func validateNetworksMatchInterfaces(field *k8sfield.Path, spec *v1.VirtualMachi
func validateInterfaceNetworkBasics(field *k8sfield.Path, networkExists bool, idx int, iface v1.Interface, networkData *v1.Network, config *virtconfig.ClusterConfig, numOfInterfaces int) (causes []metav1.StatusCause) {
if !networkExists {
causes = appendStatusCauseForNetworkNotFound(field, causes, idx, iface)
} else if iface.Slirp != nil && networkData.Pod == nil {
causes = appendStatusCauseForSlirpWithoutPodNetwork(field, causes, idx)
} else if iface.Slirp != nil && networkData.Pod != nil && !config.IsSlirpInterfaceEnabled() {
causes = appendStatusCauseForSlirpNotEnabled(field, causes, idx)
} else if iface.Masquerade != nil && networkData.Pod == nil {
causes = appendStatusCauseForMasqueradeWithoutPodNetwork(field, causes, idx)
} else if iface.Masquerade != nil && link.IsReserved(iface.MacAddress) {
Expand Down Expand Up @@ -575,23 +569,6 @@ func appendStatusCauseForMasqueradeWithoutPodNetwork(field *k8sfield.Path, cause
return causes
}

func appendStatusCauseForSlirpNotEnabled(field *k8sfield.Path, causes []metav1.StatusCause, idx int) []metav1.StatusCause {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: "Slirp interface is not enabled in kubevirt-config",
Field: field.Child("domain", "devices", "interfaces").Index(idx).Child("name").String(),
})
return causes
}

func appendStatusCauseForSlirpWithoutPodNetwork(field *k8sfield.Path, causes []metav1.StatusCause, idx int) []metav1.StatusCause {
return append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: "Slirp interface only implemented with pod network",
Field: field.Child("domain", "devices", "interfaces").Index(idx).Child("name").String(),
})
}

func appendStatusCauseForNetworkNotFound(field *k8sfield.Path, causes []metav1.StatusCause, idx int, iface v1.Interface) []metav1.StatusCause {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Expand Down Expand Up @@ -1053,14 +1030,6 @@ func validateBootOrder(field *k8sfield.Path, spec *v1.VirtualMachineInstanceSpec
return bootOrderMap, causes
}

func appendStatusCauseForMoreThanOnePodInterface(field *k8sfield.Path, causes []metav1.StatusCause) []metav1.StatusCause {
return append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueDuplicate,
Message: fmt.Sprintf("more than one interface is connected to a pod network in %s", field.Child("interfaces").String()),
Field: field.Child("interfaces").String(),
})
}

func validateCPUFeaturePolicies(field *k8sfield.Path, spec *v1.VirtualMachineInstanceSpec) (causes []metav1.StatusCause) {
if spec.Domain.CPU != nil && spec.Domain.CPU.Features != nil {
for idx, feature := range spec.Domain.CPU.Features {
Expand Down
Loading

0 comments on commit b1d9476

Please sign in to comment.