buildscripts: xDS Kubernetes Interop tests buildscript
diff --git a/buildscripts/kokoro/xds-k8s-install-test-driver.sh b/buildscripts/kokoro/xds-k8s-install-test-driver.sh
new file mode 100755
index 0000000..fba84da
--- /dev/null
+++ b/buildscripts/kokoro/xds-k8s-install-test-driver.sh
@@ -0,0 +1,342 @@
+#!/usr/bin/env bash
+# TODO(sergiitk): move to grpc/grpc when implementing support of other languages
+set -eo pipefail
+
+# Constants
+readonly PYTHON_VERSION="3.6"
+# Test driver
+readonly TEST_DRIVER_REPO_NAME="grpc"
+readonly TEST_DRIVER_REPO_URL="https://github.com/grpc/grpc.git"
+readonly TEST_DRIVER_BRANCH="${TEST_DRIVER_BRANCH:-master}"
+readonly TEST_DRIVER_PATH="tools/run_tests/xds_k8s_test_driver"
+readonly TEST_DRIVER_PROTOS_PATH="src/proto/grpc/testing"
+
+#######################################
+# Run command end report its exit code. Doesn't exit on non-zero exit code.
+# Globals:
+# None
+# Arguments:
+# Command to execute
+# Outputs:
+# Writes the output of given command to stdout, stderr
+#######################################
+run_ignore_exit_code() {
+ local exit_code=-1
+ "$@" || exit_code=$?
+ echo "Exit code: ${exit_code}"
+}
+
+#######################################
+# Parses information about git repository at given path to global variables.
+# Globals:
+# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build
+# GIT_COMMIT: Populated with the SHA-1 of git commit being built
+# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built
+# Arguments:
+# Git source dir
+#######################################
+parse_src_repo_git_info() {
+ local src_dir="${SRC_DIR:?SRC_DIR must be set}"
+ readonly GIT_ORIGIN_URL=$(git -C "${src_dir}" remote get-url origin)
+ readonly GIT_COMMIT=$(git -C "${src_dir}" rev-parse HEAD)
+ readonly GIT_COMMIT_SHORT=$(git -C "${src_dir}" rev-parse --short HEAD)
+}
+
+#######################################
+# List GCR image tags matching given tag name.
+# Arguments:
+# Image name
+# Tag name
+# Outputs:
+# Writes the table with the list of found tags to stdout.
+# If no tags found, the output is an empty string.
+#######################################
+gcloud_gcr_list_image_tags() {
+ gcloud container images list-tags --format="table[box](tags,digest,timestamp.date())" --filter="tags:$2" "$1"
+}
+
+#######################################
+# A helper to execute `gcloud -q components update`.
+# Arguments:
+# None
+# Outputs:
+# Writes the output of `gcloud` command to stdout, stderr
+#######################################
+gcloud_update() {
+ echo "Update gcloud components:"
+ gcloud -q components update
+}
+
+#######################################
+# Create kube context authenticated with GKE cluster, saves context name.
+# to KUBE_CONTEXT
+# Globals:
+# GKE_CLUSTER_NAME
+# GKE_CLUSTER_ZONE
+# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access
+# Arguments:
+# None
+# Outputs:
+# Writes the output of `gcloud` command to stdout, stderr
+# Writes authorization info $HOME/.kube/config
+#######################################
+gcloud_get_cluster_credentials() {
+ gcloud container clusters get-credentials "${GKE_CLUSTER_NAME}" --zone "${GKE_CLUSTER_ZONE}"
+ readonly KUBE_CONTEXT="$(kubectl config current-context)"
+}
+
+#######################################
+# Clone the source code of the test driver to $TEST_DRIVER_REPO_DIR, unless
+# given folder exists.
+# Globals:
+# TEST_DRIVER_REPO_URL
+# TEST_DRIVER_BRANCH
+# TEST_DRIVER_REPO_DIR: path to the repo containing the test driver
+# TEST_DRIVER_REPO_DIR_USE_EXISTING: set non-empty value to use exiting
+# clone of the driver repo located at $TEST_DRIVER_REPO_DIR.
+# Useful for debugging the build script locally.
+# Arguments:
+# None
+# Outputs:
+# Writes the output of `git` command to stdout, stderr
+# Writes driver source code to $TEST_DRIVER_REPO_DIR
+#######################################
+test_driver_get_source() {
+ if [[ -n "${TEST_DRIVER_REPO_DIR_USE_EXISTING}" && -d "${TEST_DRIVER_REPO_DIR}" ]]; then
+ echo "Using exiting driver directory: ${TEST_DRIVER_REPO_DIR}."
+ else
+ echo "Cloning driver to ${TEST_DRIVER_REPO_URL} branch ${TEST_DRIVER_BRANCH} to ${TEST_DRIVER_REPO_DIR}"
+ git clone -b "${TEST_DRIVER_BRANCH}" --depth=1 "${TEST_DRIVER_REPO_URL}" "${TEST_DRIVER_REPO_DIR}"
+ fi
+}
+
+#######################################
+# Install Python modules from required in $TEST_DRIVER_FULL_DIR/requirements.txt
+# to Python virtual environment. Creates and activates Python venv if necessary.
+# Globals:
+# TEST_DRIVER_FULL_DIR
+# PYTHON_VERSION
+# Arguments:
+# None
+# Outputs:
+# Writes the output of `python`, `pip` commands to stdout, stderr
+# Writes the list of installed modules to stdout
+#######################################
+test_driver_pip_install() {
+ echo "Install python dependencies"
+ cd "${TEST_DRIVER_FULL_DIR}"
+
+ # Create and activate virtual environment unless already using one
+ if [[ -z "${VIRTUAL_ENV}" ]]; then
+ local venv_dir="${TEST_DRIVER_FULL_DIR}/venv"
+ if [[ -d "${venv_dir}" ]]; then
+ echo "Found python virtual environment directory: ${venv_dir}"
+ else
+ echo "Creating python virtual environment: ${venv_dir}"
+ "python${PYTHON_VERSION} -m venv ${venv_dir}"
+ fi
+ # Intentional: No need to check python venv activate script.
+ # shellcheck source=/dev/null
+ source "${venv_dir}/bin/activate"
+ fi
+
+ pip install -r requirements.txt
+ echo "Installed Python packages:"
+ pip list
+}
+
+#######################################
+# Compile proto-files needed for the test driver
+# Globals:
+# TEST_DRIVER_REPO_DIR
+# TEST_DRIVER_FULL_DIR
+# TEST_DRIVER_PROTOS_PATH
+# Arguments:
+# None
+# Outputs:
+# Writes the output of `python -m grpc_tools.protoc` to stdout, stderr
+# Writes the list if compiled python code to stdout
+# Writes compiled python code with proto messages and grpc services to
+# $TEST_DRIVER_FULL_DIR/src/proto
+#######################################
+test_driver_compile_protos() {
+ declare -a protos
+ protos=(
+ "${TEST_DRIVER_PROTOS_PATH}/test.proto"
+ "${TEST_DRIVER_PROTOS_PATH}/messages.proto"
+ "${TEST_DRIVER_PROTOS_PATH}/empty.proto"
+ )
+ echo "Generate python code from grpc.testing protos: ${protos[*]}"
+ cd "${TEST_DRIVER_REPO_DIR}"
+ python -m grpc_tools.protoc \
+ --proto_path=. \
+ --python_out="${TEST_DRIVER_FULL_DIR}" \
+ --grpc_python_out="${TEST_DRIVER_FULL_DIR}" \
+ "${protos[@]}"
+ local protos_out_dir="${TEST_DRIVER_FULL_DIR}/${TEST_DRIVER_PROTOS_PATH}"
+ echo "Generated files ${protos_out_dir}:"
+ ls -Fl "${protos_out_dir}"
+}
+
+#######################################
+# Installs the test driver and it's requirements.
+# https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#installation
+# Globals:
+# TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing
+# the test driver
+# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code
+# Arguments:
+# The directory for test driver's source code
+# Outputs:
+# Writes the output to stdout, stderr
+#######################################
+test_driver_install() {
+ readonly TEST_DRIVER_REPO_DIR="${1:?Usage test_driver_install TEST_DRIVER_REPO_DIR}"
+ readonly TEST_DRIVER_FULL_DIR="${TEST_DRIVER_REPO_DIR}/${TEST_DRIVER_PATH}"
+ test_driver_get_source
+ test_driver_pip_install
+ test_driver_compile_protos
+}
+
+#######################################
+# Outputs Kokoro image version and Ubuntu's lsb_release
+# Arguments:
+# None
+# Outputs:
+# Writes the output to stdout
+#######################################
+kokoro_print_version() {
+ echo "Kokoro VM version:"
+ if [[ -f /VERSION ]]; then
+ cat /VERSION
+ fi
+ run_ignore_exit_code lsb_release -a
+}
+
+#######################################
+# Report extra information about the job via sponge properties.
+# Globals:
+# KOKORO_ARTIFACTS_DIR
+# GIT_ORIGIN_URL
+# GIT_COMMIT_SHORT
+# TESTGRID_EXCLUDE
+# Arguments:
+# None
+# Outputs:
+# Writes the output to stdout
+# Writes job properties to $KOKORO_ARTIFACTS_DIR/custom_sponge_config.csv
+#######################################
+kokoro_write_sponge_properties() {
+ # CSV format: "property_name","property_value"
+ # Bump TESTS_FORMAT_VERSION when reported test name changed enough to when it
+ # makes more sense to discard previous test results from a testgrid board.
+ # Use GIT_ORIGIN_URL to exclude test runs executed against repo forks from
+ # testgrid reports.
+ cat >"${KOKORO_ARTIFACTS_DIR}/custom_sponge_config.csv" <<EOF
+TESTS_FORMAT_VERSION,2
+TESTGRID_EXCLUDE,${TESTGRID_EXCLUDE:-0}
+GIT_ORIGIN_URL,${GIT_ORIGIN_URL:?GIT_ORIGIN_URL must be set}
+GIT_COMMIT_SHORT,${GIT_COMMIT_SHORT:?GIT_COMMIT_SHORT must be set}
+EOF
+ echo "Sponge properties:"
+ cat "${KOKORO_ARTIFACTS_DIR}/custom_sponge_config.csv"
+}
+
+#######################################
+# Configure Python virtual environment on Kokoro VM.
+# Arguments:
+# None
+# Outputs:
+# Writes the output of `pyenv` commands to stdout
+#######################################
+kokoro_setup_python_virtual_environment() {
+ # Kokoro provides pyenv, so use it instead of `python -m venv`
+ echo "Setup pyenv environment"
+ eval "$(pyenv init -)"
+ eval "$(pyenv virtualenv-init -)"
+ py_latest_patch="$(pyenv versions --bare --skip-aliases | grep -E "^${PYTHON_VERSION}\.[0-9]{1,2}$" | sort --version-sort | tail -n 1)"
+ echo "Activating python ${py_latest_patch} virtual environment"
+ pyenv virtualenv "${py_latest_patch}" k8s_xds_test_runner
+ pyenv local k8s_xds_test_runner
+ pyenv activate k8s_xds_test_runner
+}
+
+#######################################
+# Installs and configures the test driver on Kokoro VM.
+# Globals:
+# KOKORO_ARTIFACTS_DIR
+# TEST_DRIVER_REPO_NAME
+# SRC_DIR: Populated with absolute path to the source repo on Kokoro VM
+# TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing
+# the test driver
+# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code
+# TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile
+# TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report
+# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access
+# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build
+# GIT_COMMIT: Populated with the SHA-1 of git commit being built
+# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built
+# Arguments:
+# The name of github repository being built
+# Outputs:
+# Writes the output to stdout, stderr, files
+#######################################
+kokoro_setup_test_driver() {
+ local src_repository_name="${1:?Usage kokoro_setup_test_driver GITHUB_REPOSITORY_NAME}"
+ # Capture Kokoro VM version info in the log.
+ kokoro_print_version
+
+ # Kokoro clones repo to ${KOKORO_ARTIFACTS_DIR}/github/${GITHUB_REPOSITORY}
+ local github_root="${KOKORO_ARTIFACTS_DIR}/github"
+ readonly SRC_DIR="${github_root}/${src_repository_name}"
+ local test_driver_repo_dir="${github_root}/${TEST_DRIVER_REPO_NAME}"
+ parse_src_repo_git_info SRC_DIR
+ kokoro_write_sponge_properties
+ kokoro_setup_python_virtual_environment
+
+ # gcloud requires python, so this should be executed after pyenv setup
+ gcloud_update
+ gcloud_get_cluster_credentials
+ test_driver_install "${test_driver_repo_dir}"
+ # shellcheck disable=SC2034 # Used in the main script
+ readonly TEST_DRIVER_FLAGFILE="config/grpc-testing.cfg"
+ # Test artifacts dir: xml reports, logs, etc.
+ local artifacts_dir="${KOKORO_ARTIFACTS_DIR}/artifacts"
+ # Folders after $artifacts_dir reported as target name
+ readonly TEST_XML_OUTPUT_DIR="${artifacts_dir}/${KOKORO_JOB_NAME}"
+ mkdir -p "${artifacts_dir}" "${TEST_XML_OUTPUT_DIR}"
+}
+
+#######################################
+# Installs and configures the test driver for testing build script locally.
+# Globals:
+# TEST_DRIVER_REPO_NAME
+# TEST_DRIVER_REPO_DIR: Unless provided, populated with a temporary dir with
+# the path to the test driver repo
+# SRC_DIR: Populated with absolute path to the source repo
+# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code
+# TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile
+# TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report
+# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build
+# GIT_COMMIT: Populated with the SHA-1 of git commit being built
+# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built
+# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access
+# Arguments:
+# The path to the folder containing the build script
+# Outputs:
+# Writes the output to stdout, stderr, files
+#######################################
+local_setup_test_driver() {
+ local script_dir="${1:?Usage: local_setup_test_driver SCRIPT_DIR}"
+ readonly SRC_DIR="$(git -C "${script_dir}" rev-parse --show-toplevel)"
+ parse_src_repo_git_info SRC_DIR
+ readonly KUBE_CONTEXT="${KUBE_CONTEXT:-$(kubectl config current-context)}"
+ local test_driver_repo_dir
+ test_driver_repo_dir="${TEST_DRIVER_REPO_DIR:-$(mktemp -d)/${TEST_DRIVER_REPO_NAME}}"
+ test_driver_install "${test_driver_repo_dir}"
+ # shellcheck disable=SC2034 # Used in the main script
+ readonly TEST_DRIVER_FLAGFILE="config/local-dev.cfg"
+ # Test out
+ readonly TEST_XML_OUTPUT_DIR="${TEST_DRIVER_FULL_DIR}/out"
+ mkdir -p "${TEST_XML_OUTPUT_DIR}"
+}
diff --git a/buildscripts/kokoro/xds-k8s.cfg b/buildscripts/kokoro/xds-k8s.cfg
index be553d3..61fe825 100644
--- a/buildscripts/kokoro/xds-k8s.cfg
+++ b/buildscripts/kokoro/xds-k8s.cfg
@@ -3,9 +3,11 @@
# Location of the continuous shell script in repository.
build_file: "grpc-java/buildscripts/kokoro/xds-k8s.sh"
timeout_mins: 90
+
action {
define_artifacts {
- regex: "artifacts/*sponge_log.xml"
- regex: "artifacts/*sponge_log.log"
+ regex: "artifacts/**/*sponge_log.xml"
+ regex: "artifacts/**/*sponge_log.log"
+ strip_prefix: "artifacts"
}
}
diff --git a/buildscripts/kokoro/xds-k8s.sh b/buildscripts/kokoro/xds-k8s.sh
index 70f88ff..8b5bf72 100755
--- a/buildscripts/kokoro/xds-k8s.sh
+++ b/buildscripts/kokoro/xds-k8s.sh
@@ -1,4 +1,164 @@
-#!/bin/bash
+#!/usr/bin/env bash
+set -eo pipefail
-# A placeholder for xDS interop tests executed on GKE
-echo "Coming soon"
+# Constants
+readonly GITHUB_REPOSITORY_NAME="grpc-java"
+# GKE Cluster
+readonly GKE_CLUSTER_NAME="interop-test-psm-sec1-us-central1"
+readonly GKE_CLUSTER_ZONE="us-central1-a"
+## xDS test server/client Docker images
+readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-server"
+readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/java-client"
+readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}"
+readonly BUILD_APP_PATH="interop-testing/build/install/grpc-interop-testing"
+
+#######################################
+# Builds the test app using gradle and smoke-checks its binaries
+# Globals:
+# SRC_DIR
+# BUILD_APP_PATH
+# Arguments:
+# None
+# Outputs:
+# Writes the output of xds-test-client and xds-test-server --help to stderr
+#######################################
+build_java_test_app() {
+ echo "Building Java test app"
+ cd "${SRC_DIR}"
+ ./gradlew --no-daemon grpc-interop-testing:installDist -x test \
+ -PskipCodegen=true -PskipAndroid=true --console=plain
+
+ # Test-run binaries
+ run_ignore_exit_code "${SRC_DIR}/${BUILD_APP_PATH}/bin/xds-test-client" --help
+ run_ignore_exit_code "${SRC_DIR}/${BUILD_APP_PATH}/bin/xds-test-server" --help
+}
+
+#######################################
+# Builds test app Docker images and pushes them to GCR
+# Globals:
+# BUILD_APP_PATH
+# SERVER_IMAGE_NAME: Test server Docker image name
+# CLIENT_IMAGE_NAME: Test client Docker image name
+# GIT_COMMIT: SHA-1 of git commit being built
+# Arguments:
+# None
+# Outputs:
+# Writes the output of `gcloud builds submit` to stdout, stderr
+#######################################
+build_test_app_docker_images() {
+ echo "Building Java xDS interop test app Docker images"
+ local docker_dir="${SRC_DIR}/buildscripts/xds-k8s"
+ local build_dir
+ build_dir="$(mktemp -d)"
+ # Copy Docker files, log properties, and the test app to the build dir
+ cp -v "${docker_dir}/"*.Dockerfile "${build_dir}"
+ cp -v "${docker_dir}/"*.properties "${build_dir}"
+ cp -rv "${SRC_DIR}/${BUILD_APP_PATH}" "${build_dir}"
+ # Run Google Cloud Build
+ gcloud builds submit "${build_dir}" \
+ --config "${docker_dir}/cloudbuild.yaml" \
+ --substitutions "_SERVER_IMAGE_NAME=${SERVER_IMAGE_NAME},_CLIENT_IMAGE_NAME=${CLIENT_IMAGE_NAME},COMMIT_SHA=${GIT_COMMIT}"
+ # TODO(sergiitk): extra "cosmetic" tags for versioned branches, e.g. v1.34.x
+ # TODO(sergiitk): do this when adding support for custom configs per version
+}
+
+#######################################
+# Builds test app and its docker images unless they already exist
+# Globals:
+# SERVER_IMAGE_NAME: Test server Docker image name
+# CLIENT_IMAGE_NAME: Test client Docker image name
+# GIT_COMMIT: SHA-1 of git commit being built
+# FORCE_IMAGE_BUILD
+# Arguments:
+# None
+# Outputs:
+# Writes the output to stdout, stderr
+#######################################
+build_docker_images_if_needed() {
+ # Check if images already exist
+ server_tags="$(gcloud_gcr_list_image_tags "${SERVER_IMAGE_NAME}" "${GIT_COMMIT}")"
+ printf "Server image: %s:%s\n" "${SERVER_IMAGE_NAME}" "${GIT_COMMIT}"
+ echo "${server_tags:-Server image not found}"
+
+ client_tags="$(gcloud_gcr_list_image_tags "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}")"
+ printf "Client image: %s:%s\n" "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}"
+ echo "${client_tags:-Client image not found}"
+
+ # Build if any of the images are missing, or FORCE_IMAGE_BUILD=1
+ if [[ "${FORCE_IMAGE_BUILD}" == "1" || -z "${server_tags}" || -z "${client_tags}" ]]; then
+ build_java_test_app
+ build_test_app_docker_images
+ else
+ echo "Skipping Java test app build"
+ fi
+}
+
+#######################################
+# Executes the test case
+# Globals:
+# TEST_DRIVER_FLAGFILE: Relative path to test driver flagfile
+# KUBE_CONTEXT: The name of kubectl context with GKE cluster access
+# TEST_XML_OUTPUT_DIR: Output directory for the test xUnit XML report
+# SERVER_IMAGE_NAME: Test server Docker image name
+# CLIENT_IMAGE_NAME: Test client Docker image name
+# GIT_COMMIT: SHA-1 of git commit being built
+# Arguments:
+# Test case name
+# Outputs:
+# Writes the output of test execution to stdout, stderr
+# Test xUnit report to ${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml
+#######################################
+run_test() {
+ # Test driver usage:
+ # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage
+ local test_name="${1:?Usage: run_test test_name}"
+ set -x
+ python -m "tests.${test_name}" \
+ --flagfile="${TEST_DRIVER_FLAGFILE}" \
+ --kube_context="${KUBE_CONTEXT}" \
+ --server_image="${SERVER_IMAGE_NAME}:${GIT_COMMIT}" \
+ --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \
+ --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \
+ --force_cleanup
+ set +x
+}
+
+#######################################
+# Main function: provision software necessary to execute tests, and run them
+# Globals:
+# KOKORO_ARTIFACTS_DIR
+# GITHUB_REPOSITORY_NAME
+# SRC_DIR: Populated with absolute path to the source repo
+# TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing
+# the test driver
+# TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code
+# TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile
+# TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report
+# GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build
+# GIT_COMMIT: Populated with the SHA-1 of git commit being built
+# GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built
+# KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access
+# Arguments:
+# None
+# Outputs:
+# Writes the output of test execution to stdout, stderr
+#######################################
+main() {
+ local script_dir
+ script_dir="$(dirname "$0")"
+ # shellcheck source=buildscripts/kokoro/xds-k8s-install-test-driver.sh
+ source "${script_dir}/xds-k8s-install-test-driver.sh"
+ set -x
+ if [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then
+ kokoro_setup_test_driver "${GITHUB_REPOSITORY_NAME}"
+ else
+ local_setup_test_driver "${script_dir}"
+ fi
+ build_docker_images_if_needed
+ # Run tests
+ cd "${TEST_DRIVER_FULL_DIR}"
+ run_test baseline_test
+ run_test security_test
+}
+
+main "$@"
diff --git a/buildscripts/xds-k8s/cloudbuild.yaml b/buildscripts/xds-k8s/cloudbuild.yaml
new file mode 100644
index 0000000..03c5748
--- /dev/null
+++ b/buildscripts/xds-k8s/cloudbuild.yaml
@@ -0,0 +1,22 @@
+steps:
+- name: 'gcr.io/cloud-builders/docker'
+ args:
+ - 'build'
+ - '--tag=${_SERVER_IMAGE_NAME}:${COMMIT_SHA}'
+ - '--file=test-server.Dockerfile'
+ - '.'
+
+- name: 'gcr.io/cloud-builders/docker'
+ args:
+ - 'build'
+ - '--tag=${_CLIENT_IMAGE_NAME}:${COMMIT_SHA}'
+ - '--file=test-client.Dockerfile'
+ - '.'
+
+substitutions:
+ _SERVER_IMAGE_NAME: gcr.io/grpc-testing/xds-interop/java-server
+ _CLIENT_IMAGE_NAME: gcr.io/grpc-testing/xds-interop/java-client
+
+images:
+ - '${_SERVER_IMAGE_NAME}:${COMMIT_SHA}'
+ - '${_CLIENT_IMAGE_NAME}:${COMMIT_SHA}'
diff --git a/buildscripts/xds-k8s/logging-debug.properties b/buildscripts/xds-k8s/logging-debug.properties
new file mode 100644
index 0000000..8b0e327
--- /dev/null
+++ b/buildscripts/xds-k8s/logging-debug.properties
@@ -0,0 +1,7 @@
+handlers=java.util.logging.ConsoleHandler
+io.grpc.ChannelLogger.level=FINEST
+io.grpc.level=FINEST
+io.netty.level=FINEST
+java.util.logging.ConsoleHandler.level=ALL
+java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format=[%1$tF %1$tT.%1$tL %1tZ] [%4$-7s] [%3$s] %5$s %6$s %n
diff --git a/buildscripts/xds-k8s/logging-json.properties b/buildscripts/xds-k8s/logging-json.properties
new file mode 100644
index 0000000..62ca955
--- /dev/null
+++ b/buildscripts/xds-k8s/logging-json.properties
@@ -0,0 +1,8 @@
+handlers=java.util.logging.ConsoleHandler
+java.util.logging.ConsoleHandler.level=FINEST
+java.util.logging.ConsoleHandler.formatter=io.github.devatherock.json.formatter.JSONFormatter
+io.github.devatherock.json.formatter.JSONFormatter.key_timestamp=time
+io.github.devatherock.json.formatter.JSONFormatter.key_log_level=severity
+io.github.devatherock.json.formatter.JSONFormatter.use_slf4j_level_names=true
+io.grpc.ChannelLogger.level=FINEST
+io.grpc.xds.level=FINEST
diff --git a/buildscripts/xds-k8s/logging.properties b/buildscripts/xds-k8s/logging.properties
new file mode 100644
index 0000000..d7759dc
--- /dev/null
+++ b/buildscripts/xds-k8s/logging.properties
@@ -0,0 +1,6 @@
+handlers=java.util.logging.ConsoleHandler
+io.grpc.ChannelLogger.level=FINEST
+io.grpc.xds.level=FINEST
+java.util.logging.ConsoleHandler.level=FINEST
+java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format=[%1$tF %1$tT.%1$tL %1tZ] [%4$-7s] [%3$s] %5$s %6$s %n
diff --git a/buildscripts/xds-k8s/test-client.Dockerfile b/buildscripts/xds-k8s/test-client.Dockerfile
new file mode 100644
index 0000000..220ca03
--- /dev/null
+++ b/buildscripts/xds-k8s/test-client.Dockerfile
@@ -0,0 +1,15 @@
+# Build runtime image.
+FROM openjdk:11.0.9.1-jdk
+
+ENV APP_DIR=/usr/src/app
+WORKDIR $APP_DIR
+
+# Install the app
+COPY grpc-interop-testing/ $APP_DIR/
+
+# Copy all logging profiles, use json logging by default
+COPY logging*.properties $APP_DIR/
+ENV JAVA_OPTS="-Djava.util.logging.config.file=$APP_DIR/logging-json.properties"
+
+# Client
+ENTRYPOINT ["bin/xds-test-client"]
diff --git a/buildscripts/xds-k8s/test-server.Dockerfile b/buildscripts/xds-k8s/test-server.Dockerfile
new file mode 100644
index 0000000..86f8434
--- /dev/null
+++ b/buildscripts/xds-k8s/test-server.Dockerfile
@@ -0,0 +1,15 @@
+# Build runtime image.
+FROM openjdk:11.0.9.1-jdk
+
+ENV APP_DIR=/usr/src/app
+WORKDIR $APP_DIR
+
+# Install the app
+COPY grpc-interop-testing/ $APP_DIR/
+
+# Copy all logging profiles, use json logging by default
+COPY logging*.properties $APP_DIR/
+ENV JAVA_OPTS="-Djava.util.logging.config.file=$APP_DIR/logging-json.properties"
+
+# Server
+ENTRYPOINT ["bin/xds-test-server"]
diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle
index c4f2558..01af652 100644
--- a/interop-testing/build.gradle
+++ b/interop-testing/build.gradle
@@ -32,6 +32,13 @@
censusGrpcMetricDependency 'implementation'
googleOauth2Dependency 'implementation'
compileOnly libraries.javax_annotation
+ // TODO(sergiitk): replace with com.google.cloud:google-cloud-logging
+ // Used instead of google-cloud-logging because it's failing
+ // due to a circular dependency on grpc.
+ // https://cloud.google.com/logging/docs/setup/java#the_javautillogging_handler
+ // Error example: "java.util.logging.ErrorManager: 1"
+ // Latest failing version com.google.cloud:google-cloud-logging:2.1.2
+ runtimeOnly group: 'io.github.devatherock', name: 'jul-jsonformatter', version: '1.1.0'
runtimeOnly libraries.opencensus_impl,
libraries.netty_tcnative,
project(':grpc-grpclb')