Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support to customize gcloud command of LocalDatastoreHelper #137

Merged
merged 8 commits into from Jun 4, 2020
Expand Up @@ -35,7 +35,6 @@
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.threeten.bp.Duration;

Expand All @@ -50,6 +49,7 @@ public class LocalDatastoreHelper extends BaseEmulatorHelper<DatastoreOptions> {
private final List<EmulatorRunner> emulatorRunners;
private final double consistency;
private final Path gcdPath;
private boolean storeOnDisk;

// Gcloud emulator settings
private static final String GCLOUD_CMD_TEXT = "gcloud beta emulators datastore start";
Expand Down Expand Up @@ -78,39 +78,87 @@ public class LocalDatastoreHelper extends BaseEmulatorHelper<DatastoreOptions> {
}
}

private LocalDatastoreHelper(double consistency, int port) {
/** A builder for {@code LocalDatastoreHelper} objects. */
public static class Builder {
private double consistency;
private int port;
private Path dataDir;
private boolean storeOnDisk = true;

private Builder() {}

private Builder(LocalDatastoreHelper helper) {
this.consistency = helper.consistency;
this.dataDir = helper.gcdPath;
this.storeOnDisk = helper.storeOnDisk;
}

public Builder setConsistency(double consistency) {
this.consistency = consistency;
return this;
}

public Builder setPort(int port) {
this.port = port;
return this;
}

public Builder setDataDir(Path dataDir) {
this.dataDir = dataDir;
return this;
}

public Builder setStoreOnDisk(boolean storeOnDisk) {
this.storeOnDisk = storeOnDisk;
return this;
}

/** Creates a {@code LocalDatastoreHelper} object. */
public LocalDatastoreHelper build() {
return new LocalDatastoreHelper(this);
}
}

private LocalDatastoreHelper(Builder builder) {
super(
"datastore",
port > 0 ? port : BaseEmulatorHelper.findAvailablePort(DEFAULT_PORT),
builder.port > 0 ? builder.port : BaseEmulatorHelper.findAvailablePort(DEFAULT_PORT),
PROJECT_ID_PREFIX + UUID.randomUUID().toString());
Path tmpDirectory = null;
try {
tmpDirectory = Files.createTempDirectory("gcd");
} catch (IOException ex) {
getLogger().log(Level.WARNING, "Failed to create temporary directory");
}
this.gcdPath = tmpDirectory;
this.consistency = consistency;
this.consistency = builder.consistency > 0 ? builder.consistency : DEFAULT_CONSISTENCY;
this.gcdPath = builder.dataDir;
this.storeOnDisk = builder.storeOnDisk;
String binName = BIN_NAME;
if (isWindows()) {
binName = BIN_NAME.replace("/", "\\");
}
List<String> gcloudCommand = new ArrayList<>(Arrays.asList(GCLOUD_CMD_TEXT.split(" ")));
gcloudCommand.add(GCLOUD_CMD_PORT_FLAG + "localhost:" + getPort());
gcloudCommand.add(CONSISTENCY_FLAG + consistency);
gcloudCommand.add("--no-store-on-disk");
gcloudCommand.add(CONSISTENCY_FLAG + builder.consistency);
if (!builder.storeOnDisk) {
gcloudCommand.add("--no-store-on-disk");
}
GcloudEmulatorRunner gcloudRunner =
new GcloudEmulatorRunner(gcloudCommand, VERSION_PREFIX, MIN_VERSION);
List<String> binCommand = new ArrayList<>(Arrays.asList(binName, "start"));
binCommand.add("--testing");
binCommand.add(BIN_CMD_PORT_FLAG + getPort());
binCommand.add(CONSISTENCY_FLAG + consistency);
if (gcdPath != null) {
gcloudCommand.add("--data-dir=" + gcdPath.toString());
binCommand.add(CONSISTENCY_FLAG + getConsistency());
if (builder.dataDir != null) {
gcloudCommand.add("--data-dir=" + getGcdPath());
}
DownloadableEmulatorRunner downloadRunner =
new DownloadableEmulatorRunner(binCommand, EMULATOR_URL, MD5_CHECKSUM);
emulatorRunners = ImmutableList.of(gcloudRunner, downloadRunner);
this.emulatorRunners = ImmutableList.of(gcloudRunner, downloadRunner);
}

/** Returns a builder for {@code LocalDatastoreHelper} object. */
public LocalDatastoreHelper.Builder toBuilder() {
return new Builder(this);
}

/** Returns a builder for {@code LocalDatastoreHelper} object. */
public static LocalDatastoreHelper.Builder newBuilder() {
return new LocalDatastoreHelper.Builder();
}

@Override
Expand Down Expand Up @@ -153,6 +201,16 @@ public double getConsistency() {
return consistency;
}

/** Returns the data directory path of the local Datastore emulator. */
public Path getGcdPath() {
return gcdPath;
}

/** Returns {@code true} data persist on disk, otherwise {@code false} data not store on disk. */
public boolean isStoreOnDisk() {
return storeOnDisk;
}

/**
* Creates a local Datastore helper with the specified settings for project ID and consistency.
*
Expand All @@ -162,7 +220,7 @@ public double getConsistency() {
* consistency of non-ancestor queries; non-ancestor queries are eventually consistent.
*/
public static LocalDatastoreHelper create(double consistency) {
return create(consistency, 0);
return LocalDatastoreHelper.newBuilder().setConsistency(consistency).setPort(0).build();
}

/**
Expand All @@ -176,7 +234,7 @@ public static LocalDatastoreHelper create(double consistency) {
* emulator will search for a free random port.
*/
public static LocalDatastoreHelper create(double consistency, int port) {
return new LocalDatastoreHelper(consistency, port);
return LocalDatastoreHelper.newBuilder().setConsistency(consistency).setPort(port).build();
}

/**
Expand All @@ -187,7 +245,10 @@ public static LocalDatastoreHelper create(double consistency, int port) {
* emulator will search for a free random port.
*/
public static LocalDatastoreHelper create(int port) {
return new LocalDatastoreHelper(DEFAULT_CONSISTENCY, port);
return LocalDatastoreHelper.newBuilder()
.setConsistency(DEFAULT_CONSISTENCY)
.setPort(port)
.build();
}

/**
Expand All @@ -197,7 +258,7 @@ public static LocalDatastoreHelper create(int port) {
* all writes are immediately visible.
*/
public static LocalDatastoreHelper create() {
return create(DEFAULT_CONSISTENCY);
return LocalDatastoreHelper.newBuilder().setConsistency(DEFAULT_CONSISTENCY).build();
}

/**
Expand Down
Expand Up @@ -30,8 +30,11 @@
import com.google.cloud.datastore.Entity;
import com.google.cloud.datastore.Key;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.TimeoutException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
Expand All @@ -43,6 +46,12 @@ public class ITLocalDatastoreHelperTest {
private static final double TOLERANCE = 0.00001;
private static final String PROJECT_ID_PREFIX = "test-project-";
private static final String NAMESPACE = "namespace";
private Path dataDir;

@Before
public void setUp() throws IOException {
dataDir = Files.createTempDirectory("gcd");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, can you also add a delete in an @After so we're not leaving lots of directories around?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

@Test
public void testCreate() {
Expand All @@ -54,6 +63,56 @@ public void testCreate() {
assertTrue(helper.getProjectId().startsWith(PROJECT_ID_PREFIX));
}

@Test
public void testCreateWithBuilder() {
LocalDatastoreHelper helper =
LocalDatastoreHelper.newBuilder()
.setConsistency(0.75)
.setPort(8081)
.setStoreOnDisk(false)
.setDataDir(dataDir)
.build();
assertTrue(Math.abs(0.75 - helper.getConsistency()) < TOLERANCE);
assertTrue(helper.getProjectId().startsWith(PROJECT_ID_PREFIX));
assertFalse(helper.isStoreOnDisk());
assertEquals(8081, helper.getPort());
assertEquals(dataDir, helper.getGcdPath());
LocalDatastoreHelper incompleteHelper = LocalDatastoreHelper.newBuilder().build();
assertTrue(Math.abs(0.9 - incompleteHelper.getConsistency()) < TOLERANCE);
assertTrue(incompleteHelper.getProjectId().startsWith(PROJECT_ID_PREFIX));
}

@Test
public void testCreateWithToBuilder() throws IOException {
LocalDatastoreHelper helper =
LocalDatastoreHelper.newBuilder()
.setConsistency(0.75)
.setPort(8081)
.setStoreOnDisk(false)
.setDataDir(dataDir)
.build();
assertTrue(Math.abs(0.75 - helper.getConsistency()) < TOLERANCE);
assertTrue(helper.getProjectId().startsWith(PROJECT_ID_PREFIX));
assertFalse(helper.isStoreOnDisk());
assertEquals(8081, helper.getPort());
assertEquals(dataDir, helper.getGcdPath());
LocalDatastoreHelper actualHelper = helper.toBuilder().build();
assertLocalDatastoreHelpersEquivelent(helper, actualHelper);
Path dataDir = Files.createTempDirectory("gcd_data_dir");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup dir like above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

actualHelper =
helper
.toBuilder()
.setConsistency(0.85)
.setPort(9091)
.setStoreOnDisk(true)
.setDataDir(dataDir)
.build();
assertTrue(Math.abs(0.85 - actualHelper.getConsistency()) < TOLERANCE);
assertTrue(actualHelper.isStoreOnDisk());
assertEquals(9091, actualHelper.getPort());
assertEquals(dataDir, actualHelper.getGcdPath());
}

@Test
public void testCreatePort() {
LocalDatastoreHelper helper = LocalDatastoreHelper.create(0.75, 8888);
Expand Down Expand Up @@ -103,4 +162,31 @@ public void testStartStopReset() throws IOException, InterruptedException, Timeo
assertNotNull(ex.getMessage());
}
}

@Test
public void testStartStopResetWithBuilder()
throws IOException, InterruptedException, TimeoutException {
try {
LocalDatastoreHelper helper = LocalDatastoreHelper.newBuilder().build();
helper.start();
Datastore datastore = helper.getOptions().getService();
Key key = datastore.newKeyFactory().setKind("kind").newKey("name");
datastore.put(Entity.newBuilder(key).build());
assertNotNull(datastore.get(key));
helper.reset();
assertNull(datastore.get(key));
helper.stop(Duration.ofMinutes(1));
datastore.get(key);
Assert.fail();
} catch (DatastoreException ex) {
assertNotNull(ex.getMessage());
}
}

public void assertLocalDatastoreHelpersEquivelent(
LocalDatastoreHelper expected, LocalDatastoreHelper actual) {
assertEquals(expected.getConsistency(), actual.getConsistency(), 0);
assertEquals(expected.isStoreOnDisk(), actual.isStoreOnDisk());
assertEquals(expected.getGcdPath(), actual.getGcdPath());
}
}