Skip to content

Commit 27cc1a0

Browse files
committed
Move workspace activity to CRD
1 parent bc28dfb commit 27cc1a0

File tree

6 files changed

+80
-12
lines changed

6 files changed

+80
-12
lines changed

components/ws-manager-api/go/crd/v1/workspace_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ type WorkspaceStatus struct {
171171

172172
// +kubebuilder:validation:Optional
173173
Runtime *WorkspaceRuntimeStatus `json:"runtime,omitempty"`
174+
175+
LastActivity *metav1.Time `json:"lastActivity,omitempty"`
174176
}
175177

176178
func (s *WorkspaceStatus) SetCondition(cond metav1.Condition) {

components/ws-manager-api/go/crd/v1/zz_generated.deepcopy.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/ws-manager-mk2/controllers/suite_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ var _ = BeforeSuite(func() {
111111
Expect(err).ToNot(HaveOccurred())
112112
Expect(wsReconciler.SetupWithManager(k8sManager)).To(Succeed())
113113

114-
wsActivity = activity.NewWorkspaceActivity()
114+
wsActivity = activity.NewWorkspaceActivity(conf.Namespace, k8sManager.GetClient())
115115
timeoutReconciler, err := NewTimeoutReconciler(k8sManager.GetClient(), k8sManager.GetEventRecorderFor("workspace"), conf, wsActivity, maintenance)
116116
Expect(err).ToNot(HaveOccurred())
117117
Expect(timeoutReconciler.SetupWithManager(k8sManager)).To(Succeed())

components/ws-manager-mk2/controllers/timeout_controller_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ var _ = Describe("TimeoutController", func() {
3636
// Use a fake client instead of the envtest's k8s client, such that we can add objects
3737
// with custom CreationTimestamps and check timeout logic.
3838
fakeClient = fake.NewClientBuilder().WithStatusSubresource(&workspacev1.Workspace{}).WithScheme(k8sClient.Scheme()).Build()
39-
r, err = NewTimeoutReconciler(fakeClient, record.NewFakeRecorder(100), conf, activity.NewWorkspaceActivity(), &fakeMaintenance{enabled: false})
39+
r, err = NewTimeoutReconciler(fakeClient, record.NewFakeRecorder(100), conf, activity.NewWorkspaceActivity("default", k8sClient), &fakeMaintenance{enabled: false})
4040
Expect(err).ToNot(HaveOccurred())
4141
})
4242

@@ -207,7 +207,7 @@ var _ = Describe("TimeoutController", func() {
207207
var r *TimeoutReconciler
208208
BeforeEach(func() {
209209
var err error
210-
r, err = NewTimeoutReconciler(k8sClient, record.NewFakeRecorder(100), newTestConfig(), activity.NewWorkspaceActivity(), &fakeMaintenance{enabled: false})
210+
r, err = NewTimeoutReconciler(k8sClient, record.NewFakeRecorder(100), newTestConfig(), activity.NewWorkspaceActivity("default", k8sClient), &fakeMaintenance{enabled: false})
211211
Expect(err).ToNot(HaveOccurred())
212212
})
213213

components/ws-manager-mk2/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func main() {
152152
os.Exit(1)
153153
}
154154

155-
activity := activity.NewWorkspaceActivity()
155+
activity := activity.NewWorkspaceActivity(cfg.Manager.Namespace, mgr.GetClient())
156156

157157
go func() {
158158
for {

components/ws-manager-mk2/pkg/activity/activity.go

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,101 @@
55
package activity
66

77
import (
8-
"sync"
8+
"context"
99
"time"
1010

11+
"k8s.io/apimachinery/pkg/api/errors"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/types"
14+
"k8s.io/apimachinery/pkg/util/wait"
15+
"k8s.io/client-go/util/retry"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
17+
18+
"github.com/gitpod-io/gitpod/common-go/log"
1119
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
1220
)
1321

1422
// WorkspaceActivity is used to track the last user activity per workspace. This is
1523
// stored in memory instead of on the Workspace resource to limit load on the k8s API,
1624
// as this value will update often for each workspace.
1725
type WorkspaceActivity struct {
26+
client client.Client
27+
namespace string
28+
1829
ManagerStartedAt time.Time
19-
m sync.Map
2030
}
2131

22-
func NewWorkspaceActivity() *WorkspaceActivity {
32+
func NewWorkspaceActivity(namespace string, client client.Client) *WorkspaceActivity {
2333
return &WorkspaceActivity{
2434
ManagerStartedAt: time.Now().UTC(),
35+
client: client,
36+
namespace: namespace,
2537
}
2638
}
2739

40+
var (
41+
// retryParams are custom backoff parameters used to modify a workspace.
42+
// These params retry more quickly than the default retry.DefaultBackoff.
43+
retryParams = wait.Backoff{
44+
Steps: 10,
45+
Duration: 10 * time.Millisecond,
46+
Factor: 2.0,
47+
Jitter: 0.2,
48+
}
49+
)
50+
2851
func (w *WorkspaceActivity) Store(workspaceId string, lastActivity time.Time) {
29-
w.m.Store(workspaceId, &lastActivity)
52+
ctx, cancel := context.WithCancel(context.Background())
53+
defer cancel()
54+
55+
var ws workspacev1.Workspace
56+
err := w.client.Get(ctx, types.NamespacedName{Namespace: w.namespace, Name: workspaceId}, &ws)
57+
if err != nil {
58+
log.Error(err, "cannot store workspace last activity")
59+
return
60+
}
61+
62+
lastActivityStatus := metav1.NewTime(lastActivity)
63+
ws.Status.LastActivity = &lastActivityStatus
64+
65+
err = retry.RetryOnConflict(retryParams, func() error {
66+
var ws workspacev1.Workspace
67+
err := w.client.Get(ctx, types.NamespacedName{Namespace: w.namespace, Name: workspaceId}, &ws)
68+
if err != nil {
69+
return err
70+
}
71+
72+
return w.client.Status().Update(ctx, &ws)
73+
})
74+
if err != nil {
75+
log.Error(err, "cannot update workspace status")
76+
}
3077
}
3178

3279
func (w *WorkspaceActivity) GetLastActivity(ws *workspacev1.Workspace) *time.Time {
33-
lastActivity, ok := w.m.Load(ws.Name)
34-
if ok {
35-
return lastActivity.(*time.Time)
80+
ctx, cancel := context.WithCancel(context.Background())
81+
defer cancel()
82+
83+
var workspace workspacev1.Workspace
84+
err := w.client.Get(ctx, types.NamespacedName{
85+
Namespace: ws.Namespace,
86+
Name: ws.Name,
87+
}, &workspace)
88+
if err != nil {
89+
if !errors.IsNotFound(err) {
90+
log.Error(err, "unable to fetch workspace")
91+
}
92+
93+
return nil
94+
}
95+
96+
if workspace.Status.LastActivity != nil {
97+
return &workspace.Status.LastActivity.Time
3698
}
3799

38100
// In case we don't have a record of the workspace's last activity, check for the FirstUserActivity condition
39101
// to see if the lastActivity got lost on a manager restart.
40-
if ws.IsConditionTrue(workspacev1.WorkspaceConditionFirstUserActivity) {
102+
if workspace.IsConditionTrue(workspacev1.WorkspaceConditionFirstUserActivity) {
41103
// Manager was restarted, consider the workspace's last activity to be the time the manager restarted.
42104
return &w.ManagerStartedAt
43105
}

0 commit comments

Comments
 (0)