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

Deviceinfo downwardAPI #11618

Merged
merged 7 commits into from
Apr 18, 2024
Merged

Conversation

AlonaKaplan
Copy link
Member

@AlonaKaplan AlonaKaplan commented Mar 28, 2024

What this PR does

In some cases the multus status annotation contains device-info.
This info may be valuable for the binding plugin sidecar. For example SRIOV and VDPA plugins need this info.
As the multus status annotation is not there yet on virt-laucher pod creation, the suggested solution is to use downwardAPI volume and mount it to the sidecar.
The downwardAPI volume will track a new virt-launcher pod annotation kubevirt.io/network-info that will contain a list of interfaces with their device-info.
Very similar to the existing SRIOV downwardAPI solution which is mapping the networkName to the pci-address of the device and mounting the downwardAPI volume to the virt-launcher pod.

To enable the downwardAPI for a network binding plugin, it should have downwardAPI: device-info attribute.

apiVersion: kubevirt.io/v1
kind: KubeVirt
metadata:
  name: kubevirt
  namespace: kubevirt
spec:
  configuration:
    network:
      binding:
        vdpa:
          sidecarImage: quay.io/kubevirt/network-vdpa-binding
          downwardAPI: device-info

Before this PR:

  • Interfaces device info was exposed to the virt-launcher compute container via environment variables by the device plugin.
    The compute container could use those env var to construct the libvirt domain xml.
  • A downward API is used to expose network to PCI map for SRIOV interfaces.

After this PR:

  • A downward API is used to expose interfaces device info to virt-launcher hook sidecars.

Follow-up - use the new network to device info map for SRIOV interfaces instead of the network to PCI map,

Fixes #

Why we need it and why it was done in this way

The following tradeoffs were made:

The following alternatives were considered:
alternatives -

  • Instead of configuring in the kubevirt CR whether deviceInfo should be mounted to the sidecar or not we can always mount it in case it exists in the status.
  • Share via the downwardAPI only the deviceInfo of binding that request it. It can be controlled by the binding config in the kubivirt CR (e.g “deviceInfo: true/false” attribute).
  • Passing PCIDEVICE_<RESOURCE_NAME>_NET_INFO env var from the virt-launcher to the sidecar by adding it as an annotation to the vmi object passed to the onDefineDomain method. The problem with this approach is that it will reintroduce the same bug SRIOV that the SRIOV downward API solved.
  • Sharing CNIDeviceInfo file with the sidecar. It is a very similar approach to the downwardAPI option, the * disadvantage here is that the user would have to specify the file path in the NAD.
    More alternatives to a similar issue are listed in the SRIOV mapping design doc.

Links to places where the discussion took place:
design proposal

TBD: covert the to design to markdown and add it to the kubevirt/community design proposals.

Special notes for your reviewer

Checklist

This checklist is not enforcing, but it's a reminder of items that could be relevant to every PR.
Approvers are expected to review this list.

Release note

Extend network binding plugin to support device-info DownwardAPI.

@kubevirt-bot
Copy link
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@kubevirt-bot kubevirt-bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. dco-signoff: yes Indicates the PR's author has DCO signed all their commits. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. size/XL labels Mar 28, 2024
@kubevirt-bot kubevirt-bot added sig/buildsystem Denotes an issue or PR that relates to changes in the build system. sig/network labels Mar 28, 2024
@AlonaKaplan
Copy link
Member Author

/test pull-kubevirt-build
/test pull-kubevirt-build-arm64
/test pull-kubevirt-generate
/test pull-kubevirt-manifests
/test pull-kubevirt-unit-test
/test pull-kubevirt-e2e-k8s-1.27-sig-network

)

const (
MountPath = "/etc/podinfo"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would store the new device-info annotation in different dir then the sriov-network-pci one.
IMO it will be easier to debug that way, also avoid potential overwrites, if tomorrow someone changes the downward API volumes render code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is under the deviceinfo dir. What place do you find better than this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path will be /etc/podinfo/deviceinfo?

I though about saving it in its own dir /etc/net-device-info

booOrdinalIfaceName = "net2"
)

