使用 Couchbase 作为 TestContainer 的 Spring Boot 会抛出 CB 数据存储中找不到范围

问题描述 投票:0回答:1

我在 Spring Boot 示例中运行所有使用 Couchbase TestContainer 运行的集成测试时遇到问题。

这是 application.yml 文件的相关部分,如下所示

  couchbase:
    connection-string: couchbase://${COUCHBASE_HOST:127.0.0.1}
    username: ${COUCHBASE_USERNAME:Administrator}
    password: ${COUCHBASE_PASSWORD:123456}
    bucket: ${COUCHBASE_BUCKET:todo_list}
    scopes:
      user-scope: user
      task-scope: task
      invalid-token-scope : invalid-token
      log-scope: log
    collections:
      user-collection: user
      task-collection: task
      invalid-token-collection: invalid-token
      log-collection : log

这是下面所示的类

@Slf4j
@Testcontainers
public abstract class AbstractTestContainerConfiguration {

    private static final String BUCKET_NAME = "todo_list";

    static CouchbaseContainer COUCHBASE_CONTAINER = new CouchbaseContainer("couchbase/server:7.0.3")
            .withCredentials("Administrator", "123456");

    @BeforeAll
    public static void setup() {
        COUCHBASE_CONTAINER.start();

        // Connect to Couchbase cluster
        Cluster cluster = Cluster.connect(
                COUCHBASE_CONTAINER.getConnectionString(),
                COUCHBASE_CONTAINER.getUsername(),
                COUCHBASE_CONTAINER.getPassword()
        );

        // Create the bucket if it doesn't exist
        createBucketIfNotExists(cluster);

        // Access the bucket and ensure it's ready
        Bucket bucket = cluster.bucket(BUCKET_NAME);
        bucket.waitUntilReady(Duration.ofSeconds(10));
        CollectionManager collectionManager = bucket.collections();

        // Create scopes and collections (according to your configuration)
        createScopeAndCollection(collectionManager, "invalid-token", "invalid-token");
        createScopeAndCollection(collectionManager, "user", "user");
        createScopeAndCollection(collectionManager, "task", "task");
        createScopeAndCollection(collectionManager, "log", "log");

        // Ensure all necessary collections exist
        ensureCollectionExists(bucket, "invalid-token", "invalid-token");
        ensureCollectionExists(bucket, "user", "user");
        ensureCollectionExists(bucket, "task", "task");
        ensureCollectionExists(bucket, "log", "log");

        cluster.disconnect();
    }

    private static void createBucketIfNotExists(Cluster cluster) {
        try {
            // Check if the bucket already exists
            cluster.buckets().getBucket(AbstractTestContainerConfiguration.BUCKET_NAME);
        } catch (Exception e) {
            log.info("Bucket '{}' not found. Creating a new bucket.", AbstractTestContainerConfiguration.BUCKET_NAME);
            cluster.buckets().createBucket(
                    BucketSettings.create(AbstractTestContainerConfiguration.BUCKET_NAME)
                            .flushEnabled(true)
                            .ramQuotaMB(100)
                            .numReplicas(1)
            );
        }
    }

    private static void createScopeAndCollection(CollectionManager manager, String scopeName, String collectionName) {
        try {
            // Create the scope if it does not exist
            try {
                manager.createScope(scopeName);
                log.info("Scope created: {}", scopeName);
            } catch (ScopeExistsException ignored) {
                log.warn("Scope already exists: {}", scopeName);
            }

            // Create the collection under the given scope if it does not exist
            try {
                manager.createCollection(scopeName, collectionName);
                log.info("Collection created: {}.{}", scopeName, collectionName);
            } catch (CollectionExistsException ignored) {
                log.warn("Collection already exists in scope {}: {}", scopeName, collectionName);
            }
        } catch (Exception e) {
            log.error("Error creating scope or collection: {}.{}", scopeName, collectionName, e);
        }
    }


    private static void ensureCollectionExists(Bucket bucket, String scopeName, String collectionName) {
        try {
            Collection collection = bucket.scope(scopeName).collection(collectionName);
            log.info("Collection exists: {}.{}", scopeName, collectionName);
        } catch (CollectionNotFoundException e) {
            log.info("Collection not found: {}.{} - Creating it.", scopeName, collectionName);
            bucket.collections().createCollection(scopeName, collectionName);
        }
    }

