Skip to content
This repository has been archived by the owner on Apr 3, 2018. It is now read-only.

Commit

Permalink
hypervisor: add hot plugging support for Qemu Q35
Browse files Browse the repository at this point in the history
Qemu Q35 machine type does not support to hot plug devices
directly on pcie.0, hence we have to find a way to allow users
hot plug N devices.
The only way to hot plug devices in Qemu Q35 is through PCI brdiges.
Each PCI bridge is able to support until 32 devices, therefore we have a
limitation in the amount of devices we can hot plug.
This patch adds support for hot plugging using PCI bridges in
pc and q35 machine types.

Signed-off-by: Julio Montes <julio.montes@intel.com>
  • Loading branch information
Julio Montes committed Nov 8, 2017
1 parent e6ebf85 commit 6c938cf
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 26 deletions.
104 changes: 104 additions & 0 deletions bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//
// Copyright (c) 2017 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package virtcontainers

import "fmt"

type bridgeType string

const (
pciBridge bridgeType = "pci"
pcieBridge = "pcie"
)

const pciBridgeMaxCapacity = 30

// Bridge is a bridge where devices can be hot plugged
type Bridge struct {
// Address contains information about devices plugged and its address in the bridge
Address map[uint32]string

// Type is the type of the bridge (pci, pcie, etc)
Type bridgeType

//ID is used to identify the bridge in the hypervisor
ID string
}

// NewBridges creates n new pci(e) bridges depending of the machine type
func NewBridges(n uint32, machine string) []Bridge {
var bridges []Bridge
var bt bridgeType

switch machine {
case QemuQ35:
// currently only pci bridges are supported
// qemu-2.10 will introduce pcie bridges
fallthrough
case QemuPC:
bt = pciBridge
default:
return nil
}

for i := uint32(0); i < n; i++ {
bridges = append(bridges, Bridge{
Type: bt,
ID: fmt.Sprintf("%s-bridge-%d", bt, i),
Address: make(map[uint32]string),
})
}

return bridges
}

// addDevice on success adds the device ID to the bridge and return the address
// where the device was added, otherwise an error is returned
func (b *Bridge) addDevice(ID string) (uint32, error) {
var addr uint32

// looking for the first available address
for i := uint32(1); i <= pciBridgeMaxCapacity; i++ {
if _, ok := b.Address[i]; !ok {
addr = i
break
}
}

if addr == 0 {
return 0, fmt.Errorf("Unable to hot plug device on bridge: there are not empty slots")
}

// save address and device
b.Address[addr] = ID
return addr, nil
}

