Skip to content

Commit

Permalink
(to-be-fixed-up-later) make vhost-net requirement + useEmulation to s…
Browse files Browse the repository at this point in the history
…kip asking for the device
  • Loading branch information
Koichiro Den committed Oct 4, 2018
1 parent 4a815fd commit d5f9e20
Show file tree
Hide file tree
Showing 19 changed files with 42 additions and 218 deletions.
4 changes: 0 additions & 4 deletions api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -3774,10 +3774,6 @@
"bridge": {
"$ref": "#/definitions/v1.InterfaceBridge"
},
"driver": {
"description": "Backend driver name.\nOne of: qemu, vhost\n+optional",
"type": "string"
},
"macAddress": {
"description": "Interface MAC address. For example: de:ad:00:00:be:af or DE-AD-00-00-BE-AF.",
"type": "string"
Expand Down
13 changes: 10 additions & 3 deletions docs/software-emulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ By default KubeVirt uses the `/dev/kvm` device to enable hardware emulation.
This is almost always desirable, however there are a few exceptions where this
approach is problematic. For instance, running KubeVirt on a cluster where the
nodes do not support hardware emulation.

If `useEmulation` is enabled, hardware emulation via `/dev/kvm` will not be
attempted. `qemu` will be used for software emulation instead.
In the same way, by default KubeVirt requires presence of `/dev/vhost-net`
in case that at least one network interface model is virtio (note: if the NIC
model is not explicitly specified, by default virtio is chosen).

If `useEmulation` is enabled,
- hardware emulation via `/dev/kvm` will not be attempted. `qemu` will be used
for software emulation instead.
- in-kernel virtio-net backend emulation via `/dev/vhost-net` will not be
attempted. QEMU userland virtio NIC emulation will be used for virtio-net
interface instead.

# Configuration

Expand Down
2 changes: 0 additions & 2 deletions manifests/generated/vm-resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,6 @@ spec:
format: int32
type: integer
bridge: {}
driver:
type: string
macAddress:
type: string
model:
Expand Down
2 changes: 0 additions & 2 deletions manifests/generated/vmi-resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ spec:
format: int32
type: integer
bridge: {}
driver:
type: string
macAddress:
type: string
model:
Expand Down
2 changes: 0 additions & 2 deletions manifests/generated/vmipreset-resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ spec:
format: int32
type: integer
bridge: {}
driver:
type: string
macAddress:
type: string
model:
Expand Down
2 changes: 0 additions & 2 deletions manifests/generated/vmirs-resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ spec:
format: int32
type: integer
bridge: {}
driver:
type: string
macAddress:
type: string
model:
Expand Down
7 changes: 0 additions & 7 deletions pkg/api/v1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 0 additions & 16 deletions pkg/api/v1/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -751,24 +751,8 @@ type Interface struct {
// If specified, the virtual network interface will be placed on the guests pci address with the specifed PCI address. For example: 0000:81:01.10
// +optional
PciAddress string `json:"pciAddress,omitempty"`
// Backend driver name.
// One of: qemu, vhost
// +optional
Driver InterfaceDriver `json:"driver,omitempty"`
}

// InterfaceDriver represents a backend driver type for an network interface.
// ---
// +k8s:openapi-gen=true
type InterfaceDriver string

const (
// InterfaceDriverQEMU indicates that the backend for a network interface is QEMU userland NIC emulation.
InterfaceDriverQEMU InterfaceDriver = "qemu"
// InterfaceDriverVhost indicates that the backend for a network interface is vhost-net device.
InterfaceDriverVhost InterfaceDriver = "vhost"
)

// Represents the method which will be used to connect the interface to the guest.
// Only one of its members may be specified.
// ---
Expand Down
1 change: 0 additions & 1 deletion pkg/api/v1/schema_swagger_generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,6 @@ func (Interface) SwaggerDoc() map[string]string {
"macAddress": "Interface MAC address. For example: de:ad:00:00:be:af or DE-AD-00-00-BE-AF.",
"bootOrder": "BootOrder is an integer value > 0, used to determine ordering of boot devices.\nLower values take precedence.\nEach interface or disk that has a boot order must have a unique value.\nInterfaces without a boot order are not tried.\n+optional",
"pciAddress": "If specified, the virtual network interface will be placed on the guests pci address with the specifed PCI address. For example: 0000:81:01.10\n+optional",
"driver": "Backend driver name.\nOne of: qemu, vhost\n+optional",
}
}

