Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / admin / AppAdminImpl.java
blob7f7da14a2c0b2c29c46bd95e52154ad63cd28d3b
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.IOException;
16 import java.io.PrintWriter;
17 import java.io.Reader;
18 import java.io.StringWriter;
19 import java.lang.reflect.Constructor;
20 import java.nio.file.DirectoryStream;
21 import java.nio.file.Path;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
26 /**
27 * Our implementation of the AppAdmin interface.
30 public class AppAdminImpl implements AppAdmin {
32 private final ConnectOptions options;
33 private final GenericApplication app;
34 private final PrintWriter errorWriter;
35 private final ApplicationProcessingOptions appOptions;
36 private final Class<? extends AppVersionUpload> appVersionUploadClass;
37 private final UpdateOptions updateOptions;
39 protected AppAdminImpl(ConnectOptions options, GenericApplication app, PrintWriter errorWriter,
40 ApplicationProcessingOptions appOptions,
41 Class<? extends AppVersionUpload> appVersionUploadClass) {
42 this.options = options;
43 this.app = app;
44 this.errorWriter = errorWriter;
45 this.appOptions = appOptions;
46 this.appVersionUploadClass = appVersionUploadClass;
47 this.updateOptions = new UpdateOptions();
50 protected ServerConnection getServerConnection(ConnectOptions options) {
51 return ServerConnectionFactory.getServerConnection(options);
54 @Override
55 public void update(UpdateListener listener) {
56 ServerConnection connection = getServerConnection(options);
57 doUpdate(connection, listener, null);
58 listener.onSuccess(new UpdateSuccessEvent(""));
61 @Override
62 public void updateBackend(String backendName, UpdateListener listener) {
63 ServerConnection connection = getServerConnection(options);
64 doUpdate(connection, listener, backendName);
65 listener.onSuccess(new UpdateSuccessEvent(""));
68 @Override
69 public void updateBackends(List<String> backendNames, UpdateListener listener) {
70 ServerConnection connection = getServerConnection(options);
71 for (String backendName : backendNames) {
72 doUpdate(connection, listener, backendName);
74 listener.onSuccess(new UpdateSuccessEvent(""));
77 @Override
78 public void updateAllBackends(UpdateListener listener) {
79 ServerConnection connection = getServerConnection(options);
80 if (app.getBackendsXml() != null) {
81 for (BackendsXml.Entry backend : app.getBackendsXml().getBackends()) {
82 doUpdate(connection, listener, backend.getName());
85 listener.onSuccess(new UpdateSuccessEvent(""));
88 @Override
89 public void rollback() {
90 rollbackBackend(null);
93 @Override
94 public void rollbackBackend(String backend) {
95 ServerConnection connection = getServerConnection(options);
96 try {
97 AppVersionUpload uploader = createAppVersionUpload(connection, app, backend);
98 uploader.forceRollback();
99 } catch (Throwable t) {
100 errorWriter.println("Unable to rollback:");
101 t.printStackTrace(errorWriter);
102 throw new AdminException("Unable to rollback app: " + t.getMessage(), t);
106 @Override
107 public void rollbackAllBackends() {
108 ServerConnection connection = getServerConnection(options);
109 if (app.getBackendsXml() != null) {
110 try {
111 for (BackendsXml.Entry backend : app.getBackendsXml().getBackends()) {
112 AppVersionUpload uploader = createAppVersionUpload(connection, app, backend.getName());
113 uploader.forceRollback();
115 } catch (Throwable t) {
116 errorWriter.println("Unable to rollback:");
117 t.printStackTrace(errorWriter);
118 throw new AdminException("Unable to rollback app: " + t.getMessage(), t);
123 @Override
124 public void setBackendState(String backendName, BackendsXml.State newState) {
125 String url;
126 switch (newState) {
127 case START:
128 url = "/api/backends/start";
129 break;
130 case STOP:
131 url = "/api/backends/stop";
132 break;
133 default:
134 throw new IllegalArgumentException("Cannot change to state: " + newState);
137 ServerConnection connection = getServerConnection(options);
138 try {
139 connection.post(url, "", "app_id", app.getAppId(), "backend", backendName);
140 } catch (Throwable t) {
141 errorWriter.println("Unable to change backend state:");
142 t.printStackTrace(errorWriter);
143 throw new AdminException("Unable to change backend state: " + t.getMessage(), t);
147 @Override
148 public List<BackendsXml.Entry> listBackends() {
149 ServerConnection connection = getServerConnection(options);
150 try {
151 String yaml = connection.post("/api/backends/list", "", "app_id", app.getAppId());
152 if (yaml.contains("No backends configured")) {
153 return Collections.<BackendsXml.Entry>emptyList();
154 } else {
155 BackendsXml xml = BackendsYamlReader.parse(yaml);
156 return xml.getBackends();
158 } catch (Throwable t) {
159 errorWriter.println("Unable to list backends:");
160 t.printStackTrace(errorWriter);
161 throw new AdminException("Unable to list backends: " + t.getMessage(), t);
165 @Override
166 public void deleteBackend(String backendName) {
167 ServerConnection connection = getServerConnection(options);
168 try {
169 connection.post("/api/backends/delete", "", "app_id", app.getAppId(), "backend", backendName);
170 } catch (Throwable t) {
171 errorWriter.println("Unable to delete backend:");
172 t.printStackTrace(errorWriter);
173 throw new AdminException("Unable to delete backend: " + t.getMessage(), t);
177 @Override
178 public void configureBackend(String backendName) {
179 ServerConnection connection = getServerConnection(options);
180 try {
181 connection.post("/api/backends/configure", app.getBackendsXml().toYaml(),
182 "app_id", app.getAppId(), "backend", backendName);
183 } catch (Throwable t) {
184 errorWriter.println("Unable to configure backend:");
185 t.printStackTrace(errorWriter);
186 throw new AdminException("Unable to configure backend: " + t.getMessage(), t);
190 private void changeModuleState(String url) {
191 ServerConnection connection = getServerConnection(options);
192 try {
193 connection.post(url,
195 "app_id", app.getAppId(),
196 "module", app.getModule() != null ? app.getModule() : "",
197 "version", app.getVersion());
198 } catch (Throwable t) {
199 errorWriter.println("Unable to change module state:");
200 t.printStackTrace(errorWriter);
201 throw new AdminException("Unable to change module state: " + t.getMessage(), t);
205 @Override
206 public void startModuleVersion() {
207 changeModuleState("/api/modules/start");
210 @Override
211 public void stopModuleVersion() {
212 changeModuleState("/api/modules/stop");
215 @Override
216 public void updateIndexes() {
217 ServerConnection connection = getServerConnection(options);
218 try {
219 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
220 uploader.updateIndexes();
221 } catch (Throwable t) {
222 errorWriter.println("Unable to update indexes:");
223 t.printStackTrace(errorWriter);
224 throw new AdminException("Unable to update indexes for app: " + t.getMessage(), t);
228 @Override
229 public void updateCron() {
230 ServerConnection connection = getServerConnection(options);
231 try {
232 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
233 uploader.updateCron();
234 } catch (Throwable t) {
235 errorWriter.println("Unable to update cron entries:");
236 t.printStackTrace(errorWriter);
237 throw new AdminException("Unable to update cron entries for app: " + t.getMessage(), t);
241 @Override
242 public void updateQueues() {
243 ServerConnection connection = getServerConnection(options);
244 try {
245 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
246 uploader.updateQueue();
247 } catch (Throwable t) {
248 errorWriter.println("Unable to upload:");
249 t.printStackTrace(errorWriter);
250 throw new AdminException("Unable to update task queues for app: " + t.getMessage(), t);
254 @Override
255 public void updateDispatch() {
256 ServerConnection connection = getServerConnection(options);
257 try {
258 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
259 uploader.updateDispatch();
260 } catch (Throwable t) {
261 errorWriter.println("Unable to update dispatch entries:");
262 t.printStackTrace(errorWriter);
263 throw new AdminException("Unable to update dispatch entries for app: " + t.getMessage(), t);
267 @Override
268 public void updateDos() {
269 ServerConnection connection = getServerConnection(options);
270 try {
271 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
272 uploader.updateDos();
273 } catch (Throwable t) {
274 errorWriter.println("Unable to update DoS entries:");
275 t.printStackTrace(errorWriter);
276 throw new AdminException("Unable to update DoS entries for app: " + t.getMessage(), t);
280 @Override
281 public void setDefaultVersion() {
282 ServerConnection connection = getServerConnection(options);
283 try {
284 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
285 uploader.setDefaultVersion();
286 } catch (Throwable t) {
287 errorWriter.println("Unable to set default version:");
288 t.printStackTrace(errorWriter);
289 throw new AdminException("Unable to set default version for app: " + t.getMessage(), t);
293 @Override
294 public List<CronEntry> cronInfo() {
295 try {
296 List<CronEntry> result = new ArrayList<CronEntry>();
298 CronXml cron = app.getCronXml();
299 if (cron == null) {
300 return result;
302 for (CronXml.Entry entry : cron.getEntries()) {
303 result.add(new CronEntryImpl(entry.getUrl(), entry.getDescription(), entry.getSchedule(),
304 entry.getTimezone()));
306 return result;
307 } catch (Throwable t) {
308 errorWriter.println("Unable to display run times for cron entries:");
309 t.printStackTrace(errorWriter);
310 throw new AdminException("Unable to display run times for cron entries for app: "
311 + t.getMessage(), t);
315 @Override
316 public ResourceLimits getResourceLimits() {
317 ServerConnection connection = getServerConnection(options);
318 try {
319 ClientDeploySender clientDeploySender = new NoLoggingClientDeploySender(connection);
320 return ResourceLimits.request(clientDeploySender, app);
321 } catch (Throwable t) {
322 errorWriter.println("Unable to get resource limits:");
323 t.printStackTrace(errorWriter);
324 throw new AdminException("Unable to get resource limits: "
325 + t.getMessage(), t);
329 @Override
330 public void vacuumIndexes(ConfirmationCallback<IndexDeleter.DeleteIndexAction> callback,
331 UpdateListener listener) {
332 String appID = app.getAppId();
333 if (null == appID || appID.isEmpty()) {
334 String message = "This application does not have an ID.";
335 String detailMessage =
336 "The vacuum_indexes operation may not be performed for"
337 + " an application that does not have an ID.";
338 AdminException e = new AdminException(message);
339 listener.onFailure(new UpdateFailureEvent(e, message, detailMessage));
340 throw e;
342 ServerConnection connection = getServerConnection(options);
343 IndexDeleter deleter = new IndexDeleter(connection, app, callback, errorWriter, listener);
344 try {
345 deleter.deleteUnusedIndexes();
346 } catch (Exception e) {
347 String message = "Unable to perform vacuum_indexes";
348 listener.onFailure(new UpdateFailureEvent(e, message, e.getMessage()));
349 throw new AdminException(message, e);
353 @Override
354 public Reader requestLogs(int numDays, LogSeverity severity,
355 boolean includeAll) {
356 ServerConnection connection = getServerConnection(options);
357 try {
358 File logFile = File.createTempFile(app.getAppId() + "-" + app.getVersion(), ".log");
359 logFile.deleteOnExit();
360 LogFetcher logFetcher = new LogFetcher(app, connection);
361 logFetcher.fetch(numDays, severity, includeAll, new FileOutputStream(logFile));
362 return new BufferedReader(new FileReader(logFile));
363 } catch (Exception ex) {
364 throw new AdminException("Unable to retrieve the remote application logs:", ex);
369 * Retrieve the list of versions of this application.
371 @Override
372 public String listVersions() {
373 ServerConnection connection = getServerConnection(options);
374 try {
375 return connection.post("/api/versions/list", "", "app_id", app.getAppId());
376 } catch (Throwable t) {
377 errorWriter.println("Unable to retrieve versions:");
378 t.printStackTrace(errorWriter);
379 throw new AdminException("Unable to retrieve versions: " + t.getMessage(), t);
384 * Delete the specified version of this application.
386 @Override
387 public String deleteVersion(String appId, String moduleId, String versionId) {
388 ServerConnection connection = getServerConnection(options);
389 try {
390 return connection.post("/api/versions/delete", "",
391 "app_id", appId,
392 "module", moduleId != null ? moduleId : "",
393 "version_match", versionId);
394 } catch (Throwable t) {
395 errorWriter.println("Unable to delete version:");
396 t.printStackTrace(errorWriter);
397 throw new AdminException("Unable to delete version: " + t.getMessage(), t);
401 @Override
402 public String debugVersion() {
403 if (null == app.getAppId() || app.getAppId().isEmpty()) {
404 throw new AdminException("This application does not have an id");
406 if (null == app.getVersion() || app.getVersion().isEmpty()) {
407 throw new AdminException("This application does not have a version");
410 ServerConnection connection = getServerConnection(options);
411 try {
412 return connection.post("/api/vms/debug", "",
413 "app_id", app.getAppId(),
414 "module", app.getModule() != null ? app.getModule() : "",
415 "version_match", app.getVersion());
416 } catch (Throwable t) {
417 errorWriter.println("Unable to debug version:");
418 t.printStackTrace(errorWriter);
419 throw new AdminException("Unable to debug version: " + t.getMessage(), t);
423 @Override
424 public String debugVersionState() {
425 if (null == app.getAppId() || app.getAppId().isEmpty()) {
426 throw new AdminException("This application does not have an id");
428 if (null == app.getVersion() || app.getVersion().isEmpty()) {
429 throw new AdminException("This application does not have a version");
432 ServerConnection connection = getServerConnection(options);
433 try {
434 return connection.post("/api/vms/debugstate", "",
435 "app_id", app.getAppId(),
436 "module", app.getModule() != null ? app.getModule() : "",
437 "version_match", app.getVersion());
438 } catch (Throwable t) {
439 errorWriter.println("Unable to get state for debug version call:");
440 t.printStackTrace(errorWriter);
441 throw new AdminException("Unable to get state for debug version: " + t.getMessage(), t);
445 @Override
446 public void migrateTraffic() {
447 requireVersion();
448 if (app.getModule() != null && !app.getModule().equals("")
449 && !app.getModule().equals("default")) {
450 throw new AdminException("Traffic migration does not support non-default modules yet");
453 ServerConnection connection = getServerConnection(options);
454 try {
455 connection.post("/api/appversion/migratetraffic", "",
456 "app_id", app.getAppId(),
457 "version", app.getVersion());
458 } catch (Throwable t) {
459 errorWriter.println("Unable to migrate traffic:");
460 t.printStackTrace(errorWriter);
461 throw new AdminException("Unable to migrate version: " + t.getMessage(), t);
465 private void requireAppId() {
466 if (null == app.getAppId() || app.getAppId().isEmpty()) {
467 throw new AdminException("This application does not have an id");
471 private void requireVersion() {
472 requireAppId();
474 if (null == app.getVersion() || app.getVersion().isEmpty()) {
475 throw new AdminException("This application does not have a version");
479 @Override
480 public void stageApplicationWithDefaultResourceLimits(File stagingDir) {
481 stageApplication(stagingDir, false);
484 @Override
485 public void stageApplicationWithRemoteResourceLimits(File stagingDir) {
486 stageApplication(stagingDir, true);
490 * Convert a java appengine war directory into a format that can be deployed to
491 * appengine.
493 void stageApplication(File stagingDir, boolean useRemoteResourceLimits) {
494 if (stagingDir == null) {
495 throw new AdminException("Staging dir is not a valid directory (it is null)");
497 if (stagingDir.exists()) {
498 if (!stagingDir.isDirectory()) {
499 throw new AdminException(stagingDir.getPath()
500 + " is not a valid staging directory (it is a regular file)");
502 Path path = stagingDir.toPath();
503 try (DirectoryStream<Path> dirStream = java.nio.file.Files.newDirectoryStream(path)) {
504 if (dirStream.iterator().hasNext()) {
505 throw new AdminException(
506 stagingDir.getPath() + " is not a valid staging directory (it is not empty)");
508 } catch (IOException e) {
509 throw new AdminException("Unable to stage application.", e);
512 StringWriter detailsWriter = new StringWriter();
513 try {
514 ResourceLimits resourceLimits = ResourceLimits.newDefaultResourceLimits();
515 if (useRemoteResourceLimits) {
516 ServerConnection connection = getServerConnection(options);
517 ClientDeploySender clientDeploySender;
518 if (getUpdateOptions().getUpdateUsageReporting()) {
519 String sdkVersion = getUpdateOptions().getSdkVersion();
520 clientDeploySender = new LoggingClientDeploySender(connection, sdkVersion);
521 } else {
522 clientDeploySender = new NoLoggingClientDeploySender(connection);
524 resourceLimits = ResourceLimits.request(clientDeploySender, app);
526 app.resetProgress();
527 app.setDetailsWriter(new PrintWriter(detailsWriter, true));
528 app.createStagingDirectory(appOptions, resourceLimits, stagingDir);
529 } catch (Throwable t) {
530 errorWriter.println("Unable to stage:");
531 t.printStackTrace(errorWriter);
532 throw new AdminException("Unable to stage app: " + t.getMessage(), t);
537 * Deploy a new version of this application. If successful, this method will
538 * return without throwing an exception but will not call
539 * {@link UpdateListener#onSuccess(UpdateSuccessEvent)}. The caller is responsible for
540 * calling that method.
542 private void doUpdate(ServerConnection connection, UpdateListener listener, String backend) {
543 StringWriter detailsWriter = new StringWriter();
544 try {
545 AppVersionUpload uploader = createAppVersionUpload(connection, app, backend);
546 boolean updateGlobalConfigurations = getUpdateOptions().getUpdateGlobalConfigurations();
547 boolean failOnPrecompilationError = appOptions.isFailOnPrecompilationError();
548 boolean ignoreEndpointsFailures = appOptions.isIgnoreEndpointsFailures();
549 ClientDeploySender clientDeploySender;
550 if (getUpdateOptions().getUpdateUsageReporting()) {
551 String sdkVersion = getUpdateOptions().getSdkVersion();
552 clientDeploySender = new LoggingClientDeploySender(connection, sdkVersion);
553 } else {
554 clientDeploySender = new NoLoggingClientDeploySender(connection);
556 ResourceLimits resourceLimits = ResourceLimits.request(clientDeploySender, app);
557 app.resetProgress();
558 app.setListener(listener);
559 app.setDetailsWriter(new PrintWriter(detailsWriter, true));
560 app.createStagingDirectory(appOptions, resourceLimits);
561 app.exportRepoInfoFile();
562 clientDeploySender.setRuntime(AppVersionUpload.getRuntime(app.getAppYaml()));
563 uploader.doUpload(resourceLimits, updateGlobalConfigurations, failOnPrecompilationError,
564 ignoreEndpointsFailures, clientDeploySender);
565 } catch (Throwable t) {
566 errorWriter.println("Unable to update:");
567 t.printStackTrace(errorWriter);
568 listener.onFailure(new UpdateFailureEvent(t, t.toString(), detailsWriter.toString()));
569 throw new AdminException("Unable to update app: " + t.getMessage(), t);
573 private AppVersionUpload createAppVersionUpload(ServerConnection connection,
574 GenericApplication app, String backend) throws Exception {
575 Constructor<? extends AppVersionUpload> constructor =
576 appVersionUploadClass.getConstructor(ServerConnection.class, GenericApplication.class,
577 String.class, Boolean.TYPE);
578 return constructor.newInstance(connection, app, backend, appOptions.isBatchModeSet());
581 @Override
582 public UpdateOptions getUpdateOptions() {
583 return updateOptions;