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

Binding plugin API #10284

Merged
merged 7 commits into from Aug 16, 2023
Merged

Binding plugin API #10284

merged 7 commits into from Aug 16, 2023

Conversation

AlonaKaplan
Copy link
Member

@AlonaKaplan AlonaKaplan commented Aug 14, 2023

What this PR does / why we need it:
This PR introduces network bindings API as plugins external to KubeVirt

Network bindings are registered on the cluster by specifying on the Kubevirt CR their name and configuration (e.g sidecar image).

example:

piVersion: kubevirt.io/v1
kind: KubeVirt
metadata:
  name: kubevirt
  namespace: kubevirt
spec:
  configuration:
    network:
      binding:
        bridge:
          sidecarImage: quay.io/kubevirt/network-bridge-binding

On the VM/VMI specification, the binding is referenced by name.

example:

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: vmi-1-bridge
spec:
  template:
    spec:
      domain:
        devices:
          interfaces:
          - name: blue
            mac: 12:34:56:78:9a:bc
            binding:
              name: bridge
      networks:
      - name: blue
        pod: {}

The feature is behind "NetworkBindingPlugins" gate.

Which issue(s) this PR fixes (optional, in fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when PR gets merged):
Fixes #

Special notes for your reviewer:
The API is not final (v1alphav1) and may be extended as we add more plugins.

Release note:

Introduce an API for network binding plugins. The feature is behind "NetworkBindingPlugins" gate.

@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: no Indicates the PR's author has not 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/L labels Aug 14, 2023
@kubevirt-bot kubevirt-bot added the kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API label Aug 14, 2023
@kubevirt-bot kubevirt-bot added sig/network 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 Aug 14, 2023
@kubevirt-bot kubevirt-bot added dco-signoff: yes Indicates the PR's author has DCO signed all their commits. and removed dco-signoff: no Indicates the PR's author has not DCO signed all their commits. labels Aug 14, 2023
@AlonaKaplan AlonaKaplan marked this pull request as ready for review August 15, 2023 07:12
@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 Aug 15, 2023
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.

I reviewed all except the net binding plugin: Add binding sidecars to virt-launcher commit.

I think we also need a feature gate.

Please also specify in the release notes that this is an alphav1 release of this API.

Comment on lines 1332 to 1333
// Image that runs on virt-launcher pod.
//It run services and configures the libvirt domain.
Copy link
Member

Choose a reason for hiding this comment

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

// SidecarImage references a container image that runs in the virt-launcher pod.
// The sidecar handles (libvirt) domain configuration and optional services.

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.

