Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

adding custom logging test + local runtimes-common integration test #134

Merged
merged 11 commits into from
Sep 7, 2017
1 change: 0 additions & 1 deletion scripts/integration_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'

124 changes: 94 additions & 30 deletions scripts/local_integration_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,72 @@
# See the License for the specific language governing permissions and
# limitations under the License.


# exit on command failure
set -e

function usage() {
echo $1
echo "Usage: ${0} <image_under_test> [OPTIONS]"
echo "Options:"
echo "-v: verbose (displays logs for container and test driver)"
echo "-g: test integration with GCP services (requires GOOGLE_APPLICATION_CREDENTIALS)"
exit 1
}

function getPort() {
docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{(index $conf 0).HostPort}}{{end}}' ${CONTAINER}
}


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
}


readonly dir=$(dirname $0)
readonly projectRoot="$dir/.."
readonly testAppDir="$projectRoot/test-application"
readonly deployDir="$testAppDir/target/deploy"

APP_IMAGE='openjdk-local-integration'
TEST_DRIVER_IMAGE='gcr.io/java-runtime-test/integration-test'
CONTAINER=${APP_IMAGE}-container
OUTPUT_FILE=${CONTAINER}-output.txt
DEPLOYMENT_TOKEN=$(uuidgen)

readonly imageUnderTest=$1
if [[ -z "$imageUnderTest" ]]; then
echo "Usage: ${0} <image_under_test>"
exit 1
usage "Error: missing image!"
fi

shift

while getopts ":gv" opt; do
case $opt in
v)
VERBOSE="true"
DRIVER_OPTS="--verbose"
;;
g)
WITH_GCP="true"
;;
\?)
usage "Invalid option: -$OPTARG"
;;
esac
done

# build the test app
pushd ${testAppDir}
mvn clean package -Ddeployment.token="${DEPLOYMENT_TOKEN}" -DskipTests --batch-mode
Expand All @@ -44,48 +90,66 @@ pushd $deployDir
export STAGING_IMAGE=$imageUnderTest
envsubst < Dockerfile.in > Dockerfile
echo "Building app container..."
docker build -t $APP_IMAGE . || gcloud docker -- build -t $APP_IMAGE .
docker build -t $APP_IMAGE . || gcloud docker -- build -t $APP_IMAGE . || usage "Error building test-app image from base image \"${imageUnderTest}\", please make sure it exists!"


if [[ "$WITH_GCP" ]]; then
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it makes sense to me to add these integration tests to the local_integration_test.sh script. I think we originally added this script in order to test things that were outside the scope of the runtimes_common integraion testing framework (namely, container shutdown hooks).

I think if we wanted to make it easier to run the runtimes_common tests, a better way to do it would be to just simulate a cloudbuild execution locally, using the container-builder-local emulator that was just released as part of the Cloud SDK (see release notes).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oooo, I'll test out this container-build-local, thanks for the suggestion!
I created a separate script first btw, but then I figured that they were so similar that I integrated it back into the local_integration_test.sh. It's really handy to have as part of the workflow. But if container-build-local can do the magic, I'll have a look at that as that could remove the duplication around the arguments of the runtimes-common driver for example (one place here, the other place is the yaml file).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will do this in #126 though, and then separate it out from this pull request.

echo "--------"
echo "Starting test in GCP mode!"
echo "--------"

if [[ -z ${GOOGLE_APPLICATION_CREDENTIALS} ]]; then
usage "Error: In GCP mode GOOGLE_APPLICATION_CREDENTIALS must be set."
fi
# we setup the container for GCP relying on the environment
GCP_CONTAINER_OPTS="-v ${GOOGLE_APPLICATION_CREDENTIALS}:/gcp_creds.json -e GOOGLE_APPLICATION_CREDENTIALS=/gcp_creds.json"
APP_CMD=
DRIVER_OPTS="$DRIVER_OPTS --skip-standard-logging-tests \
--skip-custom-tests"
else
echo "--------"
echo "Starting test in MOCK-GCP mode!"
echo "--------"
# mock-gcp profile, the container is started as mock-gcp
GCP_CONTAINER_OPTS=
APP_CMD='java -Dspring.profiles.active=mock-gcp -jar app.jar'
DRIVER_OPTS="$DRIVER_OPTS --skip-monitoring-tests \
--skip-custom-logging-tests \
--skip-standard-logging-tests \
--skip-custom-tests"
fi

docker rm -f $CONTAINER &>/dev/null || echo "Integration-test-app container is not running, ready to start a new instance."

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" $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
}
docker run --rm --name ${CONTAINER} ${GCP_CONTAINER_OPTS} -p 8080 \
-e "SHUTDOWN_LOGGING_THREAD_DUMP=true" \
-e "SHUTDOWN_LOGGING_HEAP_INFO=true" \
${APP_IMAGE} ${APP_CMD} &> ${OUTPUT_FILE} &

waitForOutput 'Started Application'

