1.9.5
[gae.git] / java / src / main / com / google / appengine / tools / development / Modules.java
blob3613bdf2a5fe9eb47dac1f3225902fb3f97cec33
1 package com.google.appengine.tools.development;
3 import com.google.appengine.api.modules.ModulesServicePb.ModulesServiceError;
4 import com.google.apphosting.api.ApiProxy;
5 import com.google.apphosting.api.ApiProxy.ApplicationException;
6 import com.google.apphosting.utils.config.AppEngineWebXml;
7 import com.google.apphosting.utils.config.AppEngineWebXml.ManualScaling;
8 import com.google.common.collect.ImmutableList;
9 import com.google.common.collect.ImmutableMap;
11 import java.io.File;
12 import java.io.IOException;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.concurrent.TimeUnit;
16 import java.util.concurrent.atomic.AtomicReference;
17 import java.util.concurrent.locks.Lock;
18 import java.util.concurrent.locks.ReentrantLock;
19 import java.util.logging.Level;
20 import java.util.logging.Logger;
22 import javax.servlet.ServletException;
23 import javax.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpServletResponse;
26 /**
27 * Manager for {@link DevAppServer} servers.
30 public class Modules implements ModulesController, ModulesFilterHelper {
32 private static final AtomicReference<Modules> instance = new AtomicReference<Modules>();
33 private static final Logger LOGGER = Logger.getLogger(Modules.class.getName());
35 private final List<Module> modules;
36 private final Map<String, Module> moduleNameToModuleMap;
38 private final Lock dynamicConfigurationLock = new ReentrantLock();
39 private static final int DYNAMIC_CONFIGURATION_TIMEOUT_SECONDS = 2;
41 public static Modules createModules(
42 ApplicationConfigurationManager applicationConfigurationManager,
43 String serverInfo, File externalResourceDir, String address, DevAppServer devAppServer) {
44 ImmutableList.Builder<Module> builder = ImmutableList.builder();
45 for (ApplicationConfigurationManager.ModuleConfigurationHandle moduleConfigurationHandle :
46 applicationConfigurationManager.getModuleConfigurationHandles()) {
47 AppEngineWebXml appEngineWebXml =
48 moduleConfigurationHandle.getModule().getAppEngineWebXml();
49 Module module = null;
50 if (!appEngineWebXml.getBasicScaling().isEmpty()) {
51 module = new BasicModule(moduleConfigurationHandle, serverInfo, address, devAppServer,
52 appEngineWebXml);
53 } else if (!appEngineWebXml.getManualScaling().isEmpty()) {
54 module = new ManualModule(moduleConfigurationHandle, serverInfo, address, devAppServer,
55 appEngineWebXml);
56 } else {
57 module = new AutomaticModule(moduleConfigurationHandle, serverInfo, externalResourceDir,
58 address, devAppServer);
60 builder.add(module);
62 externalResourceDir = null;
64 instance.set(new Modules(builder.build()));
65 return instance.get();
68 public static Modules getInstance() {
69 return instance.get();
72 public void shutdown() throws Exception {
73 for (Module module : modules) {
74 module.shutdown();
78 public void configure(Map<String, Object>containerConfigProperties) throws Exception {
79 for (Module module : modules) {
80 module.configure(containerConfigProperties);
84 public void setApiProxyDelegate(ApiProxy.Delegate<?> apiProxyDelegate) {
85 for (Module module : modules) {
86 module.setApiProxyDelegate(apiProxyDelegate);
90 public void createConnections() throws Exception {
91 for (Module module : modules) {
92 module.createConnection();
96 public void startup() throws Exception {
97 for (Module module : modules) {
98 module.startup();
102 public Module getMainModule() {
103 return modules.get(0);
106 private Modules(List<Module> modules) {
107 if (modules.size() < 1) {
108 throw new IllegalArgumentException("modules must not be empty.");
110 this.modules = modules;
112 ImmutableMap.Builder<String, Module> mapBuilder = ImmutableMap.builder();
113 for (Module module : this.modules) {
114 mapBuilder.put(module.getModuleName(), module);
116 moduleNameToModuleMap = mapBuilder.build();
119 public LocalServerEnvironment getLocalServerEnvironment() {
120 return modules.get(0).getLocalServerEnvironment();
123 public Module getModule(String moduleName) {
124 return moduleNameToModuleMap.get(moduleName);
127 @Override
128 public Iterable<String> getModuleNames() {
129 return moduleNameToModuleMap.keySet();
132 @Override
133 public Iterable<String> getVersions(String moduleName) throws ApplicationException {
134 return ImmutableList.of(getDefaultVersion(moduleName));
137 @Override
138 public String getDefaultVersion(String moduleName) throws ApplicationException {
139 Module module = getRequiredModule(moduleName);
140 return module.getMainContainer().getAppEngineWebXmlConfig().getMajorVersionId();
143 @Override
144 public int getNumInstances(String moduleName, String version) throws ApplicationException {
145 Module module = getRequiredModule(moduleName);
146 checkVersion(version, module);
147 ManualScaling manualScaling = getRequiredManualScaling(module);
148 return Integer.parseInt(manualScaling.getInstances());
151 @Override
152 public void setNumInstances(String moduleName, String version, int numInstances)
153 throws ApplicationException {
154 throw new UnsupportedOperationException();
157 @Override
158 public String getHostname(String moduleName, String version, int instance)
159 throws ApplicationException {
160 Module module = getRequiredModule(moduleName);
161 if (instance != LocalEnvironment.MAIN_INSTANCE) {
162 checkVersion(version, module);
163 checkNotDynamicModule(module);
165 String hostAndPort = module.getHostAndPort(instance);
166 if (hostAndPort == null) {
167 throw new ApplicationException(ModulesServiceError.ErrorCode.INVALID_INSTANCES_VALUE,
168 "Instance " + instance + " not found");
170 return hostAndPort;
173 @Override
174 public ModuleState getModuleState(String moduleName) throws ApplicationException {
175 return checkModuleStopped(moduleName) ? ModuleState.STOPPED : ModuleState.RUNNING;
178 @Override
179 public String getScalingType(final String moduleName) throws ApplicationException {
180 Module module = getModule(moduleName);
181 if (module == null) {
182 return null;
184 return module.getClass().getSimpleName();
187 @Override
188 public void startModule(final String moduleName, final String version)
189 throws ApplicationException {
190 doDynamicConfiguration("startServing", new Runnable(){
191 @Override
192 public void run() {
193 doStartModule(moduleName, version);
198 private void doStartModule(String moduleName, String version) {
199 Module module = getRequiredModule(moduleName);
200 checkVersion(version, module);
201 checkNotDynamicModule(module);
202 try {
203 module.startServing();
204 } catch (Exception e) {
205 LOGGER.log(Level.SEVERE, "startServing failed", e);
206 throw new ApplicationException(ModulesServiceError.ErrorCode.UNEXPECTED_STATE_VALUE,
207 "startServing failed with error " + e.getMessage());
211 @Override
212 public void stopModule(final String moduleName, final String version)
213 throws ApplicationException {
214 doDynamicConfiguration("stopServing", new Runnable(){
215 @Override
216 public void run() {
217 doStopModule(moduleName, version);
223 * Attempts to acquire the {@link #dynamicConfigurationLock} and run the
224 * requested operation.
225 * <p>
226 * Currently only one dynamic configuration operation is allowed at a time. This
227 * reduces complexity (e.g. we don't allow the user to start a module while we are
228 * stopping it). One disadvantage of the approach is that some operations that may
229 * work in production will not work in the development environment. In particular an
230 * attempt to perform a dynamic configuration change in another thread during
231 * a dynamic configuration change will time out. For example consider
232 * {@link com.google.appengine.api.LifecycleManager#beginShutdown(long)}.
234 * @throws ApplicationException if the operation fails, we are unable to
235 * acquire the lock in {@link #DYNAMIC_CONFIGURATION_TIMEOUT_SECONDS} seconds
236 * or we are interrupted before we acquire the lock.
238 private void doDynamicConfiguration(String operation, Runnable runnable) {
239 try {
240 if (dynamicConfigurationLock.tryLock(DYNAMIC_CONFIGURATION_TIMEOUT_SECONDS,
241 TimeUnit.SECONDS)) {
242 try {
243 runnable.run();
244 } finally {
245 dynamicConfigurationLock.unlock();
247 } else {
248 LOGGER.log(Level.SEVERE, "stopServing timed out");
249 throw new ApplicationException(ModulesServiceError.ErrorCode.UNEXPECTED_STATE_VALUE,
250 operation + " timed out");
252 } catch (InterruptedException ie) {
253 LOGGER.log(Level.SEVERE, "stopServing interrupted", ie);
254 throw new ApplicationException(ModulesServiceError.ErrorCode.UNEXPECTED_STATE_VALUE,
255 operation + " interrupted " + ie.getMessage());
259 private void doStopModule(String moduleName, String version) {
260 Module module = getRequiredModule(moduleName);
261 checkVersion(version, module);
262 checkNotDynamicModule(module);
263 try {
264 module.stopServing();
265 } catch (Exception e) {
266 LOGGER.log(Level.SEVERE, "stopServing failed", e);
267 throw new ApplicationException(ModulesServiceError.ErrorCode.UNEXPECTED_STATE_VALUE,
268 "stopServing failed with error " + e.getMessage());
272 private Module getRequiredModule(String moduleName) {
273 Module module = moduleNameToModuleMap.get(moduleName);
274 if (module == null) {
275 throw new ApplicationException(ModulesServiceError.ErrorCode.INVALID_MODULE_VALUE,
276 "Module not found");
278 return module;
281 private void checkNotDynamicModule(Module module) {
282 if (module.getMainContainer().getAppEngineWebXmlConfig().getManualScaling().isEmpty() &&
283 module.getMainContainer().getAppEngineWebXmlConfig().getBasicScaling().isEmpty()) {
284 LOGGER.warning("Module " + module.getModuleName() + " cannot be a dynamic module");
285 throw new ApplicationException(ModulesServiceError.ErrorCode.INVALID_VERSION_VALUE,
286 "This operation is not supported on Dynamic modules.");
290 private ManualScaling getRequiredManualScaling(Module module) {
291 ManualScaling manualScaling =
292 module.getMainContainer().getAppEngineWebXmlConfig().getManualScaling();
293 if (manualScaling.isEmpty()) {
294 LOGGER.warning("Module " + module.getModuleName() + " must be a manual scaling module");
295 throw new ApplicationException(ModulesServiceError.ErrorCode.INVALID_VERSION_VALUE,
296 "Manual scaling is required.");
298 return manualScaling;
301 private void checkVersion(String version, Module module) {
302 String moduleVersion =
303 module.getMainContainer().getAppEngineWebXmlConfig().getMajorVersionId();
304 if (version == null || !version.equals(moduleVersion)) {
305 throw new ApplicationException(ModulesServiceError.ErrorCode.INVALID_VERSION_VALUE,
306 "Version not found");
310 @Override
311 public boolean acquireServingPermit(
312 String moduleName, int instanceNumber, boolean allowQueueOnBackends) {
313 Module module = getModule(moduleName);
314 InstanceHolder instanceHolder = module.getInstanceHolder(instanceNumber);
315 return instanceHolder.acquireServingPermit();
318 @Override
319 public int getAndReserveFreeInstance(String moduleName) {
320 Module module = getModule(moduleName);
321 InstanceHolder instanceHolder = module.getAndReserveAvailableInstanceHolder();
322 return instanceHolder == null ? -1 : instanceHolder.getInstance();
325 @Override
326 public void returnServingPermit(String moduleName, int instance) {
329 @Override
330 public boolean checkInstanceExists(String moduleName, int instance) {
331 Module module = getModule(moduleName);
332 return module != null && module.getInstanceHolder(instance) != null;
335 @Override
336 public boolean checkModuleExists(String moduleName) {
337 return getModule(moduleName) != null;
340 @Override
341 public boolean checkModuleStopped(String serverName) {
342 return checkInstanceStopped(serverName, LocalEnvironment.MAIN_INSTANCE);
345 @Override
346 public boolean checkInstanceStopped(String moduleName, int instance) {
347 Module module = getModule(moduleName);
348 InstanceHolder instanceHolder = module.getInstanceHolder(instance);
349 return instanceHolder.isStopped();
352 @Override
353 public void forwardToInstance(String requestedModule, int instance, HttpServletRequest hrequest,
354 HttpServletResponse hresponse) throws IOException, ServletException {
355 Module module = getModule(requestedModule);
356 InstanceHolder instanceHolder = module.getInstanceHolder(instance);
357 instanceHolder.getContainerService().forwardToServer(hrequest, hresponse);
360 @Override
361 public boolean isLoadBalancingInstance(String moduleName, int instance) {
362 Module module = getModule(moduleName);
363 InstanceHolder instanceHolder = module.getInstanceHolder(instance);
364 return instanceHolder.isLoadBalancingInstance();
367 @Override
368 public boolean expectsGeneratedStartRequests(String moduleName,
369 int instance) {
370 Module module = getModule(moduleName);
371 InstanceHolder instanceHolder = module.getInstanceHolder(instance);
372 return instanceHolder.expectsGeneratedStartRequest();
375 @Override
376 public int getPort(String moduleName, int instance) {
377 Module module = getModule(moduleName);
378 InstanceHolder instanceHolder = module.getInstanceHolder(instance);
379 return instanceHolder.getContainerService().getPort();