All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.apphosting.vmruntime.VmApiProxyEnvironment Maven / Gradle / Ivy

The newest version!
/**
 * 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 com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.ApiProxyException;
import com.google.apphosting.api.ApiProxy.LogRecord;
import com.google.apphosting.runtime.timer.Timer;
import com.google.apphosting.utils.http.HttpRequest;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * Implements the ApiProxy environment when running in a Google Compute Engine VM.
 * 

* Supports instantiation within a request as well as outside the context of a request. *

* Instances should be registered using ApiProxy.setEnvironmentForCurrentThread(Environment). */ public class VmApiProxyEnvironment implements ApiProxy.Environment { public static final String PROJECT_ATTRIBUTE = "attributes/gae_project"; static final String LONG_APP_ID_KEY = "GAE_LONG_APP_ID"; public static final String PARTITION_ATTRIBUTE = "attributes/gae_partition"; static final String PARTITION_KEY = "GAE_PARTITION"; public static final String BACKEND_ATTRIBUTE = "attributes/gae_backend_name"; static final String MODULE_NAME_KEY = "GAE_MODULE_NAME"; public static final String VERSION_ATTRIBUTE = "attributes/gae_backend_version"; static final String VERSION_KEY = "GAE_MODULE_VERSION"; public static final String INSTANCE_ATTRIBUTE = "attributes/gae_backend_instance"; static final String INSTANCE_KEY = "GAE_MODULE_INSTANCE"; static final String MINOR_VERSION_KEY = "GAE_MINOR_VERSION"; public static final String TICKET_HEADER = "X-AppEngine-Api-Ticket"; public static final String EMAIL_HEADER = "X-AppEngine-User-Email"; public static final String IS_ADMIN_HEADER = "X-AppEngine-User-Is-Admin"; public static final String AUTH_DOMAIN_HEADER = "X-AppEngine-Auth-Domain"; static final String BACKEND_ID_KEY = "com.google.appengine.backend.id"; static final String INSTANCE_ID_KEY = "com.google.appengine.instance.id"; static final String REQUEST_THREAD_FACTORY_ATTR = "com.google.appengine.api.ThreadManager.REQUEST_THREAD_FACTORY"; static final String BACKGROUND_THREAD_FACTORY_ATTR = "com.google.appengine.api.ThreadManager.BACKGROUND_THREAD_FACTORY"; static final String IS_FEDERATED_USER_KEY = "com.google.appengine.api.users.UserService.is_federated_user"; static final String IS_TRUSTED_IP_KEY = "com.google.appengine.runtime.is_trusted_ip"; static final String IS_TRUSTED_IP_HEADER = "X-AppEngine-Trusted-IP-Request"; private static final long DEFAULT_FLUSH_APP_LOGS_EVERY_BYTE_COUNT = 1024 * 1024L; private static final int MAX_LOG_FLUSH_SECONDS = 60; private static final int DEFAULT_MAX_LOG_LINE_SIZE = 8 * 1024; static final int MAX_CONCURRENT_API_CALLS = 100; static final int MAX_PENDING_API_CALLS = 1000; /** * Mapping from HTTP header keys to attribute keys. */ static enum AttributeMapping { USER_ID( "X-AppEngine-User-Id", "com.google.appengine.api.users.UserService.user_id_key", "", false), USER_ORGANIZATION( "X-AppEngine-User-Organization", "com.google.appengine.api.users.UserService.user_organization", "", false), FEDERATED_IDENTITY( "X-AppEngine-Federated-Identity", "com.google.appengine.api.users.UserService.federated_identity", "", false), FEDERATED_PROVIDER( "X-AppEngine-Federated-Provider", "com.google.appengine.api.users.UserService.federated_authority", "", false), DATACENTER( "X-AppEngine-Datacenter", "com.google.apphosting.api.ApiProxy.datacenter", "", false), REQUEST_ID_HASH( "X-AppEngine-Request-Id-Hash", "com.google.apphosting.api.ApiProxy.request_id_hash", null, false), REQUEST_LOG_ID( "X-AppEngine-Request-Log-Id", "com.google.appengine.runtime.request_log_id", null, false), DAPPER_ID("X-Google-DapperTraceInfo", "com.google.appengine.runtime.dapper_id", null, false), DEFAULT_VERSION_HOSTNAME( "X-AppEngine-Default-Version-Hostname", "com.google.appengine.runtime.default_version_hostname", null, false), DEFAULT_NAMESPACE_HEADER( "X-AppEngine-Default-Namespace", "com.google.appengine.api.NamespaceManager.appsNamespace", null, false), CURRENT_NAMESPACE_HEADER( "X-AppEngine-Current-Namespace", "com.google.appengine.api.NamespaceManager.currentNamespace", null, false), LOAS_PEER_USERNAME( "X-AppEngine-LOAS-Peer-Username", "com.google.net.base.peer.loas_peer_username", "", true), GAIA_ID( "X-AppEngine-Gaia-Id", "com.google.appengine.runtime.gaia_id", "", true), GAIA_AUTHUSER( "X-AppEngine-Gaia-Authuser", "com.google.appengine.runtime.gaia_authuser", "", true), GAIA_SESSION( "X-AppEngine-Gaia-Session", "com.google.appengine.runtime.gaia_session", "", true), APPSERVER_DATACENTER( "X-AppEngine-Appserver-Datacenter", "com.google.appengine.runtime.appserver_datacenter", "", true), APPSERVER_TASK_BNS( "X-AppEngine-Appserver-Task-Bns", "com.google.appengine.runtime.appserver_task_bns", "", true); String headerKey; String attributeKey; Object defaultValue; private boolean trustedAppOnly; /** * Creates a mapping between an incoming request header and the thread local request attribute * corresponding to that header. * * @param headerKey The HTTP header key. * @param attributeKey The attribute key. * @param defaultValue The default value to set if the header is missing, or null if no * attribute should be set when the header is missing. * @param trustedAppOnly If true the attribute should only be set for trusted apps. */ private AttributeMapping( String headerKey, String attributeKey, Object defaultValue, boolean trustedAppOnly) { this.headerKey = headerKey; this.attributeKey = attributeKey; this.defaultValue = defaultValue; this.trustedAppOnly = trustedAppOnly; } } /** * Helper method to use during the transition from metadata to environment variables. * * @param environmentMap the * @param envKey The name of the environment variable to check first. * @param metadataPath The path of the metadata server entry to use as fallback. * @param cache The metadata server cache. * @return If set the environment variable corresponding to envKey, the metadata entry otherwise. */ private static String getEnvOrMetadata(Map environmentMap, VmMetadataCache cache, String envKey, String metadataPath) { String envValue = environmentMap.get(envKey); return envValue != null ? envValue : cache.getMetadata(metadataPath); } public static VmApiProxyEnvironment createFromHeaders(Map envMap, VmMetadataCache cache, HttpRequest request, String server, Timer wallTimer, Long millisUntilSoftDeadline) { String longAppId = getEnvOrMetadata(envMap, cache, LONG_APP_ID_KEY, PROJECT_ATTRIBUTE); String partition = getEnvOrMetadata(envMap, cache, PARTITION_KEY, PARTITION_ATTRIBUTE); String module = getEnvOrMetadata(envMap, cache, MODULE_NAME_KEY, BACKEND_ATTRIBUTE); String majorVersion = getEnvOrMetadata(envMap, cache, VERSION_KEY, VERSION_ATTRIBUTE); String minorVersion = envMap.get(MINOR_VERSION_KEY); String instance = getEnvOrMetadata(envMap, cache, INSTANCE_KEY, INSTANCE_ATTRIBUTE); String ticket = request.getHeader(TICKET_HEADER); String email = request.getHeader(EMAIL_HEADER); boolean admin = false; String value = request.getHeader(IS_ADMIN_HEADER); if (value != null && !value.trim().isEmpty()) { try { admin = Integer.parseInt(value.trim()) != 0; } catch (NumberFormatException e) { throw new IllegalArgumentException(e.getMessage(), e); } } String authDomain = request.getHeader(AUTH_DOMAIN_HEADER); boolean trustedApp = request.getHeader(IS_TRUSTED_IP_HEADER) != null; Map attributes = new HashMap<>(); for (AttributeMapping mapping : AttributeMapping.values()) { if (mapping.trustedAppOnly && !trustedApp) { continue; } String headerValue = request.getHeader(mapping.headerKey); if (headerValue != null) { attributes.put(mapping.attributeKey, headerValue); } else if (mapping.defaultValue != null) { attributes.put(mapping.attributeKey, mapping.defaultValue); } } boolean federatedId = request.getHeader(AttributeMapping.FEDERATED_IDENTITY.headerKey) != null; attributes.put(IS_FEDERATED_USER_KEY, federatedId); attributes.put(BACKEND_ID_KEY, module); attributes.put(INSTANCE_ID_KEY, instance); if (trustedApp) { boolean trustedIp = "1".equals(request.getHeader(IS_TRUSTED_IP_HEADER)); attributes.put(IS_TRUSTED_IP_KEY, trustedIp); } VmApiProxyEnvironment requestEnvironment = new VmApiProxyEnvironment(server, ticket, longAppId, partition, module, majorVersion, minorVersion, instance, email, admin, authDomain, wallTimer, millisUntilSoftDeadline, attributes); attributes.put(REQUEST_THREAD_FACTORY_ATTR, new VmRequestThreadFactory(requestEnvironment)); attributes.put(BACKGROUND_THREAD_FACTORY_ATTR, Executors.defaultThreadFactory()); return requestEnvironment; } private final String server; private final String ticket; private final String appId; private final String module; private final String versionId; private final String email; private final boolean admin; private final String authDomain; private final Map attributes; private final Timer wallTimer; private final Long millisUntilSoftDeadline; private final VmAppLogsWriter appLogsWriter; final Semaphore pendingApiCallSemaphore; final Semaphore runningApiCallSemaphore; /** * Constructs a VM AppEngine API environment. * * @param server the host:port address of the VM's HTTP proxy server. * @param ticket the request ticket (if null the default one will be computed). * @param appId the application ID (required if ticket is null). * @param partition the partition name. * @param module the module name (required if ticket is null). * @param majorVersion the major application version (required if ticket is null). * @param minorVersion the minor application version. * @param instance the VM instance ID (required if ticket is null). * @param email the user's e-mail address (may be null). * @param admin true if the user is an administrator. * @param authDomain the user's authentication domain (may be null). * @param wallTimer optional wall clock timer for the current request (required for deadline). * @param millisUntilSoftDeadline optional soft deadline in milliseconds relative to 'wallTimer'. * @param attributes map containing any attributes set on this environment. */ private VmApiProxyEnvironment( String server, String ticket, String appId, String partition, String module, String majorVersion, String minorVersion, String instance, String email, boolean admin, String authDomain, Timer wallTimer, Long millisUntilSoftDeadline, Map attributes) { if (server == null || server.isEmpty()) { throw new IllegalArgumentException("proxy server host:port must be specified"); } if (millisUntilSoftDeadline != null && wallTimer == null) { throw new IllegalArgumentException("wallTimer required when setting millisUntilSoftDeadline"); } if (ticket == null || ticket.isEmpty()) { if ((appId == null || appId.isEmpty()) || (module == null || module.isEmpty()) || (majorVersion == null || majorVersion.isEmpty()) || (instance == null || instance.isEmpty())) { throw new IllegalArgumentException( "When ticket == null the following must be specified: appId=" + appId + ", module=" + module + ", version=" + majorVersion + "instance=" + instance); } String escapedAppId = appId.replace(':', '_').replace('.', '_'); this.ticket = escapedAppId + '/' + module + '.' + majorVersion + "." + instance; } else { this.ticket = ticket; } this.server = server; if (appId == null) { this.appId = ""; } else { this.appId = partition + "~" + appId; } this.module = module == null ? "default" : module; this.versionId = String.format("%s.%s", majorVersion == null ? "" : majorVersion, minorVersion == null ? "" : minorVersion); this.email = email == null ? "" : email; this.admin = admin; this.authDomain = authDomain == null ? "" : authDomain; this.wallTimer = wallTimer; this.millisUntilSoftDeadline = millisUntilSoftDeadline; this.attributes = Collections.synchronizedMap(attributes); this.appLogsWriter = new VmAppLogsWriter( new LinkedList<>(), DEFAULT_FLUSH_APP_LOGS_EVERY_BYTE_COUNT, DEFAULT_MAX_LOG_LINE_SIZE, MAX_LOG_FLUSH_SECONDS); this.pendingApiCallSemaphore = new Semaphore(MAX_PENDING_API_CALLS); this.runningApiCallSemaphore = new Semaphore(MAX_CONCURRENT_API_CALLS); } public void addLogRecord(LogRecord record) { appLogsWriter.addLogRecordAndMaybeFlush(record); } public void flushLogs() { appLogsWriter.flushAndWait(); } public String getServer() { return server; } public String getTicket() { return ticket; } @Override public String getAppId() { return appId; } @Override public String getModuleId() { return module; } @Override public String getVersionId() { return versionId; } @Override public String getEmail() { return email; } @Override public boolean isLoggedIn() { return getEmail() != null && !getEmail().trim().isEmpty(); } @Override public boolean isAdmin() { return admin; } @Override public String getAuthDomain() { return authDomain; } @Deprecated @Override public String getRequestNamespace() { Object currentNamespace = attributes.get(AttributeMapping.CURRENT_NAMESPACE_HEADER.attributeKey); if (currentNamespace instanceof String) { return (String) currentNamespace; } return ""; } @Override public Map getAttributes() { return attributes; } @Override public long getRemainingMillis() { if (millisUntilSoftDeadline == null) { return Long.MAX_VALUE; } return millisUntilSoftDeadline - (wallTimer.getNanoseconds() / 1000000L); } /** * Notifies the environment that an API call was queued up. * * @throws ApiProxyException */ public void aSyncApiCallAdded(long maxWaitMs) throws ApiProxyException { try { if (pendingApiCallSemaphore.tryAcquire(maxWaitMs, TimeUnit.MILLISECONDS)) { return; } throw new ApiProxyException("Timed out while acquiring a pending API call semaphore."); } catch (InterruptedException e) { throw new ApiProxyException( "Thread interrupted while acquiring a pending API call semaphore."); } } /** * Notifies the environment that an API call was started. * * @param releasePendingCall If true a pending call semaphore will be released (required if this * API call was requested asynchronously). * @throws ApiProxyException If the thread was interrupted while waiting for a semaphore. */ public void apiCallStarted(long maxWaitMs, boolean releasePendingCall) throws ApiProxyException { try { if (runningApiCallSemaphore.tryAcquire(maxWaitMs, TimeUnit.MILLISECONDS)) { return; } throw new ApiProxyException("Timed out while acquiring an API call semaphore."); } catch (InterruptedException e) { throw new ApiProxyException("Thread interrupted while acquiring an API call semaphore."); } finally { if (releasePendingCall) { pendingApiCallSemaphore.release(); } } } /** * Notifies the environment that an API call completed. */ public void apiCallCompleted() { runningApiCallSemaphore.release(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy