Skip to content

Commit 750adeb

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

File tree

1 file changed

+97
-35
lines changed

1 file changed

+97
-35
lines changed

components/server/src/authorization/spicedb.ts

Lines changed: 97 additions & 35 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_VAULE = "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,48 +66,80 @@ export function spiceDBConfigFromEnv(): SpiceDBClientConfig | undefined {
3666

3767
export class SpiceDBClientProvider {
3868
private client: Client | undefined;
69+
private previousClientOptionsString: string = DEFAULT_FEATURE_FLAG_VAULE;
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(
48-
this.clientConfig.token,
49-
this.clientConfig.address,
50-
v1.ClientSecurity.INSECURE_PLAINTEXT_CREDENTIALS,
51-
undefined, //
52-
{
53-
// we ping frequently to check if the connection is still alive
54-
"grpc.keepalive_time_ms": 1000,
55-
"grpc.keepalive_timeout_ms": 1000,
84+
private async reconcileClientOptions() {
85+
const doReconcileClientOptions = async () => {
86+
const customClientOptions = await getExperimentsClientForBackend().getValueAsync(
87+
"spicedb_client_options",
88+
DEFAULT_FEATURE_FLAG_VAULE,
89+
{},
90+
);
91+
if (customClientOptions === this.previousClientOptionsString) {
92+
return;
93+
}
94+
try {
95+
let clientOptions = DefaultClientOptions;
96+
if (customClientOptions && customClientOptions != DEFAULT_FEATURE_FLAG_VAULE) {
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;
56103

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
104+
log.info("[spicedb] client options changes", {
105+
clientOptions: new TrustedValue(clientOptions),
106+
});
74107

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,
78-
interceptors: this.interceptors,
79-
},
80-
) as Client;
108+
setTimeout(() => {
109+
oldClient.close();
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 createClient(clientOptions: grpc.ClientOptions): Client {
125+
log.debug("[spicedb] creating client", {
126+
clientOptions: new TrustedValue(clientOptions),
127+
});
128+
return v1.NewClient(
129+
this.clientConfig.token,
130+
this.clientConfig.address,
131+
v1.ClientSecurity.INSECURE_PLAINTEXT_CREDENTIALS,
132+
undefined, //
133+
{
134+
...clientOptions,
135+
interceptors: this.interceptors,
136+
},
137+
) as Client;
138+
}
139+
140+
getClient(): SpiceDBClient {
141+
if (!this.client) {
142+
this.client = this.createClient(this.clientOptions);
81143
}
82144
return this.client.promises;
83145
}

0 commit comments

Comments
 (0)