Skip to content

Commit

Permalink
Merge 50f8b12 into 8bcdddf
Browse files Browse the repository at this point in the history
  • Loading branch information
berrange committed Jul 4, 2017
2 parents 8bcdddf + 50f8b12 commit a4a54f7
Show file tree
Hide file tree
Showing 12 changed files with 145 additions and 117 deletions.
2 changes: 1 addition & 1 deletion cluster/verify-qemu-kube
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ done
# Check namespaces
echo ""
echo "Checking for required namespaces:"
NAMESPACES='pid net'
NAMESPACES='pid'
for NS in $NAMESPACES; do
NS_PRETTY=`echo $NS | tr '[:lower:]' '[:upper:]'`
VM_PID_NS=$(ls -iL /proc/$VM_PID/ns/$NS | cut -f 1 -d " ")
Expand Down
14 changes: 7 additions & 7 deletions cmd/virt-handler/migrate
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ do
key="$1"

case $key in
-p|--pod-ip)
POD_IP="$2"
-n|--node-ip)
NODE_IP="$2"
shift
;;
-s|--source)
Expand All @@ -25,15 +25,15 @@ esac
shift
done

if [ -z $POD_IP ] || [ -z $DEST ] || [ -z $SOURCE ] || [ -z $VM ] ; then
echo "Usage: migrate DOMAIN --source SOURCE --dest DESTINATION --pod-ip POD_IP"
if [ -z $NODE_IP ] || [ -z $DEST ] || [ -z $SOURCE ] || [ -z $VM ] ; then
echo "Usage: migrate DOMAIN --source SOURCE --dest DESTINATION --node-ip NODE_IP"
exit 1
fi

# Tell libvirt where qemu will listen for spice connections on the new host
virsh -c $SOURCE dumpxml $VM > $VM.xml
xmlstarlet ed --inplace -u "/domain/devices/graphics[@type='spice']/@listen" -v $POD_IP $VM.xml
xmlstarlet ed --inplace -u "/domain/devices/graphics[@type='spice']/listen/@address" -v $POD_IP $VM.xml
xmlstarlet ed --inplace -u "/domain/devices/graphics[@type='spice']/@listen" -v $NODE_IP $VM.xml
xmlstarlet ed --inplace -u "/domain/devices/graphics[@type='spice']/listen/@address" -v $NODE_IP $VM.xml

# Migrate
virsh -c $SOURCE migrate --xml $VM.xml $VM $DEST tcp://$POD_IP
virsh -c $SOURCE migrate --xml $VM.xml $VM $DEST spice://$NODE_IP
21 changes: 1 addition & 20 deletions images/libvirt-kubevirt/qemu-kube
Original file line number Diff line number Diff line change
Expand Up @@ -92,25 +92,6 @@ set -e
# Start the qemu process in the cgroups of the docker container
# to adhere to the resource limitations of the container.
exec sudo -C 10000 unshare --mount bash -s << END
function _term() {
rm -rf /etc/resolv.conf.\$\$
pkill -P \$! --signal SIG\$1
}
trap "_term TERM" TERM
trap "_term INT" INT
trap "_term HUP" HUP
trap "_term QUIT" QUIT
trap "_term TERM" EXIT
trap "_term TERM" ERR
# Make sure qemu can resolve kubernetes DNS entries
nsenter -m -t $CONTAINER_PID cat /etc/resolv.conf > /etc/resolv.conf.\$\$
mount --bind /etc/resolv.conf.\$\$ /etc/resolv.conf
cgclassify -g ${CGROUPS}:$CGROUP_PATH --sticky \$\$
nsenter -t $CONTAINER_PID -n -p $CMD &
wait
exec nsenter -t $CONTAINER_PID -p $CMD
END
7 changes: 7 additions & 0 deletions pkg/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ type VMStatus struct {
MigrationNodeName string `json:"migrationNodeName,omitempty"`
Conditions []VMCondition `json:"conditions,omitempty"`
Phase VMPhase `json:"phase"`
Graphics []VMGraphics `json:"graphics"`
}

type VMGraphics struct {
Type string `json:"type"`
Host string `json:"host"`
Port int32 `json:"port"`
}

// Required to satisfy Object interface
Expand Down
4 changes: 4 additions & 0 deletions pkg/api/v1/types_swagger_generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func (VMStatus) SwaggerDoc() map[string]string {
}
}

func (VMGraphics) SwaggerDoc() map[string]string {
return map[string]string{}
}