Expand Down
22 changes: 0 additions & 22 deletions pkg/api/v1/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,28 +382,6 @@ var _ = Describe("Schema", func() {
Expect(err).To(BeNil())
Expect(string(buf)).To(Equal(exampleJSONParsed))
})
It("Marshal struct into json with driver configure", func() {
exampleVMI.Spec.Domain.Devices.Interfaces = []Interface{
Interface{
Name: "default",
InterfaceBindingMethod: InterfaceBindingMethod{
Bridge: &InterfaceBridge{}},
Driver: InterfaceDriverVhost,
},
}
networkTemplateData := NetworkTemplateConfig{InterfaceConfig: `"bridge": {},
"driver": "vhost"`}

tmpl, err := template.New("vmexample").Parse(exampleJSON)
Expect(err).To(BeNil())
var tpl bytes.Buffer
err = tmpl.Execute(&tpl, networkTemplateData)
Expect(err).To(BeNil())
exampleJSONParsed := tpl.String()
buf, err := json.MarshalIndent(*exampleVMI, "", " ")
Expect(err).To(BeNil())
Expect(string(buf)).To(Equal(exampleJSONParsed))
})
})
Context("With example schema in json use pod network and slirp interface", func() {
It("Unmarshal json into struct", func() {
Expand Down
26 changes: 0 additions & 26 deletions pkg/virt-api/webhooks/validating-webhook/validating-webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,6 @@ func ValidateVirtualMachineInstanceSpec(field *k8sfield.Path, spec *v1.VirtualMa
bootOrderMap[order] = true
}
}

// verify that the specified pci address is valid
if iface.PciAddress != "" {
_, err := util.ParsePciAddress(iface.PciAddress)
Expand All @@ -907,31 +906,6 @@ func ValidateVirtualMachineInstanceSpec(field *k8sfield.Path, spec *v1.VirtualMa
})
}
}

// verify that the interface driver is valid
if iface.Slirp != nil && iface.Driver != "" {
if iface.Driver != v1.InterfaceDriverQEMU {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: fmt.Sprintf("Slirp interface works with qemu driver only"),
Field: field.Child("domain", "devices", "interfaces").Index(idx).Child("driver").String(),
})
}
} else if iface.Bridge != nil && iface.Driver != "" {
if iface.Driver != v1.InterfaceDriverQEMU && iface.Driver != v1.InterfaceDriverVhost {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: fmt.Sprintf("Bridge interface works with qemu or vhost driver only"),
Field: field.Child("domain", "devices", "interfaces").Index(idx).Child("driver").String(),
})
} else if iface.Driver == v1.InterfaceDriverVhost && iface.Model != "" && iface.Model != "virtio" {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: fmt.Sprintf("vhost driver can be used with virtio model only"),
Field: field.Child("domain", "devices", "interfaces").Index(idx).Child("driver").String(),
})
}
}
}

// Validate that every network was assign to an interface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1173,57 +1173,6 @@ var _ = Describe("Validating Webhook", func() {
Expect(causes[0].Field).To(Equal("fake.domain.devices.interfaces[0].pciAddress"))
}
})

