• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / containerized-data-importer / #5676

17 Nov 2025 10:06PM UTC coverage: 58.665% (-0.08%) from 58.748%
#5676

push

travis-ci

web-flow
Copy Events from tmp PVCs during Clone (#3933)

* move CopyEvents to common package so it can be called from other controllers

Signed-off-by: dsanatar <dsanatar@redhat.com>

* copy events from tmp clone pvcs to their target pvc

Signed-off-by: dsanatar <dsanatar@redhat.com>

---------

Signed-off-by: dsanatar <dsanatar@redhat.com>

19 of 64 new or added lines in 6 files covered. (29.69%)

7 existing lines in 1 file now uncovered.

17389 of 29641 relevant lines covered (58.67%)

0.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

39.69
/pkg/controller/clone/host-clone.go
1
package clone
2

3
import (
4
        "context"
5
        "fmt"
6
        "net/http"
7
        "strconv"
8
        "time"
9

10
        "github.com/go-logr/logr"
11

12
        corev1 "k8s.io/api/core/v1"
13
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
14
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15
        "k8s.io/client-go/tools/record"
16

17
        "sigs.k8s.io/controller-runtime/pkg/client"
18
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
19

20
        cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
21
        cc "kubevirt.io/containerized-data-importer/pkg/controller/common"
22
        metrics "kubevirt.io/containerized-data-importer/pkg/monitoring/metrics/cdi-cloner"
23
)
24

25
// HostClonePhaseName is the name of the host clone phase
26
const HostClonePhaseName = "HostClone"
27

28
// HostClonePhase creates and monitors a dumb clone operation
29
type HostClonePhase struct {
30
        Owner             client.Object
31
        Namespace         string
32
        SourceName        string
33
        DesiredClaim      *corev1.PersistentVolumeClaim
34
        ImmediateBind     bool
35
        OwnershipLabel    string
36
        Preallocation     bool
37
        PriorityClassName string
38
        Client            client.Client
39
        Log               logr.Logger
40
        Recorder          record.EventRecorder
41
}
42

43
var _ Phase = &HostClonePhase{}
44

45
var _ StatusReporter = &HostClonePhase{}
46

47
var httpClient *http.Client
48

49
func init() {
1✔
50
        httpClient = cc.BuildHTTPClient(httpClient)
1✔
51
}
1✔
52

53
// Name returns the name of the phase
54
func (p *HostClonePhase) Name() string {
×
55
        return HostClonePhaseName
×
56
}
×
57

58
// Status returns the phase status
59
func (p *HostClonePhase) Status(ctx context.Context) (*PhaseStatus, error) {
×
60
        result := &PhaseStatus{}
×
61
        pvc := &corev1.PersistentVolumeClaim{}
×
62
        exists, err := getResource(ctx, p.Client, p.Namespace, p.DesiredClaim.Name, pvc)
×
63
        if err != nil {
×
64
                return nil, err
×
65
        }
×
66

67
        if !exists {
×
68
                return result, nil
×
69
        }
×
70

71
        result.Annotations = pvc.Annotations
×
72

×
73
        podName := pvc.Annotations[cc.AnnCloneSourcePod]
×
74
        if podName == "" {
×
75
                return result, nil
×
76
        }
×
77

78
        args := &progressFromClaimArgs{
×
79
                Client:       p.Client,
×
80
                HTTPClient:   httpClient,
×
81
                Claim:        pvc,
×
82
                PodNamespace: p.Namespace,
×
83
                PodName:      podName,
×
84
                OwnerUID:     string(p.Owner.GetUID()),
×
85
        }
×
86

×
87
        progress, err := progressFromClaim(ctx, args)
×
88
        if err != nil {
×
89
                return nil, err
×
90
        }
×
91

92
        result.Progress = progress
×
93

×
94
        return result, nil
×
95
}
96

97
// progressFromClaimArgs are the args for ProgressFromClaim
98
type progressFromClaimArgs struct {
99
        Client       client.Client
100
        HTTPClient   *http.Client
101
        Claim        *corev1.PersistentVolumeClaim
102
        OwnerUID     string
103
        PodNamespace string
104
        PodName      string
105
}
106

107
// progressFromClaim returns the progres
108
func progressFromClaim(ctx context.Context, args *progressFromClaimArgs) (string, error) {
×
109
        // Just set 100.0% if pod is succeeded
×
110
        if args.Claim.Annotations[cc.AnnPodPhase] == string(corev1.PodSucceeded) {
×
111
                return cc.ProgressDone, nil
×
112
        }
×
113

114
        pod := &corev1.Pod{
×
115
                ObjectMeta: metav1.ObjectMeta{
×
116
                        Namespace: args.PodNamespace,
×
117
                        Name:      args.PodName,
×
118
                },
×
119
        }
×
120
        if err := args.Client.Get(ctx, client.ObjectKeyFromObject(pod), pod); err != nil {
×
121
                if k8serrors.IsNotFound(err) {
×
122
                        return "", nil
×
123
                }
×
124
                return "", err
×
125
        }
126

127
        // This will only work when the clone source pod is running
128
        if pod.Status.Phase != corev1.PodRunning {
×
129
                return "", nil
×
130
        }
×
131
        url, err := cc.GetMetricsURL(pod)
×
132
        if err != nil {
×
133
                return "", err
×
134
        }
×
135
        if url == "" {
×
136
                return "", nil
×
137
        }
×
138

139
        // We fetch the clone progress from the clone source pod metrics
140
        progressReport, err := cc.GetProgressReportFromURL(ctx, url, args.HTTPClient, metrics.CloneProgressMetricName, args.OwnerUID)
×
141
        if err != nil {
×
142
                return "", err
×
143
        }
×
144
        if progressReport != "" {
×
145
                if f, err := strconv.ParseFloat(progressReport, 64); err == nil {
×
146
                        return fmt.Sprintf("%.2f%%", f), nil
×
147
                }
×
148
        }
149

150
        return "", nil
×
151
}
152

153
// Reconcile creates the desired pvc and waits for the operation to complete
154
func (p *HostClonePhase) Reconcile(ctx context.Context) (*reconcile.Result, error) {
1✔
155
        actualClaim := &corev1.PersistentVolumeClaim{}
1✔
156
        exists, err := getResource(ctx, p.Client, p.Namespace, p.DesiredClaim.Name, actualClaim)
1✔
157
        if err != nil {
1✔
158
                return nil, err
×
159
        }
×
160

161
        if !exists {
2✔
162
                actualClaim, err = p.createClaim(ctx)
1✔
163
                if err != nil {
1✔
164
                        return nil, err
×
165
                }
×
166
        }
167

168
        targetPvc, err := cc.GetAnnotatedEventSource(ctx, p.Client, actualClaim)
1✔
169
        if err != nil {
1✔
NEW
170
                return nil, err
×
NEW
171
        }
×
172
        cc.CopyEvents(actualClaim, targetPvc, p.Client, p.Recorder)
1✔
173

1✔
174
        if !p.hostCloneComplete(actualClaim) {
2✔
175
                // requeue to update status
1✔
176
                return &reconcile.Result{RequeueAfter: 3 * time.Second}, nil
1✔
177
        }
1✔
178

179
        return nil, nil
1✔
180
}
181

182
func (p *HostClonePhase) createClaim(ctx context.Context) (*corev1.PersistentVolumeClaim, error) {
1✔
183
        claim := p.DesiredClaim.DeepCopy()
1✔
184

1✔
185
        claim.Namespace = p.Namespace
1✔
186
        cc.AddAnnotation(claim, cc.AnnPreallocationRequested, fmt.Sprintf("%t", p.Preallocation))
1✔
187
        cc.AddAnnotation(claim, cc.AnnOwnerUID, string(p.Owner.GetUID()))
1✔
188
        cc.AddAnnotation(claim, cc.AnnPodRestarts, "0")
1✔
189
        cc.AddAnnotation(claim, cc.AnnCloneRequest, fmt.Sprintf("%s/%s", p.Namespace, p.SourceName))
1✔
190
        cc.AddAnnotation(claim, cc.AnnPopulatorKind, cdiv1.VolumeCloneSourceRef)
1✔
191
        cc.AddAnnotation(claim, cc.AnnEventSourceKind, p.Owner.GetObjectKind().GroupVersionKind().Kind)
1✔
192
        cc.AddAnnotation(claim, cc.AnnEventSource, fmt.Sprintf("%s/%s", p.Owner.GetNamespace(), p.Owner.GetName()))
1✔
193
        if p.OwnershipLabel != "" {
2✔
194
                AddOwnershipLabel(p.OwnershipLabel, claim, p.Owner)
1✔
195
        }
1✔
196
        if p.ImmediateBind {
2✔
197
                cc.AddAnnotation(claim, cc.AnnImmediateBinding, "")
1✔
198
        }
1✔
199
        if p.PriorityClassName != "" {
2✔
200
                cc.AddAnnotation(claim, cc.AnnPriorityClassName, p.PriorityClassName)
1✔
201
        }
1✔
202
        cc.AddLabel(claim, cc.LabelExcludeFromVeleroBackup, "true")
1✔
203

1✔
204
        if err := p.Client.Create(ctx, claim); err != nil {
1✔
205
                checkQuotaExceeded(p.Recorder, p.Owner, err)
×
206
                return nil, err
×
207
        }
×
208

209
        return claim, nil
1✔
210
}
211

212
func (p *HostClonePhase) hostCloneComplete(pvc *corev1.PersistentVolumeClaim) bool {
1✔
213
        // this is awfully lame
1✔
214
        // both the upload controller and clone controller update the PVC status to succeeded
1✔
215
        // but only the clone controller will set the preallocation annotation
1✔
216
        // so we have to wait for that
1✔
217
        if p.Preallocation && pvc.Annotations[cc.AnnPreallocationApplied] != "true" {
2✔
218
                return false
1✔
219
        }
1✔
220
        return pvc.Annotations[cc.AnnPodPhase] == string(cdiv1.Succeeded)
1✔
221
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc