Skip to content

Commit 0d9dc26

Browse files
committed
[POC] Implement a 'Use Last Successful Prebuild' workspace creation mode
1 parent 7f36420 commit 0d9dc26

File tree

6 files changed

+100
-5
lines changed

6 files changed

+100
-5
lines changed

components/dashboard/src/start/CreateWorkspace.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ export default class CreateWorkspace extends React.Component<CreateWorkspaceProp
269269
return (
270270
<RunningPrebuildView
271271
runningPrebuild={result.runningWorkspacePrebuild}
272+
onUseLastSuccessfulPrebuild={() =>
273+
this.createWorkspace(CreateWorkspaceMode.UseLastSuccessfulPrebuild)
274+
}
272275
onIgnorePrebuild={() => this.createWorkspace(CreateWorkspaceMode.ForceNew)}
273276
onPrebuildSucceeded={() => this.createWorkspace(CreateWorkspaceMode.UsePrebuild)}
274277
/>
@@ -531,6 +534,7 @@ interface RunningPrebuildViewProps {
531534
starting: RunningWorkspacePrebuildStarting;
532535
sameCluster: boolean;
533536
};
537+
onUseLastSuccessfulPrebuild: () => void;
534538
onIgnorePrebuild: () => void;
535539
onPrebuildSucceeded: () => void;
536540
}
@@ -565,6 +569,12 @@ function RunningPrebuildView(props: RunningPrebuildViewProps) {
565569
{/* TODO(gpl) Copied around in Start-/CreateWorkspace. This should properly go somewhere central. */}
566570
<div className="h-full mt-6 w-11/12 lg:w-3/5">
567571
<PrebuildLogs workspaceId={workspaceId} onIgnorePrebuild={props.onIgnorePrebuild}>
572+
<button
573+
className="secondary"
574+
onClick={() => props.onUseLastSuccessfulPrebuild && props.onUseLastSuccessfulPrebuild()}
575+
>
576+
Use Last Successful Prebuild
577+
</button>
568578
<button className="secondary" onClick={() => props.onIgnorePrebuild && props.onIgnorePrebuild()}>
569579
Skip Prebuild
570580
</button>

components/gitpod-protocol/src/protocol.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,8 @@ export enum CreateWorkspaceMode {
13721372
UsePrebuild = "use-prebuild",
13731373
// SelectIfRunning returns a list of currently running workspaces for the context URL if there are any, otherwise falls back to Default mode
13741374
SelectIfRunning = "select-if-running",
1375+
// UseLastSuccessfulPrebuild returns ...
1376+
UseLastSuccessfulPrebuild = "use-last-successful-prebuild",
13751377
}
13761378

13771379
export namespace WorkspaceCreationResult {

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
TeamMemberRole,
4848
WORKSPACE_TIMEOUT_DEFAULT_SHORT,
4949
PrebuildEvent,
50+
StartPrebuildContext,
5051
} from "@gitpod/gitpod-protocol";
5152
import { ResponseError } from "vscode-jsonrpc";
5253
import {
@@ -963,11 +964,65 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
963964

964965
const logCtx: LogContext = { userId: user.id };
965966
const cloneUrl = context.repository.cloneUrl;
966-
const prebuiltWorkspace = await this.workspaceDb
967+
let prebuiltWorkspace = await this.workspaceDb
967968
.trace(ctx)
968969
.findPrebuiltWorkspaceByCommit(cloneUrl, commitSHAs);
969970
const logPayload = { mode, cloneUrl, commit: commitSHAs, prebuiltWorkspace };
970971
log.debug(logCtx, "Looking for prebuilt workspace: ", logPayload);
972+
if (!prebuiltWorkspace && mode === CreateWorkspaceMode.UseLastSuccessfulPrebuild) {
973+
const maxDepth = this.config.incrementalPrebuilds.commitHistory;
974+
const hostContext = this.hostContextProvider.get(context.repository.host);
975+
const repoProvider = hostContext?.services?.repositoryProvider;
976+
if (repoProvider) {
977+
(context as any).commitHistory = await repoProvider.getCommitHistory(
978+
user,
979+
context.repository.owner,
980+
context.repository.name,
981+
context.revision,
982+
maxDepth,
983+
);
984+
log.info("findPrebuiltWorkspace: incremental workspace", {
985+
commitHistory: (context as any).commitHistory,
986+
});
987+
// {"component":"server","severity":"INFO","time":"2022-10-12T12:57:06.447Z","message":"findPrebuiltWorkspace: incremental workspace","payload":{"commitHistory":["95b666410f37b5237bf416feb748fb1e8aab8fd4","ab01b49c669ce41e0fef1cb306b1ba648ff4ea6a","b3d8f3ada8e729a99b334f75b45eacf240350afa","71d1a3cae0121b2fea14a69b97bcdddce44809ac","218dd0667a7eff7ed657f27b570431919cd87be7","feb2d8b057f751ccd13a7aec07ecfd85757cbb09","9270c10e3b1c23f0a65253cd516aae55120550e9","a70117ff7ae75de4de6c922ab9953e299c711046","24c9dcb64f582ae696797dc617b9af1352a6f01a","6f2aff3ebf94cf40a0c96e87bfd00b2917fc7adb","98b4bfb6190a3bb9fb0f513ffc2bce4009177960","35c9687543411e9e1ced865a9fcd69e1003fe3ed","61d913686e3f5ac17bcd490774097dc6302c92bf","f93921910f80c085fafc5157f6d6b7205b19a4fa"]}}
988+
989+
// Note: This query returns only not-garbage-collected prebuilds in order to reduce cardinality
990+
// (e.g., at the time of writing, the Gitpod repository has 16K+ prebuilds, but only ~300 not-garbage-collected)
991+
const recentPrebuilds = await this.workspaceDb
992+
.trace(ctx)
993+
.findPrebuildsWithWorkpace(context.repository.cloneUrl);
994+
995+
const { config } = await this.workspaceFactory.configProvider.fetchConfig(ctx, user, context);
996+
const imageSource = await this.workspaceFactory.imageSourceProvider.getImageSource(
997+
ctx,
998+
user,
999+
context,
1000+
config,
1001+
);
1002+
1003+
for (const recentPrebuild of recentPrebuilds) {
1004+
if (
1005+
!(await this.workspaceFactory.isGoodBaseforIncrementalPrebuild(
1006+
context as any as StartPrebuildContext,
1007+
config,
1008+
imageSource,
1009+
recentPrebuild.prebuild,
1010+
recentPrebuild.workspace,
1011+
))
1012+
) {
1013+
log.info({ userId: user.id }, "Not using incremental workspace prebuild", {
1014+
candidatePrebuild: recentPrebuild.prebuild,
1015+
});
1016+
continue;
1017+
}
1018+
log.info({ userId: user.id }, "Using incremental workspace prebuild", {
1019+
prebuild: recentPrebuild.prebuild,
1020+
});
1021+
prebuiltWorkspace = recentPrebuild.prebuild;
1022+
break;
1023+
}
1024+
}
1025+
}
9711026
if (!prebuiltWorkspace) {
9721027
return;
9731028
}
@@ -994,7 +1049,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
9941049
const makeResult = (instanceID: string): WorkspaceCreationResult => {
9951050
return <WorkspaceCreationResult>{
9961051
runningWorkspacePrebuild: {
997-
prebuildID: prebuiltWorkspace.id,
1052+
prebuildID: prebuiltWorkspace!.id,
9981053
workspaceID,
9991054
instanceID,
10001055
starting: "queued",

components/server/ee/src/workspace/workspace-factory.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,27 +236,31 @@ export class WorkspaceFactoryEE extends WorkspaceFactory {
236236
}
237237
}
238238

239-
private async isGoodBaseforIncrementalPrebuild(
239+
public async isGoodBaseforIncrementalPrebuild(
240240
context: StartPrebuildContext,
241241
config: WorkspaceConfig,
242242
imageSource: WorkspaceImageSource,
243243
candidatePrebuild: PrebuiltWorkspace,
244244
candidate: Workspace,
245245
): Promise<boolean> {
246246
if (!context.commitHistory || context.commitHistory.length === 0) {
247+
log.info("Disqualified: no commitHistory");
247248
return false;
248249
}
249250
if (!CommitContext.is(candidate.context)) {
251+
log.info("Disqualified: candiate context not a commit context");
250252
return false;
251253
}
252254

253255
// we are only considering available prebuilds
254256
if (candidatePrebuild.state !== "available") {
257+
log.info("Disqualified: candidate prebuild not available");
255258
return false;
256259
}
257260

258261
// we are only considering full prebuilds
259262
if (!!candidate.basedOnPrebuildId) {
263+
log.info("Disqualified: candidate is based on another prebuild");
260264
return false;
261265
}
262266

@@ -266,10 +270,14 @@ export class WorkspaceFactoryEE extends WorkspaceFactory {
266270
context.additionalRepositoryCommitHistories?.length
267271
) {
268272
// different number of repos
273+
log.info(
274+
"Disqualified: candidate context additional repository checkout info !== context additional repo histories",
275+
);
269276
return false;
270277
}
271278

272279
if (!context.commitHistory.some((sha) => sha === candidateCtx.revision)) {
280+
log.info("Disqualified: candidate revision not in commit history");
273281
return false;
274282
}
275283

@@ -279,6 +287,7 @@ export class WorkspaceFactoryEE extends WorkspaceFactory {
279287
(repo) => repo.cloneUrl === subRepo.repository.cloneUrl,
280288
);
281289
if (!matchIngRepo || !matchIngRepo.commitHistory.some((sha) => sha === subRepo.revision)) {
290+
log.info("Disqualified: something about matchIngRepo");
282291
return false;
283292
}
284293
}
@@ -289,6 +298,7 @@ export class WorkspaceFactoryEE extends WorkspaceFactory {
289298
imageSource,
290299
parentImageSource: candidate.imageSource,
291300
});
301+
log.info("Disqualified: image source has changed");
292302
return false;
293303
}
294304

@@ -309,6 +319,7 @@ export class WorkspaceFactoryEE extends WorkspaceFactory {
309319
prebuildTasks,
310320
parentPrebuildTasks,
311321
});
322+
log.info("Disqualified: prebuild tasks have changed");
312323
return false;
313324
}
314325

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
11611161
}
11621162

