Skip to content

Commit b45b492

Browse files
committed
add feature flags for spicedb client options
Tool: gitpod/catfood.gitpod.cloud
1 parent 04f590d commit b45b492

File tree

1 file changed

+114
-30
lines changed

1 file changed

+114
-30
lines changed

components/server/src/authorization/spicedb.ts

Lines changed: 114 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import { v1 } from "@authzed/authzed-node";
88
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
99
import * as grpc from "@grpc/grpc-js";
10+
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
11+
import { TrustedValue } from "@gitpod/gitpod-protocol/lib/util/scrubbing";
1012

1113
export interface SpiceDBClientConfig {
1214
address: string;
@@ -15,6 +17,34 @@ export interface SpiceDBClientConfig {
1517

1618
export type SpiceDBClient = v1.ZedPromiseClientInterface;
1719
type Client = v1.ZedClientInterface & grpc.Client;
20+
const DEFAULT_FEATURE_FLAG_VALUE = "undefined";
21+
const DefaultClientOptions: grpc.ClientOptions = {
22+
// we ping frequently to check if the connection is still alive
23+
"grpc.keepalive_time_ms": 1000,
24+
"grpc.keepalive_timeout_ms": 1000,
25+
26+
"grpc.max_reconnect_backoff_ms": 5000,
27+
"grpc.initial_reconnect_backoff_ms": 500,
28+
"grpc.service_config": JSON.stringify({
29+
methodConfig: [
30+
{
31+
name: [{}],
32+
retryPolicy: {
33+
maxAttempts: 10,
34+
initialBackoff: "0.1s",
35+
maxBackoff: "5s",
36+
backoffMultiplier: 2.0,
37+
retryableStatusCodes: ["UNAVAILABLE", "DEADLINE_EXCEEDED"],
38+
},
39+
},
40+
],
41+
}),
42+
"grpc.enable_retries": 1, //TODO enabled by default
43+
44+
// Governs how log DNS resolution results are cached (at minimum!)
45+
// default is 30s, which is too long for us during rollouts (where service DNS entries are updated)
46+
"grpc.dns_min_time_between_resolutions_ms": 2000,
47+
};
1848

1949
export function spiceDBConfigFromEnv(): SpiceDBClientConfig | undefined {
2050
const token = process.env["SPICEDB_PRESHARED_KEY"];
@@ -36,49 +66,103 @@ export function spiceDBConfigFromEnv(): SpiceDBClientConfig | undefined {
3666

3767
export class SpiceDBClientProvider {
3868
private client: Client | undefined;
69+
private previousClientOptionsString: string = DEFAULT_FEATURE_FLAG_VALUE;
70+
private clientOptions: grpc.ClientOptions;
3971

4072
constructor(
4173
private readonly clientConfig: SpiceDBClientConfig,
4274
private readonly interceptors: grpc.Interceptor[] = [],
43-
) {}
75+
) {
76+
this.clientOptions = DefaultClientOptions;
77+
this.reconcileClientOptions()
78+
.then(() => {})
79+
.catch((e) => {
80+
log.error("[spicedb] Failed to reconcile client options", e);
81+
});
82+
}
4483

45-
getClient(): SpiceDBClient {
46-
if (!this.client) {
47-
this.client = v1.NewClient(
84+
private async reconcileClientOptions() {
85+
const doReconcileClientOptions = async () => {
86+
try {
87+
const customClientOptions = await getExperimentsClientForBackend().getValueAsync(
88+
"spicedb_client_options",
89+
DEFAULT_FEATURE_FLAG_VALUE,
90+
{},
91+
);
92+
if (customClientOptions === this.previousClientOptionsString) {
93+
return;
94+
}
95+
let clientOptions = DefaultClientOptions;
96+
if (customClientOptions && customClientOptions != DEFAULT_FEATURE_FLAG_VALUE) {
97+
clientOptions = JSON.parse(customClientOptions);
98+
}
99+
if (this.client != null) {
100+
const newClient = this.createClient(clientOptions);
101+
const oldClient = this.client;
102+
this.client = newClient;
103+
104+
log.info("[spicedb] Client options changes", {
105+
clientOptions: new TrustedValue(clientOptions),
106+
});
107+
108+
setTimeout(() => {
109+
this.closeClient(oldClient);
110+
}, 10 * 1000);
111+
}
112+
this.clientOptions = clientOptions;
113+
this.previousClientOptionsString = customClientOptions;
114+
} catch (e) {
115+
log.error("[spicedb] Failed to parse custom client options", e);
116+
}
117+
};
118+
while (true) {
119+
await doReconcileClientOptions();
120+
await new Promise((resolve) => setTimeout(resolve, 60 * 1000));
121+
}
122+
}
123+
124+
private closeClient(client: Client) {
125+
try {
126+
client.close();
127+
} catch (error) {
128+
log.error("[spicedb] Error closing client", error);
129+
}
130+
}
131+
132+
private createClient(clientOptions: grpc.ClientOptions): Client {
133+
log.debug("[spicedb] Creating client", {
134+
clientOptions: new TrustedValue(clientOptions),
135+
});
136+
try {
137+
return v1.NewClient(
48138
this.clientConfig.token,
49139
this.clientConfig.address,
50140
v1.ClientSecurity.INSECURE_PLAINTEXT_CREDENTIALS,
51-
undefined, //
141+
undefined,
52142
{
53-
// we ping frequently to check if the connection is still alive
54-
"grpc.keepalive_time_ms": 1000,
55-
"grpc.keepalive_timeout_ms": 1000,
56-
57-
"grpc.max_reconnect_backoff_ms": 5000,
58-
"grpc.initial_reconnect_backoff_ms": 500,
59-
"grpc.service_config": JSON.stringify({
60-
methodConfig: [
61-
{
62-
name: [{}],
63-
retryPolicy: {
64-
maxAttempts: 10,
65-
initialBackoff: "0.1s",
66-
maxBackoff: "5s",
67-
backoffMultiplier: 2.0,
68-
retryableStatusCodes: ["UNAVAILABLE", "DEADLINE_EXCEEDED"],
69-
},
70-
},
71-
],
72-
}),
73-
"grpc.enable_retries": 1, //TODO enabled by default
74-
75-
// Governs how log DNS resolution results are cached (at minimum!)
76-
// default is 30s, which is too long for us during rollouts (where service DNS entries are updated)
77-
"grpc.dns_min_time_between_resolutions_ms": 2000,
143+
...clientOptions,
144+
interceptors: this.interceptors,
145+
},
146+
) as Client;
147+
} catch (error) {
148+
log.error("[spicedb] Error create client, fallback to default options", error);
149+
return v1.NewClient(
150+
this.clientConfig.token,
151+
this.clientConfig.address,
152+
v1.ClientSecurity.INSECURE_PLAINTEXT_CREDENTIALS,
153+
undefined,
154+
{
155+
...DefaultClientOptions,
78156
interceptors: this.interceptors,
79157
},
80158
) as Client;
81159
}
160+
}
161+
162+
getClient(): SpiceDBClient {
163+
if (!this.client) {
164+
this.client = this.createClient(this.clientOptions);
165+
}
82166
return this.client.promises;
83167
}
84168
}

0 commit comments

Comments
 (0)