Skip to content

Commit cc36547

Browse files
committed
[POC] Implement a 'Use Last Successful Prebuild' workspace creation mode
1 parent 53c1a1f commit cc36547

File tree

5 files changed

+93
-3
lines changed

5 files changed

+93
-3
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: 53 additions & 0 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,6 +964,58 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
963964

964965
const logCtx: LogContext = { userId: user.id };
965966
const cloneUrl = context.repository.cloneUrl;
967+
if (mode === CreateWorkspaceMode.UseLastSuccessfulPrebuild) {
968+
const maxDepth = this.config.incrementalPrebuilds.commitHistory;
969+
const hostContext = this.hostContextProvider.get(context.repository.host);
970+
const repoProvider = hostContext?.services?.repositoryProvider;
971+
if (repoProvider) {
972+
(context as any).commitHistory = await repoProvider.getCommitHistory(
973+
user,
974+
context.repository.owner,
975+
context.repository.name,
976+
context.revision,
977+
maxDepth,
978+
);
979+
log.info("findPrebuiltWorkspace: incremental workspace", {
980+
commitHistory: (context as any).commitHistory,
981+
});
982+
// {"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"]}}
983+
984+
// Note: This query returns only not-garbage-collected prebuilds in order to reduce cardinality
985+
// (e.g., at the time of writing, the Gitpod repository has 16K+ prebuilds, but only ~300 not-garbage-collected)
986+
const recentPrebuilds = await this.workspaceDb
987+
.trace(ctx)
988+
.findPrebuildsWithWorkpace(context.repository.cloneUrl);
989+
990+
const { config } = await this.workspaceFactory.configProvider.fetchConfig(ctx, user, context);
991+
const imageSource = await this.workspaceFactory.imageSourceProvider.getImageSource(
992+
ctx,
993+
user,
994+
context,
995+
config,
996+
);
997+
998+
for (const recentPrebuild of recentPrebuilds) {
999+
if (
1000+
!(await this.workspaceFactory.isGoodBaseforIncrementalPrebuild(
1001+
context as any as StartPrebuildContext,
1002+
config,
1003+
imageSource,
1004+
recentPrebuild.prebuild,
1005+
recentPrebuild.workspace,
1006+
))
1007+
) {
1008+
log.info({ userId: user.id }, "Not using incremental workspace prebuild", {
1009+
candidatePrebuild: recentPrebuild.prebuild,
1010+
});
1011+
continue;
1012+
}
1013+
log.info({ userId: user.id }, "Using incremental workspace prebuild", {
1014+
prebuild: recentPrebuild.prebuild,
1015+
});
1016+
}
1017+
}
1018+
}
9661019
const prebuiltWorkspace = await this.workspaceDb
9671020
.trace(ctx)
9681021
.findPrebuiltWorkspaceByCommit(cloneUrl, commitSHAs);

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/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)