Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / admin / AppAdminImpl.java
bloba0c6950076002b7b9293d4d92dfeef05da566f38
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;
12 import java.io.File;
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;
23 /**
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;
40 this.app = app;
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);
51 @Override
52 public void update(UpdateListener listener) {
53 ServerConnection connection = getServerConnection(options);
54 doUpdate(connection, listener, null);
55 listener.onSuccess(new UpdateSuccessEvent(""));
58 @Override
59 public void updateBackend(String backendName, UpdateListener listener) {
60 ServerConnection connection = getServerConnection(options);
61 doUpdate(connection, listener, backendName);
62 listener.onSuccess(new UpdateSuccessEvent(""));
65 @Override
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(""));
74 @Override
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(""));
85 @Override
86 public void rollback() {
87 rollbackBackend(null);
90 @Override
91 public void rollbackBackend(String backend) {
92 ServerConnection connection = getServerConnection(options);
93 try {
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);
103 @Override
104 public void rollbackAllBackends() {
105 ServerConnection connection = getServerConnection(options);
106 if (app.getBackendsXml() != null) {
107 try {
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);
120 @Override
121 public void setBackendState(String backendName, BackendsXml.State newState) {
122 String url;
123 switch (newState) {
124 case START:
125 url = "/api/backends/start";
126 break;
127 case STOP:
128 url = "/api/backends/stop";
129 break;
130 default:
131 throw new IllegalArgumentException("Cannot change to state: " + newState);
134 ServerConnection connection = getServerConnection(options);
135 try {
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);
144 @Override
145 public List<BackendsXml.Entry> listBackends() {
146 ServerConnection connection = getServerConnection(options);
147 try {
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();
151 } else {
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);
162 @Override
163 public void deleteBackend(String backendName) {
164 ServerConnection connection = getServerConnection(options);
165 try {
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);
174 @Override
175 public void configureBackend(String backendName) {
176 ServerConnection connection = getServerConnection(options);
177 try {
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);
189 try {
190 connection.post(url,
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);
202 @Override
203 public void startModuleVersion() {
204 changeModuleState("/api/modules/start");
207 @Override
208 public void stopModuleVersion() {
209 changeModuleState("/api/modules/stop");
212 @Override
213 public void updateIndexes() {
214 ServerConnection connection = getServerConnection(options);
215 try {
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);
225 @Override
226 public void updateCron() {
227 ServerConnection connection = getServerConnection(options);
228 try {
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);
238 @Override
239 public void updateQueues() {
240 ServerConnection connection = getServerConnection(options);
241 try {
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);
251 @Override
252 public void updateDispatch() {
253 ServerConnection connection = getServerConnection(options);
254 try {
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);
264 @Override
265 public void updateDos() {
266 ServerConnection connection = getServerConnection(options);
267 try {
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);
277 @Override
278 public void setDefaultVersion() {
279 ServerConnection connection = getServerConnection(options);
280 try {
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);
290 @Override
291 public List<CronEntry> cronInfo() {
292 try {
293 List<CronEntry> result = new ArrayList<CronEntry>();
295 CronXml cron = app.getCronXml();
296 if (cron == null) {
297 return result;
299 for (CronXml.Entry entry : cron.getEntries()) {
300 result.add(new CronEntryImpl(entry.getUrl(), entry.getDescription(), entry.getSchedule(),
301 entry.getTimezone()));
303 return result;
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);
312 @Override
313 public ResourceLimits getResourceLimits() {
314 ServerConnection connection = getServerConnection(options);
315 try {
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);
326 @Override
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));
337 throw e;
339 ServerConnection connection = getServerConnection(options);
340 IndexDeleter deleter = new IndexDeleter(connection, app, callback, errorWriter, listener);
341 try {
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);
350 @Override
351 public Reader requestLogs(int numDays, LogSeverity severity,
352 boolean includeAll) {
353 ServerConnection connection = getServerConnection(options);
354 try {
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.
368 @Override
369 public String listVersions() {
370 ServerConnection connection = getServerConnection(options);
371 try {
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.
383 @Override
384 public String deleteVersion(String appId, String moduleId, String versionId) {
385 ServerConnection connection = getServerConnection(options);
386 try {
387 return connection.post("/api/versions/delete", "",
388 "app_id", appId,
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);
398 @Override
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);
408 try {
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);
420 @Override
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);
430 try {
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);
442 @Override
443 public void migrateTraffic() {
444 requireVersion();
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);
451 try {
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() {
469 requireAppId();
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();
484 try {
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);
493 } else {
494 clientDeploySender = new NoLoggingClientDeploySender(connection);
496 ResourceLimits resourceLimits = ResourceLimits.request(clientDeploySender, app);
497 app.resetProgress();
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());
520 @Override
521 public UpdateOptions getUpdateOptions() {
522 return updateOptions;