blob: ba9b81d005dc2e6f0cf84b4b83b38722eaa9032f [file] [log] [blame]
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.Configuration.DEFAULT_LOCKS_TABLE_NAME;
import static com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.Configuration.DEFAULT_REFS_DB_TABLE_NAME;
import static com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.DynamoDBRefDatabase.REF_DB_PRIMARY_KEY;
import static com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.DynamoDBRefDatabase.REF_DB_VALUE_KEY;
import static com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.DynamoDBRefDatabase.pathFor;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.DYNAMODB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.PutItemRequest;
import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.acceptance.WaitUtil;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Project;
import java.time.Duration;
import java.util.Optional;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
import org.junit.Before;
import org.junit.Test;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.utility.DockerImageName;
@TestPlugin(
name = "aws-dynamodb-refdb",
sysModule = "com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.Module")
public class DynamoDBRefDatabaseIT extends LightweightPluginDaemonTest {
private static final Duration DYNAMODB_TABLE_CREATION_TIMEOUT = Duration.ofSeconds(10);
private static final int LOCALSTACK_PORT = 4566;
private static final LocalStackContainer localstack =
new LocalStackContainer(DockerImageName.parse("localstack/localstack:0.12.8"))
.withServices(DYNAMODB)
.withExposedPorts(LOCALSTACK_PORT);
@Before
public void setUpTestPlugin() throws Exception {
localstack.start();
System.setProperty("endpoint", localstack.getEndpointOverride(DYNAMODB).toASCIIString());
System.setProperty("region", localstack.getRegion());
System.setProperty("aws.accessKeyId", localstack.getAccessKey());
// The secret key property name has changed from aws-sdk 1.11.x and 2.x [1]
// Export both names so that default credential provider chains work regardless
// he underlying library version.
// https: // docs.aws.amazon.com/sdk-for-java/latest/migration-guide/client-credential.html
System.setProperty("aws.secretKey", localstack.getSecretKey());
System.setProperty("aws.secretAccessKey", localstack.getSecretKey());
super.setUpTestPlugin();
}
@Override
public void tearDownTestPlugin() {
localstack.close();
super.tearDownTestPlugin();
}
@Test
public void shouldEnsureLockTableExists() throws Exception {
WaitUtil.waitUntil(
() -> DynamoDBLifeCycleManager.tableExists(dynamoDBClient(), DEFAULT_LOCKS_TABLE_NAME),
DYNAMODB_TABLE_CREATION_TIMEOUT);
}
@Test
public void shouldEnsureRefsDbTableExists() throws Exception {
WaitUtil.waitUntil(
() -> DynamoDBLifeCycleManager.tableExists(dynamoDBClient(), DEFAULT_REFS_DB_TABLE_NAME),
DYNAMODB_TABLE_CREATION_TIMEOUT);
}
@Test
public void getShouldBeEmptyWhenRefDoesntExists() throws Exception {
Optional<String> maybeRef = dynamoDBRefDatabase().get(project, "refs/not/in/db", String.class);
assertThat(maybeRef).isEmpty();
}
@Test
public void getShouldReturnRefValueWhenItExists() throws Exception {
String refName = "refs/changes/01/01/meta";
String refValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
createRefInDynamoDB(project, refName, refValue);
Optional<String> maybeRef = dynamoDBRefDatabase().get(project, refName, String.class);
assertThat(maybeRef).hasValue(refValue);
}
@Test
public void existsShouldReturnFalseWhenRefIsNotStored() throws Exception {
assertThat(dynamoDBRefDatabase().exists(project, "refs/not/in/db")).isFalse();
}
@Test
public void existShouldReturnTrueWhenRefIsStored() throws Exception {
String refName = "refs/changes/01/01/meta";
String refValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
createRefInDynamoDB(project, refName, refValue);
assertThat(dynamoDBRefDatabase().exists(project, refName)).isTrue();
}
@Test
public void isUpToDateShouldReturnTrueWhenRefPointsToTheStoredRefValue() throws Exception {
String refName = "refs/changes/01/01/meta";
String currentRefValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
createRefInDynamoDB(project, refName, currentRefValue);
assertThat(dynamoDBRefDatabase().isUpToDate(project, refOf(refName, currentRefValue))).isTrue();
}
@Test
public void isUpToDateShouldReturnFalseWhenRefDoesNotPointToTheStoredRefValue() {
String refName = "refs/changes/01/01/meta";
String currentRefValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
String previousRefValue = "9f6f2963cf44505428c61b935ff1ca65372cf28c";
createRefInDynamoDB(project, refName, previousRefValue);
assertThat(dynamoDBRefDatabase().isUpToDate(project, refOf(refName, currentRefValue)))
.isFalse();
}
@Test
public void isUpToDateShouldBeConsideredTrueWhenNoPreviousRefExists() {
String refName = "refs/changes/01/01/meta";
String currentRefValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
assertThat(dynamoDBRefDatabase().isUpToDate(project, refOf(refName, currentRefValue))).isTrue();
}
@Test
public void compareAndPutShouldBeSuccessfulWhenNoPreviousRefExists() {
String refName = "refs/changes/01/01/meta";
String newRefValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
assertThat(
dynamoDBRefDatabase()
.compareAndPut(project, refOf(refName, null), ObjectId.fromString(newRefValue)))
.isTrue();
}
@Test
public void compareAndPutShouldSuccessfullyUpdateRemovedRef() throws Exception {
String refName = "refs/changes/01/01/meta";
String currentRefValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
createRefInDynamoDB(project, refName, currentRefValue);
assertThat(
dynamoDBRefDatabase()
.compareAndPut(project, refOf(refName, currentRefValue), ObjectId.zeroId()))
.isTrue();
}
@Test
public void compareAndPutShouldThrowWhenStoredRefIsNotExpected() {
String refName = "refs/changes/01/01/meta";
String currentRefValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
String newRefValue = "9f6f2963cf44505428c61b935ff1ca65372cf28c";
String expectedRefValue = "875ce4b14278b64be61478f91a40cf480758bfba";
Ref expectedRef = refOf(refName, expectedRefValue);
createRefInDynamoDB(project, refName, currentRefValue);
GlobalRefDbSystemError thrown =
assertThrows(
GlobalRefDbSystemError.class,
() ->
dynamoDBRefDatabase()
.compareAndPut(project, expectedRef, ObjectId.fromString(newRefValue)));
assertThat(thrown)
.hasMessageThat()
.containsMatch(
"Conditional Check Failure when updating refPath.*expected:.*"
+ expectedRefValue
+ " New:.*"
+ newRefValue);
}
@Test
public void compareAndPutStringsShouldBeSuccessful() throws Exception {
String refName = "refs/changes/01/01/meta";
String currentRefValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
String newRefValue = "9f6f2963cf44505428c61b935ff1ca65372cf28c";
createRefInDynamoDB(project, refName, currentRefValue);
assertThat(dynamoDBRefDatabase().compareAndPut(project, refName, currentRefValue, newRefValue))
.isTrue();
}
@Test
public void compareAndPutStringsShouldBeSuccessfulWhenNoPreviousRefExists() {
String refName = "refs/changes/01/01/meta";
String newRefValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
assertThat(dynamoDBRefDatabase().compareAndPut(project, refName, null, newRefValue)).isTrue();
}
private AmazonDynamoDB dynamoDBClient() {
return plugin.getSysInjector().getInstance(AmazonDynamoDB.class);
}
private DynamoDBRefDatabase dynamoDBRefDatabase() {
return plugin.getSysInjector().getInstance(DynamoDBRefDatabase.class);
}
private void createRefInDynamoDB(Project.NameKey project, String refPath, String refValue) {
dynamoDBClient()
.putItem(
new PutItemRequest()
.withTableName(DEFAULT_REFS_DB_TABLE_NAME)
.withItem(
ImmutableMap.of(
REF_DB_PRIMARY_KEY,
new AttributeValue(pathFor(project, refPath)),
REF_DB_VALUE_KEY,
new AttributeValue(refValue))));
}
private Ref refOf(String refName, @Nullable String objectIdSha1) {
return new ObjectIdRef.Unpeeled(
Ref.Storage.NETWORK,
refName,
Optional.ofNullable(objectIdSha1).map(ObjectId::fromString).orElse(null));
}
}