Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / admin / AppAdminImpl.java
blob30067b6f4d2e7570e251108d779a05a1dc7d930b
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 ConnectOptions options;
30 private GenericApplication app;
31 private PrintWriter errorWriter;
32 private ApplicationProcessingOptions appOptions;
33 private final Class<? extends AppVersionUpload> appVersionUploadClass;
35 protected AppAdminImpl(ConnectOptions options, GenericApplication app, PrintWriter errorWriter,
36 ApplicationProcessingOptions appOptions,
37 Class<? extends AppVersionUpload> appVersionUploadClass) {
38 this.options = options;
39 this.app = app;
40 this.errorWriter = errorWriter;
41 this.appOptions = appOptions;
42 this.appVersionUploadClass = appVersionUploadClass;
45 protected ServerConnection getServerConnection(ConnectOptions options) {
46 return ServerConnectionFactory.getServerConnection(options);
49 @Override
50 public void update(UpdateListener listener) {
51 ServerConnection connection = getServerConnection(options);
52 doUpdate(connection, listener, null);
53 listener.onSuccess(new UpdateSuccessEvent(""));
56 @Override
57 public void updateBackend(String backendName, UpdateListener listener) {
58 ServerConnection connection = getServerConnection(options);
59 doUpdate(connection, listener, backendName);
60 listener.onSuccess(new UpdateSuccessEvent(""));
63 @Override
64 public void updateBackends(List<String> backendNames, UpdateListener listener) {
65 ServerConnection connection = getServerConnection(options);
66 for (String backendName : backendNames) {
67 doUpdate(connection, listener, backendName);
69 listener.onSuccess(new UpdateSuccessEvent(""));
72 @Override
73 public void updateAllBackends(UpdateListener listener) {
74 ServerConnection connection = getServerConnection(options);
75 if (app.getBackendsXml() != null) {
76 for (BackendsXml.Entry backend : app.getBackendsXml().getBackends()) {
77 doUpdate(connection, listener, backend.getName());
80 listener.onSuccess(new UpdateSuccessEvent(""));
83 @Override
84 public void rollback() {
85 rollbackBackend(null);
88 @Override
89 public void rollbackBackend(String backend) {
90 ServerConnection connection = getServerConnection(options);
91 try {
92 AppVersionUpload uploader = createAppVersionUpload(connection, app, backend);
93 uploader.forceRollback();
94 } catch (Throwable t) {
95 errorWriter.println("Unable to rollback:");
96 t.printStackTrace(errorWriter);
97 throw new AdminException("Unable to rollback app: " + t.getMessage(), t);
101 @Override
102 public void rollbackAllBackends() {
103 ServerConnection connection = getServerConnection(options);
104 if (app.getBackendsXml() != null) {
105 try {
106 for (BackendsXml.Entry backend : app.getBackendsXml().getBackends()) {
107 AppVersionUpload uploader = createAppVersionUpload(connection, app, backend.getName());
108 uploader.forceRollback();
110 } catch (Throwable t) {
111 errorWriter.println("Unable to rollback:");
112 t.printStackTrace(errorWriter);
113 throw new AdminException("Unable to rollback app: " + t.getMessage(), t);
118 @Override
119 public void setBackendState(String backendName, BackendsXml.State newState) {
120 String url;
121 switch (newState) {
122 case START:
123 url = "/api/backends/start";
124 break;
125 case STOP:
126 url = "/api/backends/stop";
127 break;
128 default:
129 throw new IllegalArgumentException("Cannot change to state: " + newState);
132 ServerConnection connection = getServerConnection(options);
133 try {
134 connection.post(url, "", "app_id", app.getAppId(), "backend", backendName);
135 } catch (Throwable t) {
136 errorWriter.println("Unable to change backend state:");
137 t.printStackTrace(errorWriter);
138 throw new AdminException("Unable to change backend state: " + t.getMessage(), t);
142 @Override
143 public List<BackendsXml.Entry> listBackends() {
144 ServerConnection connection = getServerConnection(options);
145 try {
146 String yaml = connection.post("/api/backends/list", "", "app_id", app.getAppId());
147 if (yaml.contains("No backends configured")) {
148 return Collections.<BackendsXml.Entry>emptyList();
149 } else {
150 BackendsXml xml = BackendsYamlReader.parse(yaml);
151 return xml.getBackends();
153 } catch (Throwable t) {
154 errorWriter.println("Unable to list backends:");
155 t.printStackTrace(errorWriter);
156 throw new AdminException("Unable to list backends: " + t.getMessage(), t);
160 @Override
161 public void deleteBackend(String backendName) {
162 ServerConnection connection = getServerConnection(options);
163 try {
164 connection.post("/api/backends/delete", "", "app_id", app.getAppId(), "backend", backendName);
165 } catch (Throwable t) {
166 errorWriter.println("Unable to delete backend:");
167 t.printStackTrace(errorWriter);
168 throw new AdminException("Unable to delete backend: " + t.getMessage(), t);
172 @Override
173 public void configureBackend(String backendName) {
174 ServerConnection connection = getServerConnection(options);
175 try {
176 connection.post("/api/backends/configure", app.getBackendsXml().toYaml(),
177 "app_id", app.getAppId(), "backend", backendName);
178 } catch (Throwable t) {
179 errorWriter.println("Unable to configure backend:");
180 t.printStackTrace(errorWriter);
181 throw new AdminException("Unable to configure backend: " + t.getMessage(), t);
185 private void changeServerState(String url) {
186 ServerConnection connection = getServerConnection(options);
187 try {
188 connection.post(url,
190 "app_id", app.getAppId(),
191 "server", app.getServer() != null ? app.getServer() : "",
192 "version", app.getVersion());
193 } catch (Throwable t) {
194 errorWriter.println("Unable to change server state:");
195 t.printStackTrace(errorWriter);
196 throw new AdminException("Unable to change server state: " + t.getMessage(), t);
200 @Override
201 public void startServer() {
202 changeServerState("/api/servers/start");
205 @Override
206 public void stopServer() {
207 changeServerState("/api/servers/stop");
210 @Override
211 public void updateIndexes() {
212 ServerConnection connection = getServerConnection(options);
213 try {
214 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
215 uploader.updateIndexes();
216 } catch (Throwable t) {
217 errorWriter.println("Unable to update indexes:");
218 t.printStackTrace(errorWriter);
219 throw new AdminException("Unable to update indexes for app: " + t.getMessage(), t);
223 @Override
224 public void updateCron() {
225 ServerConnection connection = getServerConnection(options);
226 try {
227 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
228 uploader.updateCron();
229 } catch (Throwable t) {
230 errorWriter.println("Unable to update cron entries:");
231 t.printStackTrace(errorWriter);
232 throw new AdminException("Unable to update cron entries for app: " + t.getMessage(), t);
236 @Override
237 public void updateQueues() {
238 ServerConnection connection = getServerConnection(options);
239 try {
240 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
241 uploader.updateQueue();
242 } catch (Throwable t) {
243 errorWriter.println("Unable to upload:");
244 t.printStackTrace(errorWriter);
245 throw new AdminException("Unable to update task queues for app: " + t.getMessage(), t);
249 @Override
250 public void updateDos() {
251 ServerConnection connection = getServerConnection(options);
252 try {
253 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
254 uploader.updateDos();
255 } catch (Throwable t) {
256 errorWriter.println("Unable to update DoS entries:");
257 t.printStackTrace(errorWriter);
258 throw new AdminException("Unable to update DoS entries for app: " + t.getMessage(), t);
262 @Override
263 public void setDefaultVersion() {
264 ServerConnection connection = getServerConnection(options);
265 try {
266 AppVersionUpload uploader = createAppVersionUpload(connection, app, null);
267 uploader.setDefaultVersion();
268 } catch (Throwable t) {
269 errorWriter.println("Unable to set default version:");
270 t.printStackTrace(errorWriter);
271 throw new AdminException("Unable to set default version for app: " + t.getMessage(), t);
275 @Override
276 public List<CronEntry> cronInfo() {
277 try {
278 List<CronEntry> result = new ArrayList<CronEntry>();
280 CronXml cron = app.getCronXml();
281 if (cron == null) {
282 return result;
284 for (CronXml.Entry entry : cron.getEntries()) {
285 result.add(new CronEntryImpl(entry.getUrl(), entry.getDescription(), entry.getSchedule(),
286 entry.getTimezone()));
288 return result;
289 } catch (Throwable t) {
290 errorWriter.println("Unable to display run times for cron entries:");
291 t.printStackTrace(errorWriter);
292 throw new AdminException("Unable to display run times for cron entries for app: "
293 + t.getMessage(), t);
297 @Override
298 public ResourceLimits getResourceLimits() {
299 ServerConnection connection = getServerConnection(options);
300 try {
301 return ResourceLimits.request(connection, app);
302 } catch (Throwable t) {
303 errorWriter.println("Unable to get resource limits:");
304 t.printStackTrace(errorWriter);
305 throw new AdminException("Unable to get resource limits: "
306 + t.getMessage(), t);
310 @Override
311 public void vacuumIndexes(ConfirmationCallback<IndexDeleter.DeleteIndexAction> callback,
312 UpdateListener listener) {
313 String appID = app.getAppId();
314 if (null == appID || appID.isEmpty()) {
315 String message = "This application does not have an ID.";
316 String detailMessage =
317 "The vacuum_indexes operation may not be performed for"
318 + " an application that does not have an ID.";
319 AdminException e = new AdminException(message);
320 listener.onFailure(new UpdateFailureEvent(e, message, detailMessage));
321 throw e;
323 ServerConnection connection = getServerConnection(options);
324 IndexDeleter deleter = new IndexDeleter(connection, app, callback, errorWriter, listener);
325 try {
326 deleter.deleteUnusedIndexes();
327 } catch (Exception e) {
328 String message = "Unable to perform vacuum_indexes";
329 listener.onFailure(new UpdateFailureEvent(e, message, e.getMessage()));
330 throw new AdminException(message, e);
334 @Override
335 public Reader requestLogs(int numDays, LogSeverity severity) {
336 ServerConnection connection = getServerConnection(options);
337 try {
338 File logFile = File.createTempFile(app.getAppId() + "-" + app.getVersion(), ".log");
339 logFile.deleteOnExit();
340 LogFetcher logFetcher = new LogFetcher(app, connection);
341 logFetcher.fetch(numDays, severity, new FileOutputStream(logFile));
342 return new BufferedReader(new FileReader(logFile));
343 } catch (Exception ex) {
344 throw new AdminException("Unable to retrieve the remote application logs:", ex);
349 * Deploy a new version of this application. If successful, this method will
350 * return without throwing an exception but will not call
351 * {@link UpdateListener#onSuccess(UpdateSuccessEvent)}. The caller is responsible for
352 * calling that method.
354 private void doUpdate(ServerConnection connection, UpdateListener listener, String backend) {
355 StringWriter detailsWriter = new StringWriter();
356 try {
357 AppVersionUpload uploader = createAppVersionUpload(connection, app,
358 backend);
359 ResourceLimits resourceLimits = ResourceLimits.request(connection, app);
360 app.resetProgress();
361 app.setListener(listener);
362 app.setDetailsWriter(new PrintWriter(detailsWriter, true));
363 app.createStagingDirectory(appOptions, resourceLimits);
364 uploader.doUpload(resourceLimits);
365 } catch (Throwable t) {
366 errorWriter.println("Unable to update:");
367 t.printStackTrace(errorWriter);
368 listener.onFailure(new UpdateFailureEvent(t, t.toString(), detailsWriter.toString()));
369 throw new AdminException("Unable to update app: " + t.getMessage(), t);
373 private AppVersionUpload createAppVersionUpload(ServerConnection connection,
374 GenericApplication app, String backend) throws Exception {
375 Constructor<? extends AppVersionUpload> constructor =
376 appVersionUploadClass.getConstructor(ServerConnection.class, GenericApplication.class,
377 String.class, Boolean.TYPE);
378 return constructor.newInstance(connection, app, backend, appOptions.isBatchModeSet());