// removeDevice on success removes the device ID from the bridge and return nil,
// otherwise an error is returned
func (b *Bridge) removeDevice(ID string) error {
// check if the device was hot plugged in the bridge
for addr, devID := range b.Address {
if devID == ID {
// free address to re-use the same slot with other devices
delete(b.Address, addr)
return nil
}
}

return fmt.Errorf("Unable to hot unplug device %s: not present on bridge", ID)
}
2 changes: 1 addition & 1 deletion container.go
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ func (c *Container) attachDevices() error {

func (c *Container) detachDevices() error {
for _, device := range c.devices {
if err := device.detach(c.pod.hypervisor); err != nil {
if err := device.detach(c.pod.hypervisor, c); err != nil {
return err
}
}
Expand Down
14 changes: 6 additions & 8 deletions device.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const (
// Device is the virtcontainers device interface.
type Device interface {
attach(hypervisor, *Container) error
detach(hypervisor) error
detach(hypervisor, *Container) error
deviceType() string
}

Expand Down Expand Up @@ -153,7 +153,7 @@ func (device *VFIODevice) attach(h hypervisor, c *Container) error {
return nil
}

func (device *VFIODevice) detach(h hypervisor) error {
func (device *VFIODevice) detach(h hypervisor, c *Container) error {
return nil
}

Expand Down Expand Up @@ -232,7 +232,7 @@ func (device *BlockDevice) attach(h hypervisor, c *Container) (err error) {

device.DeviceInfo.Hotplugged = false
} else {
if err = h.hotplugAddDevice(drive, blockDev); err != nil {
if err := c.pod.hypervisor.hotplugAddDevice(drive, blockDev); err != nil {
return err
}

Expand All @@ -243,19 +243,17 @@ func (device *BlockDevice) attach(h hypervisor, c *Container) (err error) {
return nil
}

func (device BlockDevice) detach(h hypervisor) error {
func (device BlockDevice) detach(h hypervisor, c *Container) error {
if device.DeviceInfo.Hotplugged {
deviceLogger().WithField("device", device.DeviceInfo.HostPath).Info("Unplugging block device")

drive := Drive{
ID: makeBlockDevIDForHypervisor(device.DeviceInfo.ID),
}

if err := h.hotplugRemoveDevice(drive, blockDev); err != nil {
deviceLogger().WithError(err).Error("Failed to unplug block device")
if err := c.pod.hypervisor.hotplugRemoveDevice(drive, blockDev); err != nil {
return err
}

}
return nil
}
Expand All @@ -281,7 +279,7 @@ func (device *GenericDevice) attach(h hypervisor, c *Container) error {
return nil
}

func (device *GenericDevice) detach(h hypervisor) error {
func (device *GenericDevice) detach(h hypervisor, c *Container) error {
return nil
}

Expand Down
78 changes: 76 additions & 2 deletions filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
// pods and containers.
type podResource int

type HypervisorState []byte

const (
// configFileType represents a configuration file type
configFileType podResource = iota
Expand All @@ -42,6 +44,9 @@ const (
// networkFileType represents a network file type (pod only)
networkFileType

// hypervisorFileType represents a hypervisor file type (pod only)
hypervisorFileType

// processFileType represents a process file type
processFileType

Expand Down Expand Up @@ -75,6 +80,9 @@ const mountsFile = "mounts.json"
// devicesFile is the file name storing a container's devices.
const devicesFile = "devices.json"

// hypervisorFile is the file name storing a hypervisor's state.
const hypervisorFile = "hypervisor.json"

// dirMode is the permission bits used for creating a directory
const dirMode = os.FileMode(0750)

Expand Down Expand Up @@ -109,6 +117,10 @@ type resourceStorage interface {
fetchPodNetwork(podID string) (NetworkNamespace, error)
storePodNetwork(podID string, networkNS NetworkNamespace) error

// Hypervisor resources
fetchHypervisorState(podID string, state interface{}) error
storeHypervisorState(podID string, state interface{}) error

// Container resources
storeContainerResource(podID, containerID string, resource podResource, data interface{}) error
deleteContainerResources(podID, containerID string, resources []podResource) error
Expand Down Expand Up @@ -260,6 +272,21 @@ func (fs *filesystem) fetchFile(file string, data interface{}) error {
return nil
}

func (fs *filesystem) fetchHypervisorFile(file string, state *HypervisorState) error {
if file == "" {
return errNeedFile
}

fileData, err := ioutil.ReadFile(file)
if err != nil {
return err
}

*state = fileData

return nil
}

// fetchDeviceFile is used for custom unmarshalling of device interface objects.
func (fs *filesystem) fetchDeviceFile(file string, devices *[]Device) error {
if file == "" {
Expand Down Expand Up @@ -328,7 +355,7 @@ func (fs *filesystem) fetchDeviceFile(file string, devices *[]Device) error {
func resourceNeedsContainerID(podSpecific bool, resource podResource) bool {

switch resource {
case lockFileType, networkFileType:
case lockFileType, networkFileType, hypervisorFileType:
// pod-specific resources
return false
default:
Expand All @@ -351,7 +378,7 @@ func resourceDir(podSpecific bool, podID, containerID string, resource podResour
case configFileType:
path = configStoragePath
break
case stateFileType, networkFileType, processFileType, lockFileType, mountsFileType, devicesFileType:
case stateFileType, networkFileType, processFileType, lockFileType, mountsFileType, devicesFileType, hypervisorFileType:
path = runStoragePath
break
default:
Expand Down Expand Up @@ -387,6 +414,9 @@ func (fs *filesystem) resourceURI(podSpecific bool, podID, containerID string, r
filename = stateFile
case networkFileType:
filename = networkFile
case hypervisorFileType:
filename = hypervisorFile
break
case processFileType:
filename = processFile
case lockFileType:
Expand Down Expand Up @@ -477,6 +507,20 @@ func (fs *filesystem) storeNetworkResource(podSpecific bool, podID, containerID
return fs.storeFile(networkFile, file)
}

func (fs *filesystem) storeHypervisorResource(podSpecific bool, podID, containerID string, resource podResource, file interface{}) error {
if resource != hypervisorFileType {
return errInvalidResource
}

// pod only resource
hypervisorFile, _, err := fs.resourceURI(true, podID, containerID, hypervisorFileType)
if err != nil {
return err
}

return fs.storeFile(hypervisorFile, file)
}

func (fs *filesystem) storeProcessResource(podSpecific bool, podID, containerID string, resource podResource, file interface{}) error {
if resource != processFileType {
return errInvalidResource
Expand Down Expand Up @@ -599,6 +643,15 @@ func (fs *filesystem) doFetchResource(containerID, path string, resource podReso

return networkNS, nil

case hypervisorFileType:
var hypervisorSt HypervisorState
err = fs.fetchHypervisorFile(path, &hypervisorSt)
if err != nil {
return nil, err
}

return hypervisorSt, nil

case processFileType:
process := Process{}
err = fs.fetchFile(path, &process)
Expand Down Expand Up @@ -675,10 +728,31 @@ func (fs *filesystem) fetchPodNetwork(podID string) (NetworkNamespace, error) {
return NetworkNamespace{}, fmt.Errorf("Unknown network type")
}

func (fs *filesystem) fetchHypervisorState(podID string, state interface{}) error {
data, err := fs.fetchResource(true, podID, "", hypervisorFileType)
if err != nil {
return err
}

switch st := data.(type) {
case HypervisorState:
if err = json.Unmarshal(st, state); err != nil {
return err
}
return nil
}

return fmt.Errorf("Unknown hypervisor type")
}

func (fs *filesystem) storePodNetwork(podID string, networkNS NetworkNamespace) error {
return fs.storePodResource(podID, networkFileType, networkNS)
}

func (fs *filesystem) storeHypervisorState(podID string, state interface{}) error {
return fs.storeHypervisorResource(true, podID, "", hypervisorFileType, state)
}

func (fs *filesystem) deletePodResources(podID string, resources []podResource) error {
if resources == nil {
resources = []podResource{configFileType, stateFileType}
Expand Down
7 changes: 6 additions & 1 deletion hypervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ type HypervisorConfig struct {
// Pod configuration VMConfig.Memory overwrites this.
DefaultMemSz uint32

// DefaultBridges specifies default number of bridges for the VM.
// Bridges can be used to hot plug devices
DefaultBridges uint32

// MemPrealloc specifies if the memory should be pre-allocated
MemPrealloc bool

Expand Down Expand Up @@ -335,7 +339,7 @@ func RunningOnVMM(cpuInfoPath string) (bool, error) {
// hypervisor is the virtcontainers hypervisor interface.
// The default hypervisor implementation is Qemu.
type hypervisor interface {
init(config HypervisorConfig) error
init(pod *Pod) error
createPod(podConfig PodConfig) error
startPod(startCh, stopCh chan struct{}) error
stopPod() error
Expand All @@ -346,4 +350,5 @@ type hypervisor interface {
hotplugRemoveDevice(devInfo interface{}, devType deviceType) error
getPodConsole(podID string) string
capabilities() capabilities
getState() interface{}
}
8 changes: 6 additions & 2 deletions mock_hypervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ package virtcontainers
type mockHypervisor struct {
}

func (m *mockHypervisor) init(config HypervisorConfig) error {
valid, err := config.valid()
func (m *mockHypervisor) init(pod *Pod) error {
valid, err := pod.config.HypervisorConfig.valid()
if valid == false || err != nil {
return err
}
Expand Down Expand Up @@ -69,3 +69,7 @@ func (m *mockHypervisor) hotplugRemoveDevice(devInfo interface{}, devType device
func (m *mockHypervisor) getPodConsole(podID string) string {
return ""
}

func (m *mockHypervisor) getState() interface{} {
return nil
}

0 comments on commit 6c938cf

Please sign in to comment.