Merge pull request #8597 from amshinde/vfio-hotplug-support

Implement hotplug support for physical endpoints
This commit is contained in:
Alex Lyn 2024-07-19 13:41:11 +08:00 committed by GitHub
commit d0dc67bb96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 178 additions and 92 deletions

View File

@ -94,4 +94,5 @@ type DeviceManager interface {
GetDeviceByID(string) Device
GetAllDevices() []Device
LoadDevices([]config.DeviceState)
FindDevice(*config.DeviceInfo) Device
}

View File

@ -22,13 +22,12 @@ import (
// bind/unbind paths to aid in SRIOV VF bring-up/restore
const (
pciDriverUnbindPath = "/sys/bus/pci/devices/%s/driver/unbind"
pciDriverBindPath = "/sys/bus/pci/drivers/%s/bind"
vfioNewIDPath = "/sys/bus/pci/drivers/vfio-pci/new_id"
vfioRemoveIDPath = "/sys/bus/pci/drivers/vfio-pci/remove_id"
iommuGroupPath = "/sys/bus/pci/devices/%s/iommu_group"
vfioDevPath = "/dev/vfio/%s"
vfioAPSysfsDir = "/sys/devices/vfio_ap"
pciDriverUnbindPath = "/sys/bus/pci/devices/%s/driver/unbind"
pciDriverOverridePath = "/sys/bus/pci/devices/%s/driver_override"
driversProbePath = "/sys/bus/pci/drivers_probe"
iommuGroupPath = "/sys/bus/pci/devices/%s/iommu_group"
vfioDevPath = "/dev/vfio/%s"
vfioAPSysfsDir = "/sys/devices/vfio_ap"
)
// VFIODevice is a vfio device meant to be passed to the hypervisor
@ -269,42 +268,8 @@ func GetBDF(deviceSysStr string) string {
return tokens[1]
}
// BindDevicetoVFIO binds the device to vfio driver after unbinding from host.
// Will be called by a network interface or a generic pcie device.
func BindDevicetoVFIO(bdf, hostDriver, vendorDeviceID string) (string, error) {
// Unbind from the host driver
unbindDriverPath := fmt.Sprintf(pciDriverUnbindPath, bdf)
deviceLogger().WithFields(logrus.Fields{
"device-bdf": bdf,
"driver-path": unbindDriverPath,
}).Info("Unbinding device from driver")
if err := utils.WriteToFile(unbindDriverPath, []byte(bdf)); err != nil {
return "", err
}
// Add device id to vfio driver.
deviceLogger().WithFields(logrus.Fields{
"vendor-device-id": vendorDeviceID,
"vfio-new-id-path": vfioNewIDPath,
}).Info("Writing vendor-device-id to vfio new-id path")
if err := utils.WriteToFile(vfioNewIDPath, []byte(vendorDeviceID)); err != nil {
return "", err
}
// Bind to vfio-pci driver.
bindDriverPath := fmt.Sprintf(pciDriverBindPath, "vfio-pci")
api.DeviceLogger().WithFields(logrus.Fields{
"device-bdf": bdf,
"driver-path": bindDriverPath,
}).Info("Binding device to vfio driver")
// Device may be already bound at this time because of earlier write to new_id, ignore error
utils.WriteToFile(bindDriverPath, []byte(bdf))
func GetVFIODevPath(bdf string) (string, error) {
// Determine the iommu group that the device belongs to.
groupPath, err := os.Readlink(fmt.Sprintf(iommuGroupPath, bdf))
if err != nil {
return "", err
@ -313,11 +278,66 @@ func BindDevicetoVFIO(bdf, hostDriver, vendorDeviceID string) (string, error) {
return fmt.Sprintf(vfioDevPath, filepath.Base(groupPath)), nil
}
// BindDevicetoHost binds the device to the host driver after unbinding from vfio-pci.
func BindDevicetoHost(bdf, hostDriver, vendorDeviceID string) error {
// Unbind from vfio-pci driver
// BindDevicetoVFIO binds the device to vfio driver after unbinding from host
// driver if present.
// Will be called by a network interface or a generic pcie device.
func BindDevicetoVFIO(bdf, hostDriver string) (string, error) {
overrideDriverPath := fmt.Sprintf(pciDriverOverridePath, bdf)
deviceLogger().WithFields(logrus.Fields{
"device-bdf": bdf,
"driver-override-path": overrideDriverPath,
}).Info("Write vfio-pci to driver_override")
// Write vfio-pci to driver_override file to allow the device to bind to vfio-pci
// Reference: https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-platform
if err := utils.WriteToFile(overrideDriverPath, []byte("vfio-pci")); err != nil {
return "", err
}
unbindDriverPath := fmt.Sprintf(pciDriverUnbindPath, bdf)
deviceLogger().WithFields(logrus.Fields{
"device-bdf": bdf,
"driver-path": unbindDriverPath,
}).Info("Unbinding device from driver")
// Unbind device from the host driver. In some cases, a driver may not be bound
// to the device, in which case this step may fail. Hence ignore error for this step.
utils.WriteToFile(unbindDriverPath, []byte(bdf))
deviceLogger().WithFields(logrus.Fields{
"device-bdf": bdf,
"drivers-probe-path": driversProbePath,
}).Info("Writing bdf to drivers-probe-path")
// Invoke drivers_probe so that the driver matching driver_override, in our case
// the vfio-pci driver will probe the device.
if err := utils.WriteToFile(driversProbePath, []byte(bdf)); err != nil {
return "", err
}
return GetVFIODevPath(bdf)
}
// BindDevicetoHost unbinds the device from vfio-pci driver and binds it to the
// previously bound driver.
func BindDevicetoHost(bdf, hostDriver string) error {
overrideDriverPath := fmt.Sprintf(pciDriverOverridePath, bdf)
api.DeviceLogger().WithFields(logrus.Fields{
"device-bdf": bdf,
"driver-override-path": overrideDriverPath,
}).Infof("Write %s to driver_override", hostDriver)
// write previously bound host driver to driver_override to allow the
// device to bind to it. This could be empty which means the device will not be
// bound to any driver later on.
if err := utils.WriteToFile(overrideDriverPath, []byte(hostDriver)); err != nil {
return err
}
// Unbind device from vfio-pci driver.
unbindDriverPath := fmt.Sprintf(pciDriverUnbindPath, bdf)
deviceLogger().WithFields(logrus.Fields{
"device-bdf": bdf,
"driver-path": unbindDriverPath,
}).Info("Unbinding device from driver")
@ -326,17 +346,12 @@ func BindDevicetoHost(bdf, hostDriver, vendorDeviceID string) error {
return err
}
// To prevent new VFs from binding to VFIO-PCI, remove_id
if err := utils.WriteToFile(vfioRemoveIDPath, []byte(vendorDeviceID)); err != nil {
return err
}
deviceLogger().WithFields(logrus.Fields{
"device-bdf": bdf,
"drivers-probe-path": driversProbePath,
}).Info("Writing bdf to drivers-probe-path")
// Bind back to host driver
bindDriverPath := fmt.Sprintf(pciDriverBindPath, hostDriver)
api.DeviceLogger().WithFields(logrus.Fields{
"device-bdf": bdf,
"driver-path": bindDriverPath,
}).Info("Binding back device to host driver")
return utils.WriteToFile(bindDriverPath, []byte(bdf))
// Invoke drivers_probe so that the driver matching driver_override, in this case
// the previous host driver will probe the device.
return utils.WriteToFile(driversProbePath, []byte(bdf))
}

View File

@ -82,7 +82,7 @@ func NewDeviceManager(blockDriver string, vhostUserStoreEnabled bool, vhostUserS
return dm
}
func (dm *deviceManager) findDevice(devInfo *config.DeviceInfo) api.Device {
func (dm *deviceManager) FindDevice(devInfo *config.DeviceInfo) api.Device {
// For devices with a major of -1, we use the host path to find existing instances.
if devInfo.Major == -1 {
for _, dev := range dm.devices {
@ -121,7 +121,7 @@ func (dm *deviceManager) createDevice(devInfo config.DeviceInfo) (dev api.Device
}
}()
if existingDev := dm.findDevice(&devInfo); existingDev != nil {
if existingDev := dm.FindDevice(&devInfo); existingDev != nil {
return existingDev, nil
}

View File

@ -26,8 +26,8 @@ type Endpoint interface {
SetPciPath(vcTypes.PciPath)
Attach(context.Context, *Sandbox) error
Detach(ctx context.Context, netNsCreated bool, netNsPath string) error
HotAttach(ctx context.Context, h Hypervisor) error
HotDetach(ctx context.Context, h Hypervisor, netNsCreated bool, netNsPath string) error
HotAttach(context.Context, *Sandbox) error
HotDetach(ctx context.Context, s *Sandbox, netNsCreated bool, netNsPath string) error
save() persistapi.NetworkEndpoint
load(persistapi.NetworkEndpoint)

View File

@ -125,10 +125,11 @@ func (endpoint *IPVlanEndpoint) Detach(ctx context.Context, netNsCreated bool, n
})
}
func (endpoint *IPVlanEndpoint) HotAttach(ctx context.Context, h Hypervisor) error {
func (endpoint *IPVlanEndpoint) HotAttach(ctx context.Context, s *Sandbox) error {
span, ctx := ipvlanTrace(ctx, "HotAttach", endpoint)
defer span.End()
h := s.hypervisor
if err := xConnectVMNetwork(ctx, endpoint, h); err != nil {
networkLogger().WithError(err).Error("Error bridging ipvlan ep")
return err
@ -142,7 +143,7 @@ func (endpoint *IPVlanEndpoint) HotAttach(ctx context.Context, h Hypervisor) err
return nil
}
func (endpoint *IPVlanEndpoint) HotDetach(ctx context.Context, h Hypervisor, netNsCreated bool, netNsPath string) error {
func (endpoint *IPVlanEndpoint) HotDetach(ctx context.Context, s *Sandbox, netNsCreated bool, netNsPath string) error {
if !netNsCreated {
return nil
}
@ -156,6 +157,7 @@ func (endpoint *IPVlanEndpoint) HotDetach(ctx context.Context, h Hypervisor, net
networkLogger().WithError(err).Warn("Error un-bridging ipvlan ep")
}
h := s.hypervisor
if _, err := h.HotplugRemoveDevice(ctx, endpoint, NetDev); err != nil {
networkLogger().WithError(err).Error("Error detach ipvlan ep")
return err

View File

@ -122,10 +122,11 @@ func (endpoint *MacvlanEndpoint) Detach(ctx context.Context, netNsCreated bool,
})
}
func (endpoint *MacvlanEndpoint) HotAttach(ctx context.Context, h Hypervisor) error {
func (endpoint *MacvlanEndpoint) HotAttach(ctx context.Context, s *Sandbox) error {
span, ctx := macvlanTrace(ctx, "HotAttach", endpoint)
defer span.End()
h := s.hypervisor
if err := xConnectVMNetwork(ctx, endpoint, h); err != nil {
networkLogger().WithError(err).Error("Error bridging macvlan ep")
return err
@ -139,7 +140,7 @@ func (endpoint *MacvlanEndpoint) HotAttach(ctx context.Context, h Hypervisor) er
return nil
}
func (endpoint *MacvlanEndpoint) HotDetach(ctx context.Context, h Hypervisor, netNsCreated bool, netNsPath string) error {
func (endpoint *MacvlanEndpoint) HotDetach(ctx context.Context, s *Sandbox, netNsCreated bool, netNsPath string) error {
if !netNsCreated {
return nil
}
@ -153,6 +154,7 @@ func (endpoint *MacvlanEndpoint) HotDetach(ctx context.Context, h Hypervisor, ne
networkLogger().WithError(err).Warn("Error un-bridging macvlan ep")
}
h := s.hypervisor
if _, err := h.HotplugRemoveDevice(ctx, endpoint, NetDev); err != nil {
networkLogger().WithError(err).Error("Error detach macvlan ep")
return err

View File

@ -93,12 +93,12 @@ func (endpoint *MacvtapEndpoint) Detach(ctx context.Context, netNsCreated bool,
}
// HotAttach for macvtap endpoint not supported yet
func (endpoint *MacvtapEndpoint) HotAttach(ctx context.Context, h Hypervisor) error {
func (endpoint *MacvtapEndpoint) HotAttach(ctx context.Context, s *Sandbox) error {
return fmt.Errorf("MacvtapEndpoint does not support Hot attach")
}
// HotDetach for macvtap endpoint not supported yet
func (endpoint *MacvtapEndpoint) HotDetach(ctx context.Context, h Hypervisor, netNsCreated bool, netNsPath string) error {
func (endpoint *MacvtapEndpoint) HotDetach(ctx context.Context, s *Sandbox, netNsCreated bool, netNsPath string) error {
return fmt.Errorf("MacvtapEndpoint does not support Hot detach")
}

View File

@ -202,7 +202,7 @@ func (n *LinuxNetwork) addSingleEndpoint(ctx context.Context, s *Sandbox, netInf
networkLogger().WithField("endpoint-type", endpoint.Type()).WithField("hotplug", hotplug).Info("Attaching endpoint")
if hotplug {
if err := endpoint.HotAttach(ctx, s.hypervisor); err != nil {
if err := endpoint.HotAttach(ctx, s); err != nil {
return nil, err
}
} else {
@ -265,7 +265,7 @@ func (n *LinuxNetwork) removeSingleEndpoint(ctx context.Context, s *Sandbox, end
// if required.
networkLogger().WithField("endpoint-type", endpoint.Type()).Info("Detaching endpoint")
if hotplug && s != nil {
if err := endpoint.HotDetach(ctx, s.hypervisor, n.netNSCreated, n.netNSPath); err != nil {
if err := endpoint.HotDetach(ctx, s, n.netNSCreated, n.netNSPath); err != nil {
return err
}
} else {

View File

@ -123,13 +123,65 @@ func (endpoint *PhysicalEndpoint) Detach(ctx context.Context, netNsCreated bool,
}
// HotAttach for physical endpoint not supported yet
func (endpoint *PhysicalEndpoint) HotAttach(ctx context.Context, h Hypervisor) error {
return fmt.Errorf("PhysicalEndpoint does not support Hot attach")
func (endpoint *PhysicalEndpoint) HotAttach(ctx context.Context, s *Sandbox) error {
span, ctx := physicalTrace(ctx, "HotAttach", endpoint)
defer span.End()
// Unbind physical interface from host driver and bind to vfio
// so that it can be passed to the hypervisor.
vfioPath, err := bindNICToVFIO(endpoint)
if err != nil {
return err
}
c, err := resCtrl.DeviceToCgroupDeviceRule(vfioPath)
if err != nil {
return err
}
d := config.DeviceInfo{
ContainerPath: vfioPath,
DevType: string(c.Type),
Major: c.Major,
Minor: c.Minor,
ColdPlug: false,
}
_, err = s.AddDevice(ctx, d)
return err
}
// HotDetach for physical endpoint not supported yet
func (endpoint *PhysicalEndpoint) HotDetach(ctx context.Context, h Hypervisor, netNsCreated bool, netNsPath string) error {
return fmt.Errorf("PhysicalEndpoint does not support Hot detach")
func (endpoint *PhysicalEndpoint) HotDetach(ctx context.Context, s *Sandbox, netNsCreated bool, netNsPath string) error {
span, _ := physicalTrace(ctx, "HotDetach", endpoint)
defer span.End()
var vfioPath string
var err error
if vfioPath, err = drivers.GetVFIODevPath(endpoint.BDF); err != nil {
return err
}
c, err := resCtrl.DeviceToCgroupDeviceRule(vfioPath)
if err != nil {
return err
}
d := config.DeviceInfo{
ContainerPath: vfioPath,
DevType: string(c.Type),
Major: c.Major,
Minor: c.Minor,
ColdPlug: false,
}
device := s.devManager.FindDevice(&d)
s.devManager.RemoveDevice(device.DeviceID())
// We do not need to enter the network namespace to bind back the
// physical interface to host driver.
return bindNICToHost(endpoint)
}
// isPhysicalIface checks if an interface is a physical device.
@ -218,11 +270,11 @@ func createPhysicalEndpoint(netInfo NetworkInfo) (*PhysicalEndpoint, error) {
}
func bindNICToVFIO(endpoint *PhysicalEndpoint) (string, error) {
return drivers.BindDevicetoVFIO(endpoint.BDF, endpoint.Driver, endpoint.VendorDeviceID)
return drivers.BindDevicetoVFIO(endpoint.BDF, endpoint.Driver)
}
func bindNICToHost(endpoint *PhysicalEndpoint) error {
return drivers.BindDevicetoHost(endpoint.BDF, endpoint.Driver, endpoint.VendorDeviceID)
return drivers.BindDevicetoHost(endpoint.BDF, endpoint.Driver)
}
func (endpoint *PhysicalEndpoint) save() persistapi.NetworkEndpoint {

View File

@ -27,9 +27,11 @@ func TestPhysicalEndpoint_HotAttach(t *testing.T) {
HardAddr: net.HardwareAddr{0x02, 0x00, 0xca, 0xfe, 0x00, 0x04}.String(),
}
h := &mockHypervisor{}
s := &Sandbox{
hypervisor: &mockHypervisor{},
}
err := v.HotAttach(context.Background(), h)
err := v.HotAttach(context.Background(), s)
assert.Error(err)
}
@ -40,9 +42,11 @@ func TestPhysicalEndpoint_HotDetach(t *testing.T) {
HardAddr: net.HardwareAddr{0x02, 0x00, 0xca, 0xfe, 0x00, 0x04}.String(),
}
h := &mockHypervisor{}
s := &Sandbox{
hypervisor: &mockHypervisor{},
}
err := v.HotDetach(context.Background(), h, true, "")
err := v.HotDetach(context.Background(), s, true, "")
assert.Error(err)
}

View File

@ -92,12 +92,13 @@ func (endpoint *TapEndpoint) Detach(ctx context.Context, netNsCreated bool, netN
}
// HotAttach for the tap endpoint uses hot plug device
func (endpoint *TapEndpoint) HotAttach(ctx context.Context, h Hypervisor) error {
func (endpoint *TapEndpoint) HotAttach(ctx context.Context, s *Sandbox) error {
networkLogger().Info("Hot attaching tap endpoint")
span, ctx := tapTrace(ctx, "HotAttach", endpoint)
defer span.End()
h := s.hypervisor
if err := tapNetwork(endpoint, h.HypervisorConfig().NumVCPUs(), h.HypervisorConfig().DisableVhostNet); err != nil {
networkLogger().WithError(err).Error("Error bridging tap ep")
return err
@ -111,7 +112,7 @@ func (endpoint *TapEndpoint) HotAttach(ctx context.Context, h Hypervisor) error
}
// HotDetach for the tap endpoint uses hot pull device
func (endpoint *TapEndpoint) HotDetach(ctx context.Context, h Hypervisor, netNsCreated bool, netNsPath string) error {
func (endpoint *TapEndpoint) HotDetach(ctx context.Context, s *Sandbox, netNsCreated bool, netNsPath string) error {
networkLogger().Info("Hot detaching tap endpoint")
span, ctx := tapTrace(ctx, "HotDetach", endpoint)
@ -123,6 +124,7 @@ func (endpoint *TapEndpoint) HotDetach(ctx context.Context, h Hypervisor, netNsC
networkLogger().WithError(err).Warn("Error un-bridging tap ep")
}
h := s.hypervisor
if _, err := h.HotplugRemoveDevice(ctx, endpoint, NetDev); err != nil {
networkLogger().WithError(err).Error("Error detach tap ep")
return err

View File

@ -103,12 +103,13 @@ func (endpoint *TuntapEndpoint) Detach(ctx context.Context, netNsCreated bool, n
}
// HotAttach for the tun/tap endpoint uses hot plug device
func (endpoint *TuntapEndpoint) HotAttach(ctx context.Context, h Hypervisor) error {
func (endpoint *TuntapEndpoint) HotAttach(ctx context.Context, s *Sandbox) error {
networkLogger().Info("Hot attaching tun/tap endpoint")
span, ctx := tuntapTrace(ctx, "HotAttach", endpoint)
defer span.End()
h := s.hypervisor
if err := tuntapNetwork(endpoint, h.HypervisorConfig().NumVCPUs(), h.HypervisorConfig().DisableVhostNet); err != nil {
networkLogger().WithError(err).Error("Error bridging tun/tap ep")
return err
@ -122,7 +123,7 @@ func (endpoint *TuntapEndpoint) HotAttach(ctx context.Context, h Hypervisor) err
}
// HotDetach for the tun/tap endpoint uses hot pull device
func (endpoint *TuntapEndpoint) HotDetach(ctx context.Context, h Hypervisor, netNsCreated bool, netNsPath string) error {
func (endpoint *TuntapEndpoint) HotDetach(ctx context.Context, s *Sandbox, netNsCreated bool, netNsPath string) error {
networkLogger().Info("Hot detaching tun/tap endpoint")
span, ctx := tuntapTrace(ctx, "HotDetach", endpoint)
@ -134,6 +135,7 @@ func (endpoint *TuntapEndpoint) HotDetach(ctx context.Context, h Hypervisor, net
networkLogger().WithError(err).Warn("Error un-bridging tun/tap ep")
}
h := s.hypervisor
if _, err := h.HotplugRemoveDevice(ctx, endpoint, NetDev); err != nil {
networkLogger().WithError(err).Error("Error detach tun/tap ep")
return err

View File

@ -126,10 +126,11 @@ func (endpoint *VethEndpoint) Detach(ctx context.Context, netNsCreated bool, net
}
// HotAttach for the veth endpoint uses hot plug device
func (endpoint *VethEndpoint) HotAttach(ctx context.Context, h Hypervisor) error {
func (endpoint *VethEndpoint) HotAttach(ctx context.Context, s *Sandbox) error {
span, ctx := vethTrace(ctx, "HotAttach", endpoint)
defer span.End()
h := s.hypervisor
if err := xConnectVMNetwork(ctx, endpoint, h); err != nil {
networkLogger().WithError(err).Error("Error bridging virtual ep")
return err
@ -143,7 +144,7 @@ func (endpoint *VethEndpoint) HotAttach(ctx context.Context, h Hypervisor) error
}
// HotDetach for the veth endpoint uses hot pull device
func (endpoint *VethEndpoint) HotDetach(ctx context.Context, h Hypervisor, netNsCreated bool, netNsPath string) error {
func (endpoint *VethEndpoint) HotDetach(ctx context.Context, s *Sandbox, netNsCreated bool, netNsPath string) error {
if !netNsCreated {
return nil
}
@ -157,6 +158,7 @@ func (endpoint *VethEndpoint) HotDetach(ctx context.Context, h Hypervisor, netNs
networkLogger().WithError(err).Warn("Error un-bridging virtual ep")
}
h := s.hypervisor
if _, err := h.HotplugRemoveDevice(ctx, endpoint, NetDev); err != nil {
networkLogger().WithError(err).Error("Error detach virtual ep")
return err

View File

@ -107,12 +107,12 @@ func (endpoint *VhostUserEndpoint) Detach(ctx context.Context, netNsCreated bool
}
// HotAttach for vhostuser endpoint not supported yet
func (endpoint *VhostUserEndpoint) HotAttach(ctx context.Context, h Hypervisor) error {
func (endpoint *VhostUserEndpoint) HotAttach(ctx context.Context, s *Sandbox) error {
return fmt.Errorf("VhostUserEndpoint does not support Hot attach")
}
// HotDetach for vhostuser endpoint not supported yet
func (endpoint *VhostUserEndpoint) HotDetach(ctx context.Context, h Hypervisor, netNsCreated bool, netNsPath string) error {
func (endpoint *VhostUserEndpoint) HotDetach(ctx context.Context, s *Sandbox, netNsCreated bool, netNsPath string) error {
return fmt.Errorf("VhostUserEndpoint does not support Hot detach")
}

View File

@ -97,9 +97,11 @@ func TestVhostUserEndpoint_HotAttach(t *testing.T) {
EndpointType: VhostUserEndpointType,
}
h := &mockHypervisor{}
s := &Sandbox{
hypervisor: &mockHypervisor{},
}
err := v.HotAttach(context.Background(), h)
err := v.HotAttach(context.Background(), s)
assert.Error(err)
}
@ -111,9 +113,11 @@ func TestVhostUserEndpoint_HotDetach(t *testing.T) {
EndpointType: VhostUserEndpointType,
}
h := &mockHypervisor{}
s := &Sandbox{
hypervisor: &mockHypervisor{},
}
err := v.HotDetach(context.Background(), h, true, "")
err := v.HotDetach(context.Background(), s, true, "")
assert.Error(err)
}