Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MTV-1536 | Use CDI for disk transfer in cold migration #1109

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion pkg/apis/forklift/v1beta1/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ go_library(
"//pkg/apis/forklift/v1beta1/provider",
"//pkg/apis/forklift/v1beta1/ref",
"//pkg/lib/condition",
"//pkg/lib/error",
"//vendor/k8s.io/api/core/v1:core",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:meta",
"//vendor/k8s.io/apimachinery/pkg/runtime",
Expand Down
29 changes: 4 additions & 25 deletions pkg/apis/forklift/v1beta1/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"github.com/konveyor/forklift-controller/pkg/apis/forklift/v1beta1/provider"
"github.com/konveyor/forklift-controller/pkg/apis/forklift/v1beta1/ref"
libcnd "github.com/konveyor/forklift-controller/pkg/lib/condition"
liberr "github.com/konveyor/forklift-controller/pkg/lib/error"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
)
Expand Down Expand Up @@ -93,30 +92,6 @@ type Plan struct {
Referenced `json:"-"`
}

// If the plan calls for the vm to be cold migrated to the local cluster, we can
// just use virt-v2v directly to convert the vm while copying data over. In other
// cases, we use CDI to transfer disks to the destination cluster and then use
// virt-v2v-in-place to convert these disks after cutover.
func (p *Plan) VSphereColdLocal() (bool, error) {
source := p.Referenced.Provider.Source
if source == nil {
return false, liberr.New("Cannot analyze plan, source provider is missing.")
}
destination := p.Referenced.Provider.Destination
if destination == nil {
return false, liberr.New("Cannot analyze plan, destination provider is missing.")
}

switch source.Type() {
case VSphere:
return !p.Spec.Warm && destination.IsHost(), nil
case Ova:
return true, nil
default:
return false, nil
}
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type PlanList struct {
meta.TypeMeta `json:",inline"`
Expand All @@ -139,3 +114,7 @@ func (r *Plan) IsSourceProviderOvirt() bool {
func (r *Plan) IsSourceProviderOCP() bool {
return r.Provider.Source.Type() == OpenShift
}

func (r *Plan) IsSourceProviderOVA() bool {
return r.Provider.Source.Type() == Ova
}
32 changes: 10 additions & 22 deletions pkg/controller/plan/adapter/vsphere/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,28 +434,16 @@ func (r *Builder) DataVolumes(vmRef ref.Ref, secret *core.Secret, _ *core.Config
if disk.Datastore.ID == ds.ID {
storageClass := mapped.Destination.StorageClass
var dvSource cdi.DataVolumeSource
coldLocal, vErr := r.Context.Plan.VSphereColdLocal()
if vErr != nil {
err = vErr
return
}
if coldLocal {
// Let virt-v2v do the copying
dvSource = cdi.DataVolumeSource{
Blank: &cdi.DataVolumeBlankImage{},
}
} else {
// Let CDI do the copying
dvSource = cdi.DataVolumeSource{
VDDK: &cdi.DataVolumeSourceVDDK{
BackingFile: r.baseVolume(disk.File),
UUID: vm.UUID,
URL: url,
SecretRef: secret.Name,
Thumbprint: thumbprint,
InitImageURL: r.Source.Provider.Spec.Settings[api.VDDK],
},
}
// Let CDI do the copying
dvSource = cdi.DataVolumeSource{
VDDK: &cdi.DataVolumeSourceVDDK{
BackingFile: r.baseVolume(disk.File),
UUID: vm.UUID,
URL: url,
SecretRef: secret.Name,
Thumbprint: thumbprint,
InitImageURL: r.Source.Provider.Spec.Settings[api.VDDK],
},
}
dvSpec := cdi.DataVolumeSpec{
Source: &dvSource,
Expand Down
7 changes: 0 additions & 7 deletions pkg/controller/plan/adapter/vsphere/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,6 @@ func (r *Client) getChangeIds(vmRef ref.Ref, snapshotId string, hosts util.Hosts
}

func (r *Client) getClient(vm *model.VM, hosts util.HostsFunc) (client *vim25.Client, err error) {
if coldLocal, vErr := r.Plan.VSphereColdLocal(); vErr == nil && coldLocal {
// when virt-v2v runs the migration, forklift-controller should interact only
// with the component that serves the SDK endpoint of the provider
client = r.client.Client
return
}

if r.Source.Provider.Spec.Settings[v1beta1.SDK] == v1beta1.ESXI {
// when migrating from ESXi host, we use the client of the SDK endpoint of the provider,
// there's no need in a different client (the ESXi host is the only component involved in the migration)
Expand Down
54 changes: 5 additions & 49 deletions pkg/controller/plan/kubevirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -824,18 +824,12 @@ func (r *KubeVirt) getListOptionsNamespaced() (listOptions *client.ListOptions)

// Ensure the guest conversion (virt-v2v) pod exists on the destination.
func (r *KubeVirt) EnsureGuestConversionPod(vm *plan.VMStatus, vmCr *VirtualMachine, pvcs []*core.PersistentVolumeClaim) (err error) {
labels := r.vmLabels(vm.Ref)
v2vSecret, err := r.ensureSecret(vm.Ref, r.secretDataSetterForCDI(vm.Ref), labels)
if err != nil {
return
}

configMap, err := r.ensureLibvirtConfigMap(vm.Ref, vmCr, pvcs)
if err != nil {
return
}

newPod, err := r.guestConversionPod(vm, vmCr.Spec.Template.Spec.Volumes, configMap, pvcs, v2vSecret)
newPod, err := r.guestConversionPod(vm, vmCr.Spec.Template.Spec.Volumes, configMap, pvcs)
if err != nil {
return
}
Expand Down Expand Up @@ -1667,7 +1661,7 @@ func (r *KubeVirt) findTemplate(vm *plan.VMStatus) (tmpl *template.Template, err
return
}

func (r *KubeVirt) guestConversionPod(vm *plan.VMStatus, vmVolumes []cnv.Volume, configMap *core.ConfigMap, pvcs []*core.PersistentVolumeClaim, v2vSecret *core.Secret) (pod *core.Pod, err error) {
func (r *KubeVirt) guestConversionPod(vm *plan.VMStatus, vmVolumes []cnv.Volume, configMap *core.ConfigMap, pvcs []*core.PersistentVolumeClaim) (pod *core.Pod, err error) {
volumes, volumeMounts, volumeDevices, err := r.podVolumeMounts(vmVolumes, configMap, pvcs, vm)
if err != nil {
return
Expand All @@ -1684,34 +1678,6 @@ func (r *KubeVirt) guestConversionPod(vm *plan.VMStatus, vmVolumes []cnv.Volume,
user := qemuUser
nonRoot := true
allowPrivilageEscalation := false
// virt-v2v image
coldLocal, vErr := r.Context.Plan.VSphereColdLocal()
if vErr != nil {
err = vErr
return
}
if coldLocal {
// mount the secret for the password and CA certificate
volumes = append(volumes, core.Volume{
Name: "secret-volume",
VolumeSource: core.VolumeSource{
Secret: &core.SecretVolumeSource{
SecretName: v2vSecret.Name,
},
},
})
volumeMounts = append(volumeMounts, core.VolumeMount{
Name: "secret-volume",
ReadOnly: true,
MountPath: "/etc/secret",
})
} else {
environment = append(environment,
core.EnvVar{
Name: "V2V_inPlace",
Value: "1",
})
}
// VDDK image
var initContainers []core.Container
if vddkImage, found := r.Source.Provider.Spec.Settings[api.VDDK]; found {
Expand Down Expand Up @@ -1808,19 +1774,9 @@ func (r *KubeVirt) guestConversionPod(vm *plan.VMStatus, vmVolumes []cnv.Volume,
Name: "virt-v2v",
Env: environment,
ImagePullPolicy: core.PullAlways,
EnvFrom: []core.EnvFromSource{
{
Prefix: "V2V_",
SecretRef: &core.SecretEnvSource{
LocalObjectReference: core.LocalObjectReference{
Name: v2vSecret.Name,
},
},
},
},
Image: Settings.Migration.VirtV2vImage,
VolumeMounts: volumeMounts,
VolumeDevices: volumeDevices,
Image: Settings.Migration.VirtV2vImage,
VolumeMounts: volumeMounts,
VolumeDevices: volumeDevices,
Ports: []core.ContainerPort{
{
Name: "metrics",
Expand Down
34 changes: 8 additions & 26 deletions pkg/controller/plan/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ var (
HasPostHook libitr.Flag = 0x02
RequiresConversion libitr.Flag = 0x04
CDIDiskCopy libitr.Flag = 0x08
VirtV2vDiskCopy libitr.Flag = 0x10
OvaImageMigration libitr.Flag = 0x10
OpenstackImageMigration libitr.Flag = 0x20
)

Expand All @@ -61,7 +61,6 @@ const (
CreateDataVolumes = "CreateDataVolumes"
CreateVM = "CreateVM"
CopyDisks = "CopyDisks"
AllocateDisks = "AllocateDisks"
CopyingPaused = "CopyingPaused"
AddCheckpoint = "AddCheckpoint"
AddFinalCheckpoint = "AddFinalCheckpoint"
Expand All @@ -84,7 +83,6 @@ const (
const (
Initialize = "Initialize"
Cutover = "Cutover"
DiskAllocation = "DiskAllocation"
DiskTransfer = "DiskTransfer"
ImageConversion = "ImageConversion"
DiskTransferV2v = "DiskTransferV2v"
Expand All @@ -108,10 +106,9 @@ var (
{Name: WaitForPowerOff},
{Name: CreateDataVolumes},
{Name: CopyDisks, All: CDIDiskCopy},
{Name: AllocateDisks, All: VirtV2vDiskCopy},
{Name: CreateGuestConversionPod, All: RequiresConversion},
{Name: ConvertGuest, All: RequiresConversion},
{Name: CopyDisksVirtV2V, All: RequiresConversion},
{Name: CopyDisksVirtV2V, All: OvaImageMigration},
{Name: ConvertOpenstackSnapshot, All: OpenstackImageMigration},
{Name: CreateVM},
{Name: PostHook, All: HasPostHook},
Expand Down Expand Up @@ -643,8 +640,6 @@ func (r *Migration) step(vm *plan.VMStatus) (step string) {
switch vm.Phase {
case Started, CreateInitialSnapshot, WaitForInitialSnapshot, CreateDataVolumes:
step = Initialize
case AllocateDisks:
step = DiskAllocation
case CopyDisks, CopyingPaused, CreateSnapshot, WaitForSnapshot, AddCheckpoint, ConvertOpenstackSnapshot:
step = DiskTransfer
case CreateFinalSnapshot, WaitForFinalSnapshot, AddFinalCheckpoint, Finalize:
Expand Down Expand Up @@ -882,7 +877,7 @@ func (r *Migration) execute(vm *plan.VMStatus) (err error) {
step.MarkCompleted()
step.Phase = Completed
vm.Phase = r.next(vm.Phase)
case AllocateDisks, CopyDisks:
case CopyDisks:
step, found := vm.FindStep(r.step(vm))
if !found {
vm.AddError(fmt.Sprintf("Step '%s' not found", r.step(vm)))
Expand Down Expand Up @@ -1255,7 +1250,7 @@ func (r *Migration) buildPipeline(vm *plan.VM) (pipeline []*plan.Step, err error
Phase: Pending,
},
})
case AllocateDisks, CopyDisks, CopyDisksVirtV2V, ConvertOpenstackSnapshot:
case CopyDisks, CopyDisksVirtV2V, ConvertOpenstackSnapshot:
tasks, pErr := r.builder.Tasks(vm.Ref)
if pErr != nil {
err = liberr.Wrap(pErr)
Expand All @@ -1270,9 +1265,6 @@ func (r *Migration) buildPipeline(vm *plan.VM) (pipeline []*plan.Step, err error
case CopyDisks:
task_name = DiskTransfer
task_description = "Transfer disks."
case AllocateDisks:
task_name = DiskAllocation
task_description = "Allocate disks."
case CopyDisksVirtV2V:
task_name = DiskTransferV2v
task_description = "Copy disks."
Expand Down Expand Up @@ -1658,11 +1650,7 @@ func (r *Migration) updateConversionProgress(vm *plan.VMStatus, step *plan.Step)
break
}

coldLocal, err := r.Context.Plan.VSphereColdLocal()
switch {
case err != nil:
return liberr.Wrap(err)
case coldLocal:
if r.Context.Plan.IsSourceProviderOVA() {
if err := r.updateConversionProgressV2vMonitor(pod, step); err != nil {
// Just log it. Missing progress is not fatal.
log.Error(err, "Failed to update conversion progress")
Expand Down Expand Up @@ -1853,23 +1841,17 @@ type Predicate struct {

// Evaluate predicate flags.
func (r *Predicate) Evaluate(flag libitr.Flag) (allowed bool, err error) {
coldLocal, vErr := r.context.Plan.VSphereColdLocal()
if vErr != nil {
err = vErr
return
}

switch flag {
case HasPreHook:
_, allowed = r.vm.FindHook(PreHook)
case HasPostHook:
_, allowed = r.vm.FindHook(PostHook)
case RequiresConversion:
allowed = r.context.Source.Provider.RequiresConversion()
case OvaImageMigration:
allowed = r.context.Plan.IsSourceProviderOVA()
case CDIDiskCopy:
allowed = !coldLocal
case VirtV2vDiskCopy:
allowed = coldLocal
allowed = !r.context.Plan.IsSourceProviderOVA()
case OpenstackImageMigration:
allowed = r.context.Plan.IsSourceProviderOpenstack()
}
Expand Down
30 changes: 9 additions & 21 deletions pkg/controller/plan/scheduler/vsphere/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,27 +197,15 @@ func (r *Scheduler) buildPending() (err error) {
}

func (r *Scheduler) cost(vm *model.VM, vmStatus *plan.VMStatus) int {
coldLocal, _ := r.Plan.VSphereColdLocal()
if coldLocal {
switch vmStatus.Phase {
case CreateVM, PostHook, Completed:
// In these phases we already have the disk transferred and are left only to create the VM
// By setting the cost to 0 other VMs can start migrating
return 0
default:
return 1
}
} else {
switch vmStatus.Phase {
case CreateVM, PostHook, Completed, CopyingPaused, ConvertGuest, CreateGuestConversionPod:
// The warm/remote migrations this is done on already transferred disks,
// and we can start other VM migrations at these point.
// By setting the cost to 0 other VMs can start migrating
return 0
default:
// CDI transfers the disks in parallel by different pods
return len(vm.Disks) - r.finishedDisks(vmStatus)
}
switch vmStatus.Phase {
case CreateVM, PostHook, Completed, CopyingPaused, ConvertGuest, CreateGuestConversionPod:
// The warm/remote migrations this is done on already transferred disks,
// and we can start other VM migrations at these point.
// By setting the cost to 0 other VMs can start migrating
return 0
default:
// CDI transfers the disks in parallel by different pods
return len(vm.Disks) - r.finishedDisks(vmStatus)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,6 @@ func (admitter *PlanAdmitter) validateLUKS() error {
log.Error(err, "Provider type (non-VSphere & non-OVA) does not support LUKS")
return err
}

coldLocal, vErr := admitter.plan.VSphereColdLocal()
if vErr != nil {
log.Error(vErr, "Could not analyze plan, failing")
return vErr
}
if !coldLocal {
err := liberr.New("migration of encrypted disks is not supported for warm migrations or migrations to remote providers")
log.Error(err, "Warm migration does not support LUKS")
return err
}
return nil
}

Expand Down
Loading
Loading