Skip to content

Commit

Permalink
Update SpannerEmulatorDatabaseRule to take in a Liquibase changelog.
Browse files Browse the repository at this point in the history
This also drops the unused wrapper executable around the Spanner emulator.
  • Loading branch information
SanjayVas committed May 2, 2022
1 parent 48bef89 commit fc201a0
Show file tree
Hide file tree
Showing 16 changed files with 218 additions and 209 deletions.
4 changes: 4 additions & 0 deletions build/common_jvm_maven.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ def common_jvm_maven_artifacts():
"software.amazon.awssdk:s3": "2.17.98",
"software.amazon.awssdk:sdk-core": "2.17.98",

# Liquibase.
"org.liquibase:liquibase-core": "4.9.1",
"com.google.cloudspannerecosystem:liquibase-spanner": "4.6.1",

# For grpc-kotlin. This should be a version that is compatible with the
# Kotlin release used by rules_kotlin.
"com.squareup:kotlinpoet": "1.8.0",
Expand Down
6 changes: 6 additions & 0 deletions imports/java/liquibase/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package(default_visibility = ["//visibility:public"])

alias(
name = "core",
actual = "@maven//:org_liquibase_liquibase_core",
)
6 changes: 6 additions & 0 deletions imports/java/liquibase/ext/spanner/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package(default_visibility = ["//visibility:public"])

