cdi DV create 流程分析

createDV 被 DataVolumeController 处理。代码入口为 /cmd/cdi-operator/operator.go : main()

/cmd/cdi-controller.go main() - startLeaderElection() - start(ctx, cfg)

在 start 函数中将所有 controller 与 manager 绑定,包括 DatavolumeController。

NewDatavolumeController

通过NewDatavolumeController创建DVController,其中 addDatavolumeControllerWatches,addCloneWithoutSourceWatch 会监听 pod、 pvc、 dv、 sc 的状态变化,将相关dv.name+dv.ns.name加入到reconcile.Request队列中。

其中reconciler处理queue中的dv,使其seq与status一致。该段代码需要根据dv 决定是否要进行删除操作等如果dv.source.pvc != nil 则会进入clone流程reconcileClone

Reconcile

reconcile 流程

首先获取相关的 dv.obj 若dv.DeletionTimestamp != nil 则删除相关的所有 pvc pod dv 以及相关的ObjectTransfer。

如果 dv 设置了 sourceRef 会将 dv.Spec.Source 填充为相应的 pvc。sourceRef是一种使用最新 datasource 的模式,在创建时使用最新 pvc。

1
2
3
dv.Spec.Source = &cdiv1.DataVolumeSource{
  PVC: dataSource.Spec.Source.PVC,
}

尝试获取dv同名的的PVC。

1
2
3
4
5
6
7
pvc := &corev1.PersistentVolumeClaim{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: datavolume.Namespace, Name: datavolume.Name}, pvc); err != nil {
   // If the resource doesn't exist, we'll create it
   if k8serrors.IsNotFound(err) {
      pvc = nil
   }
...

若存在则检查该pvc 相关 ann 是否为了 dvname ,并确认是否该pvc 的 owner已经设置了,如果未设置则进行设置。

这时如果 dv 的 source.pvc != nil 则进入 reconcileclone 克隆流程,其中参数包括 dvPrePopulated。

dvPrePopulated 这个概念在 \staging\src\kubevirt.io\containerized-data-importer-api\pkg\apis\core\v1alpha1\utils.go 中有介绍,dv 对应的 target pvc 在 dv status 为 succeeded 后和未被 owned 的时候为 dvPrePopulated ==true。

1
2
3
4
5
	_, dvPrePopulated := datavolume.Annotations[AnnPrePopulated]

	if isClone := datavolume.Spec.Source.PVC != nil; isClone {
		return r.reconcileClone(log, datavolume, pvc, pvcSpec, transferName, dvPrePopulated, pvcPopulated)
	}

若isNotClone 且 dvPrePopulated 为 false 则进入准备,如创建 dv 同名的 pvc 等操作。

reconcileClone

在 reconcileClone 流程中会进行一系列的检查并且确定克隆策略,根据策略进入 reconcileSmartClonePvc 的流程中。

reconcileClone 流程

首先检查检查源pvc是否存在dv和源pvc ContentType是否一致dv size是否符合 。

接着查看dv的克隆策略默认 snapshot 源 pvc 的 storgeclass 根据 name 获得storageProfile.Status.CloneStrategy 若无特殊设定则返回默认策略。

根据源pvc尝试设置目标pvc spec的size。

进入smartClone的reconcile。

reconcileSmartClonePvc

该段代码主要判断是否需要进入跨 ns 克隆流程,并且根据源 pvc 创建 snapshot。可以注意到这时创建了snapshot 但是 target pvc 还未创建,个人认为后续逻辑在 smart-clone controller 中进行。

reconcileSmartClonePvc 流程

smart-clone-controller

smart-clone-controller 的主要逻辑也在 /pkg/controller/smart-clone-controller.go。 reconcile中这段代码主要逻辑是判断 dv 的 target PVC 是否存在,如果存在则进入 reconcilePVC 如果不存在再去检查 snapshot 是否存在,从上面的步骤中我们可以知道 snapshot 是存在的 pvc 还未建立,这时候进入 reconcileSnapshot 的逻辑。然后创建 target pvc。

smart-clone-controller reconcile流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
func (r *SmartCloneReconciler) reconcileSnapshot(log logr.Logger, snapshot *snapshotv1.VolumeSnapshot) (reconcile.Result, error) {
    log.WithValues("snapshot.Name", snapshot.Name).
        WithValues("snapshot.Namespace", snapshot.Namespace).
        Info("Reconciling snapshot")

// …
// pvc may have been transferred
//这里如果pvc isnotfound会返回nil 不会返回err进入后续代码
    targetPVC, err := r.getTargetPVC(dataVolume)
    if err != nil {
        return reconcile.Result{}, err
    }

    if targetPVC != nil {
        return reconcile.Result{}, nil
    }

    if snapshot.Status == nil || snapshot.Status.ReadyToUse == nil || !*snapshot.Status.ReadyToUse {
        // wait for ready to use
        return reconcile.Result{}, nil
    }
//dv.spec.pvc or dv.spec.storage
    targetPvcSpec, err := RenderPvcSpec(r.client, r.recorder, r.log, dataVolume)
    if err != nil {
        return reconcile.Result{}, err
    }
    newPvc, err := newPvcFromSnapshot(snapshot, targetPvcSpec)
    if err != nil {
        return reconcile.Result{}, err
    }
    util.SetRecommendedLabels(newPvc, r.installerLabels, "cdi-controller")

    if err := setAnnOwnedByDataVolume(newPvc, dataVolume); err != nil {

        return reconcile.Result{}, err
    }
    if len(dataVolume.GetAnnotations()) > 0 {
        for k, v := range dataVolume.GetAnnotations() {
            if !strings.Contains(k, common.CDIAnnKey) {
                newPvc.Annotations[k] = v
            }
        }
    }
    if snapshot.Spec.Source.PersistentVolumeClaimName != nil {
        event := &DataVolumeEvent{
            eventType: corev1.EventTypeNormal,
            reason:    SmartClonePVCInProgress,
            message:   fmt.Sprintf(MessageSmartClonePVCInProgress, snapshot.Namespace, *snapshot.Spec.Source.PersistentVolumeClaimName),
        }

        r.emitEvent(snapshot, event)
    }

log.V(3).Info("Creating PVC from snapshot", "pvc.Namespace", newPvc.Namespace, "pvc.Name", newPvc.Name)
//创建pvc 应该整个smart-clone的流程走完了。
    if err := r.client.Create(context.TODO(), newPvc); err != nil {
        if errQuotaExceeded(err) {
            event := &DataVolumeEvent{
                eventType: corev1.EventTypeWarning,
                reason:    ErrExceededQuota,
                message:   err.Error(),
            }

            r.emitEvent(snapshot, event)
        }
        log.Error(err, "error creating pvc from snapshot")
        return reconcile.Result{}, err
    }

    return reconcile.Result{}, nil
}
updatedupdated2023-02-282023-02-28