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

Support VM volume storage migration between different volume and access modes #1399

Merged
merged 1 commit into from
Jan 29, 2025
Merged
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
2 changes: 2 additions & 0 deletions config/crds/migration.openshift.io_migplans.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ spec:
type: string
namespace:
type: string
ownerType:
type: string
volumeMode:
description: PersistentVolumeMode describes how a volume
is intended to be consumed, either Block or Filesystem.
Expand Down
15 changes: 15 additions & 0 deletions pkg/apis/migration/v1alpha1/migplan_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -885,13 +885,28 @@ type PV struct {
ProposedCapacity resource.Quantity `json:"proposedCapacity,omitempty"`
}

type OwnerType string

const (
VirtualMachine OwnerType = "VirtualMachine"
Deployment OwnerType = "Deployment"
DeploymentConfig OwnerType = "DeploymentConfig"
StatefulSet OwnerType = "StatefulSet"
ReplicaSet OwnerType = "ReplicaSet"
DaemonSet OwnerType = "DaemonSet"
Job OwnerType = "Job"
CronJob OwnerType = "CronJob"
Unknown OwnerType = "Unknown"
)

// PVC
type PVC struct {
Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"`
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
AccessModes []kapi.PersistentVolumeAccessMode `json:"accessModes,omitempty" protobuf:"bytes,1,rep,name=accessModes,casttype=PersistentVolumeAccessMode"`
VolumeMode kapi.PersistentVolumeMode `json:"volumeMode,omitempty"`
HasReference bool `json:"hasReference,omitempty"`
OwnerType OwnerType `json:"ownerType,omitempty"`
}