    @DynamicPropertySource
    private static void overrideProps(DynamicPropertyRegistry dynamicPropertyRegistry) {
        dynamicPropertyRegistry.add("spring.couchbase.connection-string", COUCHBASE_CONTAINER::getConnectionString);
        dynamicPropertyRegistry.add("spring.couchbase.username", COUCHBASE_CONTAINER::getUsername);
        dynamicPropertyRegistry.add("spring.couchbase.password", COUCHBASE_CONTAINER::getPassword);
        dynamicPropertyRegistry.add("spring.couchbase.bucket", () -> BUCKET_NAME);
    }
}

这是下面显示的错误

com.couchbase.client.core.error.IndexFailureException: The server reported an issue with the underlying index {"completed":true,"coreId":"0x42adbe3e00000002","errors":[{"code":12021,"message":"Scope not found in CB datastore default:todo_list.invalid_token","retry":false}],"httpStatus":500,"idempotent":true,"lastDispatchedFrom":"127.0.0.1:26562","lastDispatchedTo":"localhost:26462","requestId":47,"requestType":"QueryRequest","retried":0,"service":{"bucket":"todo_list","operationId":"null","scope":"invalid_token","statement":"SELECT `_class`, META(`invalid_token`).`id` AS __id, `TOKEN_ID`, `createdAt`, `createdBy`, `updatedAt`, `updatedBy` FROM `invalid_token` WHERE `_class` = \"com.example.todowithcouchbase.auth.model.entity.InvalidTokenEntity\" AND `TOKEN_ID` = $1","type":"query"},"timeoutMs":75000,"timings":{"dispatchMicros":34547,"totalDispatchMicros":34547,"totalMicros":99591}}

我该如何解决这个问题?

java spring-boot junit couchbase testcontainers
1个回答
0
投票

这是我的解决方案,如下所示

1)我将 scopescollections 修改为

application.yml
文件中更有意义的名称

  couchbase:
    connection-string: couchbase://${COUCHBASE_HOST:127.0.0.1}
    username: ${COUCHBASE_USERNAME:Administrator}
    password: ${COUCHBASE_PASSWORD:123456}
    bucket: ${COUCHBASE_BUCKET:todo_list}
    scopes:
      user-scope: user-scope
      task-scope: task-scope
      invalid-token-scope : invalid-token-scope
      log-scope: log-scope
    collections:
      user-collection: user-collection
      task-collection: task-collection
      invalid-token-collection: invalid-token-collection
      log-collection : log-collection

2)我在AbstractTestContainerConfiguration中定义了scopecollection

及其
indexes

@Slf4j
@Testcontainers
public abstract class AbstractTestContainerConfiguration {

    private static final String BUCKET_NAME = "todo_list";

    static CouchbaseContainer COUCHBASE_CONTAINER = new CouchbaseContainer("couchbase/server:7.0.3")
            .withCredentials("Administrator", "123456");

    @BeforeAll
    public static void setup() {
        COUCHBASE_CONTAINER.start();

        // Connect to Couchbase cluster
        Cluster cluster = Cluster.connect(
                COUCHBASE_CONTAINER.getConnectionString(),
                COUCHBASE_CONTAINER.getUsername(),
                COUCHBASE_CONTAINER.getPassword()
        );

        // Create the bucket if it doesn't exist
        createBucketIfNotExists(cluster);

        // Access the bucket and ensure it's ready
        Bucket bucket = cluster.bucket(BUCKET_NAME);
        bucket.waitUntilReady(Duration.ofSeconds(10));

        // Ensure that the query service is available
        waitForQueryService(cluster);

        CollectionManager collectionManager = bucket.collections();

        // Create scopes and collections (according to your configuration)
        createScopeAndCollection(collectionManager, "invalid-token-scope", "invalid-token-collection");
        createScopeAndCollection(collectionManager, "user-scope", "user-collection");
        createScopeAndCollection(collectionManager, "task-scope", "task-collection");
        createScopeAndCollection(collectionManager, "log-scope", "log-collection");

        // Ensure all necessary collections exist
        ensureCollectionExists(bucket, "invalid-token-scope", "invalid-token-collection");
        ensureCollectionExists(bucket, "user-scope", "user-collection");
        ensureCollectionExists(bucket, "task-scope", "task-collection");
        ensureCollectionExists(bucket, "log-scope", "log-collection");

        // Ensure primary indexes are created on all collections (across scopes)
        createPrimaryIndexIfNotExists(bucket, cluster, "invalid-token-scope", "invalid-token-collection");
        createPrimaryIndexIfNotExists(bucket, cluster, "user-scope", "user-collection");
        createPrimaryIndexIfNotExists(bucket, cluster, "task-scope", "task-collection");
        createPrimaryIndexIfNotExists(bucket, cluster, "log-scope", "log-collection");

        cluster.disconnect();
    }

