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

kubevirt / containerized-data-importer / #5195

18 Mar 2025 12:53PM UTC coverage: 59.347%. First build
#5195

Pull #3681

travis-ci

akalenyu
Avoid waiting out entire tcp timeout in progress call

Pretty sure we lose a worker thread whenever the GET call to /metrics
gets issued against a completed pod. Reducing the timeout to 2 seconds
should minimize that window.

```
{"level":"error","ts":"2025-03-17T20:40:34Z","logger":"controller.import-populator","msg":"Failed to update import progress for pvc <NAME/NS>","error":"Get \"https://<IP>:8443/metrics\": dial tcp <IP>:8443: i/o timeout"
```

Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
Pull Request #3681: Avoid waiting out entire tcp timeout in progress call

3 of 14 new or added lines in 5 files covered. (21.43%)

16800 of 28308 relevant lines covered (59.35%)

0.66 hits per line

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

38.4
/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
NEW
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
        if !p.hostCloneComplete(actualClaim) {
2✔
169
                // requeue to update status
1✔
170
                return &reconcile.Result{RequeueAfter: 3 * time.Second}, nil
1✔
171
        }
1✔
172

173
        return nil, nil
1✔
174
}
175

176
func (p *HostClonePhase) createClaim(ctx context.Context) (*corev1.PersistentVolumeClaim, error) {
1✔
177
        claim := p.DesiredClaim.DeepCopy()
1✔
178

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

1✔
198
        if err := p.Client.Create(ctx, claim); err != nil {
1✔
199
                checkQuotaExceeded(p.Recorder, p.Owner, err)
×
200
                return nil, err
×
201
        }
×
202

203
        return claim, nil
1✔
204
}
205

206
func (p *HostClonePhase) hostCloneComplete(pvc *corev1.PersistentVolumeClaim) bool {
1✔
207
        // this is awfully lame
1✔
208
        // both the upload controller and clone controller update the PVC status to succeeded
1✔
209
        // but only the clone controller will set the preallocation annotation
1✔
210
        // so we have to wait for that
1✔
211
        if p.Preallocation && pvc.Annotations[cc.AnnPreallocationApplied] != "true" {
2✔
212
                return false
1✔
213
        }
1✔
214
        return pvc.Annotations[cc.AnnPodPhase] == string(cdiv1.Succeeded)
1✔
215
}
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

© 2026 Coveralls, Inc