Skip to content

Commit

Permalink
ContainerRegistryDisk:v1alpha KubeVirt Runtime Implementation
Browse files Browse the repository at this point in the history
Signed-off-by: David Vossel <[email protected]>
  • Loading branch information
davidvossel committed Jul 27, 2017
1 parent 741fff0 commit ec6468d
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 1 deletion.
197 changes: 197 additions & 0 deletions pkg/registry-disk/registry-disk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* This file is part of the kubevirt project
*
* 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.
*
* Copyright 2017 Red Hat, Inc.
*
*/

package registrydisk

import (
"fmt"
"strconv"
"strings"

"github.com/jeevatkm/go-model"

kubev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"

"kubevirt.io/kubevirt/pkg/api/v1"
)

const registryDiskV1Alpha = "ContainerRegistryDisk:v1alpha"
const defaultIqn = "iqn.2017-01.io.kubevirt:wrapper/1"

func DisksAreReady(pod *kubev1.Pod) bool {
// Wait for readiness probes on image wrapper containers
for _, containerStatus := range pod.Status.ContainerStatuses {
if strings.Contains(containerStatus.Name, "disk") == false {
// only check readiness of disk containers
continue
}
if containerStatus.Ready == false {
return false
}
}
return true
}

// The virt-handler converts registry disks to their corresponding iscsi network
// disks when the VM spec is being defined as a domain with libvirt.
// The ports and host of the iscsi disks are already provided here by the controller.
func MapRegistryDisks(vm *v1.VM) (*v1.VM, error) {
vmCopy := &v1.VM{}
model.Copy(vmCopy, vm)

for idx, disk := range vmCopy.Spec.Domain.Devices.Disks {
if disk.Type == registryDiskV1Alpha {
newDisk := v1.Disk{}

newDisk.Type = "network"
newDisk.Device = "disk"
newDisk.Target = disk.Target

newDisk.Driver = &v1.DiskDriver{
Type: "raw",
Name: "qemu",
Cache: "none",
}

newDisk.Source.Name = defaultIqn
newDisk.Source.Protocol = "iscsi"
newDisk.Source.Host = disk.Source.Host

vmCopy.Spec.Domain.Devices.Disks[idx] = newDisk
}
}

return vmCopy, nil
}

// TODO Introduce logic that dynamically generates iscsi CHAP
// Authentication credentials for a VM spec backed by registry disks.
//
//func ApplyAuth(vm *v1.VM) {
// INSERT DYNAMIC AUTH LOGIC HERE
//}

// The controller applies ports to registry disks when a VM spec is introduced into the cluster.
func ApplyPorts(vm *v1.VM) {
wrapperStartingPort := 3261
for idx, disk := range vm.Spec.Domain.Devices.Disks {
if disk.Type == registryDiskV1Alpha {
port := fmt.Sprintf("%d", wrapperStartingPort)
name := "127.0.0.1"
if disk.Source.Host != nil {
name = disk.Source.Host.Name
}
// We fill in the port here for the iscsi target
// to coordinate avoiding port collisions in the virt-launcher pod.
vm.Spec.Domain.Devices.Disks[idx].Source.Host = &v1.DiskSourceHost{
Port: port,
Name: name,
}
wrapperStartingPort++
}
}
}

// The controller uses this function communicate the IP address of the POD
// with the containers hosting the registry disks.
func ApplyHost(vm *v1.VM, pod *kubev1.Pod) {
ip := pod.Status.PodIP
for idx, disk := range vm.Spec.Domain.Devices.Disks {
if disk.Type == registryDiskV1Alpha {
port := "3261"
name := ip
if disk.Source.Host != nil {
port = disk.Source.Host.Port
}
vm.Spec.Domain.Devices.Disks[idx].Source.Host = &v1.DiskSourceHost{
Port: port,
Name: name,
}
}
}
}

// The controller uses this function to generate the container
// specs for hosting the container registry disks.
func GenerateContainers(vm *v1.VM) ([]kubev1.Container, error) {
var containers []kubev1.Container

initialDelaySeconds := 5
timeoutSeconds := 5
periodSeconds := 10
successThreshold := 2
failureThreshold := 5

// Make VM Image Wrapper Containers
diskCount := 0
for _, disk := range vm.Spec.Domain.Devices.Disks {
if disk.Type == registryDiskV1Alpha {
diskContainerName := fmt.Sprintf("disk%d", diskCount)
// container image is disk.Source.Name
diskContainerImage := disk.Source.Name
// disk.Source.Host.Port has the port field expanded before templating.
port := disk.Source.Host.Port
portInt, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
containers = append(containers, kubev1.Container{
Name: diskContainerName,
Image: diskContainerImage,
ImagePullPolicy: kubev1.PullIfNotPresent,
Command: []string{"/entry-point.sh", port},
Ports: []kubev1.ContainerPort{
kubev1.ContainerPort{
ContainerPort: int32(portInt),
Protocol: kubev1.ProtocolTCP,
},
},
Env: []kubev1.EnvVar{
kubev1.EnvVar{
Name: "PORT",
Value: port,
},
// TODO once dynamic auth is implemented, pass creds as
// PASSWORD and USERNAME env vars. The registry disk base
// container already knows how to enable authentication
// when those env vars are present.
},
// The readiness probes ensure the ISCSI targets are available
// before the container is marked as "Ready: True"
ReadinessProbe: &kubev1.Probe{
Handler: kubev1.Handler{
TCPSocket: &kubev1.TCPSocketAction{
Port: intstr.IntOrString{
Type: intstr.Int,
IntVal: int32(portInt),
},
},
},
InitialDelaySeconds: int32(initialDelaySeconds),
PeriodSeconds: int32(periodSeconds),
TimeoutSeconds: int32(timeoutSeconds),
SuccessThreshold: int32(successThreshold),
FailureThreshold: int32(failureThreshold),
},
})
}
}
return containers, nil
}
9 changes: 8 additions & 1 deletion pkg/virt-controller/services/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"kubevirt.io/kubevirt/pkg/api/v1"
"kubevirt.io/kubevirt/pkg/logging"
"kubevirt.io/kubevirt/pkg/precond"
registrydisk "kubevirt.io/kubevirt/pkg/registry-disk"
)

type TemplateService interface {
Expand All @@ -54,6 +55,12 @@ func (t *templateService) RenderLaunchManifest(vm *v1.VM) (*kubev1.Pod, error) {
Command: []string{"/virt-launcher", "--qemu-timeout", "60s"},
}

containers, err := registrydisk.GenerateContainers(vm)
if err != nil {
return nil, err
}
containers = append(containers, container)

// TODO use constants for labels
pod := kubev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -66,7 +73,7 @@ func (t *templateService) RenderLaunchManifest(vm *v1.VM) (*kubev1.Pod, error) {
},
Spec: kubev1.PodSpec{
RestartPolicy: kubev1.RestartPolicyNever,
Containers: []kubev1.Container{container},
Containers: containers,
NodeSelector: vm.Spec.NodeSelector,
},
}
Expand Down
13 changes: 13 additions & 0 deletions pkg/virt-controller/watch/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package watch

import (
"errors"
"strings"
"time"

Expand All @@ -35,6 +36,7 @@ import (
kubev1 "kubevirt.io/kubevirt/pkg/api/v1"
"kubevirt.io/kubevirt/pkg/kubecli"
"kubevirt.io/kubevirt/pkg/logging"
registrydisk "kubevirt.io/kubevirt/pkg/registry-disk"
"kubevirt.io/kubevirt/pkg/virt-controller/services"
)

Expand Down Expand Up @@ -179,6 +181,8 @@ func (c *VMController) execute(key string) error {
}
}

registrydisk.ApplyPorts(&vmCopy)

// Create a Pod which will be the VM destination
if err := c.vmService.StartVMPod(&vmCopy); err != nil {
logger.Error().Reason(err).Msg("Defining a target pod for the VM failed.")
Expand Down Expand Up @@ -226,8 +230,17 @@ func (c *VMController) execute(key string) error {
return nil
}

// Ensure registry disks are online before placing VM
if registrydisk.DisksAreReady(&pods.Items[0]) == false {
return errors.New("waiting on image wrapper disks to become ready")
}

// VM got scheduled
vmCopy.Status.Phase = kubev1.Scheduled

// Fill in host info for container registry disks
registrydisk.ApplyHost(&vmCopy, &pods.Items[0])

// FIXME we store this in the metadata since field selectors are currently not working for TPRs
if vmCopy.GetObjectMeta().GetLabels() == nil {
vmCopy.ObjectMeta.Labels = map[string]string{}
Expand Down
7 changes: 7 additions & 0 deletions pkg/virt-handler/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"kubevirt.io/kubevirt/pkg/api/v1"
"kubevirt.io/kubevirt/pkg/kubecli"
"kubevirt.io/kubevirt/pkg/logging"
registrydisk "kubevirt.io/kubevirt/pkg/registry-disk"
"kubevirt.io/kubevirt/pkg/virt-handler/virtwrap"
"kubevirt.io/kubevirt/pkg/virt-handler/virtwrap/api"
)
Expand Down Expand Up @@ -299,6 +300,12 @@ func (d *VMHandlerDispatch) processVmUpdate(vm *v1.VM, shouldDeleteVm bool) erro
return err
}

// Map Container Registry Disks to block devices Libvirt can consume
vm, err = registrydisk.MapRegistryDisks(vm)
if err != nil {
return err
}

// TODO MigrationNodeName should be a pointer
if vm.Status.MigrationNodeName != "" {
// Only sync if the VM is not marked as migrating.
Expand Down

0 comments on commit ec6468d

Please sign in to comment.