@@ -903,6 +903,74 @@ public TestableGapicSpannerRpc(SpannerOptions options) {
903903 super (options );
904904 }
905905
906+ @ Override
907+ GcpFallbackChannelOptions createFallbackChannelOptions (
908+ GcpFallbackOpenTelemetry fallbackTelemetry ) {
909+ // Override default 1-minute period to 10ms for instant testing
910+ return GcpFallbackChannelOptions .newBuilder ()
911+ .setPrimaryChannelName ("directpath" )
912+ .setFallbackChannelName ("cloudpath" )
913+ .setPeriod (Duration .ofMillis (10 ))
914+ .setGcpFallbackOpenTelemetry (fallbackTelemetry )
915+ .build ();
916+ }
917+ }
918+
919+ @ Test
920+ public void testFallbackIntegration_doesNotSwitchWhenThresholdNotMet () throws Exception {
921+ GapicSpannerRpc .enableGcpFallbackEnv = true ;
922+
923+ // Setup OpenTelemetry to capture metrics
924+ InMemoryMetricReader metricReader = InMemoryMetricReader .create ();
925+ SdkMeterProvider meterProvider =
926+ SdkMeterProvider .builder ().registerMetricReader (metricReader ).build ();
927+ OpenTelemetrySdk openTelemetry =
928+ OpenTelemetrySdk .builder ().setMeterProvider (meterProvider ).build ();
929+
930+ // Setup Options with invalid host to force error
931+ SpannerOptions options =
932+ SpannerOptions .newBuilder ()
933+ .setProjectId ("test-project" )
934+ .setEnableDirectAccess (true )
935+ .setHost ("http://localhost:1" ) // Closed port
936+ .setCredentials (NoCredentials .getInstance ())
937+ .setOpenTelemetry (openTelemetry )
938+ .build ();
939+
940+ TestableGapicSpannerRpc rpc = new TestableGapicSpannerRpc (options );
941+
942+ try {
943+ // Make a call that is expected to fail
944+ try {
945+ rpc .executeBatchDml (
946+ com .google .spanner .v1 .ExecuteBatchDmlRequest .newBuilder ()
947+ .setSession ("projects/p/instances/i/databases/d/sessions/s" )
948+ .build (),
949+ null );
950+ } catch (Exception expected ) {
951+ // Expect a connection error
952+ }
953+
954+ // Wait briefly for the 10ms period to trigger the fallback check
955+ Thread .sleep (100 );
956+
957+ // Verify Fallback via Metrics
958+ Collection <MetricData > metrics = metricReader .collectAllMetrics ();
959+ boolean fallbackOccurred =
960+ metrics .stream ().anyMatch (md -> md .getName ().contains ("fallback_count" ) && hasValue (md ));
961+
962+ assertFalse ("Fallback metric should not be present" , fallbackOccurred );
963+
964+ } finally {
965+ rpc .shutdown ();
966+ }
967+ }
968+
969+ static class TestableGapicSpannerRpcWithLowerMinFailedCalls extends GapicSpannerRpc {
970+ public TestableGapicSpannerRpcWithLowerMinFailedCalls (SpannerOptions options ) {
971+ super (options );
972+ }
973+
906974 @ Override
907975 GcpFallbackChannelOptions createFallbackChannelOptions (
908976 GcpFallbackOpenTelemetry fallbackTelemetry ) {
@@ -938,7 +1006,8 @@ public void testFallbackIntegration_switchesToFallbackOnFailure() throws Excepti
9381006 .setOpenTelemetry (openTelemetry )
9391007 .build ();
9401008
941- TestableGapicSpannerRpc rpc = new TestableGapicSpannerRpc (options );
1009+ TestableGapicSpannerRpcWithLowerMinFailedCalls rpc =
1010+ new TestableGapicSpannerRpcWithLowerMinFailedCalls (options );
9421011
9431012 try {
9441013 // Make a call that is expected to fail
0 commit comments