networkStatusWithMixedNetworksFmt := `
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: adding some indentations could make it easier to read.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it will work with indentations. Can you please give an example of what you suggest?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the following examples, if the assertions fails you can use MatchJSON matcher:

networkStatusWithMixedNetworksFmt := `[
	{
		"name": "kindnet",
		"interface": "eth0",
		"ips": [
		  "10.244.1.9"
		],
		"mac": "3a:7e:42:fa:37:c6",
		"default": true,
		"dns": {}
	},
	{
		"name": "default/nad1",
		"interface": "%s",
		"mac": "8a:37:d9:e7:0f:18",
		"dns": {}
	},
	{
		"name": "default/nad2",
		"interface": "%s",
		"dns": {},
		"device-info": {
		  "type": "pci",
		  "version": "1.0.0",
		  "pci": {
			"pci-address": "0000:65:00.2"
		  }
		}
	}
]`

networkStatusWithPrimaryInterfaceOnly := `[{
	"name": "kindnet",
	"interface": "eth0",
	"ips": [
	  "10.244.2.131"
	],
	"mac": "82:cf:7c:98:43:7e",
	"default": true,
	"dns": {}
  }]`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

pkg/network/deviceinfo/sriov_test.go Show resolved Hide resolved
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2022 Red Hat, Inc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recently I got review comment that it should be The Kubevirt Authors instead of Red Hat, Inc.
Nevertheless, some files in the code base have the first and other has the latter, I guess it should be aligned soon to The Kubevirt Authors.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

),
)

DescribeTable("should prepare non empty network device info annotation",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that only the expected annotation varies between the tests in this table, specifically the pod interface names, hashed vs. ordinal.
Please consider simplifying it by passing only the expected annotation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

pkg/network/deviceinfo/sriov.go Outdated Show resolved Hide resolved
@@ -31,7 +31,8 @@ import (
)

const (
MountPath = "/etc/podinfo"
NetworkDeviceInfoMapAnnot = "kubevirt.io/network-device-info-map"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider removing map prefix, IMO it doesn't add value.
Also if tomorrow we decide to change the annotation value format (to not be map) the name would no longer be valid.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we change the annotation value not to be a map it is API change. virt-launcher containers assume it is a map.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a nice argue about having info about the value of a variable in its name
kubevirt/community#281 (comment)

]`

