简体   繁体   English

如何使用 Spring Boot、R2DBC 和 TestContainers(Spanner 模拟器)为 Spanner 集成测试提供凭据

[英]How to provide credentials for Spanner integration tests with Spring Boot, R2DBC and TestContainers (Spanner Emulator)

Here is the error from the execution of the integration tests:这是执行集成测试的错误:

com.google.cloud.spanner.SpannerException: 
UNAUTHENTICATED: com.google.api.gax.rpc.UnauthenticatedException: io.grpc.StatusRuntimeException: 
UNAUTHENTICATED: Request is missing required authentication credential. 
Expected OAuth 2 access token, login cookie or other valid authentication credential. 
See https://developers.google.com/identity/sign-in/web/devconsole-project.

code (simplified for easier and faster reproducing):代码(为了更容易和更快的复制而简化):

@ExtendWith(SpringExtension.class)
@SpringBootTest
@ActiveProfiles("it")
@DirtiesContext
public class SpannerIT {

    static final String PROJECT_ID = "emulator-config";
    static final String INSTANCE_ID = "test-instance";
    static final String DATABASE_NAME = "test-database";

    static SpannerEmulatorContainer spannerContainer;

    @Autowired
    private R2dbcEntityTemplate template;

    @DynamicPropertySource
    static void properties(DynamicPropertyRegistry r) {
        r.add("spring.cloud.gcp.spanner.emulator-host", spannerContainer::getEmulatorGrpcEndpoint);
        r.add("spring.r2dbc.url", () -> "r2dbc:cloudspanner://" + spannerContainer.getEmulatorHttpEndpoint() +
            "/projects/" + PROJECT_ID + "/instances/" + INSTANCE_ID + "/databases/" + DATABASE_NAME);
    }

    @BeforeAll
    public static void beforeAll() {
        spannerContainer = new SpannerEmulatorContainer(
            DockerImageName.parse("gcr.io/cloud-spanner-emulator/emulator").withTag("1.4.1"));
        spannerContainer.start();
    }

    @AfterAll
    public static void afterAll() {
        spannerContainer.stop();
    }

    @Test
    void test() {
        StepVerifier.create(
                template.select(Query.query(CriteriaDefinition.empty()), SomeClazz.class)
            )
            .verifyComplete();

    }

    @Data
    @Table("test")
    public class SomeClazz {

        @Column("column")
        private String column;
    }
}

and the configs (application-it.yml):和配置(application-it.yml):

spring:
  cloud:
    gcp:
      spanner:
        project-id: emulator-config

  r2dbc:
    url: overwritten_in_tests
    properties:
      usePlainText: true
      autoConfigEmulator: true

By setting the usePlainText to the r2dbc URL in theory we should bypass the credentials issue.通过将usePlainText设置为 r2dbc URL 理论上我们应该绕过凭据问题。 The credentials provider is correctly configured to NoCredentials ( SpannerConnectionFactoryProvider#extractCredentials ).凭据提供程序已正确配置为NoCredentials ( SpannerConnectionFactoryProvider#extractCredentials )。

Github repo with the complete code: Github repo 完整代码:
https://github.com/magiccrafter/spanner-spring-boot-r2jdbc-app https://github.com/magiccrafter/spanner-spring-boot-r2jdbc-app

The error you are getting is actually hinting that your credentials options are working, but that something is trying to connect to Cloud Spanner (or some other Cloud product) without credentials.您收到的错误实际上是在暗示您的凭据选项正在运行,但有些东西正在尝试在没有凭据的情况下连接到 Cloud Spanner(或其他一些云产品)。 The emulator would normally not return an Unauthenticated error, as it does not expect any credentials.模拟器通常不会返回Unauthenticated错误,因为它不需要任何凭据。 Could you check:你能检查一下:

  1. That your tests are really trying to connect to the emulator endpoint in all cases, and not real Cloud Spanner.在所有情况下,您的测试实际上都在尝试连接到模拟器端点,而不是真正的 Cloud Spanner。
  2. That your tests are not accidentally trying to access some other Cloud product with NoCredentials .您的测试不会意外地尝试使用NoCredentials访问其他一些云产品。

Thanks to elefeint 's hint:感谢elefeint的提示:

You would also have to set up an environment variable export SPANNER_EMULATOR_HOST=localhost:9010, so that the Spanner client library picks up the non-production host, similar to jdbc driver.您还必须设置环境变量 export SPANNER_EMULATOR_HOST=localhost:9010,以便 Spanner 客户端库选择非生产主机,类似于 jdbc 驱动程序。

Upvote #200 for allowing programmatic customization of emulator. Upvote #200 允许以编程方式自定义模拟器。

for more details check the Github issue #200有关详细信息,请查看Github 问题 #200

Here is the workaround I came up with to bypass the requirement for SPANNER_EMULATOR_HOST env variable prior to the integration tests run:这是我想出的解决方法,可以在集成测试运行之前绕过对 SPANNER_EMULATOR_HOST 环境变量的要求:

@Testcontainers
@ExtendWith(SystemStubsExtension.class)
@ExtendWith(SpringExtension.class)
@SpringBootTest
@ActiveProfiles("it")
@DirtiesContext
public class SpannerIT {

    static final String PROJECT_ID = "nv-local";
    static final String INSTANCE_ID = "test-instance";
    static final String DATABASE_NAME = "trades";

    @Container
    private static final SpannerEmulatorContainer spannerContainer =
        new SpannerEmulatorContainer(
            DockerImageName.parse("gcr.io/cloud-spanner-emulator/emulator").withTag("1.4.1"));

    @SystemStub
    private static EnvironmentVariables environmentVariables;

    @Autowired
    ConnectionFactory connectionFactory;

    @DynamicPropertySource
    static void properties(DynamicPropertyRegistry r) {
        environmentVariables.set("SPANNER_EMULATOR_HOST", spannerContainer.getEmulatorGrpcEndpoint());
        r.add("spring.r2dbc.url", () -> "r2dbc:cloudspanner://" +
            "/projects/" + PROJECT_ID + "/instances/" + INSTANCE_ID + "/databases/" + DATABASE_NAME);
    }

This way, we can have the Spanner emulator in Testcontainers without starting the spanner emulator separately and mapping a concrete port.这样,我们就可以在 Testcontainers 中拥有 Spanner 模拟器,而无需单独启动 Spanner 模拟器并映射具体端口。 The complete source code can be found here .完整的源代码可以在这里找到。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM