Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[server] Use image-builder from workspace cluster #9337

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions components/image-builder-api/typescript/src/sugar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import { BuildRequest, BuildResponse, BuildStatus, LogsRequest, LogsResponse, Re
import { injectable, inject, optional } from 'inversify';
import * as grpc from "@grpc/grpc-js";
import { TextDecoder } from "util";
import { ImageBuildLogInfo } from "@gitpod/gitpod-protocol";
import { ImageBuildLogInfo, User, Workspace, WorkspaceInstance } from "@gitpod/gitpod-protocol";

export const ImageBuilderClientProvider = Symbol("ImageBuilderClientProvider");

// ImageBuilderClientProvider caches image builder connections
export interface ImageBuilderClientProvider {
getDefault(): PromisifiedImageBuilderClient
getDefault(user: User, workspace: Workspace, instance: WorkspaceInstance): Promise<PromisifiedImageBuilderClient>
}

function withTracing(ctx: TraceContext) {
Expand Down Expand Up @@ -53,7 +53,7 @@ export class CachingImageBuilderClientProvider implements ImageBuilderClientProv
// Thus it makes sense to cache them rather than create a new connection for each request.
protected connectionCache: PromisifiedImageBuilderClient | undefined;

getDefault() {
async getDefault(user: User, workspace: Workspace, instance: WorkspaceInstance) {
let interceptors: grpc.Interceptor[] = [];
if (this.clientCallMetrics) {
interceptors = [ createClientCallMetricsInterceptor(this.clientCallMetrics) ];
Expand All @@ -78,6 +78,18 @@ export class CachingImageBuilderClientProvider implements ImageBuilderClientProv
return connection;
}

promisify(c: ImageBuilderClient): PromisifiedImageBuilderClient {
let interceptors: grpc.Interceptor[] = [];
if (this.clientCallMetrics) {
interceptors = [ createClientCallMetricsInterceptor(this.clientCallMetrics) ];
}

return new PromisifiedImageBuilderClient(
new ImageBuilderClient(this.clientConfig.address, grpc.credentials.createInsecure()),
interceptors
);
}

}

// StagedBuildResponse captures the multi-stage nature (starting, running, done) of image builds.
Expand Down
4 changes: 3 additions & 1 deletion components/server/src/container-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import { ReferrerPrefixParser } from "./workspace/referrer-prefix-context-parser
import { InstallationAdminTelemetryDataProvider } from "./installation-admin/telemetry-data-provider";
import { IDEService } from "./ide-service";
import { LicenseEvaluator } from "@gitpod/licensor/lib";
import { WorkspaceClusterImagebuilderClientProvider } from "./workspace/workspace-cluster-imagebuilder-client-provider";

export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
bind(Config).toConstantValue(ConfigFile.fromFile());
Expand Down Expand Up @@ -162,7 +163,8 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
return { address: config.imageBuilderAddr };
});
bind(CachingImageBuilderClientProvider).toSelf().inSingletonScope();
bind(ImageBuilderClientProvider).toService(CachingImageBuilderClientProvider);
bind(WorkspaceClusterImagebuilderClientProvider).toSelf().inSingletonScope();
bind(ImageBuilderClientProvider).toService(WorkspaceClusterImagebuilderClientProvider);
bind(ImageBuilderClientCallMetrics).toService(IClientCallMetrics);

/* The binding order of the context parser does not configure preference/a working order. Each context parser must be able
Expand Down
48 changes: 6 additions & 42 deletions components/server/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ import {
RemotePageMessage,
RemoteTrackMessage,
} from "@gitpod/gitpod-protocol/lib/analytics";
import { ImageBuilderClientProvider, LogsRequest } from "@gitpod/image-builder/lib";
import { ImageBuilderClientProvider } from "@gitpod/image-builder/lib";
import { WorkspaceManagerClientProvider } from "@gitpod/ws-manager/lib/client-provider";
import {
ControlPortRequest,
Expand Down Expand Up @@ -1559,11 +1559,11 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
// during roll-out this is our fall-back case.
// Afterwards we might want to do some spinning-lock and re-check for a certain period (30s?) to give db-sync
// a change to move the imageBuildLogInfo across the globe.

log.warn(logCtx, "imageBuild logs: fallback!");
ctx.span?.setTag("workspace.imageBuild.logs.fallback", true);
await this.deprecatedDoWatchWorkspaceImageBuildLogs(ctx, logCtx, workspace);
return;
log.error(logCtx, "cannot watch imagebuild logs for workspaceId: no image build info available");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

throw new ResponseError(
ErrorCodes.HEADLESS_LOG_NOT_YET_AVAILABLE,
"cannot watch imagebuild logs for workspaceId",
);
}

const aborted = new Deferred<boolean>();
Expand Down Expand Up @@ -1608,42 +1608,6 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
}
}

protected async deprecatedDoWatchWorkspaceImageBuildLogs(
ctx: TraceContext,
logCtx: LogContext,
workspace: Workspace,
) {
if (!workspace.imageNameResolved) {
log.debug(logCtx, `No imageNameResolved set for workspaceId, cannot watch logs.`);
return;
}

try {
const imgbuilder = this.imageBuilderClientProvider.getDefault();
const req = new LogsRequest();
req.setCensored(true);
req.setBuildRef(workspace.imageNameResolved);

let lineCount = 0;
await imgbuilder.logs(ctx, req, (data) => {
if (!this.client) {
return "stop";
}
data = data.replace("\n", WorkspaceImageBuild.LogLine.DELIMITER);
lineCount += data.split(WorkspaceImageBuild.LogLine.DELIMITER_REGEX).length;

this.client.onWorkspaceImageBuildLogs(undefined as any, {
text: data,
isDiff: true,
upToLine: lineCount,
});
return "continue";
});
} catch (err) {
log.error(logCtx, `cannot watch logs for workspaceId`, err);
}
}

async getHeadlessLog(ctx: TraceContext, instanceId: string): Promise<HeadlessLogUrls> {
traceAPIParams(ctx, { instanceId });

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License-AGPL.txt in the project root for license information.
*/

import { Workspace, WorkspaceInstance } from "@gitpod/gitpod-protocol";
import { IClientCallMetrics } from "@gitpod/gitpod-protocol/lib/messaging/client-call-metrics";
import { defaultGRPCOptions } from "@gitpod/gitpod-protocol/lib/util/grpc";
import {
ImageBuilderClient,
ImageBuilderClientCallMetrics,
ImageBuilderClientProvider,
PromisifiedImageBuilderClient,
} from "@gitpod/image-builder/lib";
import { WorkspaceManagerClientProvider } from "@gitpod/ws-manager/lib/client-provider";
import {
WorkspaceManagerClientProviderCompositeSource,
WorkspaceManagerClientProviderSource,
} from "@gitpod/ws-manager/lib/client-provider-source";
import { ExtendedUser } from "@gitpod/ws-manager/lib/constraints";
import { inject, injectable, optional } from "inversify";

@injectable()
export class WorkspaceClusterImagebuilderClientProvider implements ImageBuilderClientProvider {
@inject(WorkspaceManagerClientProviderCompositeSource)
protected readonly source: WorkspaceManagerClientProviderSource;
@inject(WorkspaceManagerClientProvider) protected readonly clientProvider: WorkspaceManagerClientProvider;
@inject(ImageBuilderClientCallMetrics) @optional() protected readonly clientCallMetrics: IClientCallMetrics;

// gRPC connections can be used concurrently, even across services.
// Thus it makes sense to cache them rather than create a new connection for each request.
protected readonly connectionCache = new Map<string, ImageBuilderClient>();

async getDefault(
user: ExtendedUser,
workspace: Workspace,
instance: WorkspaceInstance,
): Promise<PromisifiedImageBuilderClient> {
const clusters = await this.clientProvider.getStartClusterSets(user, workspace, instance);
for await (let cluster of clusters) {
const info = await this.source.getWorkspaceCluster(cluster.installation);
if (!info) {
continue;
}

var client = this.connectionCache.get(info.name);
if (!client) {
client = this.clientProvider.createConnection(ImageBuilderClient, info, defaultGRPCOptions);
this.connectionCache.set(info.name, client);
}
return new PromisifiedImageBuilderClient(client, []);
}

throw new Error("no image-builder available");
}
}
10 changes: 7 additions & 3 deletions components/server/src/workspace/workspace-starter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,11 @@ export class WorkspaceStarter {
auth.setTotal(allowAll);
req.setAuth(auth);

const client = this.imagebuilderClientProvider.getDefault();
const client = await this.imagebuilderClientProvider.getDefault(
user,
workspace,
{} as WorkspaceInstance,
);
const res = await client.resolveBaseImage({ span }, req);
workspace.imageSource = <WorkspaceImageSourceReference>{
baseImageResolved: res.getRef(),
Expand Down Expand Up @@ -902,7 +906,7 @@ export class WorkspaceStarter {
): Promise<boolean> {
const span = TraceContext.startSpan("needsImageBuild", ctx);
try {
const client = this.imagebuilderClientProvider.getDefault();
const client = await this.imagebuilderClientProvider.getDefault(user, workspace, instance);
const { src, auth, disposable } = await this.prepareBuildRequest(
{ span },
workspace,
Expand Down Expand Up @@ -942,7 +946,7 @@ export class WorkspaceStarter {

try {
// Start build...
const client = this.imagebuilderClientProvider.getDefault();
const client = await this.imagebuilderClientProvider.getDefault(user, workspace, instance);
const { src, auth, disposable } = await this.prepareBuildRequest(
{ span },
workspace,
Expand Down
Loading