@@ -14,10 +14,15 @@ import (
14
14
15
15
"cdr.dev/slog"
16
16
"cdr.dev/slog/sloggers/slogtest"
17
+ "github.com/coder/coder/v2/coderd/coderdtest"
17
18
"github.com/coder/coder/v2/coderd/database"
19
+ "github.com/coder/coder/v2/coderd/database/dbfake"
18
20
"github.com/coder/coder/v2/coderd/database/dbmock"
21
+ "github.com/coder/coder/v2/coderd/database/dbtestutil"
19
22
"github.com/coder/coder/v2/coderd/database/dbtime"
20
23
"github.com/coder/coder/v2/coderd/workspaceusage"
24
+ "github.com/coder/coder/v2/codersdk"
25
+ "github.com/coder/coder/v2/testutil"
21
26
)
22
27
23
28
func TestTracker (t * testing.T ) {
@@ -105,6 +110,114 @@ func TestTracker(t *testing.T) {
105
110
require .Panics (t , wut .Loop )
106
111
}
107
112
113
+ // This test performs a more 'integration-style' test with multiple instances.
114
+ func TestTracker_MultipleInstances (t * testing.T ) {
115
+ t .Parallel ()
116
+ if ! dbtestutil .WillUsePostgres () {
117
+ t .Skip ("this test only makes sense with postgres" )
118
+ }
119
+
120
+ // Given we have two coderd instances connected to the same database
121
+ var (
122
+ ctx = testutil .Context (t , testutil .WaitLong )
123
+ db , ps = dbtestutil .NewDB (t )
124
+ wuTickA = make (chan time.Time )
125
+ wuFlushA = make (chan int , 1 )
126
+ wutA = workspaceusage .New (db , workspaceusage .WithFlushChannel (wuFlushA ), workspaceusage .WithTickChannel (wuTickA ))
127
+ wuTickB = make (chan time.Time )
128
+ wuFlushB = make (chan int , 1 )
129
+ wutB = workspaceusage .New (db , workspaceusage .WithFlushChannel (wuFlushB ), workspaceusage .WithTickChannel (wuTickB ))
130
+ clientA = coderdtest .New (t , & coderdtest.Options {
131
+ WorkspaceUsageTracker : wutA ,
132
+ Database : db ,
133
+ Pubsub : ps ,
134
+ })
135
+ clientB = coderdtest .New (t , & coderdtest.Options {
136
+ WorkspaceUsageTracker : wutB ,
137
+ Database : db ,
138
+ Pubsub : ps ,
139
+ })
140
+ owner = coderdtest .CreateFirstUser (t , clientA )
141
+ now = dbtime .Now ()
142
+ )
143
+
144
+ clientB .SetSessionToken (clientA .SessionToken ())
145
+
146
+ // Create a number of workspaces
147
+ numWorkspaces := 10
148
+ w := make ([]dbfake.WorkspaceResponse , numWorkspaces )
149
+ for i := 0 ; i < numWorkspaces ; i ++ {
150
+ wr := dbfake .WorkspaceBuild (t , db , database.Workspace {
151
+ OwnerID : owner .UserID ,
152
+ OrganizationID : owner .OrganizationID ,
153
+ LastUsedAt : now ,
154
+ }).WithAgent ().Do ()
155
+ w [i ] = wr
156
+ }
157
+
158
+ // Use client A to update LastUsedAt of the first three
159
+ require .NoError (t , clientA .PostWorkspaceUsage (ctx , w [0 ].Workspace .ID ))
160
+ require .NoError (t , clientA .PostWorkspaceUsage (ctx , w [1 ].Workspace .ID ))
161
+ require .NoError (t , clientA .PostWorkspaceUsage (ctx , w [2 ].Workspace .ID ))
162
+ // Use client B to update LastUsedAt of the next three
163
+ require .NoError (t , clientB .PostWorkspaceUsage (ctx , w [3 ].Workspace .ID ))
164
+ require .NoError (t , clientB .PostWorkspaceUsage (ctx , w [4 ].Workspace .ID ))
165
+ require .NoError (t , clientB .PostWorkspaceUsage (ctx , w [5 ].Workspace .ID ))
166
+ // The next two will have updated from both instances
167
+ require .NoError (t , clientA .PostWorkspaceUsage (ctx , w [6 ].Workspace .ID ))
168
+ require .NoError (t , clientB .PostWorkspaceUsage (ctx , w [6 ].Workspace .ID ))
169
+ require .NoError (t , clientA .PostWorkspaceUsage (ctx , w [7 ].Workspace .ID ))
170
+ require .NoError (t , clientB .PostWorkspaceUsage (ctx , w [7 ].Workspace .ID ))
171
+ // The last two will not report any usage.
172
+
173
+ // Tick both with different times and wait for both flushes to complete
174
+ nowA := now .Add (time .Minute )
175
+ nowB := now .Add (2 * time .Minute )
176
+ var wg sync.WaitGroup
177
+ var flushedA , flushedB int
178
+ wg .Add (1 )
179
+ go func () {
180
+ defer wg .Done ()
181
+ wuTickA <- nowA
182
+ flushedA = <- wuFlushA
183
+ }()
184
+ wg .Add (1 )
185
+ go func () {
186
+ defer wg .Done ()
187
+ wuTickB <- nowB
188
+ flushedB = <- wuFlushB
189
+ }()
190
+ wg .Wait ()
191
+
192
+ // We expect 5 flushed IDs each
193
+ require .Equal (t , 5 , flushedA )
194
+ require .Equal (t , 5 , flushedB )
195
+
196
+ // Fetch updated workspaces
197
+ updated := make ([]codersdk.Workspace , numWorkspaces )
198
+ for i := 0 ; i < numWorkspaces ; i ++ {
199
+ ws , err := clientA .Workspace (ctx , w [i ].Workspace .ID )
200
+ require .NoError (t , err )
201
+ updated [i ] = ws
202
+ }
203
+ // We expect the first three to have the timestamp of flushA
204
+ require .Equal (t , nowA .UTC (), updated [0 ].LastUsedAt .UTC ())
205
+ require .Equal (t , nowA .UTC (), updated [1 ].LastUsedAt .UTC ())
206
+ require .Equal (t , nowA .UTC (), updated [2 ].LastUsedAt .UTC ())
207
+ // We expect the next three to have the timestamp of flushB
208
+ require .Equal (t , nowB .UTC (), updated [3 ].LastUsedAt .UTC ())
209
+ require .Equal (t , nowB .UTC (), updated [4 ].LastUsedAt .UTC ())
210
+ require .Equal (t , nowB .UTC (), updated [5 ].LastUsedAt .UTC ())
211
+ // The next two should have the timestamp of flushB as it is newer than flushA
212
+ require .Equal (t , nowB .UTC (), updated [6 ].LastUsedAt .UTC ())
213
+ require .Equal (t , nowB .UTC (), updated [7 ].LastUsedAt .UTC ())
214
+ // And the last two should be untouched
215
+ require .Equal (t , w [8 ].Workspace .LastUsedAt .UTC (), updated [8 ].LastUsedAt .UTC ())
216
+ require .Equal (t , w [8 ].Workspace .LastUsedAt .UTC (), updated [8 ].LastUsedAt .UTC ())
217
+ require .Equal (t , w [9 ].Workspace .LastUsedAt .UTC (), updated [9 ].LastUsedAt .UTC ())
218
+ require .Equal (t , w [9 ].Workspace .LastUsedAt .UTC (), updated [9 ].LastUsedAt .UTC ())
219
+ }
220
+
108
221
func TestMain (m * testing.M ) {
109
222
goleak .VerifyTestMain (m )
110
223
}
0 commit comments