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 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
;
40 this.errorWriter
= errorWriter
;
41 this.appOptions
= appOptions
;
42 this.appVersionUploadClass
= appVersionUploadClass
;
45 protected ServerConnection
getServerConnection(ConnectOptions options
) {
46 return ServerConnectionFactory
.getServerConnection(options
);
50 public void update(UpdateListener listener
) {
51 ServerConnection connection
= getServerConnection(options
);
52 doUpdate(connection
, listener
, null);
53 listener
.onSuccess(new UpdateSuccessEvent(""));
57 public void updateBackend(String backendName
, UpdateListener listener
) {
58 ServerConnection connection
= getServerConnection(options
);
59 doUpdate(connection
, listener
, backendName
);
60 listener
.onSuccess(new UpdateSuccessEvent(""));
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(""));
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(""));
84 public void rollback() {
85 rollbackBackend(null);
89 public void rollbackBackend(String backend
) {
90 ServerConnection connection
= getServerConnection(options
);
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
);
102 public void rollbackAllBackends() {
103 ServerConnection connection
= getServerConnection(options
);
104 if (app
.getBackendsXml() != null) {
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
);
119 public void setBackendState(String backendName
, BackendsXml
.State newState
) {
123 url
= "/api/backends/start";
126 url
= "/api/backends/stop";
129 throw new IllegalArgumentException("Cannot change to state: " + newState
);
132 ServerConnection connection
= getServerConnection(options
);
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
);
143 public List
<BackendsXml
.Entry
> listBackends() {
144 ServerConnection connection
= getServerConnection(options
);
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();
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
);
161 public void deleteBackend(String backendName
) {
162 ServerConnection connection
= getServerConnection(options
);
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
);
173 public void configureBackend(String backendName
) {
174 ServerConnection connection
= getServerConnection(options
);
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
);
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
);
201 public void startServer() {
202 changeServerState("/api/servers/start");
206 public void stopServer() {
207 changeServerState("/api/servers/stop");
211 public void updateIndexes() {
212 ServerConnection connection
= getServerConnection(options
);
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
);
224 public void updateCron() {
225 ServerConnection connection
= getServerConnection(options
);
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
);
237 public void updateQueues() {
238 ServerConnection connection
= getServerConnection(options
);
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
);
250 public void updateDos() {
251 ServerConnection connection
= getServerConnection(options
);
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
);
263 public void setDefaultVersion() {
264 ServerConnection connection
= getServerConnection(options
);
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
);
276 public List
<CronEntry
> cronInfo() {
278 List
<CronEntry
> result
= new ArrayList
<CronEntry
>();
280 CronXml cron
= app
.getCronXml();
284 for (CronXml
.Entry entry
: cron
.getEntries()) {
285 result
.add(new CronEntryImpl(entry
.getUrl(), entry
.getDescription(), entry
.getSchedule(),
286 entry
.getTimezone()));
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
);
298 public ResourceLimits
getResourceLimits() {
299 ServerConnection connection
= getServerConnection(options
);
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
);
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
));
323 ServerConnection connection
= getServerConnection(options
);
324 IndexDeleter deleter
= new IndexDeleter(connection
, app
, callback
, errorWriter
, listener
);
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
);
335 public Reader
requestLogs(int numDays
, LogSeverity severity
) {
336 ServerConnection connection
= getServerConnection(options
);
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();
357 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
,
359 ResourceLimits resourceLimits
= ResourceLimits
.request(connection
, app
);
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());