func (VMCondition) SwaggerDoc() map[string]string {
return map[string]string{}
}
Expand Down
41 changes: 4 additions & 37 deletions pkg/virt-api/rest/spice.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,11 @@ package rest
import (
"flag"
"fmt"
"strings"

"github.com/go-kit/kit/endpoint"
"golang.org/x/net/context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api"
kubev1 "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/rest"

"kubevirt.io/kubevirt/pkg/api/v1"
Expand Down Expand Up @@ -73,28 +67,13 @@ func spiceFromVM(vm *v1.VM, coreCli *kubernetes.Clientset) (*v1.Spice, error) {
}

// TODO allow specifying the spice device. For now select the first one.
for _, d := range vm.Spec.Domain.Devices.Graphics {
if strings.ToLower(d.Type) == "spice" {
port := d.Port
podList, err := coreCli.CoreV1().Pods(api.NamespaceDefault).List(unfinishedVMPodSelector(vm))
if err != nil {
return nil, middleware.NewInternalServerError(err)
}

// The pod could just have failed now
if len(podList.Items) == 0 {
// TODO is that the right return code?
return nil, middleware.NewResourceNotFoundError("VM is not running")
}

pod := podList.Items[0]
ip := pod.Status.PodIP

for _, d := range vm.Status.Graphics {
if d.Type == "spice" {
spice := v1.NewSpice(vm.GetObjectMeta().GetName())
spice.Info = v1.SpiceInfo{
Type: "spice",
Host: ip,
Port: port,
Host: d.Host,
Port: d.Port,
}
if spiceProxy != "" {
spice.Info.Proxy = fmt.Sprintf("http://%s", spiceProxy)
Expand All @@ -105,15 +84,3 @@ func spiceFromVM(vm *v1.VM, coreCli *kubernetes.Clientset) (*v1.Spice, error) {

return nil, middleware.NewResourceNotFoundError("No spice device attached to the VM found.")
}

// TODO for now just copied from VMService
func unfinishedVMPodSelector(vm *v1.VM) metav1.ListOptions {
fieldSelector := fields.ParseSelectorOrDie(
"status.phase!=" + string(kubev1.PodFailed) +
",status.phase!=" + string(kubev1.PodSucceeded))
labelSelector, err := labels.Parse(fmt.Sprintf(v1.DomainLabel+" in (%s)", vm.GetObjectMeta().GetName()))
if err != nil {
panic(err)
}
return metav1.ListOptions{FieldSelector: fieldSelector.String(), LabelSelector: labelSelector.String()}
}
16 changes: 1 addition & 15 deletions pkg/virt-controller/services/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ package services

import (
"fmt"
"strconv"
"strings"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubev1 "k8s.io/client-go/pkg/api/v1"
Expand Down Expand Up @@ -56,18 +54,6 @@ func (t *templateService) RenderLaunchManifest(vm *v1.VM) (*kubev1.Pod, error) {
Command: []string{"/virt-launcher", "-qemu-timeout", "60s"},
}

// Set up spice ports
ports := []kubev1.ContainerPort{}
for i, g := range vm.Spec.Domain.Devices.Graphics {
if strings.ToLower(g.Type) == "spice" {
ports = append(ports, kubev1.ContainerPort{
ContainerPort: g.Port,
Name: "spice" + strconv.Itoa(i),
})
}
}
container.Ports = ports

// TODO use constants for labels
pod := kubev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -132,7 +118,7 @@ func (t *templateService) RenderMigrationJob(vm *v1.VM, sourceNode *kubev1.Node,
Name: "virt-migration",
Image: t.migratorImage,
Command: []string{
"/migrate", vm.Spec.Domain.Name, "--source", srcUri, "--dest", destUri, "--pod-ip", targetPod.Status.PodIP,
"/migrate", vm.Spec.Domain.Name, "--source", srcUri, "--dest", destUri, "--node-ip", dstAddr,
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion pkg/virt-controller/services/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ var _ = Describe("Template", func() {
refCommand := []string{
"/migrate", "testvm", "--source", "qemu+tcp://127.0.0.2/system",
"--dest", "qemu+tcp://127.0.0.3/system",
"--pod-ip", "127.0.0.1"}
"--node-ip", "127.0.0.3"}
Expect(job.Spec.Containers[0].Command).To(Equal(refCommand))
})
})
Expand Down
8 changes: 5 additions & 3 deletions pkg/virt-handler/virtwrap/generated_mock_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
libvirt_go "github.com/libvirt/libvirt-go"

v1 "kubevirt.io/kubevirt/pkg/api/v1"
api "kubevirt.io/kubevirt/pkg/virt-handler/virtwrap/api"
)

// Mock of DomainManager interface
Expand All @@ -31,10 +32,11 @@ func (_m *MockDomainManager) EXPECT() *_MockDomainManagerRecorder {
return _m.recorder
}

func (_m *MockDomainManager) SyncVM(_param0 *v1.VM) error {
func (_m *MockDomainManager) SyncVM(_param0 *v1.VM) (*api.DomainSpec, error) {
ret := _m.ctrl.Call(_m, "SyncVM", _param0)
ret0, _ := ret[0].(error)
return ret0
ret0, _ := ret[0].(*api.DomainSpec)
ret1, _ := ret[1].(error)
return ret0, ret1
}

func (_mr *_MockDomainManagerRecorder) SyncVM(arg0 interface{}) *gomock.Call {
Expand Down
32 changes: 22 additions & 10 deletions pkg/virt-handler/virtwrap/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import (
)

type DomainManager interface {
SyncVM(*v1.VM) error
SyncVM(*v1.VM) (*api.DomainSpec, error)
KillVM(*v1.VM) error
}

Expand Down Expand Up @@ -322,11 +322,11 @@ func NewLibvirtDomainManager(connection Connection, recorder record.EventRecorde
return &manager, nil
}

func (l *LibvirtDomainManager) SyncVM(vm *v1.VM) error {
func (l *LibvirtDomainManager) SyncVM(vm *v1.VM) (*api.DomainSpec, error) {
var wantedSpec api.DomainSpec
mappingErrs := model.Copy(&wantedSpec, vm.Spec.Domain)
if len(mappingErrs) > 0 {
return errors.NewAggregate(mappingErrs)
return nil, errors.NewAggregate(mappingErrs)
}
dom, err := l.virConn.LookupDomainByName(vm.GetObjectMeta().GetName())
if err != nil {
Expand All @@ -335,26 +335,26 @@ func (l *LibvirtDomainManager) SyncVM(vm *v1.VM) error {
xmlStr, err := xml.Marshal(&wantedSpec)
if err != nil {
logging.DefaultLogger().Object(vm).Error().Reason(err).Msg("Generating the domain XML failed.")
return err
return nil, err
}
logging.DefaultLogger().Object(vm).Info().V(3).Msg("Domain XML generated.")
dom, err = l.virConn.DomainDefineXML(string(xmlStr))
if err != nil {
logging.DefaultLogger().Object(vm).Error().Reason(err).Msg("Defining the VM failed.")
return err
return nil, err
}
logging.DefaultLogger().Object(vm).Info().Msg("Domain defined.")
l.recorder.Event(vm, kubev1.EventTypeNormal, v1.Created.String(), "VM defined")
} else {
logging.DefaultLogger().Object(vm).Error().Reason(err).Msg("Getting the domain failed.")
return err
return nil, err
}
}
defer dom.Free()
domState, _, err := dom.GetState()
if err != nil {
logging.DefaultLogger().Object(vm).Error().Reason(err).Msg("Getting the domain state failed.")
return err
return nil, err
}
// TODO Suspend, Pause, ..., for now we only support reaching the running state
// TODO for migration and error detection we also need the state change reason
Expand All @@ -364,7 +364,7 @@ func (l *LibvirtDomainManager) SyncVM(vm *v1.VM) error {
err := dom.Create()
if err != nil {
logging.DefaultLogger().Object(vm).Error().Reason(err).Msg("Starting the VM failed.")
return err
return nil, err
}
logging.DefaultLogger().Object(vm).Info().Msg("Domain started.")
l.recorder.Event(vm, kubev1.EventTypeNormal, v1.Started.String(), "VM started.")
Expand All @@ -373,7 +373,7 @@ func (l *LibvirtDomainManager) SyncVM(vm *v1.VM) error {
err := dom.Resume()
if err != nil {
logging.DefaultLogger().Object(vm).Error().Reason(err).Msg("Resuming the VM failed.")
return err
return nil, err
}
logging.DefaultLogger().Object(vm).Info().Msg("Domain resumed.")
l.recorder.Event(vm, kubev1.EventTypeNormal, v1.Resumed.String(), "VM resumed")
Expand All @@ -382,8 +382,20 @@ func (l *LibvirtDomainManager) SyncVM(vm *v1.VM) error {
// TODO: blocked state
}

xmlstr, err := dom.GetXMLDesc(0)
if err != nil {
return nil, err
}

var newSpec api.DomainSpec
err = xml.Unmarshal([]byte(xmlstr), &newSpec)
if err != nil {
logging.DefaultLogger().Object(vm).Error().Reason(err).Msg("Parsing domain XML failed.")
return nil, err
}

// TODO: check if VM Spec and Domain Spec are equal or if we have to sync
return nil
return &newSpec, nil
}

func (l *LibvirtDomainManager) KillVM(vm *v1.VM) error {
Expand Down
32 changes: 27 additions & 5 deletions pkg/virt-handler/virtwrap/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,28 +68,44 @@ var _ = Describe("Manager", func() {
mockConn.EXPECT().DomainDefineXML(string(xml)).Return(mockDomain, nil)
mockDomain.EXPECT().GetState().Return(libvirt.DOMAIN_SHUTDOWN, 1, nil)
mockDomain.EXPECT().Create().Return(nil)
mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
manager, _ := NewLibvirtDomainManager(mockConn, recorder)
err = manager.SyncVM(vm)
newspec, err := manager.SyncVM(vm)
Expect(newspec).ToNot(BeNil())
Expect(err).To(BeNil())
Expect(<-recorder.Events).To(ContainSubstring(v1.Created.String()))
Expect(<-recorder.Events).To(ContainSubstring(v1.Started.String()))
Expect(recorder.Events).To(BeEmpty())
})
It("should leave a defined and started VM alone", func() {
vm := newVM("testvm")
var domainSpec api.DomainSpec
Expect(model.Copy(&domainSpec, vm.Spec.Domain)).To(BeEmpty())
xml, err := xml.Marshal(domainSpec)

mockConn.EXPECT().LookupDomainByName("testvm").Return(mockDomain, nil)
mockDomain.EXPECT().GetState().Return(libvirt.DOMAIN_RUNNING, 1, nil)
mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
manager, _ := NewLibvirtDomainManager(mockConn, recorder)
err := manager.SyncVM(newVM("testvm"))
newspec, err := manager.SyncVM(vm)
Expect(newspec).ToNot(BeNil())
Expect(err).To(BeNil())
Expect(recorder.Events).To(BeEmpty())
})
table.DescribeTable("should try to start a VM in state",
func(state libvirt.DomainState) {
vm := newVM("testvm")
var domainSpec api.DomainSpec
Expect(model.Copy(&domainSpec, vm.Spec.Domain)).To(BeEmpty())
xml, err := xml.Marshal(domainSpec)

mockConn.EXPECT().LookupDomainByName("testvm").Return(mockDomain, nil)
mockDomain.EXPECT().GetState().Return(state, 1, nil)
mockDomain.EXPECT().Create().Return(nil)
mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
manager, _ := NewLibvirtDomainManager(mockConn, recorder)
err := manager.SyncVM(newVM("testvm"))
newspec, err := manager.SyncVM(vm)
Expect(newspec).ToNot(BeNil())
Expect(err).To(BeNil())
Expect(<-recorder.Events).To(ContainSubstring(v1.Started.String()))
Expect(recorder.Events).To(BeEmpty())
Expand All @@ -100,17 +116,23 @@ var _ = Describe("Manager", func() {
table.Entry("unknown", libvirt.DOMAIN_NOSTATE),
)
It("should resume a paused VM", func() {
vm := newVM("testvm")
var domainSpec api.DomainSpec
Expect(model.Copy(&domainSpec, vm.Spec.Domain)).To(BeEmpty())
xml, err := xml.Marshal(domainSpec)

mockConn.EXPECT().LookupDomainByName("testvm").Return(mockDomain, nil)
mockDomain.EXPECT().GetState().Return(libvirt.DOMAIN_PAUSED, 1, nil)
mockDomain.EXPECT().Resume().Return(nil)
mockDomain.EXPECT().GetXMLDesc(libvirt.DomainXMLFlags(0)).Return(string(xml), nil)
manager, _ := NewLibvirtDomainManager(mockConn, recorder)
err := manager.SyncVM(newVM("testvm"))
newspec, err := manager.SyncVM(vm)
Expect(newspec).ToNot(BeNil())
Expect(err).To(BeNil())
Expect(<-recorder.Events).To(ContainSubstring(v1.Resumed.String()))
Expect(recorder.Events).To(BeEmpty())
})
})

Context("on successful VM kill", func() {
table.DescribeTable("should try to undefine a VM in state",
func(state libvirt.DomainState) {
Expand Down

0 comments on commit a4a54f7

Please sign in to comment.