/** * Copyright 2012 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; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; /** * A class to retrieve and cache the meta-data of a VM running in Google's Compute Engine. * */ public class VmMetadataCache { private static final Logger logger = Logger.getLogger(VmMetadataCache.class.getCanonicalName()); /** The meta-data server's URL prefix. */ public static final String DEFAULT_META_DATA_SERVER = "metadata"; public static final String META_DATA_PATTERN = "http://%s/computeMetadata/v1/instance/%s"; /** Maps paths to their cached values (null if a previous retrieval attempt failed). */ private final Map<String, String> cache; /** Timeout in milliseconds to retrieve data from the server. */ private static final int TIMEOUT_MILLIS = 120 * 1000; public VmMetadataCache() { cache = new HashMap<String, String>(); } /** * Returns the value of the VM's meta-data attribute, or null if retrieval has failed. * * @param path the meta-data attribute to be retrieved (e.g. "image", "attributes/sshKeys"). * @return the attribute's string value or null if retrieval has failed. */ public String getMetadata(String path) { synchronized (cache) { if (cache.containsKey(path)) { return cache.get(path); } } String value = null; try { // It is safe to concurrently retrieve the same server path. value = getMetadataFromServer(path); // We cache missing attributes (404) as null values. synchronized (cache) { cache.put(path, value); } } catch (IOException e) { // Don't cache the value if we have failed to connect or transfer. logger.info("Meta-data '" + path + "' path retrieval error: " + e.getMessage()); } return value; } /** * Clears all cached meta-data values. */ public void clear() { synchronized (cache) { cache.clear(); } } /** * Returns an HTTP URL connection to read the value of the specified attribute. * * May be overridden in tests. * * @param path the meta-data attribute to be retrieved (e.g. "image", "attributes/sshKeys"). * @return the HTTP URL connection object. * @throws IOException if the connection could not be opened. */ 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; } /** * Retrieves the specified path from the VM's meta-data server. * * @param path a path in the meta-data server (e.g. "image"). * @return the meta-data's string value or null if the attribute is not found. * @throws IOException if the connection to the meta-data server fails, or refused. */ 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(); } } } }