From ad4b0a85bab3f021d23fa23a50bc0da18cc29049 Mon Sep 17 00:00:00 2001 From: Balint Pato Date: Thu, 7 Sep 2017 18:28:24 -0400 Subject: [PATCH] adding custom logging test + local runtimes-common integration test (#134) * adding custom logging test + local runtimes-common integration test * organize imports + namings * changed test-app to lazy init + local-cloud-builder * DEVELOPING instructions updated + enforcer plugin fixed for property resolution * cleanup * cleanup * Add shutdown tests back to maven execution (#149) * cleanup + adding automated application-default credentials logic instead of GOOGLE_APPLICATION_CREDENTIALS * cleaning up unnecessary dependencies --- DEVELOPING.md | 8 +- openjdk-common/pom.xml | 11 ++ openjdk8/pom.xml | 4 + openjdk9/pom.xml | 4 + pom.xml | 42 ++++--- scripts/gcloud-init.sh | 1 + scripts/integration_test.sh | 2 +- scripts/integration_test.yaml | 1 - .../local_runtimes_common_integration_test.sh | 109 ++++++++++++++++++ ...gration_test.sh => local_shutdown_test.sh} | 0 test-application/pom.xml | 8 +- .../cloud/runtimes/ExceptionController.java | 4 +- .../cloud/runtimes/HelloController.java | 4 +- .../cloud/runtimes/LoggingTestController.java | 77 +++++++++++++ .../runtimes/MonitoringTestController.java | 10 +- .../runtimes/config/GcpConfiguration.java | 38 ++++++ .../StackDriverMonitoringService.java | 26 ++++- 17 files changed, 316 insertions(+), 33 deletions(-) create mode 100755 scripts/local_runtimes_common_integration_test.sh rename scripts/{local_integration_test.sh => local_shutdown_test.sh} (100%) create mode 100644 test-application/src/main/java/com/google/cloud/runtimes/LoggingTestController.java create mode 100644 test-application/src/main/java/com/google/cloud/runtimes/config/GcpConfiguration.java diff --git a/DEVELOPING.md b/DEVELOPING.md index 97529dfb..daded9bb 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -56,9 +56,15 @@ $ ./scripts/integration_test.sh $RUNTIME_IMAGE **Run ONLY Local Docker integration tests:** ```bash -$ ./scripts/local_integration_test.sh $RUNTIME_IMAGE +$ ./scripts/local_runtimes_common_integration_test.sh $RUNTIME_IMAGE ``` +**Run ONLY Local shutdown tests:** +```bash +$ ./scripts/local_shutdown_test.sh $RUNTIME_IMAGE +``` + + **Run ONLY App Engine flexible environment integration tests:** ```bash $ ./scripts/ae_integration_test.sh $RUNTIME_IMAGE diff --git a/openjdk-common/pom.xml b/openjdk-common/pom.xml index 24396fca..860e84d1 100644 --- a/openjdk-common/pom.xml +++ b/openjdk-common/pom.xml @@ -31,6 +31,17 @@ + + maven-enforcer-plugin + + + + enforce + + validate + + + org.apache.maven.plugins maven-assembly-plugin diff --git a/openjdk8/pom.xml b/openjdk8/pom.xml index bf55316b..deecf261 100644 --- a/openjdk8/pom.xml +++ b/openjdk8/pom.xml @@ -93,6 +93,10 @@ common-structure-test integration-test + + local-shutdown-test + integration-test + openjdk8-structure-test diff --git a/openjdk9/pom.xml b/openjdk9/pom.xml index fcec22a9..fd284bae 100644 --- a/openjdk9/pom.xml +++ b/openjdk9/pom.xml @@ -92,6 +92,10 @@ common-structure-test integration-test + + local-shutdown-test + integration-test + openjdk9-structure-test diff --git a/pom.xml b/pom.xml index 9f57088d..c6c4e683 100644 --- a/pom.xml +++ b/pom.xml @@ -67,24 +67,16 @@ org.apache.maven.plugins maven-enforcer-plugin 1.4.1 - - - enforce-maven - - enforce - - - - - [3.0,) - - + + + + [3.3,) + + [1.8,) - - - - - + + + @@ -191,6 +183,22 @@ + + local-shutdown-test + + none + + exec + + + ${project.parent.basedir}/scripts/local_shutdown_test.sh + ${project.build.outputDirectory} + + ${docker.image.name} + + + diff --git a/scripts/gcloud-init.sh b/scripts/gcloud-init.sh index 49da4885..c095030c 100755 --- a/scripts/gcloud-init.sh +++ b/scripts/gcloud-init.sh @@ -46,3 +46,4 @@ gcloud auth activate-service-account --key-file=$KEYFILE gcloud config set project $GCP_PROJECT gcloud config set compute/zone us-east1-b gcloud components install beta kubectl -q +gcloud components install beta container-builder-local diff --git a/scripts/integration_test.sh b/scripts/integration_test.sh index ae13c695..d0de962f 100755 --- a/scripts/integration_test.sh +++ b/scripts/integration_test.sh @@ -32,7 +32,7 @@ fi # is not recommended readonly gaeDeploymentVersion=$2 -${dir}/local_integration_test.sh ${imageUnderTest} +${dir}/local_shutdown_test.sh ${imageUnderTest} ${dir}/ae_integration_test.sh ${imageUnderTest} ${gaeDeploymentVersion} diff --git a/scripts/integration_test.yaml b/scripts/integration_test.yaml index 511034fc..0968b816 100755 --- a/scripts/integration_test.yaml +++ b/scripts/integration_test.yaml @@ -9,7 +9,6 @@ steps: - name: 'gcr.io/java-runtime-test/integration-test' args: - '--url=${_DEPLOYED_APP_URL}' - - '--skip-custom-logging-tests' # blocked by b/33415496 - '--skip-standard-logging-tests' # blocked by b/33415496 - '--skip-custom-tests' diff --git a/scripts/local_runtimes_common_integration_test.sh b/scripts/local_runtimes_common_integration_test.sh new file mode 100755 index 00000000..953fb3e1 --- /dev/null +++ b/scripts/local_runtimes_common_integration_test.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +# Copyright 2017 Google Inc. All rights reserved. + +# 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. + + +# exit on command failure +set -e + +readonly dir=$(dirname $0) +readonly projectRoot="$dir/.." +readonly testAppDir="$projectRoot/test-application" +readonly deployDir="$testAppDir/target/deploy" + +APP_IMAGE='openjdk-local-integration' +CONTAINER=${APP_IMAGE}-container +OUTPUT_FILE=${CONTAINER}-output.txt +DEPLOYMENT_TOKEN=$(uuidgen) + +readonly imageUnderTest=$1 +if [[ -z "$imageUnderTest" ]]; then + echo "Usage: ${0} " + exit 1 +fi + +if [[ ! -f $HOME/.config/gcloud/application_default_credentials.json ]]; then + # get default application credentials + gcloud auth application-default login +fi + +# build the test app +pushd ${testAppDir} +mvn clean package -Ddeployment.token="${DEPLOYMENT_TOKEN}" -DskipTests --batch-mode +popd + +# build app container locally +pushd $deployDir +export STAGING_IMAGE=$imageUnderTest +envsubst < Dockerfile.in > Dockerfile +echo "Building app container..." +docker build -t $APP_IMAGE . || docker build -t $APP_IMAGE . + +docker rm -f $CONTAINER || echo "Integration-test-app container is not running, ready to start a new instance." + +# run app container locally to test shutdown logging +echo "Starting app container..." +docker run --rm --name $CONTAINER -p 8080 \ + -e "SHUTDOWN_LOGGING_THREAD_DUMP=true" \ + -e "SHUTDOWN_LOGGING_HEAP_INFO=true" \ + -v "$HOME/.config/gcloud/:/root/.config/gcloud" $APP_IMAGE &> $OUTPUT_FILE & + +function waitForOutput() { + found_output='false' + for run in {1..10} + do + grep "$1" $OUTPUT_FILE && found_output='true' && break + sleep 1 + done + + if [ "$found_output" == "false" ]; then + cat $OUTPUT_FILE + echo "did not match '$1' in '$OUTPUT_FILE'" + exit 1 + fi +} + +waitForOutput 'Started Application' + +getPort() { + docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{(index $conf 0).HostPort}}{{end}}' ${CONTAINER} +} + + +PORT=`getPort` + +nslookup `hostname` | grep Address | grep -v 127.0 | awk '{print $2}' > /tmp/myip +MYIP=`cat /tmp/myip` + +DEPLOYED_APP_URL=http://$MYIP:$PORT + +echo app is deployed to $DEPLOYED_APP_URL, making sure it accepts connections + + +until [[ $(curl --silent --fail "$DEPLOYED_APP_URL/deployment.token" | grep "$DEPLOYMENT_TOKEN") ]]; do + sleep 2 +done +popd + +docker rm -f metadata || echo "ready to run local cloud builder" + +# run in cloud container builder +echo "Running integration tests on application that is deployed at $DEPLOYED_APP_URL" +echo `pwd` +container-builder-local \ + --config ${dir}/integration_test.yaml \ + --substitutions "_DEPLOYED_APP_URL=$DEPLOYED_APP_URL" \ + --dryrun=false \ + ${dir} \ No newline at end of file diff --git a/scripts/local_integration_test.sh b/scripts/local_shutdown_test.sh similarity index 100% rename from scripts/local_integration_test.sh rename to scripts/local_shutdown_test.sh diff --git a/test-application/pom.xml b/test-application/pom.xml index a4b4e11c..bb0276df 100644 --- a/test-application/pom.xml +++ b/test-application/pom.xml @@ -35,12 +35,12 @@ com.google.cloud google-cloud-monitoring - 0.20.1-alpha + 0.22.0-alpha - com.google.auth - google-auth-library-credentials - 0.7.0 + com.google.cloud + google-cloud-logging + 1.4.0 diff --git a/test-application/src/main/java/com/google/cloud/runtimes/ExceptionController.java b/test-application/src/main/java/com/google/cloud/runtimes/ExceptionController.java index ec81e63c..5b4bfab7 100644 --- a/test-application/src/main/java/com/google/cloud/runtimes/ExceptionController.java +++ b/test-application/src/main/java/com/google/cloud/runtimes/ExceptionController.java @@ -1,11 +1,11 @@ package com.google.cloud.runtimes; -import static org.springframework.web.bind.annotation.RequestMethod.POST; - import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + @RestController public class ExceptionController { diff --git a/test-application/src/main/java/com/google/cloud/runtimes/HelloController.java b/test-application/src/main/java/com/google/cloud/runtimes/HelloController.java index 55e6276b..d4779bd4 100644 --- a/test-application/src/main/java/com/google/cloud/runtimes/HelloController.java +++ b/test-application/src/main/java/com/google/cloud/runtimes/HelloController.java @@ -1,10 +1,10 @@ package com.google.cloud.runtimes; -import static org.springframework.web.bind.annotation.RequestMethod.GET; - import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + @RestController public class HelloController { diff --git a/test-application/src/main/java/com/google/cloud/runtimes/LoggingTestController.java b/test-application/src/main/java/com/google/cloud/runtimes/LoggingTestController.java new file mode 100644 index 00000000..98166192 --- /dev/null +++ b/test-application/src/main/java/com/google/cloud/runtimes/LoggingTestController.java @@ -0,0 +1,77 @@ +package com.google.cloud.runtimes; + +import com.google.cloud.MonitoredResource; +import com.google.cloud.logging.LogEntry; +import com.google.cloud.logging.Logging; +import com.google.cloud.logging.Payload; +import com.google.cloud.logging.Severity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +@RestController +public class LoggingTestController { + + @Autowired + @Lazy + private Logging logging; + + private static Logger LOG = Logger.getLogger(LoggingTestController.class.getName()); + + public static class LoggingTestRequest { + private String level; + private String log_name; + private String token; + + public void setLevel(String level) { + this.level = level; + } + + public void setLog_name(String log_name) { + this.log_name = log_name; + } + + public void setToken(String token) { + this.token = token; + } + + + @Override + public String toString() { + return "LoggingTestRequest{" + + "level='" + level + '\'' + + ", log_name='" + log_name + '\'' + + ", token='" + token + '\'' + + '}'; + } + } + + @RequestMapping(path = "/logging_custom", method = POST) + public String handleLoggingTestRequest(@RequestBody LoggingTestRequest loggingTestRequest) throws IOException, InterruptedException { + LOG.info(String.valueOf(loggingTestRequest)); + + List entries = new ArrayList<>(); + Payload.StringPayload payload = Payload.StringPayload.of(loggingTestRequest.token); + Severity severity = Severity.valueOf(loggingTestRequest.level); + LogEntry entry = LogEntry.newBuilder(payload) + .setSeverity(severity) + .setLogName(loggingTestRequest.log_name) + .setResource(MonitoredResource.newBuilder("global").build()) + .build(); + entries.add(entry); + logging.write(entries); + LOG.info("Log written to StackDriver: " + entries); + return "OK"; + } + + +} diff --git a/test-application/src/main/java/com/google/cloud/runtimes/MonitoringTestController.java b/test-application/src/main/java/com/google/cloud/runtimes/MonitoringTestController.java index 7bf5b55c..2979b92e 100644 --- a/test-application/src/main/java/com/google/cloud/runtimes/MonitoringTestController.java +++ b/test-application/src/main/java/com/google/cloud/runtimes/MonitoringTestController.java @@ -2,6 +2,8 @@ import com.google.cloud.runtimes.stackdriver.StackDriverMonitoringService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Lazy; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -9,15 +11,19 @@ import java.io.IOException; import java.util.logging.Logger; -import static com.google.cloud.ServiceOptions.getDefaultProjectId; import static org.springframework.web.bind.annotation.RequestMethod.POST; @RestController public class MonitoringTestController { @Autowired + @Lazy private StackDriverMonitoringService stackDriverMonitoringService; + @Autowired + @Qualifier("projectId") + private String projectId; + private static Logger LOG = Logger.getLogger(MonitoringTestController.class.getName()); public static class MonitoringTestRequest { @@ -45,7 +51,7 @@ public String toString() { public String handleMonitoringRequest(@RequestBody MonitoringTestRequest monitoringTestRequest) throws IOException, InterruptedException { LOG.info(String.valueOf(monitoringTestRequest)); - stackDriverMonitoringService.createMetricAndInsertTestToken(getDefaultProjectId(), + stackDriverMonitoringService.createMetricAndInsertTestToken(projectId, monitoringTestRequest.name, monitoringTestRequest.token); diff --git a/test-application/src/main/java/com/google/cloud/runtimes/config/GcpConfiguration.java b/test-application/src/main/java/com/google/cloud/runtimes/config/GcpConfiguration.java new file mode 100644 index 00000000..4e9bec77 --- /dev/null +++ b/test-application/src/main/java/com/google/cloud/runtimes/config/GcpConfiguration.java @@ -0,0 +1,38 @@ +package com.google.cloud.runtimes.config; + +import com.google.cloud.logging.Logging; +import com.google.cloud.logging.LoggingOptions; +import com.google.cloud.monitoring.v3.MetricServiceClient; +import com.google.cloud.monitoring.v3.MetricServiceSettings; +import com.google.cloud.monitoring.v3.stub.MetricServiceStub; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +import java.io.IOException; + +import static com.google.cloud.ServiceOptions.getDefaultProjectId; + +@Configuration +public class GcpConfiguration { + + @Bean + @Lazy + public Logging getLogging() { + LoggingOptions options = LoggingOptions.getDefaultInstance(); + return options.getService(); + } + + @Bean(name = "metricServiceClient") + @Lazy + public MetricServiceClient getMetricServiceClient() throws IOException { + return MetricServiceClient.create(); + } + + @Qualifier("projectId") + @Bean + public String getProjectId() { + return getDefaultProjectId(); + } +} diff --git a/test-application/src/main/java/com/google/cloud/runtimes/stackdriver/StackDriverMonitoringService.java b/test-application/src/main/java/com/google/cloud/runtimes/stackdriver/StackDriverMonitoringService.java index 21e98a77..b104feae 100644 --- a/test-application/src/main/java/com/google/cloud/runtimes/stackdriver/StackDriverMonitoringService.java +++ b/test-application/src/main/java/com/google/cloud/runtimes/stackdriver/StackDriverMonitoringService.java @@ -4,9 +4,16 @@ import com.google.api.MetricDescriptor; import com.google.api.MonitoredResource; import com.google.cloud.monitoring.v3.MetricServiceClient; +import com.google.cloud.monitoring.v3.stub.MetricServiceStub; +import com.google.cloud.runtimes.config.GcpConfiguration; import com.google.monitoring.v3.*; import com.google.protobuf.util.Timestamps; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import java.io.IOException; @@ -15,29 +22,41 @@ @Service public class StackDriverMonitoringService { + @Autowired + private ApplicationContext applicationContext; + @Value("${monitoring.write.retries}") private int maxRetries; private static Logger LOG = Logger.getLogger(StackDriverMonitoringService.class.getName()); + public void createMetricAndInsertTestToken(String projectId, String metricType, long metricValue) throws IOException { - MetricServiceClient metricServiceClient = MetricServiceClient.create(); int retries = maxRetries; while (retries > 0) { try { CreateTimeSeriesRequest timeSeriesRequest = createTimeSeriesRequest(projectId, metricType, metricValue); - metricServiceClient.createTimeSeries(timeSeriesRequest); + getClient().createTimeSeries(timeSeriesRequest); LOG.info("Metric created with timeseries."); return; } catch (Exception e) { LOG.warning("error creating timeseries request, retrying..." + e.getClass() + ": " + e.getMessage()); retries--; if (retries == 0) { - throw new IllegalStateException("Failed to store timeseries after "+ maxRetries +" attempts! Last error:", e); + throw new IllegalStateException("Failed to store timeseries after " + maxRetries + " attempts! Last error:", e); } } } } + /** + * This pairs up with the @Lazy annotation on {@link GcpConfiguration#getMetricServiceClient()}. + * As MetricServiceClient methods are all `final` and that breaks the "@Autowire @Lazy" combination, + * this is the only way to wire this bean lazily. + */ + private MetricServiceClient getClient() { + return (MetricServiceClient) applicationContext.getBean("metricServiceClient"); + } + private CreateTimeSeriesRequest createTimeSeriesRequest(String projectId, String metricType, Long metricValue) { LOG.info("Creating time series to insert token: " + metricValue); ProjectName projectName = ProjectName.create(projectId); @@ -66,4 +85,5 @@ private CreateTimeSeriesRequest createTimeSeriesRequest(String projectId, String .build(); } + }