1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.tools
.admin
;
5 import com
.google
.appengine
.tools
.admin
.AppAdminFactory
.ApplicationProcessingOptions
;
6 import com
.google
.appengine
.tools
.admin
.AppAdminFactory
.ConnectOptions
;
7 import com
.google
.apphosting
.utils
.config
.BackendsXml
;
8 import com
.google
.apphosting
.utils
.config
.BackendsYamlReader
;
9 import com
.google
.apphosting
.utils
.config
.CronXml
;
11 import java
.io
.BufferedReader
;
13 import java
.io
.FileOutputStream
;
14 import java
.io
.FileReader
;
15 import java
.io
.PrintWriter
;
16 import java
.io
.Reader
;
17 import java
.io
.StringWriter
;
18 import java
.lang
.reflect
.Constructor
;
19 import java
.util
.ArrayList
;
20 import java
.util
.Collections
;
21 import java
.util
.List
;
24 * Our implementation of the AppAdmin interface.
27 public class AppAdminImpl
implements AppAdmin
{
29 private final ConnectOptions options
;
30 private final GenericApplication app
;
31 private final PrintWriter errorWriter
;
32 private final ApplicationProcessingOptions appOptions
;
33 private final Class
<?
extends AppVersionUpload
> appVersionUploadClass
;
34 private final UpdateOptions updateOptions
;
36 protected AppAdminImpl(ConnectOptions options
, GenericApplication app
, PrintWriter errorWriter
,
37 ApplicationProcessingOptions appOptions
,
38 Class
<?
extends AppVersionUpload
> appVersionUploadClass
) {
39 this.options
= options
;
41 this.errorWriter
= errorWriter
;
42 this.appOptions
= appOptions
;
43 this.appVersionUploadClass
= appVersionUploadClass
;
44 this.updateOptions
= new UpdateOptions();
47 protected ServerConnection
getServerConnection(ConnectOptions options
) {
48 return ServerConnectionFactory
.getServerConnection(options
);
52 public void update(UpdateListener listener
) {
53 ServerConnection connection
= getServerConnection(options
);
54 doUpdate(connection
, listener
, null);
55 listener
.onSuccess(new UpdateSuccessEvent(""));
59 public void updateBackend(String backendName
, UpdateListener listener
) {
60 ServerConnection connection
= getServerConnection(options
);
61 doUpdate(connection
, listener
, backendName
);
62 listener
.onSuccess(new UpdateSuccessEvent(""));
66 public void updateBackends(List
<String
> backendNames
, UpdateListener listener
) {
67 ServerConnection connection
= getServerConnection(options
);
68 for (String backendName
: backendNames
) {
69 doUpdate(connection
, listener
, backendName
);
71 listener
.onSuccess(new UpdateSuccessEvent(""));
75 public void updateAllBackends(UpdateListener listener
) {
76 ServerConnection connection
= getServerConnection(options
);
77 if (app
.getBackendsXml() != null) {
78 for (BackendsXml
.Entry backend
: app
.getBackendsXml().getBackends()) {
79 doUpdate(connection
, listener
, backend
.getName());
82 listener
.onSuccess(new UpdateSuccessEvent(""));
86 public void rollback() {
87 rollbackBackend(null);
91 public void rollbackBackend(String backend
) {
92 ServerConnection connection
= getServerConnection(options
);
94 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, backend
);
95 uploader
.forceRollback();
96 } catch (Throwable t
) {
97 errorWriter
.println("Unable to rollback:");
98 t
.printStackTrace(errorWriter
);
99 throw new AdminException("Unable to rollback app: " + t
.getMessage(), t
);
104 public void rollbackAllBackends() {
105 ServerConnection connection
= getServerConnection(options
);
106 if (app
.getBackendsXml() != null) {
108 for (BackendsXml
.Entry backend
: app
.getBackendsXml().getBackends()) {
109 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, backend
.getName());
110 uploader
.forceRollback();
112 } catch (Throwable t
) {
113 errorWriter
.println("Unable to rollback:");
114 t
.printStackTrace(errorWriter
);
115 throw new AdminException("Unable to rollback app: " + t
.getMessage(), t
);
121 public void setBackendState(String backendName
, BackendsXml
.State newState
) {
125 url
= "/api/backends/start";
128 url
= "/api/backends/stop";
131 throw new IllegalArgumentException("Cannot change to state: " + newState
);
134 ServerConnection connection
= getServerConnection(options
);
136 connection
.post(url
, "", "app_id", app
.getAppId(), "backend", backendName
);
137 } catch (Throwable t
) {
138 errorWriter
.println("Unable to change backend state:");
139 t
.printStackTrace(errorWriter
);
140 throw new AdminException("Unable to change backend state: " + t
.getMessage(), t
);
145 public List
<BackendsXml
.Entry
> listBackends() {
146 ServerConnection connection
= getServerConnection(options
);
148 String yaml
= connection
.post("/api/backends/list", "", "app_id", app
.getAppId());
149 if (yaml
.contains("No backends configured")) {
150 return Collections
.<BackendsXml
.Entry
>emptyList();
152 BackendsXml xml
= BackendsYamlReader
.parse(yaml
);
153 return xml
.getBackends();
155 } catch (Throwable t
) {
156 errorWriter
.println("Unable to list backends:");
157 t
.printStackTrace(errorWriter
);
158 throw new AdminException("Unable to list backends: " + t
.getMessage(), t
);
163 public void deleteBackend(String backendName
) {
164 ServerConnection connection
= getServerConnection(options
);
166 connection
.post("/api/backends/delete", "", "app_id", app
.getAppId(), "backend", backendName
);
167 } catch (Throwable t
) {
168 errorWriter
.println("Unable to delete backend:");
169 t
.printStackTrace(errorWriter
);
170 throw new AdminException("Unable to delete backend: " + t
.getMessage(), t
);
175 public void configureBackend(String backendName
) {
176 ServerConnection connection
= getServerConnection(options
);
178 connection
.post("/api/backends/configure", app
.getBackendsXml().toYaml(),
179 "app_id", app
.getAppId(), "backend", backendName
);
180 } catch (Throwable t
) {
181 errorWriter
.println("Unable to configure backend:");
182 t
.printStackTrace(errorWriter
);
183 throw new AdminException("Unable to configure backend: " + t
.getMessage(), t
);
187 private void changeModuleState(String url
) {
188 ServerConnection connection
= getServerConnection(options
);
192 "app_id", app
.getAppId(),
193 "module", app
.getModule() != null ? app
.getModule() : "",
194 "version", app
.getVersion());
195 } catch (Throwable t
) {
196 errorWriter
.println("Unable to change module state:");
197 t
.printStackTrace(errorWriter
);
198 throw new AdminException("Unable to change module state: " + t
.getMessage(), t
);
203 public void startModuleVersion() {
204 changeModuleState("/api/modules/start");
208 public void stopModuleVersion() {
209 changeModuleState("/api/modules/stop");
213 public void updateIndexes() {
214 ServerConnection connection
= getServerConnection(options
);
216 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
217 uploader
.updateIndexes();
218 } catch (Throwable t
) {
219 errorWriter
.println("Unable to update indexes:");
220 t
.printStackTrace(errorWriter
);
221 throw new AdminException("Unable to update indexes for app: " + t
.getMessage(), t
);
226 public void updateCron() {
227 ServerConnection connection
= getServerConnection(options
);
229 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
230 uploader
.updateCron();
231 } catch (Throwable t
) {
232 errorWriter
.println("Unable to update cron entries:");
233 t
.printStackTrace(errorWriter
);
234 throw new AdminException("Unable to update cron entries for app: " + t
.getMessage(), t
);
239 public void updateQueues() {
240 ServerConnection connection
= getServerConnection(options
);
242 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
243 uploader
.updateQueue();
244 } catch (Throwable t
) {
245 errorWriter
.println("Unable to upload:");
246 t
.printStackTrace(errorWriter
);
247 throw new AdminException("Unable to update task queues for app: " + t
.getMessage(), t
);
252 public void updateDispatch() {
253 ServerConnection connection
= getServerConnection(options
);
255 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
256 uploader
.updateDispatch();
257 } catch (Throwable t
) {
258 errorWriter
.println("Unable to update dispatch entries:");
259 t
.printStackTrace(errorWriter
);
260 throw new AdminException("Unable to update dispatch entries for app: " + t
.getMessage(), t
);
265 public void updateDos() {
266 ServerConnection connection
= getServerConnection(options
);
268 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
269 uploader
.updateDos();
270 } catch (Throwable t
) {
271 errorWriter
.println("Unable to update DoS entries:");
272 t
.printStackTrace(errorWriter
);
273 throw new AdminException("Unable to update DoS entries for app: " + t
.getMessage(), t
);
278 public void setDefaultVersion() {
279 ServerConnection connection
= getServerConnection(options
);
281 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
282 uploader
.setDefaultVersion();
283 } catch (Throwable t
) {
284 errorWriter
.println("Unable to set default version:");
285 t
.printStackTrace(errorWriter
);
286 throw new AdminException("Unable to set default version for app: " + t
.getMessage(), t
);
291 public List
<CronEntry
> cronInfo() {
293 List
<CronEntry
> result
= new ArrayList
<CronEntry
>();
295 CronXml cron
= app
.getCronXml();
299 for (CronXml
.Entry entry
: cron
.getEntries()) {
300 result
.add(new CronEntryImpl(entry
.getUrl(), entry
.getDescription(), entry
.getSchedule(),
301 entry
.getTimezone()));
304 } catch (Throwable t
) {
305 errorWriter
.println("Unable to display run times for cron entries:");
306 t
.printStackTrace(errorWriter
);
307 throw new AdminException("Unable to display run times for cron entries for app: "
308 + t
.getMessage(), t
);
313 public ResourceLimits
getResourceLimits() {
314 ServerConnection connection
= getServerConnection(options
);
316 ClientDeploySender clientDeploySender
= new NoLoggingClientDeploySender(connection
);
317 return ResourceLimits
.request(clientDeploySender
, app
);
318 } catch (Throwable t
) {
319 errorWriter
.println("Unable to get resource limits:");
320 t
.printStackTrace(errorWriter
);
321 throw new AdminException("Unable to get resource limits: "
322 + t
.getMessage(), t
);
327 public void vacuumIndexes(ConfirmationCallback
<IndexDeleter
.DeleteIndexAction
> callback
,
328 UpdateListener listener
) {
329 String appID
= app
.getAppId();
330 if (null == appID
|| appID
.isEmpty()) {
331 String message
= "This application does not have an ID.";
332 String detailMessage
=
333 "The vacuum_indexes operation may not be performed for"
334 + " an application that does not have an ID.";
335 AdminException e
= new AdminException(message
);
336 listener
.onFailure(new UpdateFailureEvent(e
, message
, detailMessage
));
339 ServerConnection connection
= getServerConnection(options
);
340 IndexDeleter deleter
= new IndexDeleter(connection
, app
, callback
, errorWriter
, listener
);
342 deleter
.deleteUnusedIndexes();
343 } catch (Exception e
) {
344 String message
= "Unable to perform vacuum_indexes";
345 listener
.onFailure(new UpdateFailureEvent(e
, message
, e
.getMessage()));
346 throw new AdminException(message
, e
);
351 public Reader
requestLogs(int numDays
, LogSeverity severity
,
352 boolean includeAll
) {
353 ServerConnection connection
= getServerConnection(options
);
355 File logFile
= File
.createTempFile(app
.getAppId() + "-" + app
.getVersion(), ".log");
356 logFile
.deleteOnExit();
357 LogFetcher logFetcher
= new LogFetcher(app
, connection
);
358 logFetcher
.fetch(numDays
, severity
, includeAll
, new FileOutputStream(logFile
));
359 return new BufferedReader(new FileReader(logFile
));
360 } catch (Exception ex
) {
361 throw new AdminException("Unable to retrieve the remote application logs:", ex
);
366 * Retrieve the list of versions of this application.
369 public String
listVersions() {
370 ServerConnection connection
= getServerConnection(options
);
372 return connection
.post("/api/versions/list", "", "app_id", app
.getAppId());
373 } catch (Throwable t
) {
374 errorWriter
.println("Unable to retrieve versions:");
375 t
.printStackTrace(errorWriter
);
376 throw new AdminException("Unable to retrieve versions: " + t
.getMessage(), t
);
381 * Delete the specified version of this application.
384 public String
deleteVersion(String appId
, String moduleId
, String versionId
) {
385 ServerConnection connection
= getServerConnection(options
);
387 return connection
.post("/api/versions/delete", "",
389 "module", moduleId
!= null ? moduleId
: "",
390 "version_match", versionId
);
391 } catch (Throwable t
) {
392 errorWriter
.println("Unable to delete version:");
393 t
.printStackTrace(errorWriter
);
394 throw new AdminException("Unable to delete version: " + t
.getMessage(), t
);
399 public String
debugVersion() {
400 if (null == app
.getAppId() || app
.getAppId().isEmpty()) {
401 throw new AdminException("This application does not have an id");
403 if (null == app
.getVersion() || app
.getVersion().isEmpty()) {
404 throw new AdminException("This application does not have a version");
407 ServerConnection connection
= getServerConnection(options
);
409 return connection
.post("/api/vms/debug", "",
410 "app_id", app
.getAppId(),
411 "module", app
.getModule() != null ? app
.getModule() : "",
412 "version_match", app
.getVersion());
413 } catch (Throwable t
) {
414 errorWriter
.println("Unable to debug version:");
415 t
.printStackTrace(errorWriter
);
416 throw new AdminException("Unable to debug version: " + t
.getMessage(), t
);
421 public String
debugVersionState() {
422 if (null == app
.getAppId() || app
.getAppId().isEmpty()) {
423 throw new AdminException("This application does not have an id");
425 if (null == app
.getVersion() || app
.getVersion().isEmpty()) {
426 throw new AdminException("This application does not have a version");
429 ServerConnection connection
= getServerConnection(options
);
431 return connection
.post("/api/vms/debugstate", "",
432 "app_id", app
.getAppId(),
433 "module", app
.getModule() != null ? app
.getModule() : "",
434 "version_match", app
.getVersion());
435 } catch (Throwable t
) {
436 errorWriter
.println("Unable to get state for debug version call:");
437 t
.printStackTrace(errorWriter
);
438 throw new AdminException("Unable to get state for debug version: " + t
.getMessage(), t
);
443 public void migrateTraffic() {
445 if (app
.getModule() != null && !app
.getModule().equals("")
446 && !app
.getModule().equals("default")) {
447 throw new AdminException("Traffic migration does not support non-default modules yet");
450 ServerConnection connection
= getServerConnection(options
);
452 connection
.post("/api/appversion/migratetraffic", "",
453 "app_id", app
.getAppId(),
454 "version", app
.getVersion());
455 } catch (Throwable t
) {
456 errorWriter
.println("Unable to migrate traffic:");
457 t
.printStackTrace(errorWriter
);
458 throw new AdminException("Unable to migrate version: " + t
.getMessage(), t
);
462 private void requireAppId() {
463 if (null == app
.getAppId() || app
.getAppId().isEmpty()) {
464 throw new AdminException("This application does not have an id");
468 private void requireVersion() {
471 if (null == app
.getVersion() || app
.getVersion().isEmpty()) {
472 throw new AdminException("This application does not have a version");
477 * Deploy a new version of this application. If successful, this method will
478 * return without throwing an exception but will not call
479 * {@link UpdateListener#onSuccess(UpdateSuccessEvent)}. The caller is responsible for
480 * calling that method.
482 private void doUpdate(ServerConnection connection
, UpdateListener listener
, String backend
) {
483 StringWriter detailsWriter
= new StringWriter();
485 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, backend
);
486 boolean updateGlobalConfigurations
= getUpdateOptions().getUpdateGlobalConfigurations();
487 boolean failOnPrecompilationError
= appOptions
.isFailOnPrecompilationError();
488 boolean ignoreEndpointsFailures
= appOptions
.isIgnoreEndpointsFailures();
489 ClientDeploySender clientDeploySender
;
490 if (getUpdateOptions().getUpdateUsageReporting()) {
491 String sdkVersion
= getUpdateOptions().getSdkVersion();
492 clientDeploySender
= new LoggingClientDeploySender(connection
, sdkVersion
);
494 clientDeploySender
= new NoLoggingClientDeploySender(connection
);
496 ResourceLimits resourceLimits
= ResourceLimits
.request(clientDeploySender
, app
);
498 app
.setListener(listener
);
499 app
.setDetailsWriter(new PrintWriter(detailsWriter
, true));
500 app
.createStagingDirectory(appOptions
, resourceLimits
);
501 clientDeploySender
.setRuntime(AppVersionUpload
.getRuntime(app
.getAppYaml()));
502 uploader
.doUpload(resourceLimits
, updateGlobalConfigurations
, failOnPrecompilationError
,
503 ignoreEndpointsFailures
, clientDeploySender
);
504 } catch (Throwable t
) {
505 errorWriter
.println("Unable to update:");
506 t
.printStackTrace(errorWriter
);
507 listener
.onFailure(new UpdateFailureEvent(t
, t
.toString(), detailsWriter
.toString()));
508 throw new AdminException("Unable to update app: " + t
.getMessage(), t
);
512 private AppVersionUpload
createAppVersionUpload(ServerConnection connection
,
513 GenericApplication app
, String backend
) throws Exception
{
514 Constructor
<?
extends AppVersionUpload
> constructor
=
515 appVersionUploadClass
.getConstructor(ServerConnection
.class, GenericApplication
.class,
516 String
.class, Boolean
.TYPE
);
517 return constructor
.newInstance(connection
, app
, backend
, appOptions
.isBatchModeSet());
521 public UpdateOptions
getUpdateOptions() {
522 return updateOptions
;