alias(
name = "spanner",
actual = "@maven//:com_google_cloudspannerecosystem_liquibase_spanner",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2022 The Cross-Media Measurement Authors
*
* 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 org.wfanet.measurement.common

import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.Paths

private val ZIP_FS_PROPERTIES: Map<String, Any> = mapOf("create" to "true")

/**
* Returns a [Path] to the resource with the specified [name] within a JAR.
*
* This initializes the [java.io.FileSystem] for the
* [zip file system provider](https://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html)
* .
*/
fun ClassLoader.getJarResourcePath(name: String): Path? {
val resourceUri: URI = getResource(name)?.toURI() ?: return null

// Initialize zip FileSystem.
FileSystems.newFileSystem(resourceUri, ZIP_FS_PROPERTIES)

return Paths.get(resourceUri)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

package(default_visibility = ["//visibility:public"])

kt_jvm_library(
name = "liquibase",
srcs = glob(["*.kt"]),
deps = ["//imports/java/liquibase:core"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2022 The Cross-Media Measurement Authors
*
* 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 org.wfanet.measurement.common.db.liquibase

import java.lang.UnsupportedOperationException
import java.nio.file.FileSystem
import java.nio.file.Files
import java.util.SortedSet
import liquibase.resource.AbstractResourceAccessor
import liquibase.resource.InputStreamList

/**
* [liquibase.resource.ResourceAccessor] which accesses resources from the given [fileSystem].
*
* Note that this is not the same as [liquibase.resource.FileSystemResourceAccessor], which only
* accesses files from [java.io.DefaultFileSystem] or from provided archives.
*/
internal class FileSystemResourceAccessor(private val fileSystem: FileSystem) :
AbstractResourceAccessor() {
/**
* @see [AbstractResourceAccessor.openStreams]
*
* This implementation does not handle archives. Paths are expected to be valid for [fileSystem].
*/
override fun openStreams(relativeTo: String?, streamPath: String?): InputStreamList {
requireNotNull(streamPath)

val path =
if (relativeTo == null) {
fileSystem.getPath(streamPath)
} else {
fileSystem.getPath(relativeTo).resolve(streamPath)
}

return InputStreamList(path.toUri(), Files.newInputStream(path))
}

override fun list(
relativeTo: String?,
path: String?,
recursive: Boolean,
includeFiles: Boolean,
includeDirectories: Boolean,
): SortedSet<String> {
throw UnsupportedOperationException()
}

override fun describeLocations(): SortedSet<String> {
throw UnsupportedOperationException()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2022 The Cross-Media Measurement Authors
*
* 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 org.wfanet.measurement.common.db.liquibase

import java.nio.file.Path
import java.sql.Connection
import liquibase.Liquibase
import liquibase.database.DatabaseFactory
import liquibase.database.jvm.JdbcConnection

object Liquibase {
fun fromPath(connection: Connection, changelogPath: Path): Liquibase {
return Liquibase(
changelogPath.toString(),
FileSystemResourceAccessor(changelogPath.fileSystem),
DatabaseFactory.getInstance().findCorrectDatabaseImplementation(JdbcConnection(connection))
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package org.wfanet.measurement.gcloud.spanner

import com.google.cloud.spanner.DatabaseClient
import com.google.cloud.spanner.DatabaseId
import com.google.cloud.spanner.Instance
import com.google.cloud.spanner.Spanner
import java.time.Duration
import java.util.logging.Logger
Expand All @@ -27,10 +26,10 @@ import kotlinx.coroutines.TimeoutCancellationException
* [DatabaseId], waiting for the connection to be ready, etc.
*/
class SpannerDatabaseConnector(
private val instanceName: String,
private val projectName: String,
private val readyTimeout: Duration,
projectName: String,
instanceName: String,
databaseName: String,
private val readyTimeout: Duration,
emulatorHost: String?
) : AutoCloseable {

Expand All @@ -45,20 +44,6 @@ class SpannerDatabaseConnector(
val databaseClient: AsyncDatabaseClient
get() = internalDatabaseClient.asAsync()

fun createInstance(
configId: String,
nodeCount: Int,
displayName: String = instanceName
): Instance {
return spanner.createInstance(
projectName = projectName,
instanceName = instanceName,
displayName = displayName,
instanceConfigId = configId,
instanceNodeCount = nodeCount
)
}

/**
* Suspends until [databaseClient] is ready, throwing a
* [kotlinx.coroutines.TimeoutCancellationException] if [readyTimeout] is reached.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ class SpannerFlags {
/** Builds a [SpannerDatabaseConnector] from these flags. */
private fun toSpannerDatabaseConnector(): SpannerDatabaseConnector {
return SpannerDatabaseConnector(
instanceName,
projectName,
readyTimeout,
databaseName,
spannerEmulatorHost
projectName = projectName,
instanceName = instanceName,
databaseName = databaseName,
readyTimeout = readyTimeout,
emulatorHost = spannerEmulatorHost
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
load("@rules_java//java:defs.bzl", "java_binary")
load("@io_bazel_rules_docker//java:image.bzl", "java_image")

package(
default_testonly = True,
default_visibility = ["//visibility:public"],
)

kt_jvm_library(
name = "spanner_schema",
srcs = ["SpannerSchema.kt"],
)

kt_jvm_library(
name = "spanner_emulator",
srcs = ["SpannerEmulator.kt"],
Expand All @@ -29,53 +22,24 @@ kt_jvm_library(
["*.kt"],
exclude = [
"SpannerEmulator.kt",
"SpannerEmulatorMain.kt",
"SpannerSchema.kt",
],
),
exports = [":spanner_emulator"],
runtime_deps = [
"//imports/java/liquibase/ext/spanner",
],
deps = [
":spanner_emulator",
":spanner_schema",
"//imports/java/com/google/api/core",
"//imports/java/com/google/cloud/spanner",
"//imports/java/com/google/common/truth",
"//imports/java/liquibase:core",
"//imports/java/org/junit",
"//imports/kotlin/kotlinx/coroutines:core",
"//src/main/kotlin/org/wfanet/measurement/common",
"//src/main/kotlin/org/wfanet/measurement/common/db/liquibase",
"//src/main/kotlin/org/wfanet/measurement/common/testing",
"//src/main/kotlin/org/wfanet/measurement/gcloud/common",
"//src/main/kotlin/org/wfanet/measurement/gcloud/spanner",
],
)

kt_jvm_library(
name = "spanner_emulator_main",
srcs = ["SpannerEmulatorMain.kt"],
data = ["@cloud_spanner_emulator//:emulator"],
runtime_deps = [
# Workaround for `NoClassDefFoundError: kotlin/jdk7/AutoCloseableKt`.
# See https://github.com/bazelbuild/rules_kotlin/issues/333
"//imports/kotlin/kotlin:stdlib_jdk7",
],
deps = [
":spanner_emulator",
":spanner_schema",
"//imports/java/com/google/cloud/spanner",
"//imports/java/picocli",
"//src/main/kotlin/org/wfanet/measurement/common",
"//src/main/kotlin/org/wfanet/measurement/gcloud/spanner",
],
)

java_binary(
name = "SpannerEmulator",
main_class = "org.wfanet.measurement.db.gcp.testing.SpannerEmulatorMainKt",
runtime_deps = [":spanner_emulator_main"],
)

java_image(
name = "spanner_emulator_image",
main_class = "org.wfanet.measurement.db.gcp.testing.SpannerEmulatorMainKt",
runtime_deps = [":spanner_emulator_main"],
)
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,17 @@ internal class EmulatorWithInstance : AutoCloseable {
spanner = spannerOptions.service
}

val instance: Instance
init {
instance =
spanner
.instanceAdminClient
.createInstance(
InstanceInfo.newBuilder(InstanceId.of(PROJECT_ID, INSTANCE_NAME))
.setDisplayName(INSTANCE_DISPLAY_NAME)
.setInstanceConfigId(InstanceConfigId.of(PROJECT_ID, INSTANCE_CONFIG))
.setNodeCount(1)
.build()
)
.get()
}
val instance: Instance =
spanner
.instanceAdminClient
.createInstance(
InstanceInfo.newBuilder(InstanceId.of(PROJECT_ID, INSTANCE_NAME))
.setDisplayName(INSTANCE_DISPLAY_NAME)
.setInstanceConfigId(InstanceConfigId.of(PROJECT_ID, INSTANCE_CONFIG))
.setNodeCount(1)
.build()
)
.get()

override fun close() {
instance.delete()
Expand All @@ -61,6 +58,9 @@ internal class EmulatorWithInstance : AutoCloseable {
fun getDatabaseClient(databaseId: DatabaseId): DatabaseClient =
spanner.getDatabaseClient(databaseId)

fun getJdbcConnectionString(databaseId: DatabaseId): String =
spannerEmulator.getJdbcConnectionString(databaseId)

companion object {
private const val PROJECT_ID = "test-project"
private const val INSTANCE_NAME = "test-instance"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package org.wfanet.measurement.gcloud.spanner.testing

import com.google.cloud.spanner.DatabaseId
import java.net.ServerSocket
import java.nio.file.Files
import java.nio.file.Path
Expand Down Expand Up @@ -88,6 +89,12 @@ class SpannerEmulator(private val port: Int = 0) : AutoCloseable {
}
}

fun getJdbcConnectionString(databaseId: DatabaseId): String {
val instanceId = databaseId.instanceId
return "jdbc:cloudspanner://$emulatorHost/projects/${instanceId.project}/instances/" +
"${instanceId.instance}/databases/${databaseId.database};usePlainText=true"
}

companion object {
private val emulatorPath: Path
init {
Expand Down

0 comments on commit fc201a0

Please sign in to comment.