getPort() {
docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{(index $conf 0).HostPort}}{{end}}' ${CONTAINER}
}


PORT=`getPort`

echo port is $PORT
DEPLOYED_APP_URL=http://localhost:${PORT}

echo "App deployed to URL: $DEPLOYED_APP_URL, making sure it accepts connections..."

until [[ $(curl --silent --fail "http://localhost:$PORT/deployment.token" | grep "$DEPLOYMENT_TOKEN") ]]; do
until [[ $(curl --silent --fail "${DEPLOYED_APP_URL}/deployment.token" | grep "${DEPLOYMENT_TOKEN}") ]]; do
sleep 2
done

DRIVER_OPTS="${DRIVER_OPTS} --url=${DEPLOYED_APP_URL}"

[[ "${VERBOSE}" ]] && docker logs -f ${CONTAINER} &

docker run --rm ${GCP_CONTAINER_OPTS} --net=host ${TEST_DRIVER_IMAGE} ${DRIVER_OPTS}

docker stop $CONTAINER
docker stop ${CONTAINER}

docker rmi $APP_IMAGE
docker rmi -f ${APP_IMAGE}

echo 'verify thread dump'
waitForOutput 'Full thread dump OpenJDK 64-Bit Server VM'
Expand Down
11 changes: 11 additions & 0 deletions test-application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<deployment.token>default_deployment_token</deployment.token>
<spring.profile>gcp</spring.profile>
</properties>

<dependencies>
Expand All @@ -42,6 +43,16 @@
<artifactId>google-auth-library-credentials</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-logging</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.8.47</version>
Copy link
Member

Choose a reason for hiding this comment

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

<scope>test</scope>

</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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 com.google.cloud.runtimes.stackdriver.StackDriverMonitoringService;
Copy link
Member

Choose a reason for hiding this comment

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

Unused import

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sun.util.logging.resources.logging;
Copy link
Member

Choose a reason for hiding this comment

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

Unusued import


import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import static com.google.cloud.ServiceOptions.getDefaultProjectId;
import static org.springframework.web.bind.annotation.RequestMethod.POST;

@RestController
public class LoggingTestController {

@Autowired
private Logging logging;
@Autowired
@Qualifier("projectId")
private String projectId;

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 handleMonitoringRequest(@RequestBody LoggingTestRequest loggingTestRequest) throws IOException, InterruptedException {
Copy link
Member

Choose a reason for hiding this comment

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

handleLoggingRequest?

LOG.info(String.valueOf(loggingTestRequest));

List<LogEntry> 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);
Copy link
Member

Choose a reason for hiding this comment

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

Instead of using the Logging client lib directly, we should be able to use the JUL appender. Can you also look at #72 that has been put on the back-burner, and reconcile it with what you're doing in this PR? Ideally, we would like to close out #72 as well, but I think there are some unresolved complications there with setting up JUL.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that the Custom Logging Test should use the client library and make an explicit call to Stackdriver Logging (by creating only one LogEntry), whereas the Standard Logging Test should redirect the output of JUL to Stackdriver.

Copy link
Contributor Author

@balopat balopat Aug 22, 2017

Choose a reason for hiding this comment

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

I agree with @cassand - I was planning to use JUL for standard logging tests and Logging library directly for custom, just so that we have both covered.
However, we are free to decide what we want to do, as we can setup custom logging with JUL too.

If you're okay with that, I will review #72 in the context of the Standard Logging test (I just added a new, separate issue for it: #139)

Copy link
Member

Choose a reason for hiding this comment

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

I'm OK with Damien's suggestion.

LOG.info("Log written to StackDriver: " + entries);
return "OK";
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import com.google.cloud.runtimes.stackdriver.StackDriverMonitoringService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.logging.Logger;

import static com.google.cloud.ServiceOptions.getDefaultProjectId;
import static org.springframework.web.bind.annotation.RequestMethod.POST;

@RestController
Expand All @@ -18,6 +18,10 @@ public class MonitoringTestController {
@Autowired
private StackDriverMonitoringService stackDriverMonitoringService;

@Autowired
@Qualifier("projectId")
private String projectId;

private static Logger LOG = Logger.getLogger(MonitoringTestController.class.getName());

public static class MonitoringTestRequest {
Expand Down Expand Up @@ -45,7 +49,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);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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 org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import java.io.IOException;

import static com.google.cloud.ServiceOptions.getDefaultProjectId;

@Configuration
@Profile("gcp")
public class GCPConfiguration {
Copy link
Member

Choose a reason for hiding this comment

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

GcpConfiguration


@Bean
public Logging getLogging() {
LoggingOptions options = LoggingOptions.getDefaultInstance();
return options.getService();
}

@Bean
public MetricServiceClient getMetricServiceClient() throws IOException {
return MetricServiceClient.create();
}

@Qualifier("projectId")
@Bean
public String getProjectId() {
return getDefaultProjectId();
}
}
Loading