11631163
const prebuiltWorkspace = await this.findPrebuiltWorkspace(ctx, user, context, mode);
1164+
log.info("Found prebuiltWorkspace", { prebuiltWorkspace });
11641165
if (WorkspaceCreationResult.is(prebuiltWorkspace)) {
11651166
ctx.span?.log({ prebuild: "running" });
11661167
return prebuiltWorkspace as WorkspaceCreationResult;
@@ -1171,6 +1172,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
11711172
}
11721173

11731174
const workspace = await this.workspaceFactory.createForContext(ctx, user, context, normalizedContextUrl);
1175+
log.info("Created workspace", { workspace });
11741176
await this.mayStartWorkspace(ctx, user, workspace, runningInstancesPromise);
11751177
try {
11761178
await this.guardAccess({ kind: "workspace", subject: workspace }, "create");
@@ -1183,6 +1185,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
11831185

11841186
logContext.workspaceId = workspace.id;
11851187
traceWI(ctx, { workspaceId: workspace.id });
1188+
log.info("Starting workspace", { workspace });
11861189
const startWorkspaceResult = await this.workspaceStarter.startWorkspace(
11871190
ctx,
11881191
workspace,

components/server/src/workspace/workspace-factory.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ import {
99
AdditionalContentContext,
1010
CommitContext,
1111
IssueContext,
12+
PrebuiltWorkspace,
1213
PrebuiltWorkspaceContext,
1314
PullRequestContext,
1415
Repository,
1516
SnapshotContext,
17+
StartPrebuildContext,
1618
User,
1719
Workspace,
20+
WorkspaceConfig,
1821
WorkspaceContext,
22+
WorkspaceImageSource,
1923
} from "@gitpod/gitpod-protocol";
2024
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
2125
import { generateWorkspaceID } from "@gitpod/gitpod-protocol/lib/util/generate-workspace-id";
@@ -32,8 +36,8 @@ export class WorkspaceFactory {
3236
@inject(TracedWorkspaceDB) protected readonly db: DBWithTracing<WorkspaceDB>;
3337
@inject(ProjectDB) protected readonly projectDB: ProjectDB;
3438
@inject(TeamDB) protected readonly teamDB: TeamDB;
35-
@inject(ConfigProvider) protected configProvider: ConfigProvider;
36-
@inject(ImageSourceProvider) protected imageSourceProvider: ImageSourceProvider;
39+
@inject(ConfigProvider) public configProvider: ConfigProvider;
40+
@inject(ImageSourceProvider) public imageSourceProvider: ImageSourceProvider;
3741

3842
public async createForContext(
3943
ctx: TraceContext,
@@ -50,6 +54,16 @@ export class WorkspaceFactory {
5054
throw new Error("Couldn't create workspace for context");
5155
}
5256

57+
public async isGoodBaseforIncrementalPrebuild(
58+
context: StartPrebuildContext,
59+
config: WorkspaceConfig,
60+
imageSource: WorkspaceImageSource,
61+
candidatePrebuild: PrebuiltWorkspace,
62+
candidate: Workspace,
63+
): Promise<boolean> {
64+
throw new Error("Not implemented in this version");
65+
}
66+
5367
protected async createForSnapshot(ctx: TraceContext, user: User, context: SnapshotContext): Promise<Workspace> {
5468
const span = TraceContext.startSpan("createForSnapshot", ctx);
5569

0 commit comments

Comments
 (0)