It("should accept valid driver", func() {
vmi := v1.NewMinimalVMI("testvm")
vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{*v1.DefaultNetworkInterface()}
vmi.Spec.Networks = []v1.Network{*v1.DefaultPodNetwork()}
for _, driver := range []v1.InterfaceDriver{v1.InterfaceDriverQEMU, v1.InterfaceDriverVhost} {
vmi.Spec.Domain.Devices.Interfaces[0].Driver = driver
causes := ValidateVirtualMachineInstanceSpec(k8sfield.NewPath("fake"), &vmi.Spec)
// if this is processed correctly, it should not result in any error
Expect(len(causes)).To(Equal(0))
}
})
It("should reject invalid driver", func() {
vmi := v1.NewMinimalVMI("testvm")
vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{*v1.DefaultNetworkInterface()}
vmi.Spec.Networks = []v1.Network{*v1.DefaultPodNetwork()}
vmi.Spec.Domain.Devices.Interfaces[0].Driver = "invalid_driver"
causes := ValidateVirtualMachineInstanceSpec(k8sfield.NewPath("fake"), &vmi.Spec)
Expect(len(causes)).To(Equal(1))
Expect(causes[0].Field).To(Equal("fake.domain.devices.interfaces[0].driver"))
})
It("should reject invalid combination of interface binding method and driver", func() {
vmi := v1.NewMinimalVMI("testvm")
vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{v1.Interface{
Name: "default",
InterfaceBindingMethod: v1.InterfaceBindingMethod{
Slirp: &v1.InterfaceSlirp{},
},
Driver: v1.InterfaceDriverVhost,
}}
vmi.Spec.Networks = []v1.Network{*v1.DefaultPodNetwork()}
causes := ValidateVirtualMachineInstanceSpec(k8sfield.NewPath("fake"), &vmi.Spec)
Expect(len(causes)).To(Equal(1))
Expect(causes[0].Field).To(Equal("fake.domain.devices.interfaces[0].driver"))
})
It("should reject invalid combination of interface model and driver", func() {
vmi := v1.NewMinimalVMI("testvm")
vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{*v1.DefaultNetworkInterface()}
vmi.Spec.Networks = []v1.Network{*v1.DefaultPodNetwork()}
// virtio works with all drivers, so we omit empty model case (default: virtio).
for _, model := range validInterfaceModels {
if model == "virtio" {
continue
}
vmi.Spec.Domain.Devices.Interfaces[0].Model = model
vmi.Spec.Domain.Devices.Interfaces[0].Driver = v1.InterfaceDriverVhost
causes := ValidateVirtualMachineInstanceSpec(k8sfield.NewPath("fake"), &vmi.Spec)
Expect(len(causes)).To(Equal(1))
Expect(causes[0].Field).To(Equal("fake.domain.devices.interfaces[0].driver"))
}
})
})
Context("with cpu pinning", func() {
var vmi *v1.VirtualMachineInstance
Expand Down
10 changes: 7 additions & 3 deletions pkg/virt-controller/services/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ func (t *templateService) RenderLaunchManifest(vmi *v1.VirtualMachineInstance) (
resources.Limits = make(k8sv1.ResourceList)
}

extraResources := getRequiredResources(vmi)
extraResources := getRequiredResources(vmi, useEmulation)
for key, val := range extraResources {
resources.Limits[key] = val
}
Expand Down Expand Up @@ -557,13 +557,17 @@ func getRequiredCapabilities(vmi *v1.VirtualMachineInstance) []k8sv1.Capability
return res
}

func getRequiredResources(vmi *v1.VirtualMachineInstance) k8sv1.ResourceList {
func getRequiredResources(vmi *v1.VirtualMachineInstance, useEmulation bool) k8sv1.ResourceList {
res := k8sv1.ResourceList{}
if (vmi.Spec.Domain.Devices.AutoattachPodInterface == nil) || (*vmi.Spec.Domain.Devices.AutoattachPodInterface == true) {
res[TunDevice] = resource.MustParse("1")
}
for _, iface := range vmi.Spec.Domain.Devices.Interfaces {
if iface.Driver == v1.InterfaceDriverVhost {
if !useEmulation && (iface.Model == "" || iface.Model == "virtio") {
// Note that about network interface, useEmulation does not make
// any difference on eventual Domain xml, but uniformly making
// /dev/vhost-net unavailable and libvirt implicitly fallback
// to use QEMU userland NIC emulation.
res[VhostNetDevice] = resource.MustParse("1")
}
}
Expand Down
28 changes: 20 additions & 8 deletions pkg/virt-launcher/virtwrap/api/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,18 @@ func Convert_v1_VirtualMachine_To_api_Domain(vmi *v1.VirtualMachineInstance, dom
return err
}

virtioNetProhibited := false
if _, err := os.Stat("/dev/vhost-net"); os.IsNotExist(err) {
if c.UseEmulation {
logger := log.DefaultLogger()
logger.Infof("In-kernel virtio-net device emulation '/dev/vhost-net' not present. Falling back to QEMU userland emulation.")
} else {
virtioNetProhibited = true
}
} else if err != nil {
return err
}

// Spec metadata
domain.Spec.Metadata.KubeVirt.UID = vmi.UID
if vmi.Spec.TerminationGracePeriodSeconds != nil {
Expand Down Expand Up @@ -642,15 +654,22 @@ func Convert_v1_VirtualMachine_To_api_Domain(vmi *v1.VirtualMachineInstance, dom
return fmt.Errorf("failed to find network %s", iface.Name)
}

ifaceType := getInterfaceType(&iface)
domainIface := Interface{
Model: &Model{
Type: getInterfaceType(&iface),
Type: ifaceType,
},
Alias: &Alias{
Name: iface.Name,
},
}

// if UseEmulation unset and at least one NIC model is virtio,
// /dev/vhost-net must be present as we should have asked for it.
if ifaceType == "virtio" && virtioNetProhibited {
return fmt.Errorf("In-kernel virtio-net device emulation '/dev/vhost-net' not present")
}

// Add a pciAddress if specifed
if iface.PciAddress != "" {
dbsfFields, err := util.ParsePciAddress(iface.PciAddress)
Expand Down Expand Up @@ -702,13 +721,6 @@ func Convert_v1_VirtualMachine_To_api_Domain(vmi *v1.VirtualMachineInstance, dom
return err
}
}
if iface.Driver != "" {
if iface.Driver != v1.InterfaceDriverQEMU && iface.Driver != v1.InterfaceDriverVhost {
return fmt.Errorf("The network interface driver type of %s should not be set.", iface.Name)
} else {
domainIface.Driver = &InterfaceDriver{Name: string(iface.Driver)}
}
}
domain.Spec.Devices.Interfaces = append(domain.Spec.Devices.Interfaces, domainIface)
}

Expand Down
24 changes: 0 additions & 24 deletions pkg/virt-launcher/virtwrap/api/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -803,30 +803,6 @@ var _ = Describe("Converter", func() {
Expect(domain.Spec.Devices.Interfaces[0].BootOrder.Order).To(Equal(uint(bootOrder)))
Expect(domain.Spec.Devices.Interfaces[1].BootOrder).To(BeNil())
})
table.DescribeTable("should allow setting driver", func(drivers []v1.InterfaceDriver) {
for i, driver := range drivers {
name := fmt.Sprintf("Name%d", i)
iface := v1.DefaultNetworkInterface()
net := v1.DefaultPodNetwork()
iface.Name = name
iface.Driver = driver
net.Name = name
vmi.Spec.Domain.Devices.Interfaces = append(vmi.Spec.Domain.Devices.Interfaces, *iface)
vmi.Spec.Networks = append(vmi.Spec.Networks, *net)
}
domain := vmiToDomain(vmi, c)

Expect(len(domain.Spec.Devices.Interfaces)).To(Equal(len(drivers)))
for i, driver := range drivers {
Expect(domain.Spec.Devices.Interfaces[i].Driver.Name).To(Equal(string(driver)))
}
},
table.Entry("one interface backed by qemu driver", []v1.InterfaceDriver{v1.InterfaceDriverQEMU}),
table.Entry("one interface backed by vhost driver", []v1.InterfaceDriver{v1.InterfaceDriverVhost}),
table.Entry("two interfaces backed by qemu drivers explicitly", []v1.InterfaceDriver{v1.InterfaceDriverVhost, v1.InterfaceDriverQEMU}),
table.Entry("two interfaces backed by vhost drivers", []v1.InterfaceDriver{v1.InterfaceDriverVhost, v1.InterfaceDriverQEMU}),
table.Entry("one interface backed by vhost driver and another by qemu", []v1.InterfaceDriver{v1.InterfaceDriverVhost, v1.InterfaceDriverQEMU}),
)
})
Context("Function ParseNameservers()", func() {
It("should return a byte array of nameservers", func() {
Expand Down
25 changes: 0 additions & 25 deletions pkg/virt-launcher/virtwrap/api/deepcopy_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d5f9e20

Please sign in to comment.