@@ -1201,6 +1201,9 @@ type Interface struct {
// BindingMethod specifies the method which will be used to connect the interface to the guest.
// Defaults to Bridge.
InterfaceBindingMethod `json:",inline"`
// Binding specifies the binding plugin that will be used to to connect the interface to the guest.
Copy link
Member

Choose a reason for hiding this comment

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

typo: double "to".

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.

@@ -1310,6 +1313,12 @@ type InterfaceMacvtap struct{}
// InterfacePasst connects to a given network.
type InterfacePasst struct{}

// PluggedBinding represents a binding implemented in a plugin.
type PluggedBinding struct {
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps Plugin instead of Plugged?

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
Contributor

Choose a reason for hiding this comment

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

How about BindingPlugin?

@@ -1310,6 +1313,12 @@ type InterfaceMacvtap struct{}
// InterfacePasst connects to a given network.
type InterfacePasst struct{}

// PluggedBinding represents a binding implemented in a plugin.
type PluggedBinding struct {
// Reference to the binding name as denined in the kubevirt CR.
Copy link
Member

Choose a reason for hiding this comment

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

// Name references to the binding 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.

done.

@@ -58,3 +58,24 @@ func validateInterfaceStateValue(field *k8sfield.Path, spec *v1.VirtualMachineIn
}
return causes
}

func validateInterfaceBinding(field *k8sfield.Path, spec *v1.VirtualMachineInstanceSpec) []metav1.StatusCause {
Copy link
Member

Choose a reason for hiding this comment

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

Someone needs to call it :)

Unit tests will be nice too.

Copy link
Member Author

Choose a reason for hiding this comment

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

oops:) done.

Comment on lines 66 to 71
if iface.InterfaceBindingMethod.Bridge != nil ||
iface.InterfaceBindingMethod.Slirp != nil ||
iface.InterfaceBindingMethod.Masquerade != nil ||
iface.InterfaceBindingMethod.SRIOV != nil ||
iface.InterfaceBindingMethod.Macvtap != nil ||
iface.InterfaceBindingMethod.Passt != nil {
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 extract it to a small helper that accepts this InterfaceBindingMethod struct and checks if one of them exists? It will make this code clearer.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks, you can resolve it now.

@@ -0,0 +1,30 @@
package services
Copy link
Member

Choose a reason for hiding this comment

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

Please add the header.

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/kubevirt/pkg/hooks"
)

func BindingPluginSidecarList(vmi *v1.VirtualMachineInstance, config *v1.KubeVirtConfiguration) (hooks.HookSidecarList, error) {
Copy link
Member

Choose a reason for hiding this comment

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

We need a context for this being network related. Perhaps just add Net as a prefix.

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.

}

if !exist {
return nil, fmt.Errorf("couldn't find configuration for bindining: %s", iface.Binding.Name)
Copy link
Member

Choose a reason for hiding this comment

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

"binding" >> "network binding".

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.

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
v1 "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 import is not in the same block as the above.

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.

@@ -0,0 +1,85 @@
package services_test
Copy link
Member

Choose a reason for hiding this comment

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

Please add a header.

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.

@@ -359,6 +359,12 @@ func (t *templateService) renderLaunchManifest(vmi *v1.VirtualMachineInstance, i
return nil, err
}

bindingSidecars, err := BindingPluginSidecarList(vmi, t.clusterConfig.GetConfig())
requestedHookSidecarList = append(requestedHookSidecarList, bindingSidecars...)
Copy link
Member

Choose a reason for hiding this comment

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

This should come after the error check.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch!

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.

shouldSucceed = true
)

BeforeEach(func() {
Copy link
Member

Choose a reason for hiding this comment

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

Leftover.

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.

},
}
sidecars, err := services.BindingPluginSidecarList(vmi, config)
if shouldSucceed {
Copy link
Member

Choose a reason for hiding this comment

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

This usually is a hint that two different test tables are a better fit.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't like this pattern because it makes tests a bit harder to follow, I think its worth adding another DescribeTable.

hooks.HookSidecarList{{Image: testSidecarImage1}},
shouldSucceed),
Entry("VMI has multiple plugin bindings",
libvmi.New(libvmi.WithInterface(v1.Interface{Name: testNetworkName1, Binding: &v1.PluggedBinding{Name: testBindingName1}}),
Copy link
Member

Choose a reason for hiding this comment

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

Please add another interface with the same plugin binding as an existing one.
We still need to see one sidecare even more than one interface ref it.

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 for discovering a bug:)

@@ -1201,6 +1201,9 @@ type Interface struct {
// BindingMethod specifies the method which will be used to connect the interface to the guest.
// Defaults to Bridge.
InterfaceBindingMethod `json:",inline"`
// Binding specifies the binding plugin that will be used to to connect the interface to the guest.
// It provides an alternative to InterfaceBindingMethod.
Binding *PluggedBinding `json:"binding,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense/possible to change InterfaceBindingMethodattribute to be non mandatory, and make the validation webhook to force having binding-method or binding-plugin?

It could make it easier to work with (e.g: check if iface has both binding-plugin and binding-method: if iface.binding != nil && iface.bindingMethod != nil).

@@ -1310,6 +1313,12 @@ type InterfaceMacvtap struct{}
// InterfacePasst connects to a given network.
type InterfacePasst struct{}

// PluggedBinding represents a binding implemented in a plugin.
type PluggedBinding struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about BindingPlugin?

@@ -58,3 +58,24 @@ func validateInterfaceStateValue(field *k8sfield.Path, spec *v1.VirtualMachineIn
}
return causes
}

func validateInterfaceBinding(field *k8sfield.Path, spec *v1.VirtualMachineInstanceSpec) []metav1.StatusCause {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: How about validateInterfaceBindingPlugin?

Copy link
Member

Choose a reason for hiding this comment

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

It will be incorrect. It checks all binding configuration, even the old one.

},
}
sidecars, err := services.BindingPluginSidecarList(vmi, config)
if shouldSucceed {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't like this pattern because it makes tests a bit harder to follow, I think its worth adding another DescribeTable.

}
},
Entry("VMI has binding plugin but config doesn't exist",
libvmi.New(libvmi.WithInterface(v1.Interface{Name: testNetworkName1, Binding: &v1.PluggedBinding{Name: testBindingName1}}),
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it was raised before, we should not use e2e tests helpers in unit tests, isn't it? (talking about libvmi)

pluginInfo, exist = config.NetworkConfiguration.Binding[iface.Binding.Name]
}

if !exist {
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 adding validation for adding binding plugin to Kubevirt CR, at least that the image is not empty.
Running pods with containers who has empty image should fail.

It may also reduce some error checking like this one if its validated at the API level.

Copy link
Member

Choose a reason for hiding this comment

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

That is not recomended, we are not validating the KV CR and we should not add such dependencies in the webhook for inter components dependencies.

@@ -359,6 +359,12 @@ func (t *templateService) renderLaunchManifest(vmi *v1.VirtualMachineInstance, i
return nil, err
}

bindingSidecars, err := BindingPluginSidecarList(vmi, t.clusterConfig.GetConfig())
requestedHookSidecarList = append(requestedHookSidecarList, bindingSidecars...)
Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch!

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.

Very nice, thanks!

pluginInfo, exist = config.NetworkConfiguration.Binding[iface.Binding.Name]
}

if !exist {
Copy link
Member

Choose a reason for hiding this comment

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

That is not recomended, we are not validating the KV CR and we should not add such dependencies in the webhook for inter components dependencies.

// SR-IOV devices are not part of the phases
if iface.SRIOV != nil {
// Binding plugin and SR-IOV devices are not part of the phases
if iface.Binding != nil || iface.SRIOV != nil {
Copy link
Member

Choose a reason for hiding this comment

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

This may not work well when we'll need to work with tap devices (or other future exceptions).
But these thoughts are futuristic, for now this seem fine.

@xpivarc
Copy link
Member

xpivarc commented Aug 16, 2023

FYI @kvaps

@@ -1201,6 +1201,9 @@ type Interface struct {
// BindingMethod specifies the method which will be used to connect the interface to the guest.
// Defaults to Bridge.
InterfaceBindingMethod `json:",inline"`
// Binding specifies the binding plugin that will be used to connect the interface to the guest.
// It provides an alternative to InterfaceBindingMethod.
Copy link
Member

Choose a reason for hiding this comment

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

Lets try to do our best and declare all the new fields as v1alphav1 in order to warn about using them.

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.

@@ -1310,6 +1313,12 @@ type InterfaceMacvtap struct{}
// InterfacePasst connects to a given network.
type InterfacePasst struct{}

// PluginBinding represents a binding implemented in a plugin.
type PluginBinding struct {
// Name references to the binding name as denined in the kubevirt CR.
Copy link
Member

Choose a reason for hiding this comment

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

Lets try to do our best and declare all the new fields as v1alphav1 in order to warn about using them.

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.


type InterfaceBindingPlugin struct {
// SidecarImage references a container image that runs in the virt-launcher pod.
// The sidecar handles (libvirt) domain configuration and optional services.
Copy link
Member

Choose a reason for hiding this comment

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

Lets try to do our best and declare all the new fields as v1alphav1 in order to warn about using them.

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.

@EdDev
Copy link
Member

EdDev commented Aug 16, 2023

/hold

@kubevirt-bot kubevirt-bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 16, 2023
@kubevirt-bot kubevirt-bot removed the lgtm Indicates that a PR is ready to be merged. label Aug 16, 2023
@@ -72,6 +72,8 @@ const (
VMLiveUpdateFeaturesGate = "VMLiveUpdateFeatures"
// When BochsDisplayForEFIGuests is enabled, EFI guests will be started with Bochs display instead of VGA
BochsDisplayForEFIGuests = "BochsDisplayForEFIGuests"
// BindingPlugingsGate enables using a plugin to bind the pod and the VM network
BindingPlugingsGate = "BindingPlugins"
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 add a Network prefix? It is not clear what binding is referred here.

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.

@EdDev
Copy link
Member

EdDev commented Aug 16, 2023

/hold cancel

@kubevirt-bot kubevirt-bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Aug 16, 2023
@EdDev
Copy link
Member

EdDev commented Aug 16, 2023

Can you also add the FG info in the release notes and the PR description?

@ormergi ormergi mentioned this pull request Aug 16, 2023
The kubevirt CR will contain the sidecar image per each
binding.

example:

piVersion: kubevirt.io/v1
kind: KubeVirt
metadata:
  name: kubevirt
  namespace: kubevirt
spec:
  configuration:
    network:
      binding:
        bridge:
          sidecarImage: quay.io/kubevirt/network-bridge-binding

Signed-off-by: Alona Paz <alkaplan@redhat.com>
The binding will represent the binding plugin.

Signed-off-by: Alona Paz <alkaplan@redhat.com>
A VMI interface can have only one binding method.
In case binding plugin is define, no other binding method can exist on
this interface.

Signed-off-by: Alona Paz <alkaplan@redhat.com>
Signed-off-by: Alona Paz <alkaplan@redhat.com>
Since those parts are done by the plugin (cni or sidecar) it should be
skipped in the core code.

Signed-off-by: Alona Paz <alkaplan@redhat.com>
Signed-off-by: Or Mergi <ormergi@redhat.com>
PreferredInterfaceMasquerade shouldn't be applied on an interface with
binding plugin.


Signed-off-by: Alona Paz <alkaplan@redhat.com>
Signed-off-by: Alona Paz <alkaplan@redhat.com>
@kubevirt-bot kubevirt-bot added the lgtm Indicates that a PR is ready to be merged. label Aug 16, 2023
@AlonaKaplan
Copy link
Member Author

2 lgtms, raising 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 Aug 16, 2023
@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 9c14b3e into kubevirt:main Aug 16, 2023
35 of 36 checks passed
@ormergi ormergi mentioned this pull request Apr 4, 2024
8 tasks
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/network size/XL
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants