Skip to content

Commit 7ccdcf4

Browse files
committed
Incremental workspaces
1 parent 4976dcb commit 7ccdcf4

File tree

11 files changed

+128
-108
lines changed

11 files changed

+128
-108
lines changed

components/dashboard/src/projects/Project.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -378,17 +378,7 @@ export default function () {
378378
)}
379379
</a>
380380
<span className="flex-grow" />
381-
<a
382-
href={
383-
prebuild
384-
? gitpodHostUrl
385-
.withContext(
386-
`open-prebuild/${prebuild.info.id}/${prebuild.info.changeUrl}`,
387-
)
388-
.toString()
389-
: gitpodHostUrl.withContext(`${branch.url}`).toString()
390-
}
391-
>
381+
<a href={gitpodHostUrl.withContext(`${branch.url}`).toString()}>
392382
<button
393383
className={`primary mr-2 py-2 opacity-0 group-hover:opacity-100`}
394384
>

components/dashboard/src/projects/ProjectSettings.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ export default function () {
9090
checked={!project.settings?.keepOutdatedPrebuildsRunning}
9191
onChange={({ target }) => updateProjectSettings({ keepOutdatedPrebuildsRunning: !target.checked })}
9292
/>
93+
<h3 className="mt-12">Workspace Starts</h3>
94+
<CheckBox
95+
title={<span>Incrementally update from old prebuilds</span>}
96+
desc={
97+
<span>
98+
Whether new workspaces can be started based on prebuilds that ran on older Git commits and get
99+
incrementally updated.
100+
</span>
101+
}
102+
checked={!!project.settings?.allowUsingPreviousPrebuilds}
103+
onChange={({ target }) => updateProjectSettings({ allowUsingPreviousPrebuilds: target.checked })}
104+
/>
93105
{showPersistentVolumeClaimUI && (
94106
<>
95107
<br></br>

components/dashboard/src/start/CreateWorkspace.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66

77
import React, { useEffect, useContext, useState } from "react";
88
import {
9-
CreateWorkspaceMode,
109
WorkspaceCreationResult,
1110
RunningWorkspacePrebuildStarting,
1211
ContextURL,
1312
DisposableCollection,
1413
Team,
14+
GitpodServer,
1515
} from "@gitpod/gitpod-protocol";
1616
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
1717
import Modal from "../components/Modal";
@@ -52,7 +52,7 @@ export default class CreateWorkspace extends React.Component<CreateWorkspaceProp
5252
this.createWorkspace();
5353
}
5454

55-
async createWorkspace(mode = CreateWorkspaceMode.SelectIfRunning, forceDefaultConfig = false) {
55+
async createWorkspace(options?: Omit<GitpodServer.CreateWorkspaceOptions, "contextUrl">) {
5656
// Invalidate any previous result.
5757
this.setState({ result: undefined, stillParsing: true });
5858

@@ -62,8 +62,7 @@ export default class CreateWorkspace extends React.Component<CreateWorkspaceProp
6262
try {
6363
const result = await getGitpodService().server.createWorkspace({
6464
contextUrl: this.props.contextUrl,
65-
mode,
66-
forceDefaultConfig,
65+
...options,
6766
});
6867
if (result.workspaceURL) {
6968
window.location.href = result.workspaceURL;
@@ -148,7 +147,7 @@ export default class CreateWorkspace extends React.Component<CreateWorkspaceProp
148147
<button
149148
className=""
150149
onClick={() => {
151-
this.createWorkspace(CreateWorkspaceMode.Default, true);
150+
this.createWorkspace({ forceDefaultConfig: true });
152151
}}
153152
>
154153
Continue with default configuration
@@ -262,7 +261,9 @@ export default class CreateWorkspace extends React.Component<CreateWorkspaceProp
262261
</>
263262
</div>
264263
<div className="flex justify-end mt-6">
265-
<button onClick={() => this.createWorkspace(CreateWorkspaceMode.Default)}>New Workspace</button>
264+
<button onClick={() => this.createWorkspace({ ignoreRunningWorkspaceOnSameCommit: true })}>
265+
New Workspace
266+
</button>
266267
</div>
267268
</Modal>
268269
);
@@ -271,10 +272,10 @@ export default class CreateWorkspace extends React.Component<CreateWorkspaceProp
271272
<RunningPrebuildView
272273
runningPrebuild={result.runningWorkspacePrebuild}
273274
onUseLastSuccessfulPrebuild={() =>
274-
this.createWorkspace(CreateWorkspaceMode.UseLastSuccessfulPrebuild)
275+
this.createWorkspace({ allowUsingPreviousPrebuilds: true, ignoreRunningPrebuild: true })
275276
}
276-
onIgnorePrebuild={() => this.createWorkspace(CreateWorkspaceMode.ForceNew)}
277-
onPrebuildSucceeded={() => this.createWorkspace(CreateWorkspaceMode.UsePrebuild)}
277+
onIgnorePrebuild={() => this.createWorkspace({ ignoreRunningPrebuild: true })}
278+
onPrebuildSucceeded={() => this.createWorkspace()}
278279
/>
279280
);
280281
}

components/gitpod-protocol/src/gitpod-service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
WhitelistedRepository,
1313
WorkspaceImageBuild,
1414
AuthProviderInfo,
15-
CreateWorkspaceMode,
1615
Token,
1716
UserEnvVarValue,
1817
Terms,
@@ -422,7 +421,10 @@ export namespace GitpodServer {
422421
}
423422
export interface CreateWorkspaceOptions {
424423
contextUrl: string;
425-
mode?: CreateWorkspaceMode;
424+
// whether running workspaces on the same context should be ignored. If false (default) users will be asked.
425+
ignoreRunningWorkspaceOnSameCommit?: boolean;
426+
ignoreRunningPrebuild?: boolean;
427+
allowUsingPreviousPrebuilds?: boolean;
426428
forceDefaultConfig?: boolean;
427429
}
428430
export interface StartWorkspaceOptions {

components/gitpod-protocol/src/protocol.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,19 +1381,6 @@ export interface WorkspaceCreationResult {
13811381
}
13821382
export type RunningWorkspacePrebuildStarting = "queued" | "starting" | "running";
13831383

1384-
export enum CreateWorkspaceMode {
1385-
// Default returns a running prebuild if there is any, otherwise creates a new workspace (using a prebuild if one is available)
1386-
Default = "default",
1387-
// ForceNew creates a new workspace irrespective of any running prebuilds. This mode is guaranteed to actually create a workspace - but may degrade user experience as currently runnig prebuilds are ignored.
1388-
ForceNew = "force-new",
1389-
// UsePrebuild polls the database waiting for a currently running prebuild to become available. This mode exists to handle the db-sync delay.
1390-
UsePrebuild = "use-prebuild",
1391-
// SelectIfRunning returns a list of currently running workspaces for the context URL if there are any, otherwise falls back to Default mode
1392-
SelectIfRunning = "select-if-running",
1393-
// UseLastSuccessfulPrebuild returns ...
1394-
UseLastSuccessfulPrebuild = "use-last-successful-prebuild",
1395-
}
1396-
13971384
export namespace WorkspaceCreationResult {
13981385
export function is(data: any): data is WorkspaceCreationResult {
13991386
return (

components/gitpod-protocol/src/teams-projects-protocol.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export interface ProjectSettings {
1717
useIncrementalPrebuilds?: boolean;
1818
usePersistentVolumeClaim?: boolean;
1919
keepOutdatedPrebuildsRunning?: boolean;
20+
// whether new workspaces can start on older prebuilds and incrementally update
21+
allowUsingPreviousPrebuilds?: boolean;
2022
}
2123

2224
export interface Project {

components/server/ee/src/prebuilds/prebuild-manager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ export class PrebuildManager {
191191
const workspace = await this.workspaceFactory.createForContext(
192192
{ span },
193193
user,
194+
project,
194195
prebuildContext,
195196
context.normalizedContextURL!,
196197
);

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

Lines changed: 56 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import {
2828
WorkspaceTimeoutValues,
2929
SetWorkspaceTimeoutResult,
3030
WorkspaceContext,
31-
CreateWorkspaceMode,
3231
WorkspaceCreationResult,
3332
PrebuiltWorkspaceContext,
3433
CommitContext,
@@ -976,7 +975,8 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
976975
parentCtx: TraceContext,
977976
user: User,
978977
context: WorkspaceContext,
979-
mode: CreateWorkspaceMode,
978+
ignoreRunningPrebuild?: boolean,
979+
allowUsingPreviousPrebuilds?: boolean,
980980
): Promise<WorkspaceCreationResult | PrebuiltWorkspaceContext | undefined> {
981981
const ctx = TraceContext.childContext("findPrebuiltWorkspace", parentCtx);
982982
try {
@@ -989,29 +989,38 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
989989
const logCtx: LogContext = { userId: user.id };
990990
const cloneUrl = context.repository.cloneUrl;
991991
let prebuiltWorkspace: PrebuiltWorkspace | undefined;
992+
const logPayload = {
993+
allowUsingPreviousPrebuilds,
994+
ignoreRunningPrebuild,
995+
cloneUrl,
996+
commit: commitSHAs,
997+
prebuiltWorkspace,
998+
};
992999
if (OpenPrebuildContext.is(context)) {
9931000
prebuiltWorkspace = await this.workspaceDb.trace(ctx).findPrebuildByID(context.openPrebuildID);
994-
if (prebuiltWorkspace?.cloneURL !== cloneUrl) {
1001+
if (
1002+
prebuiltWorkspace?.cloneURL !== cloneUrl &&
1003+
(ignoreRunningPrebuild || prebuiltWorkspace?.state === "available")
1004+
) {
9951005
// prevent users from opening arbitrary prebuilds this way - they must match the clone URL so that the resource guards are correct.
9961006
return;
9971007
}
9981008
} else {
999-
prebuiltWorkspace = await this.workspaceDb
1000-
.trace(ctx)
1001-
.findPrebuiltWorkspaceByCommit(cloneUrl, commitSHAs);
1002-
}
1003-
1004-
const logPayload = { mode, cloneUrl, commit: commitSHAs, prebuiltWorkspace };
1005-
log.debug(logCtx, "Looking for prebuilt workspace: ", logPayload);
1006-
if (prebuiltWorkspace?.state !== "available" && mode === CreateWorkspaceMode.UseLastSuccessfulPrebuild) {
1007-
const { config } = await this.configProvider.fetchConfig({}, user, context);
1008-
const history = await this.incrementalPrebuildsService.getCommitHistoryForContext(context, user);
1009-
prebuiltWorkspace = await this.incrementalPrebuildsService.findGoodBaseForIncrementalBuild(
1010-
context,
1011-
config,
1012-
history,
1013-
user,
1014-
);
1009+
log.debug(logCtx, "Looking for prebuilt workspace: ", logPayload);
1010+
if (!allowUsingPreviousPrebuilds) {
1011+
prebuiltWorkspace = await this.workspaceDb
1012+
.trace(ctx)
1013+
.findPrebuiltWorkspaceByCommit(cloneUrl, commitSHAs);
1014+
} else {
1015+
const { config } = await this.configProvider.fetchConfig({}, user, context);
1016+
const history = await this.incrementalPrebuildsService.getCommitHistoryForContext(context, user);
1017+
prebuiltWorkspace = await this.incrementalPrebuildsService.findGoodBaseForIncrementalBuild(
1018+
context,
1019+
config,
1020+
history,
1021+
user,
1022+
);
1023+
}
10151024
}
10161025
if (!prebuiltWorkspace) {
10171026
return;
@@ -1026,13 +1035,9 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
10261035
};
10271036
return result;
10281037
} else if (prebuiltWorkspace.state === "queued" || prebuiltWorkspace.state === "building") {
1029-
if (mode === CreateWorkspaceMode.ForceNew) {
1038+
if (ignoreRunningPrebuild) {
10301039
// in force mode we ignore running prebuilds as we want to start a workspace as quickly as we can.
10311040
return;
1032-
// TODO(janx): Fall back to parent prebuild instead, if it's available:
1033-
// const buildWorkspace = await this.workspaceDb.trace({span}).findById(prebuiltWorkspace.buildWorkspaceId);
1034-
// const parentPrebuild = await this.workspaceDb.trace({span}).findPrebuildByID(buildWorkspace.basedOnPrebuildId);
1035-
// Also, make sure to initialize it by both printing the parent prebuild logs AND re-runnnig the before/init/prebuild tasks.
10361041
}
10371042

10381043
const workspaceID = prebuiltWorkspace.buildWorkspaceId;
@@ -1097,36 +1102,34 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
10971102

10981103
const inSameCluster = wsi.region === this.config.installationShortname;
10991104
if (!inSameCluster) {
1100-
if (mode === CreateWorkspaceMode.UsePrebuild) {
1101-
/* We need to wait for this prebuild to finish before we return from here.
1102-
* This creation mode is meant to be used once we have gone through default mode, have confirmation from the
1103-
* message bus that the prebuild is done, and now only have to wait for dbsync to come through. Thus,
1104-
* in this mode we'll poll the database until the prebuild is ready (or we time out).
1105-
*
1106-
* Note: This polling mechanism only makes sense if the prebuild runs in cluster different from ours.
1107-
* Otherwise there's no dbsync inbetween that we might have to wait for.
1108-
*
1109-
* DB sync interval is 2 seconds at the moment, we wait ten "ticks" for the data to be synchronized.
1110-
*/
1111-
const finishedPrebuiltWorkspace = await this.pollDatabaseUntilPrebuildIsAvailable(
1112-
ctx,
1113-
prebuiltWorkspace.id,
1114-
20000,
1105+
/* We need to wait for this prebuild to finish before we return from here.
1106+
* This creation mode is meant to be used once we have gone through default mode, have confirmation from the
1107+
* message bus that the prebuild is done, and now only have to wait for dbsync to come through. Thus,
1108+
* in this mode we'll poll the database until the prebuild is ready (or we time out).
1109+
*
1110+
* Note: This polling mechanism only makes sense if the prebuild runs in cluster different from ours.
1111+
* Otherwise there's no dbsync inbetween that we might have to wait for.
1112+
*
1113+
* DB sync interval is 2 seconds at the moment, we wait ten "ticks" for the data to be synchronized.
1114+
*/
1115+
const finishedPrebuiltWorkspace = await this.pollDatabaseUntilPrebuildIsAvailable(
1116+
ctx,
1117+
prebuiltWorkspace.id,
1118+
20000,
1119+
);
1120+
if (!finishedPrebuiltWorkspace) {
1121+
log.warn(
1122+
logCtx,
1123+
"did not find a finished prebuild in the database despite waiting long enough after msgbus confirmed that the prebuild had finished",
1124+
logPayload,
11151125
);
1116-
if (!finishedPrebuiltWorkspace) {
1117-
log.warn(
1118-
logCtx,
1119-
"did not find a finished prebuild in the database despite waiting long enough after msgbus confirmed that the prebuild had finished",
1120-
logPayload,
1121-
);
1122-
return;
1123-
} else {
1124-
return {
1125-
title: context.title,
1126-
originalContext: context,
1127-
prebuiltWorkspace: finishedPrebuiltWorkspace,
1128-
} as PrebuiltWorkspaceContext;
1129-
}
1126+
return;
1127+
} else {
1128+
return {
1129+
title: context.title,
1130+
originalContext: context,
1131+
prebuiltWorkspace: finishedPrebuiltWorkspace,
1132+
} as PrebuiltWorkspaceContext;
11301133
}
11311134
}
11321135

0 commit comments

Comments
 (0)