/**
* Copyright 2015 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.
*/
package com.google.apphosting.vmruntime.jetty9;
import com.google.apphosting.vmruntime.VmApiProxyEnvironment;
import static com.google.apphosting.vmruntime.VmMetadataCache.DEFAULT_META_DATA_SERVER;
import static com.google.apphosting.vmruntime.VmMetadataCache.META_DATA_PATTERN;
import junit.framework.TestCase;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletResponse;
import org.junit.Ignore;
/**
* Base test class for the Java VmRuntime.
*
* Test methods that are Jetty version independent should be implemented in this class.
*/
@Ignore
public class VmRuntimeTestBase extends TestCase {
protected static final Logger logger = Logger.getLogger(VmRuntimeTestBase.class.getName());
private static final String HOME_FOLDER = System.getProperty("HOME_FOLDER", "jetty_home");
public static final String JETTY_HOME_PATTERN = ""//TestUtil.getRunfilesDir()
+ "com/google/apphosting/vmruntime/jetty9/" + HOME_FOLDER;
// Wait at the most 30 seconds for Jetty to come up.
private static final int JETTY_START_DELAY = 45;
public static final String PROJECT = "google.com:test-project";
public static final String PARTITION = "testpartition";
public static final String VERSION = "testversion";
public static final String BACKEND = "testbackend";
public static final String INSTANCE = "frontend1";
public static final String AFFINITY = "true";
public static final String APPENGINE_HOSTNAME = "testhostname";
public int port;
public int externalPort;
public String appengineWebXml = "WEB-INF/appengine-web.xml";
TestMetadataServer metadataServer;
JettyRunner runner;
/**
* Returns the host:port the server is listening on externally. For VM Runtimes the local server
* is listening on port 8080, but the requests can come in on either 80 or 443 depending on if
* they are sent over https. For Jetty9 we are correcting the request port to account for this.
*
* @return The external port the request was sent to.
*/
protected String getServerHost() {
return "localhost" + (externalPort == 80 ? "" : ":" + externalPort);
}
/**
* Creates a Localhost URL for accessing the Jetty instance under test.
*
* @param servletPath The path to request, for example "/test".
* @return An URL object pointing at the local Jetty instance.
* @throws MalformedURLException
*/
protected URL createUrl(String servletPath) throws MalformedURLException {
return new URL("http://localhost:" + port + servletPath);
}
/**
* Creates a URL for accessing the Jetty instance under test, accessed by host ip.
*
* @param servletPath The path to request, for example "/test".
* @return An URL object pointing at the local Jetty instance.
* @throws MalformedURLException
* @throws UnknownHostException
*/
protected URL createUrlForHostIP(String servletPath)
throws MalformedURLException, UnknownHostException {
return new URL(
"http://" + InetAddress.getLocalHost().getHostAddress() + ":" + port + servletPath);
}
/**
* Convenience method for fetching a URL. Fails the test if the HTTP response code is not "OK".
*
* @param url The URL to fetch.
* @return A string array of the lines in the response.
* @throws IOException
*/
protected String[] fetchUrl(URL url) throws IOException {
return fetchUrlConnection((HttpURLConnection) url.openConnection());
}
/**
* Convenience method for fetching from a HttpURLConnection. This allows headers to be set.
* @param connection the connection to use
* @return A string array of the lines in the response.
* @throws IOException
*/
protected String[] fetchUrlConnection(HttpURLConnection connection) throws IOException {
connection.connect();
int code = connection.getResponseCode();
assertEquals(HttpServletResponse.SC_OK, code);
ArrayList<String> lines = new ArrayList<>();
String line;
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
while ((line = in.readLine()) != null) {
lines.add(line);
}
return lines.toArray(new String[lines.size()]);
}
protected String getUseMvmAgent() {
return "false";
}
/**
* Stub out the metadata cache so that any requests for metadata will be mocked locally and not
* served from the metadata server (which only is available when running in an actual VM).
*/
private void stubMetadataRequests() {
int metadataPort = me.alexpanov.net.FreePortFinder.findFreeLocalPort();
System.setProperty("metadata_server", "127.0.0.1:" + metadataPort);
metadataServer = new TestMetadataServer(metadataPort);
metadataServer.addMetadata("STOP", "STOP");
metadataServer.addMetadata(VmApiProxyEnvironment.PROJECT_ATTRIBUTE, PROJECT);
metadataServer.addMetadata(VmApiProxyEnvironment.PARTITION_ATTRIBUTE, PARTITION);
metadataServer.addMetadata(VmApiProxyEnvironment.BACKEND_ATTRIBUTE, BACKEND);
metadataServer.addMetadata(VmApiProxyEnvironment.VERSION_ATTRIBUTE, VERSION);
metadataServer.addMetadata(VmApiProxyEnvironment.INSTANCE_ATTRIBUTE, INSTANCE);
metadataServer.addMetadata(VmApiProxyEnvironment.AFFINITY_ATTRIBUTE, AFFINITY);
metadataServer.addMetadata(
VmApiProxyEnvironment.APPENGINE_HOSTNAME_ATTRIBUTE, APPENGINE_HOSTNAME);
metadataServer.addMetadata(
VmApiProxyEnvironment.USE_MVM_AGENT_ATTRIBUTE, getUseMvmAgent());
Thread metadataThread = new Thread(metadataServer);
metadataThread.setName("Metadata server");
metadataThread.setDaemon(true);
metadataThread.start();
}
@Override
protected void setUp() throws Exception {
super.setUp();
port = me.alexpanov.net.FreePortFinder.findFreeLocalPort();
externalPort = port;
stubMetadataRequests();
// Start jetty using the Runnable configured by the sub class.
runner = new JettyRunner(port);
runner.setAppEngineWebXml(appengineWebXml);
Thread jettyRunnerThread = new Thread(runner);
jettyRunnerThread.setName("JettyRunnerThread");
jettyRunnerThread.setDaemon(true);
jettyRunnerThread.start();
runner.waitForStarted(JETTY_START_DELAY, TimeUnit.SECONDS);
}
@Override
protected void tearDown() throws Exception {
runner.stop();
getMetadataFromServer("STOP");
super.tearDown();
Thread.sleep(50);
}
protected HttpURLConnection openConnection(String path) throws IOException {
String server = System.getProperty("metadata_server", DEFAULT_META_DATA_SERVER);
URL url = new URL(String.format(META_DATA_PATTERN, server, path));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Metadata-Flavor", "Google");
return conn;
}
/** Timeout in milliseconds to retrieve data from the server. */
private static final int TIMEOUT_MILLIS = 120 * 1000;
protected String getMetadataFromServer(String path) throws IOException {
BufferedReader reader = null;
HttpURLConnection connection = null;
try {
connection = openConnection(path);
connection.setConnectTimeout(TIMEOUT_MILLIS);
connection.setReadTimeout(TIMEOUT_MILLIS);
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuffer result = new StringBuffer();
char[] buffer = new char[4096];
int read;
while ((read = reader.read(buffer)) != -1) {
result.append(buffer, 0, read);
}
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
return result.toString().trim();
} else if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return null;
}
throw new IOException("Meta-data request for '" + path + "' failed with error: "
+ connection.getResponseMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.info("Error closing connection for " + path + ": " + e.getMessage());
}
}
if (connection != null) {
connection.disconnect();
}
}
}
}