Context("Generate pod network annotations", func() {
It("should have empty device info annotation when there are no networks", func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider using the "equal" matcher instead of (haveLen + haveKeyWithValue), it can provide better visibility for expected results.

In general it can be easier to maintain: in case there it require adding another annotation, you could change the expected annotation slice instead adding new assert and incrementing the haveLen assert.

@kubevirt-bot kubevirt-bot added kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API release-note Denotes a PR that will be considered when it comes time to generate release notes. and removed do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. labels Mar 31, 2024
@AlonaKaplan
Copy link
Member Author

/test pull-kubevirt-build
/test pull-kubevirt-build-arm64
/test pull-kubevirt-generate
/test pull-kubevirt-manifests
/test pull-kubevirt-unit-test
/test pull-kubevirt-e2e-k8s-1.27-sig-network

@AlonaKaplan
Copy link
Member Author

/test pull-kubevirt-build
/test pull-kubevirt-build-arm64
/test pull-kubevirt-generate
/test pull-kubevirt-manifests
/test pull-kubevirt-unit-test
/test pull-kubevirt-e2e-k8s-1.27-sig-network

Copy link
Contributor

@ormergi ormergi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes, second round

@@ -2777,6 +2777,10 @@ type InterfaceBindingPlugin struct {
// Migration means the VM using the plugin can be safely migrated
// version: 1alphav1
Migration *InterfaceBindingMigration `json:"migration,omitempty"`
// DownwardAPI specifies what kind of data should be exposed to the binding plugin sidecar.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// DownwardAPI specifies what kind of data should be exposed to the binding plugin sidecar.
// DownwardAPI specifies what kind of data should be exposed to the binding plugin sidecar using Kubernetes Downward API
https://kubernetes.io/docs/concepts/workloads/pods/downward-api/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should I mention here what method is used to implement it. It is an implementation detail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well this impl. detail is well exposed by the downwardAPI field name :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name downward API means we want to expose to the container some of the pod fields.
It doesn't expose how we implement it and whether we use a k8s volume or some other method (e.g env vars).

Reference - https://kubernetes.io/docs/concepts/workloads/pods/downward-api/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we need to think of better name then DownwardAPI because this name imply k8s downward API feature is being used, which is an impl. detail according to you.

I dont have better name to suggest though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think the the name is a trade-mark.
It sounds a reasonable name to me, taking API data "down".

If there is no better name to suggest, continuing this thread is not useful.

@@ -2777,6 +2777,10 @@ type InterfaceBindingPlugin struct {
// Migration means the VM using the plugin can be safely migrated
// version: 1alphav1
Migration *InterfaceBindingMigration `json:"migration,omitempty"`
// DownwardAPI specifies what kind of data should be exposed to the binding plugin sidecar.
// Supported values: "DeviceInfo"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be device-info?
If so, please note there are other places it should be changed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

)

const (
MountPath = "/etc/podinfo"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path will be /etc/podinfo/deviceinfo?

I though about saving it in its own dir /etc/net-device-info

booOrdinalIfaceName = "net2"
)

networkStatusWithMixedNetworksFmt := `
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the following examples, if the assertions fails you can use MatchJSON matcher:

networkStatusWithMixedNetworksFmt := `[
	{
		"name": "kindnet",
		"interface": "eth0",
		"ips": [
		  "10.244.1.9"
		],
		"mac": "3a:7e:42:fa:37:c6",
		"default": true,
		"dns": {}
	},
	{
		"name": "default/nad1",
		"interface": "%s",
		"mac": "8a:37:d9:e7:0f:18",
		"dns": {}
	},
	{
		"name": "default/nad2",
		"interface": "%s",
		"dns": {},
		"device-info": {
		  "type": "pci",
		  "version": "1.0.0",
		  "pci": {
			"pci-address": "0000:65:00.2"
		  }
		}
	}
]`

networkStatusWithPrimaryInterfaceOnly := `[{
	"name": "kindnet",
	"interface": "eth0",
	"ips": [
	  "10.244.2.131"
	],
	"mac": "82:cf:7c:98:43:7e",
	"default": true,
	"dns": {}
  }]`


func CreateNetworkDeviceInfoAnnotationValue(networks []v1.Network, interfaces []v1.Interface, networkStatusAnnotationValue string, bindingPlugins map[string]v1.InterfaceBindingPlugin) string {
networkDeviceInfoMap, err := mapNetworkNameToDeviceInfo(networks, networkStatusAnnotationValue, func() []v1.Interface {
return filterDeviceInfoBindingPluginEntries(interfaces, bindingPlugins)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider simplifying mapNetworkNameToDeviceInfo by passing interfaces slice instead of a lambda.
Also we can use vmispec.FilterInterfacesSpec for filtering:

vmispec.FilterInterfacesSpec(interfaces, func(iface v1.Interface) bool {
    if iface.Binding != nil {
        plugin, exist := bindingPlugins[iface.Binding.Name]
        return exist && binding.DownwardAPI == v1.DeviceInfo
    }
})

Comment on lines 41 to 50
if err != nil {
log.Log.Warningf("failed to create network-device-info-map: %v", err)
networkDeviceInfoMap = map[string]*networkv1.DeviceInfo{}
}

networkDeviceInfoMapBytes, err := json.Marshal(networkDeviceInfoMap)
if err != nil {
log.Log.Warningf("failed to marshal network-device-info-map: %v", err)
return ""
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not add the annotation at all if it has empty value, having it with empty value is equivalent to no exist at all.

@@ -31,7 +31,8 @@ import (
)

const (
MountPath = "/etc/podinfo"
NetworkDeviceInfoMapAnnot = "kubevirt.io/network-device-info-map"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a nice argue about having info about the value of a variable in its name
kubevirt/community#281 (comment)

@@ -171,3 +171,14 @@ func FilterInterfacesByNetworks(interfaces []v1.Interface, networks []v1.Network
}
return ifaces
}

func BindingPluginNetworkWithDeviceInfoExist(ifaces []v1.Interface, bindingPlugins map[string]v1.InterfaceBindingPlugin) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would call it NetworkBindingPluginRequireDeviceInfo

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the original name more explicit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BindingPluginNetwork confusing it should be NetworkBindingPlugin as the feature name IMO.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is mot checking whether there is NetworkBindingPlugin with device info (in the kubevirt CR). It checks whether a VM has a network using this kind of BindingPlugin. Therefore, I find the current name as more suitable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I understand what it means, maybe mentioning interface fit better a because the function receives interfaces slice.
Although, we already know the API suffer from the network/interface naming semantics, so maybe network can still work.

Still a nit, not a must.

nonDeviceInfoPlugin: {},
}

It("returns false when there is no network with device onfo plugin", func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/onfo/info/g

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -28,6 +28,8 @@ import (
"strconv"
"strings"

"kubevirt.io/kubevirt/pkg/network/deviceinfo"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to remain at the same place

@AlonaKaplan AlonaKaplan force-pushed the deviceinfo_downwardapi branch 2 times, most recently from cbf267a to b5e666a Compare April 3, 2024 15:15
@AlonaKaplan AlonaKaplan marked this pull request as ready for review April 4, 2024 07:24
@kubevirt-bot kubevirt-bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 4, 2024
Copy link
Member

@EdDev EdDev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review.

@@ -2777,6 +2777,10 @@ type InterfaceBindingPlugin struct {
// Migration means the VM using the plugin can be safely migrated
// version: 1alphav1
Migration *InterfaceBindingMigration `json:"migration,omitempty"`
// DownwardAPI specifies what kind of data should be exposed to the binding plugin sidecar.
// Supported values: "device-info"
// version: 1alphav1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be v1alphav1.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. We need a separate PR to change all the occurrences.

staging/src/kubevirt.io/api/core/v1/types.go Show resolved Hide resolved
pkg/network/downwardapi/types.go Show resolved Hide resolved
pkg/network/deviceinfo/deviceinfo.go Show resolved Hide resolved

func mapNetworkNameToDeviceInfo(networks []v1.Network,
networkStatusAnnotationValue string, filterMethod func() []v1.Interface) (map[string]*networkv1.DeviceInfo, error) {
multusInterfaceNameToNetworkStatusMap, err := mapMultusInterfaceNameToNetworkStatus(networkStatusAnnotationValue)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: You can drop the Map, it is ugly. Same for networkDeviceInfoMap.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines 54 to 56
return mapNetworkNameToDeviceInfo(networks, networkStatusAnnotationValue, func() []v1.Interface {
return filterDeviceInfoBindingPluginEntries(interfaces, bindingPlugins)
})
Copy link
Member

@EdDev EdDev Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if it is something I missed, but this seems over complicated.
Why not pass the filtered interface instead of the function?

Will this work?

return mapNetworkNameToDeviceInfo(networks, networkStatusAnnotationValue, vmispec.FilterInterfacesSpec(interfaces, func(iface v1.Interface) bool {
    if iface.Binding != nil {
	if binding, exist := bindingPlugins[iface.Binding.Name]; exist && binding.DownwardAPI == v1.DeviceInfo {
	    return true
	}
    }
    return false
})

You can even consider making this an object, as a large number of data is passed around.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

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

func TestDeviceInfo(t *testing.T) {
testutils.KubeVirtTestSuiteSetup(t)
}

func newBridgeInterface(name string) virtv1.Interface {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use libvmi for this one and all the rest?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, much nicer:)

Define the API for passing network info to virt-launcher containers
using DownwardAPI volume.

Signed-off-by: Alona Paz <alkaplan@redhat.com>
Copy link
Member

@EdDev EdDev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the 1st 5 commits.

pkg/network/deviceinfo/deviceinfo.go Show resolved Hide resolved
func MapBindingPluginNetworkNameToDeviceInfo(networks []v1.Network, interfaces []v1.Interface,
networkStatusAnnotationValue string, bindingPlugins map[string]v1.InterfaceBindingPlugin) (map[string]*networkv1.DeviceInfo, error) {
return mapNetworkNameToDeviceInfo(networks, networkStatusAnnotationValue,
filterDeviceInfoBindingPluginEntries(interfaces, bindingPlugins))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason you have not used vmispec.FilterInterfacesSpec as suggested previously?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines 50 to 54
networkStatusEntry, exist := multusInterfaceNameToNetworkStatus[multusInterfaceName]
if !exist {
continue // The interface is not plugged yet
}
networkDeviceInfo[iface.Name] = networkStatusEntry.DeviceInfo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find continue messy.
Perhaps you will agree to a direct approach:

if networkStatusEntry, exist := multusInterfaceNameToNetworkStatus[multusInterfaceName]; exist {
    networkDeviceInfo[iface.Name] = networkStatusEntry.DeviceInfo
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

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

virtv1 "kubevirt.io/api/core/v1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This is "our" API, I think it can be a simple v1.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines 106 to 107
networkDeviceInfoMap, err :=
deviceinfo.MapBindingPluginNetworkNameToDeviceInfo(networkList, interfaceList, networkStatusAnnotationValue, bindingPlugins)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not familiar with this style, usually we do:

Expect(deviceinfo.MapBindingPluginNetworkNameToDeviceInfo(
    networkList,
    interfaceList,
    networkStatusAnnotationValue,
    bindingPlugins,
)).To(BeEmpty())

The error is implicitly checked.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still use additional variable in between (networkDeviceInfoMap), just not break the line right at the assignment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines +34 to +42
log.Log.Warningf("failed to marshal network-info: %v", err)
return ""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to ignore the error so deep here? Why not let the caller decide if to ignore it or not?

If you can explain what is the reason for this in the commit message, it will be great.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior was copied from SRIOV to keep consistency.
Updated the commit message.

Comment on lines +46 to +54
networkInfo := NetworkInfo{Interfaces: downwardAPIInterfaces}
return networkInfo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: return NetworkInfo{Interfaces: downwardAPIInterfaces}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

}

func generateNetworkInfo(networkDeviceInfoMap map[string]*networkv1.DeviceInfo) NetworkInfo {
downwardAPIInterfaces := []Interface{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will return an empty list and not a nil one.
Any reason it is not a simple uninitialized slice? var downwardAPIInterfaces []Interface

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines +24 to +26
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I can you keep these separated from the above one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

expectedNetworkInfo := `{"interfaces":[{"network":"boo"},{"network":"foo","deviceInfo":{"type":"fooType"}}]}`
Expect(downwardapi.CreateNetworkInfoAnnotationValue(networkDeviceInfoMap)).To(Equal(expectedNetworkInfo))
})

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consider:

  • An empty list one.
  • One with an error in the marshaling (if possible).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An empty list one.

Done.

One with an error in the marshaling.

I don't know how to simulate it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marshaling errors results in empty annotation value.

vmi.Spec.Networks, vmi.Spec.Domain.Devices.Interfaces, pod.Annotations[networkv1.NetworkStatusAnnot],
)
newAnnotations := map[string]string{deviceinfo.NetworkPCIMapAnnot: networkPCIMapAnnotationValue}
newAnnotations := network.GeneratePodAnnotations(vmi.Spec.Networks, vmi.Spec.Domain.Devices.Interfaces, pod.Annotations[networkv1.NetworkStatusAnnot], c.clusterConfig.GetNetworkBindings())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note line 1282, there is already code that update multus network annotation (c.updateMultus, it also calls syncAnnotations) and patches the pod.
Is it possible to consolidate these two pieces of code so there will be one patch operation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not in the scope of this big enough PR.

Comment on lines 122 to 151
func newMultusNetwork(name, networkName string) virtv1.Network {
return virtv1.Network{
Name: name,
NetworkSource: virtv1.NetworkSource{
Multus: &virtv1.MultusNetwork{
NetworkName: networkName,
},
},
}
}

func newInterface(name string) virtv1.Interface {
return virtv1.Interface{
Name: name,
}
}

func newSRIOVInterface(name string) virtv1.Interface {
return virtv1.Interface{
Name: name,
InterfaceBindingMethod: virtv1.InterfaceBindingMethod{SRIOV: &virtv1.InterfaceSRIOV{}},
}
}

func newBindingPluginInterface(name, bindingPlugin string) virtv1.Interface {
return virtv1.Interface{
Name: name,
Binding: &virtv1.PluginBinding{Name: bindingPlugin},
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Myabe you can replace those with libvmi as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

),
)

It("should prepare network device info annotation with multiple networks", func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
It("should prepare network device info annotation with multiple networks", func() {
It("should prepare network to device-info mapping given multiple networks", func() {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is fine as is, the suggestion is not better than the existing. And it is a super nit.

Copy link
Contributor

@ormergi ormergi Apr 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep not a must, sorry for not mentioning it a nit and not meaningful

newBindingPluginInterface("foo", deviceInfoPlugin),
newBindingPluginInterface("doo", deviceInfoPlugin),
}
expectedMap :=
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
expectedMap :=
expectedMap := map[string]*networkv1.DeviceInfo{
"foo": {Type: "pci", Version: "1.0.0", Pci: &networkv1.PciDevice{PciAddress: "0000:65:00.2"}}
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

pkg/network/deviceinfo/sriov.go Show resolved Hide resolved
@@ -171,3 +171,14 @@ func FilterInterfacesByNetworks(interfaces []v1.Interface, networks []v1.Network
}
return ifaces
}

func BindingPluginNetworkWithDeviceInfoExist(ifaces []v1.Interface, bindingPlugins map[string]v1.InterfaceBindingPlugin) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BindingPluginNetwork confusing it should be NetworkBindingPlugin as the feature name IMO.

ifaces := []v1.Interface{interfaceWithBindingPlugin("net1", nonDeviceInfoPlugin)}
Expect(netvmispec.BindingPluginNetworkWithDeviceInfoExist(ifaces, bindingPlugins)).To(BeFalse())
})
It("returns true when there is no network with device info plugin", func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
It("returns true when there is no network with device info plugin", func() {
It("returns true when there is at least one network with device-info plugin", func() {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, thanks.

Comment on lines 29 to 61
networkStatus := `
[
{
"name": "default/no-device-info",
"interface": "pod6446d58d6df",
"mac": "8a:37:d9:e7:0f:18",
"dns": {}
},
{
"name": "default/with-device-info",
"interface": "pod2c26b46b68f",
"dns": {},
"device-info": {
"type": "pci",
"version": "1.0.0",
"pci": {
"pci-address": "0000:65:00.2"
}
}
},
{
"name": "default/sriov",
"interface": "pod778c553efa0",
"dns": {},
"device-info": {
"type": "pci",
"version": "1.0.0",
"pci": {
"pci-address": "0000:65:00.3"
}
}
}
]`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: indentation would be nice, also it seems it can defined as const

networkStatus := `[
      {
        "name": "default/no-device-info",
        "interface": "pod6446d58d6df",
        "mac": "8a:37:d9:e7:0f:18",
        "dns": {}
      },
      {
        "name": "default/with-device-info",
        "interface": "pod2c26b46b68f",
        "dns": {},
        "device-info": {
          "type": "pci",
          "version": "1.0.0",
          "pci": {
            "pci-address": "0000:65:00.2"
          }
        }
      },
      {
        "name": "default/sriov",
        "interface": "pod778c553efa0",
        "dns": {},
        "device-info": {
          "type": "pci",
          "version": "1.0.0",
          "pci": {
            "pci-address": "0000:65:00.3"
          }
        }
      }
  ]`

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines 101 to 117
Expect(podAnnotationMap).To(HaveLen(1))
Expect(podAnnotationMap).To(HaveKeyWithValue(downwardapi.NetworkInfoAnnot, `{"interfaces":[{"network":"foo","deviceInfo":{"type":"pci","version":"1.0.0","pci":{"pci-address":"0000:65:00.2"}}}]}`))
})
It("should have both network-pci-map, network-info entries when there is SRIOV interface and binding plugin interface with device-info", func() {
networks := []virtv1.Network{
newMultusNetwork("foo", "default/with-device-info"),
newMultusNetwork("doo", "default/sriov"),
}

interfaces := []virtv1.Interface{
newBindingPluginInterface("foo", deviceInfoPlugin),
newSRIOVInterface("doo"),
}
podAnnotationMap := network.GeneratePodAnnotations(networks, interfaces, networkStatus, bindingPlugins)
Expect(podAnnotationMap).To(HaveLen(2))
Expect(podAnnotationMap).To(HaveKeyWithValue(downwardapi.NetworkInfoAnnot, `{"interfaces":[{"network":"foo","deviceInfo":{"type":"pci","version":"1.0.0","pci":{"pci-address":"0000:65:00.2"}}}]}`))
Expect(podAnnotationMap).To(HaveKeyWithValue(deviceinfo.NetworkPCIMapAnnot, `{"doo":"0000:65:00.3"}`))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Using equal matcher would improve visibility of expected results.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -2777,6 +2777,10 @@ type InterfaceBindingPlugin struct {
// Migration means the VM using the plugin can be safely migrated
// version: 1alphav1
Migration *InterfaceBindingMigration `json:"migration,omitempty"`
// DownwardAPI specifies what kind of data should be exposed to the binding plugin sidecar.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think the the name is a trade-mark.
It sounds a reasonable name to me, taking API data "down".

If there is no better name to suggest, continuing this thread is not useful.

),
)

It("should prepare network device info annotation with multiple networks", func() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is fine as is, the suggestion is not better than the existing. And it is a super nit.

Comment on lines 142 to 144
actualMap, err := deviceinfo.MapBindingPluginNetworkNameToDeviceInfo(networks, interfaces, networkStatusWithMixedNetworks, bindingPlugins)
Expect(err).NotTo(HaveOccurred())
Expect(actualMap).To(Equal(expectedMap))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Expect(deviceinfo.MapBindingPluginNetworkNameToDeviceInfo(networks, interfaces, networkStatusWithMixedNetworks, bindingPlugins)).To(Equal(expectedMap))

The error is implicitly checked.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

pkg/network/deviceinfo/sriov.go Show resolved Hide resolved
Comment on lines 68 to 69
for _, sriovIface := range vmispec.FilterSRIOVInterfaces(interfaces) {
multusInterfaceName := networkNameScheme[sriovIface.Name]
networkStatusEntry, exist := multusInterfaceNameToNetworkStatusMap[multusInterfaceName]
deviceInfo, exist := networkNameToDeviceInfo[sriovIface.Name]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In continuation or Or's comment a few lines before.
I think you can do this now:

for netName, deviceInfo := range networkNameToDeviceInfo {
    ...
}

Wherever sriovIface.Name appears, netName can be used.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Copy link
Member

@EdDev EdDev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6th commit review

newAnnotations[deviceinfo.NetworkPCIMapAnnot] = networkPCIMapAnnotationValue
}
if vmispec.BindingPluginNetworkWithDeviceInfoExist(interfaces, bindingPlugins) {
networkDeviceInfoMap, err := deviceinfo.MapBindingPluginNetworkNameToDeviceInfo(networks, interfaces, multusStatusAnnotation, bindingPlugins)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the only production usage of this function, therefore it may be a good idea to reconsider its signature.
There is duplication with BindingPluginNetworkWithDeviceInfoExist and I hope we can avoid adding that other function.

How about this: The bindingPlugins is only needed for the filtering, both here and in the prev one.
So we can pass in an already filtered interfaces slice.

ifacesWithDeviceInfo := vmispec.FilterInterfacesSpec(interfaces, func(iface v1.Interface) bool {
    if iface.Binding != nil {
        if binding, exist := bindingPlugins[iface.Binding.Name]; exist && binding.DownwardAPI == v1.DeviceInfo {
            return true
        }
    }
    return false
})
if len(ifacesWithDeviceInfo) > 0 {
    networkDeviceInfoMap, err := deviceinfo.MapBindingPluginNetworkNameToDeviceInfo(networks, ifacesWithDeviceInfo, multusStatusAnnotation)
    if err != nil {
        ...
    }
    ...
}
return newAnnotations

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method has one more usage in the next commit. Also, I find it much more readable and telling the story of the method the way it is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the only readability problem is with binding, exist := bindingPlugins[iface.Binding.Name]; exist && binding.DownwardAPI == v1.DeviceInfo , which you could extract to a function if you want.

If the problem is re-use, you can also just move the filter to vmispec and name it.

vmispec.FilterInterfacesSpec(interfaces, func(iface v1.Interface) bool {
    if iface.Binding != nil {
        if binding, exist := bindingPlugins[iface.Binding.Name]; exist && binding.DownwardAPI == v1.DeviceInfo {
            return true
        }
    }
    return false
})

We will benefit here from the filtered interfaces slice.

I find the current helper in vmispec not elegant.

But if you do not like it, I won't insist further.

@@ -679,6 +680,9 @@ func newSidecarContainerRenderer(sidecarName string, vmiSpec *v1.VirtualMachineI

var mounts []k8sv1.VolumeMount
mounts = append(mounts, sidecarVolumeMount())
if requestedHookSidecar.DownwardAPI == v1.DeviceInfo {
mounts = append(mounts, mountPath(downwardapi.NetworkInfoVolumeName, downwardapi.MountPath))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Exposing the consts, it may have been better to just return from downwardapi package the k8sv1.VolumeMount. The advantage would have been that no one will misuse the consts.
WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current option is more readable and the consts (at least downwardapi.NetworkInfoVolumeName) are used on volume creation as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unsafe to use consts like this, because they may be used in the wrong place.
Returning the object itself would have been safer over exposing the consts.

I will not insist on this.

Args []string `json:"args,omitempty"`
ConfigMap *ConfigMap `json:"configMap,omitempty"`
PVC *PVC `json:"pvc,omitempty"`
DownwardAPI v1.NetworkBindingDownwardAPIType `json:"downwardAPI,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that when you add it here, it is exposed to the sidecar-hook annotation [1].

[1]

hooks.HookSidecarListAnnotationName: `
[
{
"image": "some-image:v1",
"imagePullPolicy": "IfNotPresent"
},
{
"image": "another-image:v1",
"imagePullPolicy": "Always"
}
]
`,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to json:"-" so the DownwardAPI value won't be unmarshaled from the annotation.

Create a map between networks and their device info using the mutlus
status annotation.

Since a similar map between the network to PCI address is already created
for the SRIOV use case, the SRIOV code was changed to reuse the device
info general methods.

Signed-off-by: Alona Paz <alkaplan@redhat.com>
The VMI controller can use this method when generating the pod
annotations. The method doesn't retrun an error in case of marshaling
failure, so the VMI controller using it will just get an empty annotation.
If evetually the annotation won't be created, a follow-up step will fail,
there is no need to corrupt the vmi controller.


Signed-off-by: Alona Paz <alkaplan@redhat.com>
The annotation should appear only on pods with binding plugin network that
has DeviceInfo downwardAPI.

Signed-off-by: Alona Paz <alkaplan@redhat.com>
Expose network-info annotation using downward API dir volume.
Mount this volume to the hook sidecars.

Signed-off-by: Alona Paz <alkaplan@redhat.com>
Copy link
Member

@EdDev EdDev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

I don't like the addition added to vmispec, but it is not worth blocking this great work.

We should wait for the design to be merged before taking this one in.
Hope we can resolve the current discussion there soon.

Comment on lines +46 to +54
networkInfo := NetworkInfo{Interfaces: downwardAPIInterfaces}
return networkInfo
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

newAnnotations[deviceinfo.NetworkPCIMapAnnot] = networkPCIMapAnnotationValue
}
if vmispec.BindingPluginNetworkWithDeviceInfoExist(interfaces, bindingPlugins) {
networkDeviceInfoMap, err := deviceinfo.MapBindingPluginNetworkNameToDeviceInfo(networks, interfaces, multusStatusAnnotation, bindingPlugins)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the only readability problem is with binding, exist := bindingPlugins[iface.Binding.Name]; exist && binding.DownwardAPI == v1.DeviceInfo , which you could extract to a function if you want.

If the problem is re-use, you can also just move the filter to vmispec and name it.

vmispec.FilterInterfacesSpec(interfaces, func(iface v1.Interface) bool {
    if iface.Binding != nil {
        if binding, exist := bindingPlugins[iface.Binding.Name]; exist && binding.DownwardAPI == v1.DeviceInfo {
            return true
        }
    }
    return false
})

We will benefit here from the filtered interfaces slice.

I find the current helper in vmispec not elegant.

But if you do not like it, I won't insist further.

@@ -679,6 +680,9 @@ func newSidecarContainerRenderer(sidecarName string, vmiSpec *v1.VirtualMachineI

var mounts []k8sv1.VolumeMount
mounts = append(mounts, sidecarVolumeMount())
if requestedHookSidecar.DownwardAPI == v1.DeviceInfo {
mounts = append(mounts, mountPath(downwardapi.NetworkInfoVolumeName, downwardapi.MountPath))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unsafe to use consts like this, because they may be used in the wrong place.
Returning the object itself would have been safer over exposing the consts.

I will not insist on this.

@kubevirt-bot kubevirt-bot added the lgtm Indicates that a PR is ready to be merged. label Apr 9, 2024
Copy link
Contributor

@ormergi ormergi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, awesome work

@lmilleri
Copy link

I have tested this PR content in my vdpa environment and it works as expected, thank you!
This is an example of the file mounted in the vdpa sidecar plugin:

{ "interfaces": [ { "network": "nic1", "deviceInfo": { "type": "vdpa", "version": "1.1.0", "vdpa": { "parent-device": "vdpa:0000:65:00.2", "driver": "vhost", "path": "/dev/vhost-vdpa-0", "pci-address": "0000:65:00.2" } } } ] }

@AlonaKaplan
Copy link
Member Author

Raising 2 lgtms to approve.

/approve

@kubevirt-bot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: AlonaKaplan

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubevirt-bot kubevirt-bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 17, 2024
@kubevirt-commenter-bot
Copy link

Required labels detected, running phase 2 presubmits:
/test pull-kubevirt-e2e-windows2016
/test pull-kubevirt-e2e-kind-1.27-vgpu
/test pull-kubevirt-e2e-kind-sriov
/test pull-kubevirt-e2e-k8s-1.29-ipv6-sig-network
/test pull-kubevirt-e2e-k8s-1.27-sig-network
/test pull-kubevirt-e2e-k8s-1.27-sig-storage
/test pull-kubevirt-e2e-k8s-1.27-sig-compute
/test pull-kubevirt-e2e-k8s-1.27-sig-operator
/test pull-kubevirt-e2e-k8s-1.28-sig-network
/test pull-kubevirt-e2e-k8s-1.28-sig-storage
/test pull-kubevirt-e2e-k8s-1.28-sig-compute
/test pull-kubevirt-e2e-k8s-1.28-sig-operator

@kubevirt-commenter-bot
Copy link

/retest-required
This bot automatically retries required jobs that failed/flaked on approved PRs.
Silence the bot with an /lgtm cancel or /hold comment for consistent failures.

@kubevirt-bot kubevirt-bot merged commit 895075a into kubevirt:main Apr 18, 2024
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. dco-signoff: yes Indicates the PR's author has DCO signed all their commits. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API lgtm Indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/buildsystem Denotes an issue or PR that relates to changes in the build system. sig/network size/XXL
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants