-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathannotations.go
217 lines (180 loc) · 8.01 KB
/
annotations.go
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package manager
import (
"context"
"strings"
"time"
"golang.org/x/xerrors"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"github.com/gitpod-io/gitpod/common-go/log"
"github.com/gitpod-io/gitpod/common-go/tracing"
csapi "github.com/gitpod-io/gitpod/content-service/api"
)
const (
// workspaceIDAnnotation is the annotation on the WS pod which contains the workspace ID
workspaceIDAnnotation = "gitpod/id"
// servicePrefixAnnotation is the annotation on the WS pod which contains the service prefix
servicePrefixAnnotation = "gitpod/servicePrefix"
// workspaceNeverReadyAnnotation marks a workspace as having never been ready. It's the inverse of the former workspaceReadyAnnotation
workspaceNeverReadyAnnotation = "gitpod/never-ready"
// workspaceTimedOutAnnotation marks a workspae as timed out by the ws-manager
workspaceTimedOutAnnotation = "gitpod/timedout"
// workspaceClosedAnnotation marks a workspace as closed by the user - this affects the timeout of a workspace
workspaceClosedAnnotation = "gitpod/closed"
// workspaceExplicitFailAnnotation marks a workspace as failed because of some runtime reason, e.g. the task that ran in it failed (used for headless workspaces)
workspaceExplicitFailAnnotation = "gitpod/explicitFail"
// workspaceSnapshotAnnotation stores a workspace's snapshot if one was taken prior to shutdown
workspaceSnapshotAnnotation = "gitpod/snapshot"
// workspaceInitializerAnnotation contains the protobuf serialized initializer config in base64 encoding. We need to keep this around post-request
// as we'll pass on the request to ws-daemon later in the workspace's lifecycle. This is not a configmap as we cannot create the map prior to the pod,
// because then we would not know which configmaps to delete; we cannot create the map after the pod as then the pod could reach the state what the
// configmap is needed, but isn't present yet.
// According to the K8S documentation, storing "large" amounts of data in annotations is not an issue:
// https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#attaching-metadata-to-objects
workspaceInitializerAnnotation = "gitpod/contentInitializer"
// workspaceFailedBeforeStoppingAnnotation marks a workspace as failed even before we tried
// to stop it. We do not extract the failure state from this annotation, but just stabilize
// the state computation.
workspaceFailedBeforeStoppingAnnotation = "gitpod/failedBeforeStopping"
// customTimeoutAnnotation configures the activity timeout of a workspace, i.e. the timeout a user experiences when not using an otherwise active workspace for some time.
// This is handy if you want to prevent a workspace from timing out during lunch break.
customTimeoutAnnotation = "gitpod/customTimeout"
// firstUserActivityAnnotation marks a workspace woth the timestamp of first user activity in it
firstUserActivityAnnotation = "gitpod/firstUserActivity"
// fullWorkspaceBackupAnnotation is set on workspaces which operate using a full workspace backup
fullWorkspaceBackupAnnotation = "gitpod/fullWorkspaceBackup"
// gitpodFinalizerName is the name of the Gitpod finalizer we use to clean up a workspace
gitpodFinalizerName = "gitpod.io/finalizer"
// disposalStatusAnnotation contains the status of the workspace disposal process
disposalStatusAnnotation = "gitpod.io/disposalStatus"
// nodeNameAnnotation contains the name of the node the pod ran on. We use this to remeber the name in case the pod gets evicted.
nodeNameAnnotation = "gitpod.io/nodeName"
// workspaceAnnotationPrefix prefixes pod annotations that contain annotations specified during the workspaces start request
workspaceAnnotationPrefix = "gitpod.io/annotation."
// stoppedByRequestAnnotation is set on a pod when it was requested to stop using a StopWorkspace call
stoppedByRequestAnnotation = "gitpod.io/stoppedByRequest"
)
// markWorkspaceAsReady adds annotations to a workspace pod
func (m *Manager) markWorkspace(ctx context.Context, workspaceID string, annotations ...*annotation) error {
// use custom backoff, as default one fails after 1.5s, this one will try for about 25s
// we want to try harder to remove or add annotation, as failure to remove "gitpod/never-ready" annotation
// would cause whole workspace to be marked as failed, hence the reason to try harder here.
var backoff = wait.Backoff{
Steps: 7,
Duration: 100 * time.Millisecond,
Factor: 2.0,
Jitter: 0.1,
}
// Retry on failure. Sometimes this doesn't work because of concurrent modification. The Kuberentes way is to just try again after waiting a bit.
err := retry.RetryOnConflict(backoff, func() error {
pod, err := m.findWorkspacePod(ctx, workspaceID)
if err != nil {
return xerrors.Errorf("cannot find workspace %s: %w", workspaceID, err)
}
if pod == nil {
return xerrors.Errorf("workspace %s does not exist", workspaceID)
}
for _, a := range annotations {
a.Apply(pod.Annotations)
// Optimization: if we're failing the workspace explicitly, we might as well add the workspaceFailedBeforeStoppingAnnotation
// as well. If we didin't do this here, the monitor would do that for us down the road, but this way need one fewer modification
// of the pod.
if a.Name == workspaceExplicitFailAnnotation {
pod.Annotations[workspaceFailedBeforeStoppingAnnotation] = "true"
}
}
return m.Clientset.Update(ctx, pod)
})
if err != nil {
an := make([]string, len(annotations))
for i, a := range annotations {
if a.Delete {
an[i] = "-" + a.Name
} else {
an[i] = "+" + a.Name
}
}
return xerrors.Errorf("cannot mark workspace %s with %v: %w", workspaceID, strings.Join(an, ", "), err)
}
return nil
}
func addMark(name, value string) *annotation {
return &annotation{name, value, false}
}
func deleteMark(name string) *annotation {
return &annotation{name, "", true}
}
// annotation is a piece of metadata added to a workspace
type annotation struct {
Name string
Value string
Delete bool
}
func (a *annotation) Apply(dst map[string]string) (needsUpdate bool) {
_, wasPresent := dst[a.Name]
if a.Delete {
needsUpdate = wasPresent
delete(dst, a.Name)
} else {
needsUpdate = !wasPresent
dst[a.Name] = a.Value
}
return
}
// workspaceDisposalStatus indicates the status of the workspace diposal
type workspaceDisposalStatus struct {
BackupComplete bool `json:"backupComplete,omitempty"`
BackupFailure string `json:"backupFailure,omitempty"`
GitStatus *csapi.GitStatus `json:"gitStatus,omitempty"`
}
func (m *Manager) modifyFinalizer(ctx context.Context, workspaceID string, finalizer string, add bool) error {
// Retry on failure. Sometimes this doesn't work because of concurrent modification. The Kuberentes way is to just try again after waiting a bit.
return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
span, ctx := tracing.FromContext(ctx, "modifyFinalizer")
tracing.ApplyOWI(span, log.OWI("", "", workspaceID))
defer tracing.FinishSpan(span, &err)
span.LogKV("finalizer", finalizer, "add", add)
pod, err := m.findWorkspacePod(ctx, workspaceID)
if err != nil {
if isKubernetesObjNotFoundError(err) {
return nil
}
return xerrors.Errorf("unexpected error searching workspace %s: %w", workspaceID, err)
}
if pod == nil {
return xerrors.Errorf("workspace %s does not exist", workspaceID)
}
var update bool
if add {
var exists bool
for _, x := range pod.Finalizers {
if x == gitpodFinalizerName {
exists = true
break
}
}
if !exists {
pod.Finalizers = append(pod.Finalizers, finalizer)
update = true
}
} else {
n := 0
for _, x := range pod.Finalizers {
if x == gitpodFinalizerName {
update = true
} else {
pod.Finalizers[n] = x
n++
}
}
pod.Finalizers = pod.Finalizers[:n]
}
if !update {
return nil
}
return m.Clientset.Update(ctx, pod)
})
}