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
;
12 import java
.io
.IOException
;
13 import java
.util
.List
;
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
;
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();
50 if (!appEngineWebXml
.getBasicScaling().isEmpty()) {
51 module
= new BasicModule(moduleConfigurationHandle
, serverInfo
, address
, devAppServer
,
53 } else if (!appEngineWebXml
.getManualScaling().isEmpty()) {
54 module
= new ManualModule(moduleConfigurationHandle
, serverInfo
, address
, devAppServer
,
57 module
= new AutomaticModule(moduleConfigurationHandle
, serverInfo
, externalResourceDir
,
58 address
, devAppServer
);
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
) {
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
) {
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
);
128 public Iterable
<String
> getModuleNames() {
129 return moduleNameToModuleMap
.keySet();
133 public Iterable
<String
> getVersions(String moduleName
) throws ApplicationException
{
134 return ImmutableList
.of(getDefaultVersion(moduleName
));
138 public String
getDefaultVersion(String moduleName
) throws ApplicationException
{
139 Module module
= getRequiredModule(moduleName
);
140 return module
.getMainContainer().getAppEngineWebXmlConfig().getMajorVersionId();
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());
152 public void setNumInstances(String moduleName
, String version
, int numInstances
)
153 throws ApplicationException
{
154 throw new UnsupportedOperationException();
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");
174 public ModuleState
getModuleState(String moduleName
) throws ApplicationException
{
175 return checkModuleStopped(moduleName
) ? ModuleState
.STOPPED
: ModuleState
.RUNNING
;
179 public String
getScalingType(final String moduleName
) throws ApplicationException
{
180 Module module
= getModule(moduleName
);
181 if (module
== null) {
184 return module
.getClass().getSimpleName();
188 public void startModule(final String moduleName
, final String version
)
189 throws ApplicationException
{
190 doDynamicConfiguration("startServing", new Runnable(){
193 doStartModule(moduleName
, version
);
198 private void doStartModule(String moduleName
, String version
) {
199 Module module
= getRequiredModule(moduleName
);
200 checkVersion(version
, module
);
201 checkNotDynamicModule(module
);
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());
212 public void stopModule(final String moduleName
, final String version
)
213 throws ApplicationException
{
214 doDynamicConfiguration("stopServing", new Runnable(){
217 doStopModule(moduleName
, version
);
223 * Attempts to acquire the {@link #dynamicConfigurationLock} and run the
224 * requested operation.
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
) {
240 if (dynamicConfigurationLock
.tryLock(DYNAMIC_CONFIGURATION_TIMEOUT_SECONDS
,
245 dynamicConfigurationLock
.unlock();
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
);
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
,
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");
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();
319 public int getAndReserveFreeInstance(String moduleName
) {
320 Module module
= getModule(moduleName
);
321 InstanceHolder instanceHolder
= module
.getAndReserveAvailableInstanceHolder();
322 return instanceHolder
== null ?
-1 : instanceHolder
.getInstance();
326 public void returnServingPermit(String moduleName
, int instance
) {
330 public boolean checkInstanceExists(String moduleName
, int instance
) {
331 Module module
= getModule(moduleName
);
332 return module
!= null && module
.getInstanceHolder(instance
) != null;
336 public boolean checkModuleExists(String moduleName
) {
337 return getModule(moduleName
) != null;
341 public boolean checkModuleStopped(String serverName
) {
342 return checkInstanceStopped(serverName
, LocalEnvironment
.MAIN_INSTANCE
);
346 public boolean checkInstanceStopped(String moduleName
, int instance
) {
347 Module module
= getModule(moduleName
);
348 InstanceHolder instanceHolder
= module
.getInstanceHolder(instance
);
349 return instanceHolder
.isStopped();
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
);
361 public boolean isLoadBalancingInstance(String moduleName
, int instance
) {
362 Module module
= getModule(moduleName
);
363 InstanceHolder instanceHolder
= module
.getInstanceHolder(instance
);
364 return instanceHolder
.isLoadBalancingInstance();
368 public boolean expectsGeneratedStartRequests(String moduleName
,
370 Module module
= getModule(moduleName
);
371 InstanceHolder instanceHolder
= module
.getInstanceHolder(instance
);
372 return instanceHolder
.expectsGeneratedStartRequest();
376 public int getPort(String moduleName
, int instance
) {
377 Module module
= getModule(moduleName
);
378 InstanceHolder instanceHolder
= module
.getInstanceHolder(instance
);
379 return instanceHolder
.getContainerService().getPort();