    private static void createBucketIfNotExists(Cluster cluster) {
        try {
            // Check if the bucket already exists
            cluster.buckets().getBucket(BUCKET_NAME);
        } catch (Exception e) {
            log.info("Bucket '{}' not found. Creating a new bucket.", BUCKET_NAME);
            cluster.buckets().createBucket(
                    BucketSettings.create(BUCKET_NAME)
                            .flushEnabled(true)
                            .ramQuotaMB(100)
                            .numReplicas(1)
            );
        }
    }

    private static void createScopeAndCollection(CollectionManager manager, String scopeName, String collectionName) {
        try {
            // Create the scope if it does not exist
            try {
                manager.createScope(scopeName);
                log.info("Scope created: {}", scopeName);
            } catch (ScopeExistsException ignored) {
                log.warn("Scope already exists: {}", scopeName);
            }

            // Create the collection under the given scope if it does not exist
            try {
                manager.createCollection(scopeName, collectionName);
                log.info("Collection created: {}.{}", scopeName, collectionName);
            } catch (CollectionExistsException ignored) {
                log.warn("Collection already exists in scope {}: {}", scopeName, collectionName);
            }
        } catch (Exception e) {
            log.error("Error creating scope or collection: {}.{}", scopeName, collectionName, e);
        }
    }

    private static void ensureCollectionExists(Bucket bucket, String scopeName, String collectionName) {
        try {
            Collection collection = bucket.scope(scopeName).collection(collectionName);
            log.info("Collection exists: {}.{}", scopeName, collectionName);
        } catch (CollectionNotFoundException e) {
            log.info("Collection not found: {}.{} - Creating it.", scopeName, collectionName);
            bucket.collections().createCollection(scopeName, collectionName);
        }
    }

    // Create primary index on the collection to cover all items within the collection
    private static void createPrimaryIndexIfNotExists(Bucket bucket, Cluster cluster, String scopeName, String collectionName) {
        try {
            String createIndexQuery = String.format(
                    "CREATE PRIMARY INDEX ON `%s`.`%s`.`%s` USING GSI",
                    bucket.name(),
                    scopeName,
                    collectionName
            );

            // Run the query to create the primary index
            try {
                cluster.query(createIndexQuery);
                log.info("Primary index created on collection {}.{} in scope {}.", collectionName, scopeName, bucket.name());
            } catch (CouchbaseQueryExecutionException e) {
                if (e.getCause().getMessage().contains("No index available")) {
                    log.warn("Primary index creation failed on collection {}.{} in scope {}. Retrying...", collectionName, scopeName, bucket.name());
                    cluster.query(createIndexQuery); // Retry creating index
                    log.info("Primary index created on collection {}.{} in scope {} after retry.", collectionName, scopeName, bucket.name());
                } else {
                    log.error("Error executing query for primary index on collection {}.{} in scope {}: {}", collectionName, scopeName, bucket.name(), e.getMessage());
                }
            }
        } catch (Exception e) {
            log.error("Error creating primary index for collection {}.{} in scope {}: {}", collectionName, scopeName, bucket.name(), e);
        }
    }

    // Wait for the query service to be ready before creating indexes
    private static void waitForQueryService(Cluster cluster) {
        try {
            cluster.query("SELECT 1");
            log.info("Query service is available.");
        } catch (Exception e) {
            log.error("Query service is not available. Retrying...", e);
            // Retry mechanism could be implemented if needed
            try {
                Thread.sleep(5000); // Wait for 5 seconds before retrying
            } catch (InterruptedException interruptedException) {
                Thread.currentThread().interrupt();
            }
            waitForQueryService(cluster); // Retry
        }
    }

    @DynamicPropertySource
    private static void overrideProps(DynamicPropertyRegistry dynamicPropertyRegistry) {
        dynamicPropertyRegistry.add("spring.couchbase.connection-string", COUCHBASE_CONTAINER::getConnectionString);
        dynamicPropertyRegistry.add("spring.couchbase.username", COUCHBASE_CONTAINER::getUsername);
        dynamicPropertyRegistry.add("spring.couchbase.password", COUCHBASE_CONTAINER::getPassword);
        dynamicPropertyRegistry.add("spring.couchbase.bucket", () -> BUCKET_NAME);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.