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 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
);
49 public void update(UpdateListener listener
) {
50 ServerConnection connection
= getServerConnection(options
);
51 doUpdate(connection
, listener
, null);
52 listener
.onSuccess(new UpdateSuccessEvent(""));
55 public void updateBackend(String backendName
, UpdateListener listener
) {
56 ServerConnection connection
= getServerConnection(options
);
57 doUpdate(connection
, listener
, backendName
);
58 listener
.onSuccess(new UpdateSuccessEvent(""));
61 public void updateBackends(List
<String
> backendNames
, UpdateListener listener
) {
62 ServerConnection connection
= getServerConnection(options
);
63 for (String backendName
: backendNames
) {
64 doUpdate(connection
, listener
, backendName
);
66 listener
.onSuccess(new UpdateSuccessEvent(""));
69 public void updateAllBackends(UpdateListener listener
) {
70 ServerConnection connection
= getServerConnection(options
);
71 if (app
.getBackendsXml() != null) {
72 for (BackendsXml
.Entry backend
: app
.getBackendsXml().getBackends()) {
73 doUpdate(connection
, listener
, backend
.getName());
76 listener
.onSuccess(new UpdateSuccessEvent(""));
79 public void rollback() {
80 rollbackBackend(null);
83 public void rollbackBackend(String backend
) {
84 ServerConnection connection
= getServerConnection(options
);
86 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, backend
);
87 uploader
.forceRollback();
88 } catch (Throwable t
) {
89 errorWriter
.println("Unable to rollback:");
90 t
.printStackTrace(errorWriter
);
91 throw new AdminException("Unable to rollback app: " + t
.getMessage(), t
);
95 public void rollbackAllBackends() {
96 ServerConnection connection
= getServerConnection(options
);
97 if (app
.getBackendsXml() != null) {
99 for (BackendsXml
.Entry backend
: app
.getBackendsXml().getBackends()) {
100 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, backend
.getName());
101 uploader
.forceRollback();
103 } catch (Throwable t
) {
104 errorWriter
.println("Unable to rollback:");
105 t
.printStackTrace(errorWriter
);
106 throw new AdminException("Unable to rollback app: " + t
.getMessage(), t
);
111 public void setBackendState(String backendName
, BackendsXml
.State newState
) {
115 url
= "/api/backends/start";
118 url
= "/api/backends/stop";
121 throw new IllegalArgumentException("Cannot change to state: " + newState
);
124 ServerConnection connection
= getServerConnection(options
);
126 connection
.post(url
, "", "app_id", app
.getAppId(), "backend", backendName
);
127 } catch (Throwable t
) {
128 errorWriter
.println("Unable to change backend state:");
129 t
.printStackTrace(errorWriter
);
130 throw new AdminException("Unable to change backend state: " + t
.getMessage(), t
);
134 public List
<BackendsXml
.Entry
> listBackends() {
135 ServerConnection connection
= getServerConnection(options
);
137 String yaml
= connection
.post("/api/backends/list", "", "app_id", app
.getAppId());
138 if (yaml
.contains("No backends configured")) {
139 return Collections
.<BackendsXml
.Entry
>emptyList();
141 BackendsXml xml
= BackendsYamlReader
.parse(yaml
);
142 return xml
.getBackends();
144 } catch (Throwable t
) {
145 errorWriter
.println("Unable to list backends:");
146 t
.printStackTrace(errorWriter
);
147 throw new AdminException("Unable to list backends: " + t
.getMessage(), t
);
151 public void deleteBackend(String backendName
) {
152 ServerConnection connection
= getServerConnection(options
);
154 connection
.post("/api/backends/delete", "", "app_id", app
.getAppId(), "backend", backendName
);
155 } catch (Throwable t
) {
156 errorWriter
.println("Unable to delete backend:");
157 t
.printStackTrace(errorWriter
);
158 throw new AdminException("Unable to delete backend: " + t
.getMessage(), t
);
162 public void configureBackend(String backendName
) {
163 ServerConnection connection
= getServerConnection(options
);
165 connection
.post("/api/backends/configure", app
.getBackendsXml().toYaml(),
166 "app_id", app
.getAppId(), "backend", backendName
);
167 } catch (Throwable t
) {
168 errorWriter
.println("Unable to configure backend:");
169 t
.printStackTrace(errorWriter
);
170 throw new AdminException("Unable to configure backend: " + t
.getMessage(), t
);
174 public void updateIndexes() {
175 ServerConnection connection
= getServerConnection(options
);
177 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
178 uploader
.updateIndexes();
179 } catch (Throwable t
) {
180 errorWriter
.println("Unable to update indexes:");
181 t
.printStackTrace(errorWriter
);
182 throw new AdminException("Unable to update indexes for app: " + t
.getMessage(), t
);
186 public void updateCron() {
187 ServerConnection connection
= getServerConnection(options
);
189 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
190 uploader
.updateCron();
191 } catch (Throwable t
) {
192 errorWriter
.println("Unable to update cron entries:");
193 t
.printStackTrace(errorWriter
);
194 throw new AdminException("Unable to update cron entries for app: " + t
.getMessage(), t
);
198 public void updateQueues() {
199 ServerConnection connection
= getServerConnection(options
);
201 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
202 uploader
.updateQueue();
203 } catch (Throwable t
) {
204 errorWriter
.println("Unable to upload:");
205 t
.printStackTrace(errorWriter
);
206 throw new AdminException("Unable to update task queues for app: " + t
.getMessage(), t
);
210 public void updateDos() {
211 ServerConnection connection
= getServerConnection(options
);
213 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
214 uploader
.updateDos();
215 } catch (Throwable t
) {
216 errorWriter
.println("Unable to update DoS entries:");
217 t
.printStackTrace(errorWriter
);
218 throw new AdminException("Unable to update DoS entries for app: " + t
.getMessage(), t
);
223 public void setDefaultVersion() {
224 ServerConnection connection
= getServerConnection(options
);
226 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
, null);
227 uploader
.setDefaultVersion();
228 } catch (Throwable t
) {
229 errorWriter
.println("Unable to set default version:");
230 t
.printStackTrace(errorWriter
);
231 throw new AdminException("Unable to set default version for app: " + t
.getMessage(), t
);
235 public List
<CronEntry
> cronInfo() {
237 List
<CronEntry
> result
= new ArrayList
<CronEntry
>();
239 CronXml cron
= app
.getCronXml();
243 for (CronXml
.Entry entry
: cron
.getEntries()) {
244 result
.add(new CronEntryImpl(entry
.getUrl(), entry
.getDescription(), entry
.getSchedule(),
245 entry
.getTimezone()));
248 } catch (Throwable t
) {
249 errorWriter
.println("Unable to display run times for cron entries:");
250 t
.printStackTrace(errorWriter
);
251 throw new AdminException("Unable to display run times for cron entries for app: "
252 + t
.getMessage(), t
);
257 public ResourceLimits
getResourceLimits() {
258 ServerConnection connection
= getServerConnection(options
);
260 return ResourceLimits
.request(connection
, app
);
261 } catch (Throwable t
) {
262 errorWriter
.println("Unable to get resource limits:");
263 t
.printStackTrace(errorWriter
);
264 throw new AdminException("Unable to get resource limits: "
265 + t
.getMessage(), t
);
269 public void vacuumIndexes(ConfirmationCallback
<IndexDeleter
.DeleteIndexAction
> callback
,
270 UpdateListener listener
) {
271 String appID
= app
.getAppId();
272 if (null == appID
|| appID
.isEmpty()) {
273 String message
= "This application does not have an ID.";
274 String detailMessage
=
275 "The vacuum_indexes operation may not be performed for"
276 + " an application that does not have an ID.";
277 AdminException e
= new AdminException(message
);
278 listener
.onFailure(new UpdateFailureEvent(e
, message
, detailMessage
));
281 ServerConnection connection
= getServerConnection(options
);
282 IndexDeleter deleter
= new IndexDeleter(connection
, app
, callback
, errorWriter
, listener
);
284 deleter
.deleteUnusedIndexes();
285 } catch (Exception e
) {
286 String message
= "Unable to perform vacuum_indexes";
287 listener
.onFailure(new UpdateFailureEvent(e
, message
, e
.getMessage()));
288 throw new AdminException(message
, e
);
292 public Reader
requestLogs(int numDays
, LogSeverity severity
) {
293 ServerConnection connection
= getServerConnection(options
);
295 File logFile
= File
.createTempFile(app
.getAppId() + "-" + app
.getVersion(), ".log");
296 logFile
.deleteOnExit();
297 LogFetcher logFetcher
= new LogFetcher(app
, connection
);
298 logFetcher
.fetch(numDays
, severity
, new FileOutputStream(logFile
));
299 return new BufferedReader(new FileReader(logFile
));
300 } catch (Exception ex
) {
301 throw new AdminException("Unable to retrieve the remote application logs:", ex
);
306 * Deploy a new version of this application. If successful, this method will
307 * return without throwing an exception but will not call
308 * {@link UpdateListener#onSuccess(UpdateSuccessEvent)}. The caller is responsible for
309 * calling that method.
311 private void doUpdate(ServerConnection connection
, UpdateListener listener
, String backend
) {
312 StringWriter detailsWriter
= new StringWriter();
314 AppVersionUpload uploader
= createAppVersionUpload(connection
, app
,
316 ResourceLimits resourceLimits
= ResourceLimits
.request(connection
, app
);
318 app
.setListener(listener
);
319 app
.setDetailsWriter(new PrintWriter(detailsWriter
, true));
320 app
.createStagingDirectory(appOptions
, resourceLimits
);
321 uploader
.doUpload(resourceLimits
);
322 } catch (Throwable t
) {
323 errorWriter
.println("Unable to update:");
324 t
.printStackTrace(errorWriter
);
325 listener
.onFailure(new UpdateFailureEvent(t
, t
.toString(), detailsWriter
.toString()));
326 throw new AdminException("Unable to update app: " + t
.getMessage(), t
);
330 private AppVersionUpload
createAppVersionUpload(ServerConnection connection
,
331 GenericApplication app
, String backend
) throws Exception
{
332 Constructor
<?
extends AppVersionUpload
> constructor
=
333 appVersionUploadClass
.getConstructor(ServerConnection
.class, GenericApplication
.class,
334 String
.class, Boolean
.TYPE
);
335 return constructor
.newInstance(connection
, app
, backend
, appOptions
.isBatchModeSet());