44import com .google .common .annotations .VisibleForTesting ;
55import com .google .common .util .concurrent .ThreadFactoryBuilder ;
66import com .google .protobuf .Struct ;
7+ import com .spotify .confidence .TokenHolder .Token ;
78import com .spotify .confidence .shaded .flags .admin .v1 .FlagAdminServiceGrpc ;
89import com .spotify .confidence .shaded .flags .admin .v1 .ResolverStateServiceGrpc ;
10+ import com .spotify .confidence .shaded .flags .admin .v1 .ResolverStateServiceGrpc .ResolverStateServiceBlockingStub ;
911import com .spotify .confidence .shaded .flags .resolver .v1 .InternalFlagLoggerServiceGrpc ;
1012import com .spotify .confidence .shaded .flags .resolver .v1 .Sdk ;
1113import com .spotify .confidence .shaded .iam .v1 .AuthServiceGrpc ;
12- import com .spotify .confidence .shaded .iam .v1 .ClientCredential ;
14+ import com .spotify .confidence .shaded .iam .v1 .AuthServiceGrpc .AuthServiceBlockingStub ;
15+ import com .spotify .confidence .shaded .iam .v1 .ClientCredential .ClientSecret ;
1316import io .grpc .Channel ;
1417import io .grpc .ClientInterceptors ;
1518import io .grpc .ManagedChannel ;
1821import java .time .Duration ;
1922import java .time .Instant ;
2023import java .util .List ;
24+ import java .util .Map ;
2125import java .util .Optional ;
2226import java .util .concurrent .CompletableFuture ;
2327import java .util .concurrent .Executors ;
2630import java .util .concurrent .atomic .AtomicReference ;
2731import java .util .function .Supplier ;
2832import org .apache .commons .lang3 .RandomStringUtils ;
33+ import org .slf4j .LoggerFactory ;
2934
3035class LocalResolverServiceFactory implements ResolverServiceFactory {
3136
@@ -35,9 +40,7 @@ class LocalResolverServiceFactory implements ResolverServiceFactory {
3540 private final SwapWasmResolverApi wasmResolveApi ;
3641 private final Supplier <Instant > timeSupplier ;
3742 private final Supplier <String > resolveIdSupplier ;
38- private final ResolveLogger resolveLogger ;
39- private final AssignLogger assignLogger ;
40- private final boolean enableExposureLogs ;
43+ private final FlagLogger flagLogger ;
4144 private static final MetricRegistry metricRegistry = new MetricRegistry ();
4245 private static final String CONFIDENCE_DOMAIN = "edge-grpc.spotify.com" ;
4346 private static final Duration ASSIGN_LOG_INTERVAL = Duration .ofSeconds (10 );
@@ -60,30 +63,28 @@ private static ManagedChannel createConfidenceChannel() {
6063 }
6164
6265 static FlagResolverService from (ApiSecret apiSecret , String clientSecret , boolean isWasm ) {
63- return createFlagResolverService (apiSecret , clientSecret , isWasm , true );
66+ return createFlagResolverService (apiSecret , clientSecret , isWasm );
6467 }
6568
66- static FlagResolverService from (
67- ApiSecret apiSecret , String clientSecret , boolean isWasm , boolean enableExposureLogs ) {
68- return createFlagResolverService (apiSecret , clientSecret , isWasm , enableExposureLogs );
69+ static FlagResolverService from (AccountStateProvider accountStateProvider ) {
70+ return createFlagResolverService (accountStateProvider );
6971 }
7072
7173 private static FlagResolverService createFlagResolverService (
72- ApiSecret apiSecret , String clientSecret , boolean isWasm , boolean enableExposureLogs ) {
74+ ApiSecret apiSecret , String clientSecret , boolean isWasm ) {
7375 final var channel = createConfidenceChannel ();
74- final AuthServiceGrpc .AuthServiceBlockingStub authService =
75- AuthServiceGrpc .newBlockingStub (channel );
76+ final AuthServiceBlockingStub authService = AuthServiceGrpc .newBlockingStub (channel );
7677 final TokenHolder tokenHolder =
7778 new TokenHolder (apiSecret .clientId (), apiSecret .clientSecret (), authService );
78- final TokenHolder . Token token = tokenHolder .getToken ();
79+ final Token token = tokenHolder .getToken ();
7980 final Channel authenticatedChannel =
8081 ClientInterceptors .intercept (channel , new JwtAuthClientInterceptor (tokenHolder ));
8182 final var flagLoggerStub = InternalFlagLoggerServiceGrpc .newBlockingStub (authenticatedChannel );
8283 final long assignLogCapacity =
8384 Optional .ofNullable (System .getenv ("CONFIDENCE_ASSIGN_LOG_CAPACITY" ))
8485 .map (Long ::parseLong )
8586 .orElseGet (() -> (long ) (Runtime .getRuntime ().maxMemory () / 3.0 ));
86- final ResolverStateServiceGrpc . ResolverStateServiceBlockingStub resolverStateService =
87+ final ResolverStateServiceBlockingStub resolverStateService =
8788 ResolverStateServiceGrpc .newBlockingStub (authenticatedChannel );
8889 final HealthStatusManager healthStatusManager = new HealthStatusManager ();
8990 final HealthStatus healthStatus = new HealthStatus (healthStatusManager );
@@ -103,7 +104,7 @@ private static FlagResolverService createFlagResolverService(
103104 flagLoggerStub , ASSIGN_LOG_INTERVAL , metricRegistry , assignLogCapacity );
104105 final ResolveLogger resolveLogger =
105106 ResolveLogger .createStarted (() -> flagsAdminStub , RESOLVE_INFO_LOG_INTERVAL );
106- final var flagLogger = getFlagLogger (resolveLogger , assignLogger , enableExposureLogs );
107+ final var flagLogger = getFlagLogger (resolveLogger , assignLogger );
107108 if (isWasm ) {
108109 final SwapWasmResolverApi wasmResolverApi =
109110 new SwapWasmResolverApi (
@@ -117,96 +118,87 @@ private static FlagResolverService createFlagResolverService(
117118 pollIntervalSeconds ,
118119 pollIntervalSeconds ,
119120 TimeUnit .SECONDS );
120- return new LocalResolverServiceFactory (
121- wasmResolverApi ,
122- sidecarFlagsAdminFetcher .stateHolder (),
123- resolveTokenConverter ,
124- resolveLogger ,
125- assignLogger ,
126- enableExposureLogs )
127- .create (clientSecret );
121+ return request -> CompletableFuture .completedFuture (wasmResolverApi .resolve (request ));
128122 } else {
129123 flagsFetcherExecutor .scheduleWithFixedDelay (
130124 sidecarFlagsAdminFetcher ::reload ,
131125 pollIntervalSeconds ,
132126 pollIntervalSeconds ,
133127 TimeUnit .SECONDS );
134128 return new LocalResolverServiceFactory (
135- sidecarFlagsAdminFetcher .stateHolder (),
136- resolveTokenConverter ,
137- resolveLogger ,
138- assignLogger ,
139- enableExposureLogs )
129+ sidecarFlagsAdminFetcher .stateHolder (), resolveTokenConverter , flagLogger )
140130 .create (clientSecret );
141131 }
142132 }
143133
144- LocalResolverServiceFactory (
145- SwapWasmResolverApi wasmResolveApi ,
146- AtomicReference <ResolverState > resolverStateHolder ,
147- ResolveTokenConverter resolveTokenConverter ,
148- ResolveLogger resolveLogger ,
149- AssignLogger assignLogger ,
150- boolean enableExposureLogs ) {
151- this (
152- wasmResolveApi ,
153- resolverStateHolder ,
154- resolveTokenConverter ,
155- Instant ::now ,
156- () -> RandomStringUtils .randomAlphanumeric (32 ),
157- resolveLogger ,
158- assignLogger ,
159- enableExposureLogs );
160- }
134+ private static FlagResolverService createFlagResolverService (
135+ AccountStateProvider accountStateProvider ) {
136+ final long pollIntervalSeconds =
137+ Optional .ofNullable (System .getenv ("CONFIDENCE_RESOLVER_POLL_INTERVAL_SECONDS" ))
138+ .map (Long ::parseLong )
139+ .orElse (Duration .ofMinutes (5 ).toSeconds ());
140+ final AccountState initialAccountState = accountStateProvider .provide ();
141+ final AtomicReference <ResolverState > stateHolder =
142+ new AtomicReference <>(
143+ new ResolverState (
144+ Map .of (initialAccountState .account ().name (), initialAccountState ),
145+ initialAccountState .secrets ()));
146+ final AtomicReference <com .spotify .confidence .shaded .flags .admin .v1 .ResolverState >
147+ rawStateHolder = new AtomicReference <>(stateHolder .get ().toProto ());
148+ final FlagLogger flagLogger = new NoopFlagLogger ();
149+ final SwapWasmResolverApi wasmResolverApi =
150+ new SwapWasmResolverApi (flagLogger , rawStateHolder .get ().toByteArray ());
151+ flagsFetcherExecutor .scheduleAtFixedRate (
152+ () -> {
153+ try {
154+ final AccountState newAccountState = accountStateProvider .provide ();
155+ final ResolverState newResolverState =
156+ new ResolverState (
157+ Map .of (newAccountState .account ().name (), newAccountState ),
158+ newAccountState .secrets ());
159+ stateHolder .set (newResolverState );
161160
162- LocalResolverServiceFactory (
163- SwapWasmResolverApi wasmResolveApi ,
164- AtomicReference <ResolverState > resolverStateHolder ,
165- ResolveTokenConverter resolveTokenConverter ,
166- ResolveLogger resolveLogger ,
167- AssignLogger assignLogger ) {
168- this (
169- wasmResolveApi ,
170- resolverStateHolder ,
171- resolveTokenConverter ,
172- Instant ::now ,
173- () -> RandomStringUtils .randomAlphanumeric (32 ),
174- resolveLogger ,
175- assignLogger ,
176- true );
161+ final com .spotify .confidence .shaded .flags .admin .v1 .ResolverState newRawState =
162+ newResolverState .toProto ();
163+ rawStateHolder .set (newRawState );
164+ wasmResolverApi .updateState (newRawState .toByteArray ());
165+ } catch (Exception e ) {
166+ LoggerFactory .getLogger (LocalResolverServiceFactory .class )
167+ .warn ("Failed to refresh AccountState from provider, ignoring refresh" , e );
168+ }
169+ },
170+ pollIntervalSeconds ,
171+ pollIntervalSeconds ,
172+ TimeUnit .SECONDS );
173+
174+ return request -> CompletableFuture .completedFuture (wasmResolverApi .resolve (request ));
177175 }
178176
179177 LocalResolverServiceFactory (
180178 AtomicReference <ResolverState > resolverStateHolder ,
181179 ResolveTokenConverter resolveTokenConverter ,
182- ResolveLogger resolveLogger ,
183- AssignLogger assignLogger ,
184- boolean enableExposureLogs ) {
180+ FlagLogger flagLogger ) {
185181 this (
186182 null ,
187183 resolverStateHolder ,
188184 resolveTokenConverter ,
189185 Instant ::now ,
190186 () -> RandomStringUtils .randomAlphanumeric (32 ),
191- resolveLogger ,
192- assignLogger ,
193- enableExposureLogs );
187+ flagLogger );
194188 }
195189
196190 LocalResolverServiceFactory (
191+ SwapWasmResolverApi wasmResolveApi ,
197192 AtomicReference <ResolverState > resolverStateHolder ,
198193 ResolveTokenConverter resolveTokenConverter ,
199- ResolveLogger resolveLogger ,
200- AssignLogger assignLogger ) {
194+ FlagLogger flagLogger ) {
201195 this (
202- null ,
196+ wasmResolveApi ,
203197 resolverStateHolder ,
204198 resolveTokenConverter ,
205199 Instant ::now ,
206200 () -> RandomStringUtils .randomAlphanumeric (32 ),
207- resolveLogger ,
208- assignLogger ,
209- true );
201+ flagLogger );
210202 }
211203
212204 LocalResolverServiceFactory (
@@ -215,17 +207,13 @@ private static FlagResolverService createFlagResolverService(
215207 ResolveTokenConverter resolveTokenConverter ,
216208 Supplier <Instant > timeSupplier ,
217209 Supplier <String > resolveIdSupplier ,
218- ResolveLogger resolveLogger ,
219- AssignLogger assignLogger ,
220- boolean enableExposureLogs ) {
210+ FlagLogger flagLogger ) {
221211 this .wasmResolveApi = wasmResolveApi ;
222212 this .resolverStateHolder = resolverStateHolder ;
223213 this .resolveTokenConverter = resolveTokenConverter ;
224214 this .timeSupplier = timeSupplier ;
225215 this .resolveIdSupplier = resolveIdSupplier ;
226- this .resolveLogger = resolveLogger ;
227- this .assignLogger = assignLogger ;
228- this .enableExposureLogs = enableExposureLogs ;
216+ this .flagLogger = flagLogger ;
229217 }
230218
231219 @ VisibleForTesting
@@ -236,15 +224,11 @@ public void setState(byte[] state) {
236224 }
237225
238226 @ Override
239- public FlagResolverService create (ClientCredential .ClientSecret clientSecret ) {
240- if (wasmResolveApi != null ) {
241- return request -> CompletableFuture .completedFuture (wasmResolveApi .resolve (request ));
242- }
227+ public FlagResolverService create (ClientSecret clientSecret ) {
243228 return createJavaFlagResolverService (clientSecret );
244229 }
245230
246- private FlagResolverService createJavaFlagResolverService (
247- ClientCredential .ClientSecret clientSecret ) {
231+ private FlagResolverService createJavaFlagResolverService (ClientSecret clientSecret ) {
248232 final ResolverState state = resolverStateHolder .get ();
249233
250234 final AccountClient accountClient = state .secrets ().get (clientSecret );
@@ -254,8 +238,6 @@ private FlagResolverService createJavaFlagResolverService(
254238 }
255239
256240 final AccountState accountState = state .accountStates ().get (accountClient .accountName ());
257- final var flagLogger = getFlagLogger (resolveLogger , assignLogger , enableExposureLogs );
258-
259241 return new JavaFlagResolverService (
260242 accountState ,
261243 accountClient ,
@@ -265,31 +247,7 @@ private FlagResolverService createJavaFlagResolverService(
265247 resolveIdSupplier );
266248 }
267249
268- private static FlagLogger getFlagLogger (
269- ResolveLogger resolveLogger , AssignLogger assignLogger , boolean enableExposureLogs ) {
270- if (!enableExposureLogs ) {
271- return new FlagLogger () {
272- @ Override
273- public void logResolve (
274- String resolveId ,
275- Struct evaluationContext ,
276- Sdk sdk ,
277- AccountClient accountClient ,
278- List <ResolvedValue > values ) {
279- // Logging disabled - no-op
280- }
281-
282- @ Override
283- public void logAssigns (
284- String resolveId ,
285- Sdk sdk ,
286- List <FlagToApply > flagsToApply ,
287- AccountClient accountClient ) {
288- // Logging disabled - no-op
289- }
290- };
291- }
292-
250+ private static FlagLogger getFlagLogger (ResolveLogger resolveLogger , AssignLogger assignLogger ) {
293251 return new FlagLogger () {
294252 @ Override
295253 public void logResolve (
0 commit comments