com.google.api.grpc
grpc-google-cloud-bigtable-admin-v2
diff --git a/google-cloud-bigtable-emulator-core/pom.xml b/google-cloud-bigtable-emulator-core/pom.xml
new file mode 100644
index 000000000000..a93a4ec393c3
--- /dev/null
+++ b/google-cloud-bigtable-emulator-core/pom.xml
@@ -0,0 +1,75 @@
+
+
+ 4.0.0
+
+
+ google-cloud-bigtable-parent
+ com.google.cloud
+ 2.7.1-SNAPSHOT
+
+
+ google-cloud-bigtable-emulator-core
+ 0.144.1-SNAPSHOT
+
+
+ A Java wrapper for the Cloud Bigtable emulator.
+
+
+ https://github.com/googleapis/java-bigtable
+
+ scm:git:git@github.com:googleapis/java-bigtable.git
+ scm:git:git@github.com:googleapis/java-bigtable.git
+ https://github.com/googleapis/java-bigtable
+ HEAD
+
+
+
+ igorberstein
+ Igor Bernstein
+ igorbernstein@google.com
+ Google
+
+ Developer
+
+
+
+
+
+ 8
+ 8
+
+
+
+
+
+
+ com.google.cloud
+ google-cloud-gcloud-maven-plugin
+ 0.1.5
+
+
+
+ gen-sources
+ generate-resources
+
+ download
+
+
+
+ bigtable-darwin-arm
+ bigtable-darwin-x86_64
+ bigtable-linux-arm
+ bigtable-linux-x86
+ bigtable-linux-x86_64
+ bigtable-windows-x86
+ bigtable-windows-x86_64
+
+
+
+
+
+
+
+
diff --git a/google-cloud-bigtable-emulator-core/src/main/java/com/google/cloud/bigtable/emulator/core/EmulatorController.java b/google-cloud-bigtable-emulator-core/src/main/java/com/google/cloud/bigtable/emulator/core/EmulatorController.java
new file mode 100644
index 000000000000..9ac9245f2201
--- /dev/null
+++ b/google-cloud-bigtable-emulator-core/src/main/java/com/google/cloud/bigtable/emulator/core/EmulatorController.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * 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
+ *
+ * https://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.google.cloud.bigtable.emulator.core;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.file.Path;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Wraps the Bigtable emulator in a java api.
+ *
+ * This class will use the golang binaries embedded in this jar to launch the emulator as an
+ * external process and redirect its output to a {@link Logger}.
+ */
+public class EmulatorController {
+ private static final Logger LOGGER = Logger.getLogger(EmulatorController.class.getName());
+
+ private final Path executable;
+ private Process process;
+ private boolean isStopped = true;
+ private Thread shutdownHook;
+
+ private int port;
+
+ public static EmulatorController createFromPath(Path path) {
+ return new EmulatorController(path);
+ }
+ /**
+ * Create a new instance of emulator. The emulator will use the bundled binaries in this jar.
+ * Please note that the emulator is created in a stopped state, please use {@link #start()} after
+ * creating it.
+ */
+ public static EmulatorController createBundled() throws IOException {
+ String resourcePath = getBundledResourcePath();
+
+ File tmpEmulator = File.createTempFile("cbtemulator", "");
+ tmpEmulator.deleteOnExit();
+
+ try (InputStream is = EmulatorController.class.getResourceAsStream(resourcePath);
+ FileOutputStream os = new FileOutputStream(tmpEmulator)) {
+
+ if (is == null) {
+ throw new FileNotFoundException(
+ "Failed to find the bundled emulator binary: " + resourcePath);
+ }
+
+ byte[] buff = new byte[2048];
+ int length;
+
+ while ((length = is.read(buff)) != -1) {
+ os.write(buff, 0, length);
+ }
+ }
+ tmpEmulator.setExecutable(true);
+
+ return new EmulatorController(tmpEmulator.toPath());
+ }
+
+ private EmulatorController(Path executable) {
+ this.executable = executable;
+ }
+
+ public synchronized boolean isRunning() {
+ return !isStopped;
+ }
+ /** Starts the emulator process and waits for it to be ready. */
+ public synchronized void start() throws IOException, TimeoutException, InterruptedException {
+ if (!isStopped) {
+ throw new IllegalStateException("Emulator is already started");
+ }
+ this.port = getAvailablePort();
+
+ // Try to align the localhost address across java & golang emulator
+ // This should fix issues on systems that default to ipv4 but the jvm is started with
+ // -Djava.net.preferIPv6Addresses=true
+ Optional localhostAddress = Optional.empty();
+ try {
+ localhostAddress = Optional.of(InetAddress.getByName(null).getHostAddress());
+ } catch (UnknownHostException e) {
+ }
+
+ // Workaround https://bugs.openjdk.java.net/browse/JDK-8068370
+ for (int attemptsLeft = 3; process == null; attemptsLeft--) {
+ try {
+ String cmd = executable.toString();
+ if (localhostAddress.isPresent()) {
+ cmd += String.format(" -host [%s]", localhostAddress.get());
+ }
+ cmd += String.format(" -port %d", port);
+ process = Runtime.getRuntime().exec(cmd);
+ } catch (IOException e) {
+ if (attemptsLeft > 0) {
+ Thread.sleep(1000);
+ continue;
+ }
+ throw e;
+ }
+ }
+ pipeStreamToLog(process.getInputStream(), Level.INFO);
+ pipeStreamToLog(process.getErrorStream(), Level.WARNING);
+ isStopped = false;
+
+ shutdownHook =
+ new Thread(
+ () -> {
+ if (!isStopped) {
+ isStopped = true;
+ process.destroy();
+ }
+ });
+
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
+
+ waitForPort(port);
+ }
+
+ /** Stops the emulator process. */
+ public synchronized void stop() {
+ if (isStopped) {
+ throw new IllegalStateException("Emulator already stopped");
+ }
+
+ try {
+ Runtime.getRuntime().removeShutdownHook(shutdownHook);
+ shutdownHook = null;
+ } finally {
+ isStopped = true;
+ process.destroy();
+ }
+ }
+
+ public synchronized int getPort() {
+ if (isStopped) {
+ throw new IllegalStateException("Emulator is not running");
+ }
+ return port;
+ }
+ //
+
+ /** Gets the current platform, which will be used to select the appropriate emulator binary. */
+ private static String getBundledResourcePath() {
+ String unformattedOs = System.getProperty("os.name", "unknown").toLowerCase(Locale.ENGLISH);
+ String os;
+ String suffix = "";
+
+ if (unformattedOs.contains("mac") || unformattedOs.contains("darwin")) {
+ os = "darwin";
+ } else if (unformattedOs.contains("win")) {
+ os = "windows";
+ suffix = ".exe";
+ } else if (unformattedOs.contains("linux")) {
+ os = "linux";
+ } else {
+ throw new UnsupportedOperationException(
+ "Emulator is not supported on your platform: " + unformattedOs);
+ }
+
+ String unformattedArch = System.getProperty("os.arch");
+ String arch;
+
+ switch (unformattedArch) {
+ case "x86":
+ arch = "x86";
+ break;
+ case "x86_64":
+ case "amd64":
+ arch = "x86_64";
+ break;
+ case "aarch64":
+ arch = "arm";
+ break;
+ default:
+ throw new UnsupportedOperationException("Unsupported architecture: " + unformattedArch);
+ }
+
+ return String.format(
+ "/gcloud/bigtable-%s-%s/platform/bigtable-emulator/cbtemulator%s", os, arch, suffix);
+ }
+
+ /** Gets a random open port number. */
+ private static int getAvailablePort() {
+ try (ServerSocket serverSocket = new ServerSocket(0)) {
+ return serverSocket.getLocalPort();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to find open port");
+ }
+ }
+
+ /** Waits for a port to open. It's used to wait for the emulator's gRPC server to be ready. */
+ private static void waitForPort(int port) throws InterruptedException, TimeoutException {
+ for (int i = 0; i < 100; i++) {
+ try (Socket ignored = new Socket("localhost", port)) {
+ return;
+ } catch (IOException e) {
+ Thread.sleep(200);
+ }
+ }
+
+ throw new TimeoutException("Timed out waiting for server to start");
+ }
+
+ /** Creates a thread that will pipe an {@link InputStream} to this class' Logger. */
+ private static void pipeStreamToLog(final InputStream stream, final Level level) {
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+
+ Thread thread =
+ new Thread(
+ () -> {
+ try {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ LOGGER.log(level, line);
+ }
+ } catch (IOException e) {
+ if (!"Stream closed".equals(e.getMessage())) {
+ LOGGER.log(Level.WARNING, "Failed to read process stream", e);
+ }
+ }
+ });
+ thread.setDaemon(true);
+ thread.start();
+ }
+ //
+}
diff --git a/google-cloud-bigtable-emulator/pom.xml b/google-cloud-bigtable-emulator/pom.xml
index 0da780bd8a32..bccb3aeaf0b6 100644
--- a/google-cloud-bigtable-emulator/pom.xml
+++ b/google-cloud-bigtable-emulator/pom.xml
@@ -69,8 +69,8 @@
org.apache.maven.plugins
maven-dependency-plugin
-
- io.grpc:grpc-netty-shaded
+
+ com.google.api:gax-grpc
@@ -96,16 +96,28 @@
-
+
+ com.google.cloud
+ google-cloud-bigtable-emulator-core
+ 0.144.1-SNAPSHOT
+
+
com.google.api
api-common
+
+ com.google.guava
+ guava
+
io.grpc
grpc-api
-
- provided
+
+
+
+ com.google.api
+ gax-grpc
@@ -160,11 +172,5 @@
-
-
- io.grpc
- grpc-netty-shaded
- test
-
diff --git a/google-cloud-bigtable-emulator/src/main/java/com/google/cloud/bigtable/emulator/v2/Emulator.java b/google-cloud-bigtable-emulator/src/main/java/com/google/cloud/bigtable/emulator/v2/Emulator.java
index b43322831ccc..b30fad7ebb0a 100644
--- a/google-cloud-bigtable-emulator/src/main/java/com/google/cloud/bigtable/emulator/v2/Emulator.java
+++ b/google-cloud-bigtable-emulator/src/main/java/com/google/cloud/bigtable/emulator/v2/Emulator.java
@@ -16,29 +16,18 @@
package com.google.cloud.bigtable.emulator.v2;
import com.google.api.core.BetaApi;
+import com.google.cloud.bigtable.emulator.core.EmulatorController;
+import com.google.common.base.Preconditions;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.UnknownHostException;
import java.nio.file.Path;
-import java.util.Locale;
-import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.logging.Level;
import java.util.logging.Logger;
/**
- * Wraps the Bigtable emulator in a java api.
+ * Wraps the Bigtable emulator in a java api and decorates it with grpc channel builders.
*
* This class will use the golang binaries embedded in this jar to launch the emulator as an
* external process and redirect its output to a {@link Logger}.
@@ -47,17 +36,12 @@
public class Emulator {
private static final Logger LOGGER = Logger.getLogger(Emulator.class.getName());
- private final Path executable;
- private Process process;
- private boolean isStopped = true;
- private Thread shutdownHook;
-
- private int port;
+ private final EmulatorController controller;
private ManagedChannel dataChannel;
private ManagedChannel adminChannel;
public static Emulator createFromPath(Path path) {
- return new Emulator(path);
+ return new Emulator(EmulatorController.createFromPath(path));
}
/**
* Create a new instance of emulator. The emulator will use the bundled binaries in this jar.
@@ -65,98 +49,23 @@ public static Emulator createFromPath(Path path) {
* creating it.
*/
public static Emulator createBundled() throws IOException {
- String resourcePath = getBundledResourcePath();
-
- File tmpEmulator = File.createTempFile("cbtemulator", "");
- tmpEmulator.deleteOnExit();
-
- try (InputStream is = Emulator.class.getResourceAsStream(resourcePath);
- FileOutputStream os = new FileOutputStream(tmpEmulator)) {
-
- if (is == null) {
- throw new FileNotFoundException(
- "Failed to find the bundled emulator binary: " + resourcePath);
- }
-
- byte[] buff = new byte[2048];
- int length;
-
- while ((length = is.read(buff)) != -1) {
- os.write(buff, 0, length);
- }
- }
- tmpEmulator.setExecutable(true);
-
- return new Emulator(tmpEmulator.toPath());
+ return new Emulator(EmulatorController.createBundled());
}
- private Emulator(Path executable) {
- this.executable = executable;
+ private Emulator(EmulatorController controller) {
+ this.controller = controller;
}
/** Starts the emulator process and waits for it to be ready. */
public synchronized void start() throws IOException, TimeoutException, InterruptedException {
- if (!isStopped) {
- throw new IllegalStateException("Emulator is already started");
- }
- this.port = getAvailablePort();
-
- // Try to align the localhost address across java & golang emulator
- // This should fix issues on systems that default to ipv4 but the jvm is started with
- // -Djava.net.preferIPv6Addresses=true
- Optional localhostAddress = Optional.empty();
- try {
- localhostAddress = Optional.of(InetAddress.getByName(null).getHostAddress());
- } catch (UnknownHostException e) {
- }
-
- // Workaround https://bugs.openjdk.java.net/browse/JDK-8068370
- for (int attemptsLeft = 3; process == null; attemptsLeft--) {
- try {
- String cmd = executable.toString();
- if (localhostAddress.isPresent()) {
- cmd += String.format(" -host [%s]", localhostAddress.get());
- }
- cmd += String.format(" -port %d", port);
- process = Runtime.getRuntime().exec(cmd);
- } catch (IOException e) {
- if (attemptsLeft > 0) {
- Thread.sleep(1000);
- continue;
- }
- throw e;
- }
- }
- pipeStreamToLog(process.getInputStream(), Level.INFO);
- pipeStreamToLog(process.getErrorStream(), Level.WARNING);
- isStopped = false;
-
- shutdownHook =
- new Thread() {
- @Override
- public void run() {
- if (!isStopped) {
- isStopped = true;
- process.destroy();
- }
- }
- };
-
- Runtime.getRuntime().addShutdownHook(shutdownHook);
-
- waitForPort(port);
+ controller.start();
}
/** Stops the emulator process. */
public synchronized void stop() {
- if (isStopped) {
- throw new IllegalStateException("Emulator already stopped");
- }
+ controller.stop();
try {
- Runtime.getRuntime().removeShutdownHook(shutdownHook);
- shutdownHook = null;
-
// Shutdown channels in parallel
if (dataChannel != null) {
dataChannel.shutdownNow();
@@ -177,27 +86,19 @@ public synchronized void stop() {
} catch (InterruptedException e) {
LOGGER.warning("Interrupted while waiting for client channels to close");
Thread.currentThread().interrupt();
- } finally {
- isStopped = true;
- process.destroy();
}
}
public synchronized int getPort() {
- if (isStopped) {
- throw new IllegalStateException("Emulator is not running");
- }
- return port;
+ return controller.getPort();
}
public synchronized ManagedChannel getDataChannel() {
- if (isStopped) {
- throw new IllegalStateException("Emulator is not running");
- }
+ Preconditions.checkState(controller.isRunning(), "Emulator is not running");
if (dataChannel == null) {
dataChannel =
- newChannelBuilder(port)
+ newChannelBuilder(controller.getPort())
.maxInboundMessageSize(256 * 1024 * 1024)
.keepAliveTimeout(10, TimeUnit.SECONDS)
.keepAliveTime(10, TimeUnit.SECONDS)
@@ -208,110 +109,20 @@ public synchronized ManagedChannel getDataChannel() {
}
public synchronized ManagedChannel getAdminChannel() {
- if (isStopped) {
- throw new IllegalStateException("Emulator is not running");
- }
+ Preconditions.checkState(controller.isRunning(), "Emulator is not running");
if (adminChannel == null) {
- adminChannel = newChannelBuilder(port).build();
+ adminChannel = newChannelBuilder(controller.getPort()).build();
}
return adminChannel;
}
-
//
- /** Gets the current platform, which will be used to select the appropriate emulator binary. */
- private static String getBundledResourcePath() {
- String unformattedOs = System.getProperty("os.name", "unknown").toLowerCase(Locale.ENGLISH);
- String os;
- String suffix = "";
-
- if (unformattedOs.contains("mac") || unformattedOs.contains("darwin")) {
- os = "darwin";
- } else if (unformattedOs.contains("win")) {
- os = "windows";
- suffix = ".exe";
- } else if (unformattedOs.contains("linux")) {
- os = "linux";
- } else {
- throw new UnsupportedOperationException(
- "Emulator is not supported on your platform: " + unformattedOs);
- }
-
- String unformattedArch = System.getProperty("os.arch");
- String arch;
-
- switch (unformattedArch) {
- case "x86":
- arch = "x86";
- break;
- case "x86_64":
- case "amd64":
- arch = "x86_64";
- break;
- case "aarch64":
- arch = "arm";
- break;
- default:
- throw new UnsupportedOperationException("Unsupported architecture: " + unformattedArch);
- }
-
- return String.format(
- "/gcloud/bigtable-%s-%s/platform/bigtable-emulator/cbtemulator%s", os, arch, suffix);
- }
-
- /** Gets a random open port number. */
- private static int getAvailablePort() {
- try (ServerSocket serverSocket = new ServerSocket(0)) {
- return serverSocket.getLocalPort();
- } catch (IOException e) {
- throw new RuntimeException("Failed to find open port");
- }
- }
-
- /** Waits for a port to open. It's used to wait for the emulator's gRPC server to be ready. */
- private static void waitForPort(int port) throws InterruptedException, TimeoutException {
- for (int i = 0; i < 100; i++) {
- try (Socket ignored = new Socket("localhost", port)) {
- return;
- } catch (IOException e) {
- Thread.sleep(200);
- }
- }
-
- throw new TimeoutException("Timed out waiting for server to start");
- }
-
/** Creates a {@link io.grpc.ManagedChannelBuilder} preconfigured for the emulator's port. */
private static ManagedChannelBuilder> newChannelBuilder(int port) {
// NOTE: usePlaintext is currently @ExperimentalAPI.
// See https://github.com/grpc/grpc-java/issues/1772 for discussion
return ManagedChannelBuilder.forAddress("localhost", port).usePlaintext();
}
-
- /** Creates a thread that will pipe an {@link java.io.InputStream} to this class' Logger. */
- private static void pipeStreamToLog(final InputStream stream, final Level level) {
- final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
-
- Thread thread =
- new Thread(
- new Runnable() {
- @Override
- public void run() {
- try {
- String line;
- while ((line = reader.readLine()) != null) {
- LOGGER.log(level, line);
- }
- } catch (IOException e) {
- if (!"Stream closed".equals(e.getMessage())) {
- LOGGER.log(Level.WARNING, "Failed to read process stream", e);
- }
- }
- }
- });
- thread.setDaemon(true);
- thread.start();
- }
//
}
diff --git a/pom.xml b/pom.xml
index a8f4cedfc9bb..6942e5072550 100644
--- a/pom.xml
+++ b/pom.xml
@@ -355,6 +355,7 @@
grpc-google-cloud-bigtable-v2
proto-google-cloud-bigtable-admin-v2
proto-google-cloud-bigtable-v2
+ google-cloud-bigtable-emulator-core
google-cloud-bigtable-emulator
google-cloud-bigtable-bom
google-cloud-bigtable-deps-bom
diff --git a/versions.txt b/versions.txt
index b27f1923ae93..94ce02e11e54 100644
--- a/versions.txt
+++ b/versions.txt
@@ -7,3 +7,4 @@ grpc-google-cloud-bigtable-v2:2.7.0:2.7.1-SNAPSHOT
proto-google-cloud-bigtable-admin-v2:2.7.0:2.7.1-SNAPSHOT
proto-google-cloud-bigtable-v2:2.7.0:2.7.1-SNAPSHOT
google-cloud-bigtable-emulator:0.144.0:0.144.1-SNAPSHOT
+google-cloud-bigtable-emulator-core:2.7.0:2.7.1-SNAPSHOT