Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ws-daemon/pkg/controller/snapshot_controller.go
2500 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License-AGPL.txt in the project root for license information.
4
5
package controller
6
7
import (
8
"context"
9
"fmt"
10
11
corev1 "k8s.io/api/core/v1"
12
"k8s.io/client-go/tools/record"
13
"k8s.io/client-go/util/retry"
14
ctrl "sigs.k8s.io/controller-runtime"
15
"sigs.k8s.io/controller-runtime/pkg/client"
16
"sigs.k8s.io/controller-runtime/pkg/controller"
17
"sigs.k8s.io/controller-runtime/pkg/event"
18
"sigs.k8s.io/controller-runtime/pkg/log"
19
"sigs.k8s.io/controller-runtime/pkg/predicate"
20
21
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
22
)
23
24
// SnapshotReconciler reconciles a Snapshot object
25
type SnapshotReconciler struct {
26
client.Client
27
maxConcurrentReconciles int
28
nodeName string
29
operations WorkspaceOperations
30
recorder record.EventRecorder
31
}
32
33
func NewSnapshotController(c client.Client, recorder record.EventRecorder, nodeName string, maxConcurrentReconciles int, wso WorkspaceOperations) *SnapshotReconciler {
34
return &SnapshotReconciler{
35
Client: c,
36
maxConcurrentReconciles: maxConcurrentReconciles,
37
nodeName: nodeName,
38
operations: wso,
39
recorder: recorder,
40
}
41
}
42
43
// SetupWithManager sets up the controller with the Manager.
44
func (r *SnapshotReconciler) SetupWithManager(mgr ctrl.Manager) error {
45
return ctrl.NewControllerManagedBy(mgr).
46
Named("snapshot").
47
WithOptions(controller.Options{
48
MaxConcurrentReconciles: r.maxConcurrentReconciles,
49
}).
50
For(&workspacev1.Snapshot{}).
51
WithEventFilter(snapshotEventFilter(r.nodeName)).
52
Complete(r)
53
}
54
55
func snapshotEventFilter(nodeName string) predicate.Predicate {
56
return predicate.Funcs{
57
CreateFunc: func(e event.CreateEvent) bool {
58
if ss, ok := e.Object.(*workspacev1.Snapshot); ok {
59
return ss.Spec.NodeName == nodeName
60
}
61
return false
62
},
63
UpdateFunc: func(ue event.UpdateEvent) bool {
64
return false
65
},
66
DeleteFunc: func(de event.DeleteEvent) bool {
67
return false
68
},
69
}
70
}
71
72
//+kubebuilder:rbac:groups=workspace.gitpod.io,resources=snapshots,verbs=get;list;watch;create;update;patch;delete
73
//+kubebuilder:rbac:groups=workspace.gitpod.io,resources=snapshots/status,verbs=get;update;patch
74
//+kubebuilder:rbac:groups=workspace.gitpod.io,resources=snapshots/finalizers,verbs=update
75
76
// Reconcile is part of the main kubernetes reconciliation loop which aims to
77
// move the current state of the cluster closer to the desired state.
78
// For more details, check Reconcile and its Result here:
79
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
80
func (ssc *SnapshotReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
81
log := log.FromContext(ctx)
82
83
var snapshot workspacev1.Snapshot
84
if err := ssc.Client.Get(ctx, req.NamespacedName, &snapshot); err != nil {
85
return ctrl.Result{}, client.IgnoreNotFound(err)
86
}
87
88
if snapshot.Status.Completed {
89
return ctrl.Result{}, nil
90
}
91
92
snapshotURL, snapshotName, snapshotErr := ssc.operations.SnapshotIDs(ctx, snapshot.Spec.WorkspaceID)
93
if snapshotErr != nil {
94
return ctrl.Result{}, fmt.Errorf("failed to get snapshot name and URL: %w", snapshotErr)
95
}
96
97
err := retry.RetryOnConflict(retryParams, func() error {
98
err := ssc.Client.Get(ctx, req.NamespacedName, &snapshot)
99
if err != nil {
100
return err
101
}
102
103
snapshot.Status.URL = snapshotURL
104
return ssc.Client.Status().Update(ctx, &snapshot)
105
})
106
107
if err != nil {
108
log.Error(err, "could not set snapshot url", "workspace", snapshot.Spec.WorkspaceID)
109
return ctrl.Result{}, fmt.Errorf("could not set snapshot url: %w", err)
110
}
111
112
snapshotErr = ssc.operations.Snapshot(ctx, snapshot.Spec.WorkspaceID, snapshotName)
113
if snapshotErr != nil {
114
log.Error(snapshotErr, "could not take snapshot", "workspace", snapshot.Spec.WorkspaceID)
115
}
116
117
err = retry.RetryOnConflict(retryParams, func() error {
118
err := ssc.Client.Get(ctx, req.NamespacedName, &snapshot)
119
if err != nil {
120
return err
121
}
122
123
snapshot.Status.Completed = true
124
if snapshotErr != nil {
125
snapshot.Status.Error = fmt.Errorf("could not take snapshot: %w", snapshotErr).Error()
126
}
127
128
return ssc.Status().Update(ctx, &snapshot)
129
})
130
131
if err != nil {
132
log.Error(err, "could not set completion status for snapshot", "workspace", snapshot.Spec.WorkspaceID)
133
err = fmt.Errorf("could not set completion status for snapshot: %w", err)
134
}
135
136
ssc.emitEvent(&snapshot, snapshotErr)
137
return ctrl.Result{}, err
138
}
139
140
func (ssc *SnapshotReconciler) emitEvent(s *workspacev1.Snapshot, failure error) {
141
eventType := corev1.EventTypeNormal
142
reason := "Succeeded"
143
message := ""
144
145
if failure != nil {
146
eventType = corev1.EventTypeWarning
147
reason = "Failed"
148
message = failure.Error()
149
}
150
151
ssc.recorder.Event(s, eventType, reason, message)
152
}
153
154