diff --git a/pkg/kubecli/generated_mock_kubevirt.go b/pkg/kubecli/generated_mock_kubevirt.go index aead4ed7a933..646d5d4b3a0d 100644 --- a/pkg/kubecli/generated_mock_kubevirt.go +++ b/pkg/kubecli/generated_mock_kubevirt.go @@ -7,6 +7,7 @@ import ( time "time" gomock "github.com/golang/mock/gomock" + versioned "github.com/phoracek/network-attachment-definition-client/pkg/client/clientset/versioned" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" discovery "k8s.io/client-go/discovery" @@ -41,7 +42,7 @@ import ( v1beta110 "k8s.io/client-go/kubernetes/typed/storage/v1beta1" rest "k8s.io/client-go/rest" - versioned "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned" + versioned0 "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned" v19 "kubevirt.io/kubevirt/pkg/api/v1" ) @@ -126,9 +127,9 @@ func (_mr *_MockKubevirtClientRecorder) RestClient() *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "RestClient") } -func (_m *MockKubevirtClient) CdiClient() versioned.Interface { +func (_m *MockKubevirtClient) CdiClient() versioned0.Interface { ret := _m.ctrl.Call(_m, "CdiClient") - ret0, _ := ret[0].(versioned.Interface) + ret0, _ := ret[0].(versioned0.Interface) return ret0 } @@ -136,6 +137,16 @@ func (_mr *_MockKubevirtClientRecorder) CdiClient() *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "CdiClient") } +func (_m *MockKubevirtClient) NetworkClient() versioned.Interface { + ret := _m.ctrl.Call(_m, "NetworkClient") + ret0, _ := ret[0].(versioned.Interface) + return ret0 +} + +func (_mr *_MockKubevirtClientRecorder) NetworkClient() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "NetworkClient") +} + func (_m *MockKubevirtClient) Discovery() discovery.DiscoveryInterface { ret := _m.ctrl.Call(_m, "Discovery") ret0, _ := ret[0].(discovery.DiscoveryInterface) diff --git a/pkg/kubecli/kubecli.go b/pkg/kubecli/kubecli.go index 90b999ec00bc..12a4f9cb8d27 100644 --- a/pkg/kubecli/kubecli.go +++ b/pkg/kubecli/kubecli.go @@ -33,6 +33,8 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + networkclient "github.com/phoracek/network-attachment-definition-client/pkg/client/clientset/versioned" + cdiclient "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned" "kubevirt.io/kubevirt/pkg/api/v1" ) @@ -73,12 +75,18 @@ func GetKubevirtSubresourceClientFromFlags(master string, kubeconfig string) (Ku return nil, err } + networkClient, err := networkclient.NewForConfig(config) + if err != nil { + return nil, err + } + return &kubevirt{ master, kubeconfig, restClient, config, cdiClient, + networkClient, coreClient, }, nil } @@ -177,12 +185,18 @@ func GetKubevirtClientFromRESTConfig(config *rest.Config) (KubevirtClient, error return nil, err } + networkClient, err := networkclient.NewForConfig(config) + if err != nil { + return nil, err + } + return &kubevirt{ master, kubeconfig, restClient, config, cdiClient, + networkClient, coreClient, }, nil } diff --git a/pkg/kubecli/kubevirt.go b/pkg/kubecli/kubevirt.go index dfbe617966a2..8e98fa439f8c 100644 --- a/pkg/kubecli/kubevirt.go +++ b/pkg/kubecli/kubevirt.go @@ -34,6 +34,8 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + networkclient "github.com/phoracek/network-attachment-definition-client/pkg/client/clientset/versioned" + cdiclient "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned" "kubevirt.io/kubevirt/pkg/api/v1" ) @@ -46,15 +48,17 @@ type KubevirtClient interface { ServerVersion() *ServerVersion RestClient() *rest.RESTClient CdiClient() cdiclient.Interface + NetworkClient() networkclient.Interface kubernetes.Interface } type kubevirt struct { - master string - kubeconfig string - restClient *rest.RESTClient - config *rest.Config - cdiClient *cdiclient.Clientset + master string + kubeconfig string + restClient *rest.RESTClient + config *rest.Config + cdiClient *cdiclient.Clientset + networkClient *networkclient.Clientset *kubernetes.Clientset } @@ -62,6 +66,10 @@ func (k kubevirt) CdiClient() cdiclient.Interface { return k.cdiClient } +func (k kubevirt) NetworkClient() networkclient.Interface { + return k.networkClient +} + func (k kubevirt) RestClient() *rest.RESTClient { return k.restClient } diff --git a/pkg/virt-api/rest/subresource_test.go b/pkg/virt-api/rest/subresource_test.go index f60c9494f112..54a7aa14a9cb 100644 --- a/pkg/virt-api/rest/subresource_test.go +++ b/pkg/virt-api/rest/subresource_test.go @@ -25,6 +25,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/ghttp" + networkv1 "github.com/phoracek/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" k8sv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/client-go/tools/cache" @@ -53,7 +54,7 @@ var _ = Describe("VirtualMachineInstance Subresources", func() { vmi := v1.NewMinimalVMI("testvmi") vmi.Status.Phase = v1.Running vmi.ObjectMeta.SetUID(uuid.NewUUID()) - templateService := services.NewTemplateService("whatever", "whatever", "whatever", "whatever", configCache, pvcCache) + templateService := services.NewTemplateService("whatever", "whatever", "whatever", "whatever", configCache, pvcCache, app.VirtCli) pod, err := templateService.RenderLaunchManifest(vmi) Expect(err).ToNot(HaveOccurred()) @@ -66,11 +67,17 @@ var _ = Describe("VirtualMachineInstance Subresources", func() { podList.Items = []k8sv1.Pod{} podList.Items = append(podList.Items, *pod) + networkList := networkv1.NetworkAttachmentDefinitionList{} + server.AppendHandlers( ghttp.CombineHandlers( ghttp.VerifyRequest("GET", "/api/v1/namespaces/default/pods"), ghttp.RespondWithJSONEncoded(http.StatusOK, podList), ), + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/apis/k8s.cni.cncf.io/v1/namespaces/default/network-attachment-definitions"), + ghttp.RespondWithJSONEncoded(http.StatusOK, networkList), + ), ) podName, httpStatusCode, err := app.remoteExecInfo(vmi) diff --git a/pkg/virt-controller/services/template.go b/pkg/virt-controller/services/template.go index 15a19cc6cbf8..94f788dd5a2b 100644 --- a/pkg/virt-controller/services/template.go +++ b/pkg/virt-controller/services/template.go @@ -30,9 +30,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/cache" + networkv1 "github.com/phoracek/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + "kubevirt.io/kubevirt/pkg/api/v1" "kubevirt.io/kubevirt/pkg/config" "kubevirt.io/kubevirt/pkg/hooks" + "kubevirt.io/kubevirt/pkg/kubecli" "kubevirt.io/kubevirt/pkg/log" "kubevirt.io/kubevirt/pkg/precond" "kubevirt.io/kubevirt/pkg/registry-disk" @@ -62,6 +65,7 @@ type templateService struct { imagePullSecret string configMapStore cache.Store persistentVolumeClaimStore cache.Store + virtClient kubecli.KubevirtClient } type PvcNotFoundError error @@ -571,6 +575,14 @@ func (t *templateService) RenderLaunchManifest(vmi *v1.VirtualMachineInstance) ( podLabels[v1.AppLabel] = "virt-launcher" podLabels[v1.CreatedByLabel] = string(vmi.UID) + cniNetworks, cniAnnotation, networkToResourceMap := getCniInterfaceList(t.virtClient, vmi) + for networkName, resourceName := range networkToResourceMap { + // TODO: move it to a separate helper + // TODO: ideally, this mapping would be provided to us by other components (Multus?) + varName := fmt.Sprintf("KUBEVIRT_RESOURCE_NAME_%s", networkName) + container.Env = append(container.Env, k8sv1.EnvVar{Name: varName, Value: resourceName}) + } + containers = append(containers, container) for i, requestedHookSidecar := range requestedHookSidecarList { @@ -603,7 +615,6 @@ func (t *templateService) RenderLaunchManifest(vmi *v1.VirtualMachineInstance) ( v1.OwnedByAnnotation: "virt-controller", } - cniNetworks, cniAnnotation := getCniInterfaceList(vmi) if len(cniNetworks) > 0 { annotationsList[cniAnnotation] = cniNetworks } @@ -781,17 +792,36 @@ func getPortsFromVMI(vmi *v1.VirtualMachineInstance) []k8sv1.ContainerPort { return ports } -func getCniInterfaceList(vmi *v1.VirtualMachineInstance) (ifaceListString string, cniAnnotation string) { +func getResourceNameForNetwork(networks *networkv1.NetworkAttachmentDefinitionList, networkName string) string { + logger := log.DefaultLogger() + logger.Errorf("number of networks: %d", len(networks.Items)) + for _, network := range networks.Items { + if network.Name == networkName { + return network.Annotations["k8s.v1.cni.cncf.io/resourceName"] + } + } + return "" +} + +func getCniInterfaceList(virtClient kubecli.KubevirtClient, vmi *v1.VirtualMachineInstance) (ifaceListString string, cniAnnotation string, networkToResourceMap map[string]string) { ifaceList := make([]string, 0) + networkToResourceMap = make(map[string]string) + networks, err := virtClient.NetworkClient().K8sCniCncfIo().NetworkAttachmentDefinitions("default").List(metav1.ListOptions{}) + if err != nil { + logger := log.DefaultLogger() + logger.Errorf("error fetching networks: %e", err) + } for _, network := range vmi.Spec.Networks { // set the type for the first network // all other networks must have same type if network.Multus != nil { - ifaceList = append(ifaceList, network.Multus.NetworkName) + networkName := network.Multus.NetworkName + ifaceList = append(ifaceList, networkName) if cniAnnotation == "" { cniAnnotation = "k8s.v1.cni.cncf.io/networks" } + networkToResourceMap[networkName] = getResourceNameForNetwork(networks, networkName) } else if network.Genie != nil { ifaceList = append(ifaceList, network.Genie.NetworkName) if cniAnnotation == "" { @@ -809,7 +839,8 @@ func NewTemplateService(launcherImage string, ephemeralDiskDir string, imagePullSecret string, configMapCache cache.Store, - persistentVolumeClaimCache cache.Store) TemplateService { + persistentVolumeClaimCache cache.Store, + virtClient kubecli.KubevirtClient) TemplateService { precond.MustNotBeEmpty(launcherImage) svc := templateService{ @@ -819,6 +850,7 @@ func NewTemplateService(launcherImage string, imagePullSecret: imagePullSecret, configMapStore: configMapCache, persistentVolumeClaimStore: persistentVolumeClaimCache, + virtClient: virtClient, } return &svc } diff --git a/pkg/virt-controller/services/template_test.go b/pkg/virt-controller/services/template_test.go index 5c994faa235f..5ba732e4ba51 100644 --- a/pkg/virt-controller/services/template_test.go +++ b/pkg/virt-controller/services/template_test.go @@ -24,9 +24,11 @@ import ( "testing" "time" + "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo" "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" + fakenetworkclient "github.com/phoracek/network-attachment-definition-client/pkg/client/clientset/versioned/fake" kubev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -36,6 +38,7 @@ import ( "kubevirt.io/kubevirt/pkg/api/v1" "kubevirt.io/kubevirt/pkg/hooks" + "kubevirt.io/kubevirt/pkg/kubecli" "kubevirt.io/kubevirt/pkg/log" . "kubevirt.io/kubevirt/pkg/virt-controller/services" ) @@ -47,12 +50,24 @@ var _ = Describe("Template", func() { log.Log.SetIOWriter(GinkgoWriter) configCache := cache.NewIndexer(cache.DeletionHandlingMetaNamespaceKeyFunc, nil) pvcCache := cache.NewIndexer(cache.DeletionHandlingMetaNamespaceKeyFunc, nil) + + ctrl := gomock.NewController(GinkgoT()) + virtClient := kubecli.NewMockKubevirtClient(ctrl) + var networkClient *fakenetworkclient.Clientset + svc := NewTemplateService("kubevirt/virt-launcher", "/var/run/kubevirt", "/var/run/kubevirt-ephemeral-disks", "pull-secret-1", configCache, - pvcCache) + pvcCache, + virtClient) + + BeforeEach(func() { + // Set up mock client + networkClient = fakenetworkclient.NewSimpleClientset() + virtClient.EXPECT().NetworkClient().Return(networkClient).AnyTimes() + }) Describe("Rendering", func() { Context("launch template with correct parameters", func() { diff --git a/pkg/virt-controller/watch/application.go b/pkg/virt-controller/watch/application.go index e18cebbc5621..cafe6da51358 100644 --- a/pkg/virt-controller/watch/application.go +++ b/pkg/virt-controller/watch/application.go @@ -264,6 +264,11 @@ func (vca *VirtControllerApp) getNewRecorder(namespace string, componentName str func (vca *VirtControllerApp) initCommon() { var err error + virtClient, err := kubecli.GetKubevirtClient() + if err != nil { + golog.Fatal(err) + } + registrydisk.SetLocalDirectory(vca.ephemeralDiskDir + "/registry-disk-data") if err != nil { golog.Fatal(err) @@ -273,7 +278,8 @@ func (vca *VirtControllerApp) initCommon() { vca.ephemeralDiskDir, vca.imagePullSecret, vca.configMapCache, - vca.persistentVolumeClaimCache) + vca.persistentVolumeClaimCache, + virtClient) vca.vmiController = NewVMIController(vca.templateService, vca.vmiInformer, vca.podInformer, vca.vmiRecorder, vca.clientSet, vca.configMapInformer, vca.dataVolumeInformer) recorder := vca.getNewRecorder(k8sv1.NamespaceAll, "node-controller") diff --git a/pkg/virt-controller/watch/migration_test.go b/pkg/virt-controller/watch/migration_test.go index 965be2543756..5448d41004e4 100644 --- a/pkg/virt-controller/watch/migration_test.go +++ b/pkg/virt-controller/watch/migration_test.go @@ -24,6 +24,7 @@ import ( . "github.com/onsi/ginkgo" "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" + fakenetworkclient "github.com/phoracek/network-attachment-definition-client/pkg/client/clientset/versioned/fake" k8sv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -60,6 +61,7 @@ var _ = Describe("Migration watcher", func() { var podFeeder *testutils.PodFeeder var virtClient *kubecli.MockKubevirtClient var kubeClient *fake.Clientset + var networkClient *fakenetworkclient.Clientset var configMapInformer cache.SharedIndexInformer var pvcInformer cache.SharedIndexInformer @@ -163,7 +165,7 @@ var _ = Describe("Migration watcher", func() { pvcInformer, _ = testutils.NewFakeInformerFor(&k8sv1.PersistentVolumeClaim{}) controller = NewMigrationController( - services.NewTemplateService("a", "b", "c", "d", configMapInformer.GetStore(), pvcInformer.GetStore()), + services.NewTemplateService("a", "b", "c", "d", configMapInformer.GetStore(), pvcInformer.GetStore(), virtClient), vmiInformer, podInformer, migrationInformer, @@ -179,6 +181,8 @@ var _ = Describe("Migration watcher", func() { virtClient.EXPECT().VirtualMachineInstanceMigration(k8sv1.NamespaceDefault).Return(migrationInterface).AnyTimes() virtClient.EXPECT().VirtualMachineInstance(k8sv1.NamespaceDefault).Return(vmiInterface).AnyTimes() virtClient.EXPECT().CoreV1().Return(kubeClient.CoreV1()).AnyTimes() + networkClient = fakenetworkclient.NewSimpleClientset() + virtClient.EXPECT().NetworkClient().Return(networkClient).AnyTimes() // Make sure that all unexpected calls to kubeClient will fail kubeClient.Fake.PrependReactor("*", "*", func(action testing.Action) (handled bool, obj runtime.Object, err error) { diff --git a/pkg/virt-controller/watch/vmi_test.go b/pkg/virt-controller/watch/vmi_test.go index d7ff7f533722..c3a9a6d67ed6 100644 --- a/pkg/virt-controller/watch/vmi_test.go +++ b/pkg/virt-controller/watch/vmi_test.go @@ -37,7 +37,10 @@ import ( "k8s.io/client-go/tools/cache/testing" "k8s.io/client-go/tools/record" + fakenetworkclient "github.com/phoracek/network-attachment-definition-client/pkg/client/clientset/versioned/fake" + cdiv1 "kubevirt.io/containerized-data-importer/pkg/apis/datavolumecontroller/v1alpha1" + "kubevirt.io/kubevirt/pkg/api/v1" "kubevirt.io/kubevirt/pkg/kubecli" "kubevirt.io/kubevirt/pkg/log" @@ -61,6 +64,7 @@ var _ = Describe("VirtualMachineInstance watcher", func() { var podFeeder *testutils.PodFeeder var virtClient *kubecli.MockKubevirtClient var kubeClient *fake.Clientset + var networkClient *fakenetworkclient.Clientset var configMapInformer cache.SharedIndexInformer var pvcInformer cache.SharedIndexInformer @@ -163,7 +167,7 @@ var _ = Describe("VirtualMachineInstance watcher", func() { configMapInformer, _ = testutils.NewFakeInformerFor(&k8sv1.ConfigMap{}) pvcInformer, _ = testutils.NewFakeInformerFor(&k8sv1.PersistentVolumeClaim{}) controller = NewVMIController( - services.NewTemplateService("a", "b", "c", "d", configMapInformer.GetStore(), pvcInformer.GetStore()), + services.NewTemplateService("a", "b", "c", "d", configMapInformer.GetStore(), pvcInformer.GetStore(), virtClient), vmiInformer, podInformer, recorder, @@ -180,6 +184,8 @@ var _ = Describe("VirtualMachineInstance watcher", func() { virtClient.EXPECT().VirtualMachineInstance(k8sv1.NamespaceDefault).Return(vmiInterface).AnyTimes() kubeClient = fake.NewSimpleClientset() virtClient.EXPECT().CoreV1().Return(kubeClient.CoreV1()).AnyTimes() + networkClient = fakenetworkclient.NewSimpleClientset() + virtClient.EXPECT().NetworkClient().Return(networkClient).AnyTimes() // Make sure that all unexpected calls to kubeClient will fail kubeClient.Fake.PrependReactor("*", "*", func(action testing.Action) (handled bool, obj runtime.Object, err error) { diff --git a/pkg/virt-launcher/virtwrap/api/converter.go b/pkg/virt-launcher/virtwrap/api/converter.go index 10215d64d5ff..668b034b067a 100644 --- a/pkg/virt-launcher/virtwrap/api/converter.go +++ b/pkg/virt-launcher/virtwrap/api/converter.go @@ -57,7 +57,7 @@ type ConverterContext struct { VirtualMachine *v1.VirtualMachineInstance CPUSet []int IsBlockPVC map[string]bool - SRIOVDevices []string + SRIOVDevices map[string][]string } func Convert_v1_Disk_To_api_Disk(diskDevice *v1.Disk, disk *Disk, devicePerBus map[string]int, numQueues *uint) error { @@ -508,11 +508,13 @@ func Convert_v1_FeatureHyperv_To_api_FeatureHyperv(source *v1.FeatureHyperv, hyp return nil } -func popSRIOVPCIAddress(addrs []string) (string, []string, error) { - if len(addrs) > 0 { - return addrs[0], addrs[1:], nil +func popSRIOVPCIAddress(networkName string, addrsMap map[string][]string) (string, map[string][]string, error) { + if len(addrsMap[networkName]) > 0 { + addr := addrsMap[networkName][0] + addrsMap[networkName] = addrsMap[networkName][1:] + return addr, addrsMap, nil } - return "", addrs, fmt.Errorf("no more SR-IOV PCI addresses to allocate") + return "", addrsMap, fmt.Errorf("no more SR-IOV PCI addresses to allocate") } func Convert_v1_VirtualMachine_To_api_Domain(vmi *v1.VirtualMachineInstance, domain *Domain, c *ConverterContext) (err error) { @@ -871,7 +873,10 @@ func Convert_v1_VirtualMachine_To_api_Domain(vmi *v1.VirtualMachineInstance, dom networks[network.Name] = network.DeepCopy() } - sriovPciAddresses := append([]string{}, c.SRIOVDevices...) + sriovPciAddresses := make(map[string][]string) + for key, value := range c.SRIOVDevices { + sriovPciAddresses[key] = append([]string{}, value...) + } for _, iface := range vmi.Spec.Domain.Devices.Interfaces { net, isExist := networks[iface.Name] @@ -881,7 +886,7 @@ func Convert_v1_VirtualMachine_To_api_Domain(vmi *v1.VirtualMachineInstance, dom if iface.SRIOV != nil { var pciAddr string - pciAddr, sriovPciAddresses, err = popSRIOVPCIAddress(sriovPciAddresses) + pciAddr, sriovPciAddresses, err = popSRIOVPCIAddress(iface.Name, sriovPciAddresses) if err != nil { return err } diff --git a/pkg/virt-launcher/virtwrap/api/converter_test.go b/pkg/virt-launcher/virtwrap/api/converter_test.go index 9e85a9394572..26c4f32a0636 100644 --- a/pkg/virt-launcher/virtwrap/api/converter_test.go +++ b/pkg/virt-launcher/virtwrap/api/converter_test.go @@ -536,7 +536,7 @@ var _ = Describe("Converter", func() { }, UseEmulation: true, IsBlockPVC: isBlockPVCMap, - SRIOVDevices: []string{}, + SRIOVDevices: map[string][]string{}, } }) @@ -1372,10 +1372,13 @@ var _ = Describe("Converter", func() { } vmi.Spec.Networks = append(vmi.Spec.Networks, sriovNetwork2) - It("should convert sriov interface into host device", func() { + It("should convert sriov interfaces into host devices", func() { c := &ConverterContext{ UseEmulation: true, - SRIOVDevices: []string{"0000:81:11.1", "0000:81:11.2"}, + SRIOVDevices: map[string][]string{ + "sriov": []string{"0000:81:11.1"}, + "sriov2": []string{"0000:81:11.2"}, + }, } domain := vmiToDomain(vmi, c) @@ -1399,17 +1402,21 @@ var _ = Describe("Converter", func() { }) var _ = Describe("popSRIOVPCIAddress", func() { - It("fails on empty slice", func() { - _, _, err := popSRIOVPCIAddress([]string{}) + It("fails on empty map", func() { + _, _, err := popSRIOVPCIAddress("testnet", map[string][]string{}) + Expect(err).To(HaveOccurred()) + }) + It("fails on empty map entry", func() { + _, _, err := popSRIOVPCIAddress("testnet", map[string][]string{"testnet": []string{}}) Expect(err).To(HaveOccurred()) }) It("pops the next address from a non-empty slice", func() { - addrs := []string{"0000:81:11.1", "0001:02:00.0"} - addr, rest, err := popSRIOVPCIAddress(addrs) + addrsMap := map[string][]string{"testnet": []string{"0000:81:11.1", "0001:02:00.0"}} + addr, rest, err := popSRIOVPCIAddress("testnet", addrsMap) Expect(err).ToNot(HaveOccurred()) Expect(addr).To(Equal("0000:81:11.1")) - Expect(len(rest)).To(Equal(1)) - Expect(rest[0]).To(Equal("0001:02:00.0")) + Expect(len(rest["testnet"])).To(Equal(1)) + Expect(rest["testnet"][0]).To(Equal("0001:02:00.0")) }) }) diff --git a/pkg/virt-launcher/virtwrap/api/deepcopy_generated.go b/pkg/virt-launcher/virtwrap/api/deepcopy_generated.go index a93518699ec0..361a3f1403b4 100644 --- a/pkg/virt-launcher/virtwrap/api/deepcopy_generated.go +++ b/pkg/virt-launcher/virtwrap/api/deepcopy_generated.go @@ -623,8 +623,15 @@ func (in *ConverterContext) DeepCopyInto(out *ConverterContext) { } if in.SRIOVDevices != nil { in, out := &in.SRIOVDevices, &out.SRIOVDevices - *out = make([]string, len(*in)) - copy(*out, *in) + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + if val == nil { + (*out)[key] = nil + } else { + (*out)[key] = make([]string, len(val)) + copy((*out)[key], val) + } + } } return } diff --git a/pkg/virt-launcher/virtwrap/manager.go b/pkg/virt-launcher/virtwrap/manager.go index f6c3a878df78..61c26f155eb2 100644 --- a/pkg/virt-launcher/virtwrap/manager.go +++ b/pkg/virt-launcher/virtwrap/manager.go @@ -374,26 +374,39 @@ func (l *LibvirtDomainManager) preStartHook(vmi *v1.VirtualMachineInstance, doma return domain, err } -// This function parses the SRIOV-VF-PCI-ADDR variable that is set by SR-IOV -// device plugin listing PCI IDs for devices allocated to the pod. The format -// is as follows: +// This function parses variables that are set by SR-IOV device plugin listing +// PCI IDs for devices allocated to the pod. It also parses variables that +// virt-controller sets mapping network names to their respective resource +// names (if any). // +// Format for PCI ID variables set by SR-IOV DP is: // "": for no allocated devices -// "0000:81:11.1,": for a single device -// "0000:81:11.1,0000:81:11.2[,...]": for multiple devices -func getSRIOVPCIAddresses() []string { - pciAddrString, isSet := os.LookupEnv("SRIOV-VF-PCI-ADDR") - if isSet { - addrs := strings.Split(pciAddrString, ",") - naddrs := len(addrs) - if naddrs > 0 { - if addrs[naddrs-1] == "" { - addrs = addrs[:naddrs-1] +// ="0000:81:11.1,": for a single device +// ="0000:81:11.1,0000:81:11.2[,...]": for multiple devices +// +// Format for network to resource mapping variables is: +// = +// +func getSRIOVPCIAddresses(ifaces []v1.Interface) map[string][]string { + networkToAddressesMap := map[string][]string{} + for _, iface := range ifaces { + varName := fmt.Sprintf("KUBEVIRT_RESOURCE_NAME_%s", iface.Name) + resourceName, isSet := os.LookupEnv(varName) + if isSet { + pciAddrString, isSet := os.LookupEnv(resourceName) + if isSet { + addrs := strings.Split(pciAddrString, ",") + naddrs := len(addrs) + if naddrs > 0 { + if addrs[naddrs-1] == "" { + addrs = addrs[:naddrs-1] + } + } + networkToAddressesMap[iface.Name] = addrs } } - return addrs } - return []string{} + return networkToAddressesMap } func (l *LibvirtDomainManager) SyncVMI(vmi *v1.VirtualMachineInstance, useEmulation bool) (*api.DomainSpec, error) { @@ -429,7 +442,7 @@ func (l *LibvirtDomainManager) SyncVMI(vmi *v1.VirtualMachineInstance, useEmulat UseEmulation: useEmulation, CPUSet: podCPUSet, IsBlockPVC: isBlockPVCMap, - SRIOVDevices: getSRIOVPCIAddresses(), + SRIOVDevices: getSRIOVPCIAddresses(vmi.Spec.Domain.Devices.Interfaces), } if err := api.Convert_v1_VirtualMachine_To_api_Domain(vmi, domain, c); err != nil { logger.Error("Conversion failed.") diff --git a/pkg/virt-launcher/virtwrap/manager_test.go b/pkg/virt-launcher/virtwrap/manager_test.go index 757ccece596a..b77a73bd2be5 100644 --- a/pkg/virt-launcher/virtwrap/manager_test.go +++ b/pkg/virt-launcher/virtwrap/manager_test.go @@ -259,21 +259,26 @@ var _ = Describe("Manager", func() { }) var _ = Describe("getSRIOVPCIAddresses", func() { - It("returns empty slice", func() { - Expect(len(getSRIOVPCIAddresses())).To(Equal(0)) + It("returns empty map when empty interfaces", func() { + Expect(len(getSRIOVPCIAddresses([]v1.Interface{}))).To(Equal(0)) + }) + It("returns empty map when variables are not set", func() { + Expect(len(getSRIOVPCIAddresses([]v1.Interface{v1.Interface{Name: "testnet"}}))).To(Equal(0)) }) It("gracefully handles trailing comma", func() { - os.Setenv("SRIOV-VF-PCI-ADDR", "0000:81:11.1,") - addrs := getSRIOVPCIAddresses() + os.Setenv("testnet_pool", "0000:81:11.1,") + os.Setenv("KUBEVIRT_RESOURCE_NAME_testnet", "testnet_pool") + addrs := getSRIOVPCIAddresses([]v1.Interface{v1.Interface{Name: "testnet"}}) Expect(len(addrs)).To(Equal(1)) - Expect(addrs[0]).To(Equal("0000:81:11.1")) + Expect(addrs["testnet"][0]).To(Equal("0000:81:11.1")) }) It("returns multiple PCI addresses", func() { - os.Setenv("SRIOV-VF-PCI-ADDR", "0000:81:11.1,0001:02:00.0") - addrs := getSRIOVPCIAddresses() - Expect(len(addrs)).To(Equal(2)) - Expect(addrs[0]).To(Equal("0000:81:11.1")) - Expect(addrs[1]).To(Equal("0001:02:00.0")) + os.Setenv("testnet_pool", "0000:81:11.1,0001:02:00.0") + os.Setenv("KUBEVIRT_RESOURCE_NAME_testnet", "testnet_pool") + addrs := getSRIOVPCIAddresses([]v1.Interface{v1.Interface{Name: "testnet"}}) + Expect(len(addrs["testnet"])).To(Equal(2)) + Expect(addrs["testnet"][0]).To(Equal("0000:81:11.1")) + Expect(addrs["testnet"][1]).To(Equal("0001:02:00.0")) }) }) diff --git a/tests/utils.go b/tests/utils.go index 6ee2e51ecf46..e73f86839eb6 100644 --- a/tests/utils.go +++ b/tests/utils.go @@ -1760,31 +1760,6 @@ func LoggedInFedoraExpecter(vmi *v1.VirtualMachineInstance) (expect.Expecter, er return expecter, err } -func LoggedInFedoraExpecter(vmi *v1.VirtualMachineInstance) (expect.Expecter, error) { - virtClient, err := kubecli.GetKubevirtClient() - PanicOnError(err) - expecter, _, err := NewConsoleExpecter(virtClient, vmi, 10*time.Second) - if err != nil { - return nil, err - } - b := append([]expect.Batcher{ - &expect.BSnd{S: "\n"}, - &expect.BSnd{S: "\n"}, - &expect.BExp{R: "login:"}, - &expect.BSnd{S: "fedora\n"}, - &expect.BExp{R: "Password:"}, - // yes, we assume the password here - &expect.BSnd{S: "fedora\n"}, - &expect.BExp{R: "\\$"}}) - res, err := expecter.ExpectBatch(b, 180*time.Second) - if err != nil { - log.DefaultLogger().Object(vmi).Infof("Login: %v", res) - expecter.Close() - return nil, err - } - return expecter, err -} - type VMIExpecterFactory func(*v1.VirtualMachineInstance) (expect.Expecter, error) func NewVirtctlCommand(args ...string) *cobra.Command {