// GetTargetName returns name of the target PVC
Expand Down
1 change: 1 addition & 0 deletions pkg/controller/directvolumemigration/pvcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func (t *Task) createDestinationDV(srcClient, destClient compat.Client, pvc miga
}
}
destPVC.Spec.Resources.Requests[corev1.ResourceStorage] = size
destPVC.Spec.VolumeMode = pvc.TargetVolumeMode
return createBlankDataVolumeFromPVC(destClient, destPVC)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/migmigration/migmigration_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func (r *ReconcileMigMigration) Reconcile(ctx context.Context, request reconcile
// Validate
err = r.validate(ctx, migration)
if err != nil {
log.Info("Validation failed, requeueing")
log.V(3).Info("Validation failed, requeueing")
sink.Trace(err)
return reconcile.Result{Requeue: true}, nil
}
Expand Down
108 changes: 83 additions & 25 deletions pkg/controller/migplan/pvlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import (
migpods "github.com/konveyor/mig-controller/pkg/pods"
migref "github.com/konveyor/mig-controller/pkg/reference"
"github.com/opentracing/opentracing-go"
appsv1 "k8s.io/api/apps/v1"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
)

Expand Down Expand Up @@ -352,30 +354,32 @@ func isStorageConversionPlan(plan *migapi.MigPlan) bool {
// Get a list of PVCs found within the specified namespaces.
func (r *ReconcileMigPlan) getClaims(client compat.Client, plan *migapi.MigPlan) (Claims, error) {
claims := Claims{}
list := &core.PersistentVolumeClaimList{}
err := client.List(context.TODO(), list, &k8sclient.ListOptions{})
if err != nil {
return nil, liberr.Wrap(err)
pvcList := []core.PersistentVolumeClaim{}
for _, namespace := range plan.GetSourceNamespaces() {
list := &core.PersistentVolumeClaimList{}
err := client.List(context.TODO(), list, k8sclient.InNamespace(namespace))
if err != nil {
return nil, liberr.Wrap(err)
}
pvcList = append(pvcList, list.Items...)
}

podList, err := migpods.ListTemplatePods(client, plan.GetSourceNamespaces())
if err != nil {
return nil, liberr.Wrap(err)
}

runningPods := &core.PodList{}
err = client.List(context.TODO(), runningPods, &k8sclient.ListOptions{})
if err != nil {
return nil, liberr.Wrap(err)
}

inNamespaces := func(objNamespace string, namespaces []string) bool {
for _, ns := range namespaces {
if ns == objNamespace {
return true
for _, namespace := range plan.GetSourceNamespaces() {
pods := &core.PodList{}
err = client.List(context.TODO(), pods, k8sclient.InNamespace(namespace))
if err != nil {
return nil, liberr.Wrap(err)
}
for _, pod := range pods.Items {
if pod.Status.Phase == core.PodRunning {
podList = append(podList, pod)
}
}
return false
}

alreadyMigrated := func(pvc core.PersistentVolumeClaim) bool {
Expand All @@ -398,17 +402,11 @@ func (r *ReconcileMigPlan) getClaims(client compat.Client, plan *migapi.MigPlan)

isStorageConversionPlan := isStorageConversionPlan(plan)

for _, pod := range runningPods.Items {
if inNamespaces(pod.Namespace, plan.GetSourceNamespaces()) {
podList = append(podList, pod)
}
pvcToOwnerMap, err := r.createPVCToOwnerTypeMap(podList)
if err != nil {
return nil, liberr.Wrap(err)
}

for _, pvc := range list.Items {
if !inNamespaces(pvc.Namespace, plan.GetSourceNamespaces()) {
continue
}

for _, pvc := range pvcList {
if isStorageConversionPlan && (alreadyMigrated(pvc) || migrationSourceOtherPlan(pvc)) {
continue
}
Expand All @@ -434,11 +432,71 @@ func (r *ReconcileMigPlan) getClaims(client compat.Client, plan *migapi.MigPlan)
AccessModes: accessModes,
VolumeMode: volumeMode,
HasReference: pvcInPodVolumes(pvc, podList),
OwnerType: pvcToOwnerMap[pvc.Name],
})
}
return claims, nil
}

func (r *ReconcileMigPlan) createPVCToOwnerTypeMap(podList []core.Pod) (map[string]migapi.OwnerType, error) {
pvcToOwnerMap := make(map[string]migapi.OwnerType)
for _, pod := range podList {
for _, vol := range pod.Spec.Volumes {
if vol.PersistentVolumeClaim != nil {
// Only check for owner references if there is a single owner and the volume wasn't set already.
ownerType, ok := pvcToOwnerMap[vol.PersistentVolumeClaim.ClaimName]
if pod.OwnerReferences != nil && len(pod.OwnerReferences) == 1 {
for _, owner := range pod.OwnerReferences {
newOwnerType := migapi.Unknown
if owner.Kind == "StatefulSet" && owner.APIVersion == "apps/v1" {
newOwnerType = migapi.StatefulSet
} else if owner.Kind == "ReplicaSet" && owner.APIVersion == "apps/v1" {
// Check if the owner is a Deployment
replicaSet := &appsv1.ReplicaSet{}
if owner.Name != "" {
err := r.Client.Get(context.TODO(), k8sclient.ObjectKey{
Namespace: pod.Namespace,
Name: owner.Name,
}, replicaSet)
if err != nil && !errors.IsNotFound(err) {
return nil, err
}
}
if len(replicaSet.OwnerReferences) == 1 && replicaSet.OwnerReferences[0].Kind == "Deployment" && replicaSet.OwnerReferences[0].APIVersion == "apps/v1" {
newOwnerType = migapi.Deployment
} else {
newOwnerType = migapi.ReplicaSet
}
} else if owner.Kind == "Deployment" && owner.APIVersion == "apps/v1" {
newOwnerType = migapi.Deployment
} else if owner.Kind == "DaemonSet" && owner.APIVersion == "apps/v1" {
newOwnerType = migapi.DaemonSet
} else if owner.Kind == "Job" && owner.APIVersion == "batch/v1" {
newOwnerType = migapi.Job
} else if owner.Kind == "CronJob" && owner.APIVersion == "batch/v1" {
newOwnerType = migapi.CronJob
} else if owner.Kind == "VirtualMachineInstance" && (owner.APIVersion == "kubevirt.io/v1" || owner.APIVersion == "kubevirt.io/v1alpha3") {
newOwnerType = migapi.VirtualMachine
} else if owner.Kind == "Pod" && strings.HasPrefix(pod.Name, "hp-") {
newOwnerType = migapi.VirtualMachine
} else {
newOwnerType = migapi.Unknown
}
if !ok {
pvcToOwnerMap[vol.PersistentVolumeClaim.ClaimName] = newOwnerType
} else if ownerType != newOwnerType {
pvcToOwnerMap[vol.PersistentVolumeClaim.ClaimName] = migapi.Unknown
}
}
} else {
pvcToOwnerMap[vol.PersistentVolumeClaim.ClaimName] = migapi.Unknown
}
}
}
}
return pvcToOwnerMap, nil
}

// Determine the supported PV actions.
func (r *ReconcileMigPlan) getSupportedActions(pv core.PersistentVolume, claim migapi.PVC) []string {
supportedActions := []string{}
Expand Down
Loading