-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Deviceinfo downwardAPI #11618
Conversation
Skipping CI for Draft Pull Request. |
/test pull-kubevirt-build |
38a493b
to
cd49eab
Compare
pkg/network/deviceinfo/deviceinfo.go
Outdated
) | ||
|
||
const ( | ||
MountPath = "/etc/podinfo" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 := ` |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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": {}
}]`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
pkg/network/deviceinfo/deviceinfo.go
Outdated
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
* Copyright 2022 Red Hat, Inc. |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
pkg/network/deviceinfo/deviceinfo.go
Outdated
@@ -31,7 +31,8 @@ import ( | |||
) | |||
|
|||
const ( | |||
MountPath = "/etc/podinfo" | |||
NetworkDeviceInfoMapAnnot = "kubevirt.io/network-device-info-map" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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.
cd49eab
to
4803c83
Compare
/test pull-kubevirt-build |
4803c83
to
fde2363
Compare
/test pull-kubevirt-build |
fde2363
to
d188b9a
Compare
There was a problem hiding this 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// 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/ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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/
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
pkg/network/deviceinfo/deviceinfo.go
Outdated
) | ||
|
||
const ( | ||
MountPath = "/etc/podinfo" |
There was a problem hiding this comment.
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 := ` |
There was a problem hiding this comment.
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": {}
}]`
pkg/network/deviceinfo/deviceinfo.go
Outdated
|
||
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) |
There was a problem hiding this comment.
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
}
})
pkg/network/deviceinfo/deviceinfo.go
Outdated
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 "" | ||
} |
There was a problem hiding this comment.
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.
pkg/network/deviceinfo/deviceinfo.go
Outdated
@@ -31,7 +31,8 @@ import ( | |||
) | |||
|
|||
const ( | |||
MountPath = "/etc/podinfo" | |||
NetworkDeviceInfoMapAnnot = "kubevirt.io/network-device-info-map" |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/onfo/info/g
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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
cbf267a
to
b5e666a
Compare
b5e666a
to
802f483
Compare
There was a problem hiding this 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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
pkg/network/deviceinfo/deviceinfo.go
Outdated
|
||
func mapNetworkNameToDeviceInfo(networks []v1.Network, | ||
networkStatusAnnotationValue string, filterMethod func() []v1.Interface) (map[string]*networkv1.DeviceInfo, error) { | ||
multusInterfaceNameToNetworkStatusMap, err := mapMultusInterfaceNameToNetworkStatus(networkStatusAnnotationValue) |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
pkg/network/deviceinfo/deviceinfo.go
Outdated
return mapNetworkNameToDeviceInfo(networks, networkStatusAnnotationValue, func() []v1.Interface { | ||
return filterDeviceInfoBindingPluginEntries(interfaces, bindingPlugins) | ||
}) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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>
802f483
to
4363b69
Compare
There was a problem hiding this 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
Outdated
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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
pkg/network/deviceinfo/deviceinfo.go
Outdated
networkStatusEntry, exist := multusInterfaceNameToNetworkStatus[multusInterfaceName] | ||
if !exist { | ||
continue // The interface is not plugged yet | ||
} | ||
networkDeviceInfo[iface.Name] = networkStatusEntry.DeviceInfo |
There was a problem hiding this comment.
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
}
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
networkDeviceInfoMap, err := | ||
deviceinfo.MapBindingPluginNetworkNameToDeviceInfo(networkList, interfaceList, networkStatusAnnotationValue, bindingPlugins) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
log.Log.Warningf("failed to marshal network-info: %v", err) | ||
return "" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
networkInfo := NetworkInfo{Interfaces: downwardAPIInterfaces} | ||
return networkInfo |
There was a problem hiding this comment.
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}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
There was a problem hiding this comment.
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{} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)) | ||
}) | ||
|
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
4363b69
to
cf736fa
Compare
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()) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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}, | ||
} | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It("should prepare network device info annotation with multiple networks", func() { | |
It("should prepare network to device-info mapping given multiple networks", func() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 := |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
expectedMap := | |
expectedMap := map[string]*networkv1.DeviceInfo{ | |
"foo": {Type: "pci", Version: "1.0.0", Pci: &networkv1.PciDevice{PciAddress: "0000:65:00.2"}} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
@@ -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 { |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, thanks.
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" | ||
} | ||
} | ||
} | ||
]` |
There was a problem hiding this comment.
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"
}
}
}
]`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
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"}`)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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.
actualMap, err := deviceinfo.MapBindingPluginNetworkNameToDeviceInfo(networks, interfaces, networkStatusWithMixedNetworks, bindingPlugins) | ||
Expect(err).NotTo(HaveOccurred()) | ||
Expect(actualMap).To(Equal(expectedMap)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
for _, sriovIface := range vmispec.FilterSRIOVInterfaces(interfaces) { | ||
multusInterfaceName := networkNameScheme[sriovIface.Name] | ||
networkStatusEntry, exist := multusInterfaceNameToNetworkStatusMap[multusInterfaceName] | ||
deviceInfo, exist := networkNameToDeviceInfo[sriovIface.Name] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
There was a problem hiding this 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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
pkg/hooks/hooks.go
Outdated
Args []string `json:"args,omitempty"` | ||
ConfigMap *ConfigMap `json:"configMap,omitempty"` | ||
PVC *PVC `json:"pvc,omitempty"` | ||
DownwardAPI v1.NetworkBindingDownwardAPIType `json:"downwardAPI,omitempty"` |
There was a problem hiding this comment.
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]
kubevirt/pkg/hooks/hooks_test.go
Lines 49 to 60 in 25b3733
hooks.HookSidecarListAnnotationName: ` | |
[ | |
{ | |
"image": "some-image:v1", | |
"imagePullPolicy": "IfNotPresent" | |
}, | |
{ | |
"image": "another-image:v1", | |
"imagePullPolicy": "Always" | |
} | |
] | |
`, |
There was a problem hiding this comment.
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.
cf736fa
to
91a27af
Compare
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>
91a27af
to
eda85c5
Compare
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>
eda85c5
to
c9a80b5
Compare
There was a problem hiding this 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.
networkInfo := NetworkInfo{Interfaces: downwardAPIInterfaces} | ||
return networkInfo |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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.
There was a problem hiding this 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
I have tested this PR content in my vdpa environment and it works as expected, thank you!
|
Raising 2 lgtms to approve. /approve |
[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 |
Required labels detected, running phase 2 presubmits: |
/retest-required |
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.Before this PR:
The compute container could use those env var to construct the libvirt domain xml.
After this PR:
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 -
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