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

Commit 42df5a6

Browse files
author
Julio Montes
committed
hypervisor: add hot plugging support for Qemu Q35
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 devices on PCI bridges in pc and q35 machine types. Signed-off-by: Julio Montes <julio.montes@intel.com>
1 parent 506f076 commit 42df5a6

11 files changed

Lines changed: 367 additions & 25 deletions

api_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,9 @@ func TestStatusPodSuccessfulStateReady(t *testing.T) {
595595
KernelPath: filepath.Join(testDir, testKernel),
596596
ImagePath: filepath.Join(testDir, testImage),
597597
HypervisorPath: filepath.Join(testDir, testHypervisor),
598+
DefaultVCPUs: defaultVCPUs,
599+
DefaultMemSz: defaultMemSzMiB,
600+
DefaultBridges: defaultBridges,
598601
}
599602

600603
expectedStatus := PodStatus{
@@ -648,6 +651,9 @@ func TestStatusPodSuccessfulStateRunning(t *testing.T) {
648651
KernelPath: filepath.Join(testDir, testKernel),
649652
ImagePath: filepath.Join(testDir, testImage),
650653
HypervisorPath: filepath.Join(testDir, testHypervisor),
654+
DefaultVCPUs: defaultVCPUs,
655+
DefaultMemSz: defaultMemSzMiB,
656+
DefaultBridges: defaultBridges,
651657
}
652658

653659
expectedStatus := PodStatus{

bridge.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//
2+
// Copyright (c) 2017 Intel Corporation
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
package virtcontainers
18+
19+
import "fmt"
20+
21+
type bridgeType string
22+
23+
const (
24+
pciBridge bridgeType = "pci"
25+
pcieBridge = "pcie"
26+
)
27+
28+
const pciBridgeMaxCapacity = 30
29+
30+
// Bridge is a bridge where devices can be hot plugged
31+
type Bridge struct {
32+
// Address contains information about devices plugged and its address in the bridge
33+
Address map[uint32]string
34+
35+
// Type is the type of the bridge (pci, pcie, etc)
36+
Type bridgeType
37+
38+
//ID is used to identify the bridge in the hypervisor
39+
ID string
40+
}
41+
42+
// NewBridges creates n new pci(e) bridges depending of the machine type
43+
func NewBridges(n uint32, machine string) []Bridge {
44+
var bridges []Bridge
45+
var bt bridgeType
46+
47+
switch machine {
48+
case QemuQ35:
49+
// currently only pci bridges are supported
50+
// qemu-2.10 will introduce pcie bridges
51+
fallthrough
52+
case QemuPC:
53+
bt = pciBridge
54+
default:
55+
return nil
56+
}
57+
58+
for i := uint32(0); i < n; i++ {
59+
bridges = append(bridges, Bridge{
60+
Type: bt,
61+
ID: fmt.Sprintf("%s-bridge-%d", bt, i),
62+
Address: make(map[uint32]string),
63+
})
64+
}
65+
66+
return bridges
67+
}
68+
69+
// addDevice on success adds the device ID to the bridge and return the address
70+
// where the device was added, otherwise an error is returned
71+
func (b *Bridge) addDevice(ID string) (uint32, error) {
72+
var addr uint32
73+
74+
// looking for the first available address
75+
for i := uint32(1); i <= pciBridgeMaxCapacity; i++ {
76+
if _, ok := b.Address[i]; !ok {
77+
addr = i
78+
break
79+
}
80+
}
81+
82+
if addr == 0 {
83+
return 0, fmt.Errorf("Unable to hot plug device on bridge: there are not empty slots")
84+
}
85+
86+
// save address and device
87+
b.Address[addr] = ID
88+
return addr, nil
89+
}
90+
91+
// removeDevice on success removes the device ID from the bridge and return nil,
92+
// otherwise an error is returned
93+
func (b *Bridge) removeDevice(ID string) error {
94+
// check if the device was hot plugged in the bridge
95+
for addr, devID := range b.Address {
96+
if devID == ID {
97+
// free address to re-use the same slot with other devices
98+
delete(b.Address, addr)
99+
return nil
100+
}
101+
}
102+
103+
return fmt.Errorf("Unable to hot unplug device %s: not present on bridge", ID)
104+
}

bridge_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// Copyright (c) 2017 Intel Corporation
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
package virtcontainers
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
)
24+
25+
func TestNewBridges(t *testing.T) {
26+
assert := assert.New(t)
27+
var countBridges uint32 = 1
28+
29+
bridges := NewBridges(countBridges, "")
30+
assert.Nil(bridges)
31+
32+
bridges = NewBridges(countBridges, QemuQ35)
33+
assert.Len(bridges, int(countBridges))
34+
35+
b := bridges[0]
36+
assert.NotEmpty(b.ID)
37+
assert.NotNil(b.Address)
38+
}
39+
40+
func TestAddRemoveDevice(t *testing.T) {
41+
assert := assert.New(t)
42+
var countBridges uint32 = 1
43+
44+
// create a bridge
45+
bridges := NewBridges(countBridges, "")
46+
assert.Nil(bridges)
47+
bridges = NewBridges(countBridges, QemuQ35)
48+
assert.Len(bridges, int(countBridges))
49+
50+
// add device
51+
devID := "abc123"
52+
b := bridges[0]
53+
addr, err := b.addDevice(devID)
54+
assert.NoError(err)
55+
if addr < 1 {
56+
assert.Fail("address cannot be less then 1")
57+
}
58+
59+
// remove device
60+
err = b.removeDevice("")
61+
assert.Error(err)
62+
63+
err = b.removeDevice(devID)
64+
assert.NoError(err)
65+
}

filesystem.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ const (
4242
// networkFileType represents a network file type (pod only)
4343
networkFileType
4444

45+
// hypervisorFileType represents a hypervisor file type (pod only)
46+
hypervisorFileType
47+
4548
// processFileType represents a process file type
4649
processFileType
4750

@@ -64,6 +67,9 @@ const stateFile = "state.json"
6467
// networkFile is the file name storing a pod network.
6568
const networkFile = "network.json"
6669

70+
// hypervisorFile is the file name storing a hypervisor's state.
71+
const hypervisorFile = "hypervisor.json"
72+
6773
// processFile is the file name storing a container process.
6874
const processFile = "process.json"
6975

@@ -109,6 +115,10 @@ type resourceStorage interface {
109115
fetchPodNetwork(podID string) (NetworkNamespace, error)
110116
storePodNetwork(podID string, networkNS NetworkNamespace) error
111117

118+
// Hypervisor resources
119+
fetchHypervisorState(podID string, state interface{}) error
120+
storeHypervisorState(podID string, state interface{}) error
121+
112122
// Container resources
113123
storeContainerResource(podID, containerID string, resource podResource, data interface{}) error
114124
deleteContainerResources(podID, containerID string, resources []podResource) error
@@ -319,7 +329,7 @@ func (fs *filesystem) fetchDeviceFile(fileData []byte, devices *[]Device) error
319329
func resourceNeedsContainerID(podSpecific bool, resource podResource) bool {
320330

321331
switch resource {
322-
case lockFileType, networkFileType:
332+
case lockFileType, networkFileType, hypervisorFileType:
323333
// pod-specific resources
324334
return false
325335
default:
@@ -342,7 +352,7 @@ func resourceDir(podSpecific bool, podID, containerID string, resource podResour
342352
case configFileType:
343353
path = configStoragePath
344354
break
345-
case stateFileType, networkFileType, processFileType, lockFileType, mountsFileType, devicesFileType:
355+
case stateFileType, networkFileType, processFileType, lockFileType, mountsFileType, devicesFileType, hypervisorFileType:
346356
path = runStoragePath
347357
break
348358
default:
@@ -378,6 +388,8 @@ func (fs *filesystem) resourceURI(podSpecific bool, podID, containerID string, r
378388
filename = stateFile
379389
case networkFileType:
380390
filename = networkFile
391+
case hypervisorFileType:
392+
filename = hypervisorFile
381393
case processFileType:
382394
filename = processFile
383395
case lockFileType:
@@ -429,6 +441,7 @@ func (fs *filesystem) commonResourceChecks(podSpecific bool, podID, containerID
429441
case configFileType:
430442
case stateFileType:
431443
case networkFileType:
444+
case hypervisorFileType:
432445
case processFileType:
433446
case mountsFileType:
434447
case devicesFileType:
@@ -598,10 +611,23 @@ func (fs *filesystem) fetchPodNetwork(podID string) (NetworkNamespace, error) {
598611
return networkNS, nil
599612
}
600613

614+
func (fs *filesystem) fetchHypervisorState(podID string, state interface{}) error {
615+
return fs.fetchResource(true, podID, "", hypervisorFileType, state)
616+
}
617+
601618
func (fs *filesystem) storePodNetwork(podID string, networkNS NetworkNamespace) error {
602619
return fs.storePodResource(podID, networkFileType, networkNS)
603620
}
604621

622+
func (fs *filesystem) storeHypervisorState(podID string, state interface{}) error {
623+
hypervisorFile, _, err := fs.resourceURI(true, podID, "", hypervisorFileType)
624+
if err != nil {
625+
return err
626+
}
627+
628+
return fs.storeFile(hypervisorFile, state)
629+
}
630+
605631
func (fs *filesystem) deletePodResources(podID string, resources []podResource) error {
606632
if resources == nil {
607633
resources = []podResource{configFileType, stateFileType}

hypervisor.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ const (
4444
defaultVCPUs = 1
4545
// 2 GiB
4646
defaultMemSzMiB = 2048
47+
48+
defaultBridges = 1
4749
)
4850

4951
// deviceType describes a virtualized device type.
@@ -161,6 +163,10 @@ type HypervisorConfig struct {
161163
// Pod configuration VMConfig.Memory overwrites this.
162164
DefaultMemSz uint32
163165

166+
// DefaultBridges specifies default number of bridges for the VM.
167+
// Bridges can be used to hot plug devices
168+
DefaultBridges uint32
169+
164170
// MemPrealloc specifies if the memory should be pre-allocated
165171
MemPrealloc bool
166172

@@ -203,6 +209,10 @@ func (conf *HypervisorConfig) valid() (bool, error) {
203209
conf.DefaultMemSz = defaultMemSzMiB
204210
}
205211

212+
if conf.DefaultBridges == 0 {
213+
conf.DefaultBridges = defaultBridges
214+
}
215+
206216
return true, nil
207217
}
208218

@@ -434,7 +444,7 @@ func RunningOnVMM(cpuInfoPath string) (bool, error) {
434444
// hypervisor is the virtcontainers hypervisor interface.
435445
// The default hypervisor implementation is Qemu.
436446
type hypervisor interface {
437-
init(config HypervisorConfig) error
447+
init(pod *Pod) error
438448
createPod(podConfig PodConfig) error
439449
startPod(startCh, stopCh chan struct{}) error
440450
stopPod() error
@@ -445,4 +455,5 @@ type hypervisor interface {
445455
hotplugRemoveDevice(devInfo interface{}, devType deviceType) error
446456
getPodConsole(podID string) string
447457
capabilities() capabilities
458+
getState() interface{}
448459
}

hypervisor_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ func TestHypervisorConfigDefaults(t *testing.T) {
179179
HypervisorPath: "",
180180
DefaultVCPUs: defaultVCPUs,
181181
DefaultMemSz: defaultMemSzMiB,
182+
DefaultBridges: defaultBridges,
182183
}
183184
if reflect.DeepEqual(hypervisorConfig, hypervisorConfigDefaultsExpected) == false {
184185
t.Fatal()

mock_hypervisor.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ package virtcontainers
1919
type mockHypervisor struct {
2020
}
2121

22-
func (m *mockHypervisor) init(config HypervisorConfig) error {
23-
valid, err := config.valid()
22+
func (m *mockHypervisor) init(pod *Pod) error {
23+
valid, err := pod.config.HypervisorConfig.valid()
2424
if valid == false || err != nil {
2525
return err
2626
}
@@ -69,3 +69,7 @@ func (m *mockHypervisor) hotplugRemoveDevice(devInfo interface{}, devType device
6969
func (m *mockHypervisor) getPodConsole(podID string) string {
7070
return ""
7171
}
72+
73+
func (m *mockHypervisor) getState() interface{} {
74+
return nil
75+
}

mock_hypervisor_test.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,30 @@ import (
2525
func TestMockHypervisorInit(t *testing.T) {
2626
var m *mockHypervisor
2727

28-
wrongConfig := HypervisorConfig{
29-
KernelPath: "",
30-
ImagePath: "",
31-
HypervisorPath: "",
28+
pod := &Pod{
29+
config: &PodConfig{
30+
HypervisorConfig: HypervisorConfig{
31+
KernelPath: "",
32+
ImagePath: "",
33+
HypervisorPath: "",
34+
},
35+
},
3236
}
3337

34-
err := m.init(wrongConfig)
38+
// wrong config
39+
err := m.init(pod)
3540
if err == nil {
3641
t.Fatal()
3742
}
3843

39-
rightConfig := HypervisorConfig{
44+
pod.config.HypervisorConfig = HypervisorConfig{
4045
KernelPath: fmt.Sprintf("%s/%s", testDir, testKernel),
4146
ImagePath: fmt.Sprintf("%s/%s", testDir, testImage),
4247
HypervisorPath: fmt.Sprintf("%s/%s", testDir, testHypervisor),
4348
}
4449

45-
err = m.init(rightConfig)
50+
// right config
51+
err = m.init(pod)
4652
if err != nil {
4753
t.Fatal(err)
4854
}

0 commit comments

Comments
 (0)