Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
839 lines (773 sloc) 26.7 KB
---
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
title: "Deploy API"
---
<p>
This is the API specification and some examples for the RESTful
HTTP Deploy API that can be used to deploy applications in a Vespa. The API supports multiple tenants
and applications. Each tenant may have multiple applications deployed in different environments and
different regions, all of which can be deployed using this API.
</p><p>
For a tenant,
<a href="#create-session">upload</a>,
<a href="#prepare-session">prepare</a> and
<a href="#activate-session">activate</a> applications.
The response format is JSON. Examples are found in the <a href="#use-cases">use-cases</a>.
</p><p>
The current API version is 2. The API port is 19071 - use
<code><a href="../reference/vespa-cmdline-tools.html#vespa-model-inspect">vespa-model-inspect</a> service configserver</code>
to find config server hosts. Example:
<code>http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session</code>.
All operations are synchronous across the cluster of config servers,
meaning that requests return after the operations complete on all servers.
</p><p>
Note: An application file path in a request URL or parameter refers to a
relative path in the application package. A path ending with '/' refers to a directory.
</p><p>
Entities:
<table class="table">
<thead>
</thead><tbody>
<tr><th id="tenant">Tenant</th>
<td>
The tenant used in this API is created using the <a href="tenant-rest-api.html">tenant REST API</a>.
The tenant name is required for all operations.
Tenant names may only contain word characters and dash: <em>[a-zA-Z_0-9-]</em>.
</td>
</tr>
<tr><th id="application">Application</th>
<td>
The application is the name of the application taken from the application package
or given during <a href="#prepare-session">prepare</a>.
Application names may only contain word characters: <em>[a-zA-Z_0-9]</em>.
</td>
</tr>
<tr><th id="environment">Environment</th>
<td>
Multiple environments are not yet supported. <em>Default</em> is used.
</td>
</tr>
<tr><th id="region">Region</th>
<td>
Multiple regions are not yet supported. <em>Default</em> is used.
</td>
</tr>
<tr><th id="instance">Instance</th>
<td>
Multiple instances are not yet supported. <em>Default</em> is used.
</td>
</tr>
<tr><th id="session-id">Session-id</th>
<td>
The session id used in this API is generated by the server and is required
for all operations after <a href="#create-session">creating</a> a session.
The session id is valid as long as it is one of the last 10 session ids created.
</td>
</tr>
<tr><th id="path">Path</th>
<td>
<!-- ToDo -->
</td>
</tr>
</tbody>
</table>
</p><p>
When deploying to Vespa, use the <a href="application-packages.html#deploy">vespa-deploy</a>
tool which wraps the Deploy API.
</p>
<h2 id="create-session">POST /application/v2/tenant/[<a href="#tenant">tenant name</a>]/session</h2>
<p>
Creates a new session with the application package that is included in the request.
</p>
<table class="table">
<thead>
</thead><tbody>
<tr><th id="create-get-params">Parameters</th>
<td>
<table class="table">
<thead>
<tr><th>Name</th><th>Default</th><th>Description</th></tr>
</thead>
<tbody>
<tr>
<td>from</td>
<td>N/A</td>
<td>Should only be used when you want to create a session based
on an active application. Valid values
are a url to an active application.
A new session will be created based on the corresponding application.
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr><th id="create-request-body">Request body</th>
<td>
<table class="table">
<thead>
<tr><th>Required</th><th>Content</th><th>Note</th></tr>
</thead>
<tbody>
<tr>
<td>Yes, unless <code>from</code> parameter is used</td>
<td>A compressed <a href="application-packages.html">application
package</a> (with gzip or zip compression)</td>
<td> It is required to set the <code>Content-Type</code> HTTP
header to <code>application/x-gzip</code> or <code>application/zip</code>, unless the
<code>from</code> parameter is used.</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr><th id="create-session-response">Response</th>
<td>
A response includes:
<ul>
<li>A <a href="#session-id">session id</a> to the application that was created.</li>
<li>A <a href="#prepare-session">prepared</a> URL for preparing the application.</li>
</ul>
</td>
</tr>
</tbody>
</table>
<p>
Examples (both requests return same response):<br/>
<code>POST /application/v2/tenant/mytenant/session</code><br/>
<code>POST /application/v2/tenant/mytenant/session?from=http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/application/myapp/environment/default/region/default/instance/default</code>
<pre>
{
"tenant": "mytenant",
"session-id": "1",
"prepared": "http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/<em>session-id</em>/prepared/",
"content": "http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/<em>session-id</em>/content/",
"message": "Session 1 for tenant 'mytenant' created."
}
</pre>
</p>
<h2 id="content-put">PUT /application/v2/tenant/[<a href="#tenant">tenant name</a>]/session/[<a href="#session-id">session id</a>]/content/[<a href="#path">path</a>]</h2>
<p>
Writes the content to the given path, or creates a directory if the path ends with '/'.
</p>
<table class="table">
<thead>
</thead><tbody>
<tr><th>Parameters</th>
<td>
None
</td>
</tr>
<tr><th id="content-put-request-body">Request body</th>
<td>
<ul>
<li>If path is a directory, none.</li>
<li>If path is a file, the contents of the file.</li>
</ul>
</td>
</tr>
<tr><th id="content-put-response">Response</th>
<td>
None
<ul>
<li>Any errors or warnings from writing the file/creating the directory.</li>
</ul>
</td>
</tr>
</tbody>
</table>
<h2 id="content-get">GET /application/v2/tenant/[<a href="#tenant">tenant name</a>]/session/[<a href="#session-id">session id</a>]/content/[<a href="#path">path</a>]</h2>
<p>
Returns the content of the file at this path, or lists files and
directories if <code>path</code> ends with '/'.
<table class="table">
<thead>
</thead><tbody>
<tr><th id="content-get-params">Parameters</th>
<td>
<table class="table">
<thead>
<tr>
<th>Name</th><th>Default</th><th>Description</th></tr>
</thead>
<tbody>
<tr>
<td>recursive</td>
<td>false</td>
<td>If true, directory content will be listed recursively.</td>
</tr>
<tr>
<td>return</td>
<td>content</td>
<td>
<ul>
<li>If set to content and path refers to a file, the content will be
returned.</li>
<li>If set to content and path refers to a directory, the files and
subdirectories in the directory will be listed.</li>
<li>If set to status and path refers to a file, the file status and hash will
be returned.</li>
<li>If set to status and path refers to a directory, a list of file/subdirectory
statuses and hashes will be returned.</li>
</ul>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr><th>Request body</th>
<td>
None.
</td>
</tr>
<tr><th id="content-get-response">Response</th>
<td>
<ul>
<li>If path is a directory: a JSON array of URLs to the files and subdirectories of
that directory.</li>
<li>If path is a file: the contents of the file.</li>
<li>If status parameter is set, the status and hash will be returned.</li>
</ul>
</td>
</tr>
</tbody>
</table>
Examples:
<code>GET /application/v2/tenant/mytenant/session/3/content/</code>
<pre>
[
"http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/hosts.xml",
"http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/services.xml",
"http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/searchdefinitions/"
]
</pre>
<code>GET /application/v2/tenant/mytenant/session/3/content/?recursive=true</code>
<pre>
[
"http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/hosts.xml",
"http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/services.xml",
"http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/searchdefinitions/",
"http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/searchdefinitions/music.sd",
"http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/searchdefinitions/video.sd"
]
</pre>
<code>GET /application/v2/tenant/mytenant/session/3/content/hosts.xml</code>
<pre>
&lt;?xml version="1.0" encoding="utf-8" ?&gt;
&lt;hosts&gt;
&lt;host name="myhost.mydomain.com"&gt;
&lt;alias&gt;vespa1&lt;/alias&gt;
&lt;/host&gt;
&lt;host name="myhost.mydomain.com"&gt;
&lt;alias&gt;vespa2&lt;/alias&gt;
&lt;/host&gt;
&lt;/hosts&gt;
</pre>
<code>GET /application/v2/tenant/mytenant/session/3/content/hosts.xml?return=status</code>
<pre>
{
"name": "http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/hosts.xml",
"status": "new",
"md5": "03d7cff861fcc2d88db70b7857d4d452"
}
</pre>
<code>GET /application/v2/tenant/mytenant/session/3/content/searchdefinitions/?return=status</code>
<pre>
[
{
"name": "http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/searchdefinitions/music.sd",
"status": "new",
"md5": "03d7cff861fcc2d88db70b7857d4d452"
},
{
"name": "http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/searchdefinitions/video.sd",
"status": "changed",
"md5": "03d7cff861fcc2d88db70b7857d4d452"
},
{
"name": "http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/content/searchdefinitions/book.sd",
"status": "deleted",
"md5": "03d7cff861fcc2d88db70b7857d4d452"
}
]
</pre>
</p>
<h2 id="content-delete">DELETE /application/v2/tenant/[<a href="#tenant">tenant name</a>]/session/[<a href="#session-id">session id</a>]/content/[<a href="#path">path</a>]</h2>
<p>
Deletes the resource at the given path.
<table class="table">
<thead>
</thead><tbody>
<tr><th>Parameters</th>
<td>
None
</td>
</tr>
<tr><th id="content-delete-request-body">Request body</th>
<td>
None
</td>
</tr>
<tr><th id="content-delete-response">Response</th>
<td>
Any errors or warnings from deleting the resource.
</td>
</tr>
</tbody>
</table>
</p>
<h2 id="prepare-session">PUT /application/v2/tenant/[<a href="#tenant">tenant name</a>]/session/[<a href="#session-id">session id</a>]/prepared</h2>
<p>
Prepares an application with the <a href="#session-id">session id</a> given.
<table class="table">
<thead>
</thead><tbody>
<tr><th id="prepare-put-params">Parameters</th>
<td>
<table class="table">
<thead>
<tr>
<th>Parameter</th><th>Default</th><th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>applicationName</td>
<td>N/A</td>
<td>Name of the application to be deployed</td>
</tr>
<tr>
<td>environment</td>
<td>default</td>
<td>Environment where application should be deployed</td>
</tr>
<tr>
<td>region</td>
<td>default</td>
<td>Region where application should be deployed</td>
</tr>
<tr>
<td>instance</td>
<td>default</td>
<td>Name of application instance</td>
</tr>
<tr>
<td>debug</td>
<td>false</td>
<td>If true, include stack trace in response if prepare fails.</td>
</tr>
<tr>
<td>timeout</td>
<td>300 seconds</td>
<td>Timeout in seconds to wait for session to be prepared.</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr><th id="prepare-session-request-body">Request body</th>
<td>
None
</td>
</tr>
<tr><th id="prepare-session-response">Response</th>
<td>
Returns a <a href="#session-id">session id</a> and a link to activate the session.
<ul>
<li>Log with any errors or warnings from preparing the application.</li>
<li>An <a href="#activate-session">activate</a> URL for activating
the application with this <a href="#session-id">session id</a>, if there were no
errors.</li>
<li>A list of actions (possibly empty) that must be performed in order to apply some config changes
between the current active application and this next prepared application.
These actions are organized into two categories; restart and refeed:
<ul>
<li>Restart actions are done after the application has been activated and are handled by restarting all listed services. Please see <a href="../search-definitions.html#modify-search-definitions">
search definitions</a> for more information.</li>
<li>Refeed actions require several steps to handle. Please see
<a href="../search-definitions.html#modify-search-definitions">search definitions</a>
for more information.</li>
</ul>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
Example: <code>PUT /application/v2/tenant/mytenant/session/3/prepared</code>
<pre>
{
"tenant": "mytenant",
"activate": "http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/session/3/active",
"message": "Session 3 for tenant 'mytenant' prepared.",
"log": [
{ "level": "WARNING",
"message": "Warning message 1",
"time": 1430134091319
},
{ "level": "WARNING",
"message": "Warning message 2",
"time": 1430134091320
}
],
"configChangeActions": {
"restart": [ {
"clusterName": "mycluster",
"clusterType": "search",
"serviceType": "searchnode",
"messages": [ "Document type 'test': Field 'f1' changed: add attribute aspect" ],
"services": [ {
"serviceName": "searchnode",
"serviceType": "searchnode",
"configId": "mycluster/search/cluster.mycluster/0",
"hostName": "myhost.mydomain.com"
} ]
} ],
"refeed": [ {
"documentType": "test",
"clusterName": "mycluster",
"messages": [ "Document type 'test': Field 'f1' changed: add index aspect" ],
"services": [ {
"serviceName": "searchnode",
"serviceType": "searchnode",
"configId": "mycluster/search/cluster.mycluster/0",
"hostName": "myhost.mydomain.com"
} ]
} ]
}
}
</pre>
<p/>
<h2 id="get-prepare-session">GET /application/v2/tenant/mytenant/session/[<a href="#session-id">session id</a>]/prepared</h2>
<p>
Returns the state of a prepared session.
The response is the same as a successful <a href="#prepare-session">prepare</a> operation (above),
however the <em>configChangeActions</em> element will be empty.
</p>
<h2 id="activate-session">PUT /application/v2/tenant/[<a href="#tenant">tenant name</a>]/session/[<a href="#session-id">session id</a>]/active</h2>
<p>
Activates an application with the <a href="#session-id">session id</a> given.
The <a href="#session-id">session id</a> must be for a
<a href="#prepare-session">prepared session</a>.
The operation will make sure the session is activated on all config servers,
if more than one is configured.
<table class="table">
<thead>
</thead><tbody>
<tr><th id="activate-params">Parameters</th>
<td>
<table class="table">
<thead>
<tr>
<th>Parameter</th><th>Default</th><th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>timeout</td>
<td>60 seconds</td>
<td>Timeout in seconds to wait for session to be activated (when
several config servers are used, they might need to sync
before activate can be done).</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr><th>Request body</th>
<td>
None
</td>
</tr>
<tr><th id="activate-session-response">Response</th>
<td>
Returns a <a href="#session-id">session id</a>, a message and a URL to the activated application.
<ul>
<li><a href="#session-id">session id</a></li>
<li>Message</li>
</ul>
</td>
</tr>
</tbody>
</table>
Example: <code>PUT /application/v2/tenant/mytenant/session/3/active</code>
<pre>
{
"tenant": "mytenant",
"message": "Session 3 for tenant 'mytenant' activated.",
"url": "http://myconfigserver.mydomain.com:19071/application/v2/tenant/mytenant/application/myapp/environment/default/region/default/instance/default"
}
</pre>
</p>
<h2 id="get-application">GET /application/v2/tenant/[<a href="#tenant">tenant name</a>]/application/</h2>
<p>
Returns a list of the currently active applications for the given
tenant
<table class="table">
<thead>
</thead><tbody>
<tr><th>Parameters</th>
<td>
None
</td>
</tr>
<tr><th>Request body</th>
<td>
None
</td>
</tr>
<tr><th id="get-application-response">Response</th>
<td>
Returns a list of applications
<ul>
<li>Array of active applications</li>
</ul>
</td>
</tr>
</tbody>
</table>
Example: <code>GET /application/v2/tenant/mytenant/application/</code>
<pre>
{
["http://myconfigserver.mydomain.com:19071/application/v2/tenant/default/application/default/environment/default/region/default/instance/default"]
}
</pre>
</p>
<h2 id="get-application-info">GET /application/v2/tenant/[<a href="#tenant">tenant name</a>]/application/[<a href="#application">application name</a>]</h2>
<p>
Gets info about the application.
<table class="table">
<thead>
</thead><tbody>
<tr><th>Parameters</th>
<td>
None
</td>
</tr>
<tr><th>Request body</th>
<td>
None
</td>
</tr>
<tr><th id="get-application-info-response">Response</th>
<td>
Returns information about the application specified.
<ul>
<li>config generation</li>
</ul>
</td>
</tr>
</tbody>
</table>
Example: <code>GET /application/v2/tenant/mytenant/application/foo</code>
<pre>
{
"generation": 2
}
</pre>
</p>
<h2 id="delete-application">DELETE /application/v2/tenant/[<a href="#tenant">tenant name</a>]/application/[<a href="#application">application name</a>]</h2>
<p>
Deletes an active application
<table class="table">
<thead>
</thead><tbody>
<tr><th>Parameters</th>
<td>
None
</td>
</tr>
<tr><th>Request body</th>
<td>
None
</td>
</tr>
<tr><th id="delete-application-response">Response</th>
<td>
Returns a message stating if the operation was successful or not
</td>
</tr>
</tbody>
</table>
Example: <code>DELETE /application/v2/tenant/mytenant/application/foo</code>
<pre>
{
"message": "Application 'foo' was deleted"
}
</pre>
</p>
<h2 id="get-host-info">GET /application/v2/host/[hostname]</h2>
<p>
Gets information about which tenant and application a hostname is used by.
<table class="table">
<thead>
</thead><tbody>
<tr><th>Parameters</th>
<td>
None
</td>
</tr>
<tr><th>Request body</th>
<td>
None
</td>
</tr>
<tr><th id="get-host-info-response">Response</th>
<td>
Returns a message with tenant and application details.
</td>
</tr>
</tbody>
</table>
Example: <code>GET /application/v2/host/myhost.mydomain.com</code>
<pre>
{
"tenant":"mytenant"
"application":"foo"
"environment":"test"
"region":"Europe"
"instance":"1"
}
</pre>
</p>
<h2 id="error-handling">Error Handling</h2>
<p>
Errors are returned using standard HTTP status codes. Any additional
info is included in the body of the return call, JSON-formatted.
The general format for an error response is:
<pre>
{
"error-code": "ERROR_CODE",
"message": "An error message"
}
</pre>
<table class="table">
<thead>
<tr>
<th>HTTP status code</th><th>Error code</th><th>Description</th>
</tr>
</thead><tbody>
<tr>
<td>400</td><td>BAD_REQUEST</td>
<td>Bad request. Client error. The error message should indicate the cause.</td>
</tr><tr>
<td>400</td><td>INVALID_APPLICATION_PACKAGE</td>
<td>There is an error in the application package. The error message should indicate the cause.</td>
</tr><tr>
<td>400</td><td>OUT_OF_CAPACITY</td>
<td>Not enough nodes available for the request to be fulfilled.</td>
</tr><tr>
<td>401</td><td></td>
<td>Not authorized. The error message should indicate the cause.</td>
</tr><tr>
<td>404</td><td>NOT_FOUND</td>
<td>Not found. E.g. when using a session id that doesn't exist.</td>
</tr><tr>
<td>405</td><td>METHOD_NOT_ALLOWED</td>
<td>Method not implemented. E.g. using GET where only POST or PUT is allowed.</td>
</tr><tr>
<td>409</td><td>CONFLICT</td>
<td>Conflict, returned when activating an application fails due
to a conflict with other changes to the same application (in
another session). Client should retry.</td>
</tr><tr>
<td>500</td><td>INTERNAL_SERVER_ERROR</td>
<td>Internal server error. Generic error. The error message should indicate the cause.</td>
</tr>
</tbody>
</table>
</p>
<h2 id="request-log">Request log</h2>
<p>
The log files for calls to the Deploy API are named configserver.&lt;timestamp&gt; and are found in
<em>$VESPA_HOME/logs/vespa/configserver/access.log</em>. The format is the same as for the
<a href="../reference/logs.html#access-log-file-content">container access log</a> - example:
<pre>
10.76.246.20 - - [31/Jan/2013:22:05:45 +0000] "PUT /application/v2/tenant/mytenant/session/1/active HTTP/1.1" 200 1238 "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) Gecko/20100101 Firefox/18.0" 1359669945.93 1 0.0 1
</pre>
</p>
<h2 id="use-cases">Use Cases</h2>
<p>
It is assumed that the tenant <em>mytenant</em> is already created in these use cases,
and the application package is in <em>app</em>.
</p>
<h3 id="use-case-start">Create, prepare and activate an application</h3>
<p>
Create a session with the application package:
<pre>
$ tar -C app -cf - . | gzip | curl -s --header "Content-Type:
application/x-gzip" --data-binary @- \
"http://host:19071/application/v2/tenant/mytenant/session"
</pre>
Prepare the application with the URL in the <em>prepared</em> link
from the response:
<pre>
$ curl -s -X PUT "http://host:19071/application/v2/tenant/mytenant/session/1/prepared?applicationName=fooapp"
</pre>
Activate the application with the URL in the <em>activate</em> link from the response:
<pre>
$ curl -s -X PUT "http://host:19071/application/v2/tenant/mytenant/session/1/active"
</pre>
</p>
<h3 id="use-case-modify">Modify the application package</h3>
<p>
Dump <em>services.xml</em> from session 1:
<pre>
$ curl -s -X GET "http://host:19071/application/v2/tenant/mytenant/session/1/content/services.xml"
&lt;?xml version="1.0" encoding="utf-8" ?&gt;
&lt;services version="1.0"&gt;
&lt;admin version="2.0"&gt;
&lt;config name="config.logd"&gt;
&lt;logserver&gt;
&lt;port&gt;12345&lt;/port&gt;
&lt;/logserver&gt;
&lt;/config&gt;
&lt;adminserver hostalias="node1" /&gt;
&lt;/admin&gt;
&lt;/services&gt;
</pre>
Session 1 is activated and cannot be changed - create a new session based on the active session:
<pre>
$ curl -s -X POST "http://host:19071/application/v2/tenant/mytenant/session?from=http://host:19071/application/v2/tenant/mytenant/application/fooapp/environment/default/region/default/instance/default"
</pre>
</p><p>
Modify port number in <em>services.xml</em>, deploy the change:
<pre>
$ curl -s -X PUT --data-binary @app/services.xml \
"http://host:19071/application/v2/tenant/mytenant/session/2/content/services.xml"
</pre>
Get <em>services.xml</em> from session 2 to validate:
<pre>
$ curl -s -X GET "http://host:19071/application/v2/tenant/mytenant/session/2/content/services.xml"
&lt;?xml version="1.0" encoding="utf-8" ?&gt;
&lt;services version="1.0"&gt;
&lt;admin version="2.0"&gt;
&lt;config name="config.logd"&gt;
&lt;logserver&gt;
&lt;port&gt;12346&lt;/port&gt;
&lt;/logserver&gt;
&lt;/config&gt;
&lt;adminserver hostalias="node1" /&gt;
&lt;/admin&gt;
&lt;/services&gt;
</pre>
To add the file <em>files/test1.txt</em>, first create the directory, then add the file:
<pre>
$ curl -s -X PUT "http://host:19071/application/v2/tenant/mytenant/session/2/content/files/"
$ curl -s -X PUT --data-binary @app/files/test1.txt "http://host:19071/application/v2/tenant/mytenant/session/2/content/files/test1.txt"
</pre>
Prepare and activate the session:
<pre>
$ curl -s -X PUT "http://host:19071/application/v2/tenant/mytenant/session/2/prepared?applicationName=fooapp"
$ curl -s -X PUT "http://host:19071/application/v2/tenant/mytenant/session/2/active"
</pre>
</p>
<h3 id="use-case-rollback">Rollback</h3>
<p>
Say that you find out that the changes done in the latest application did not work
out as expected and you want to rollback to a previous state known
to work. This is achieved by having multiple instance of an application. To rollback,
simply create a new session passing the previous instance as the from argument.
</p>
<!-- TODO: Need multiple instances to support this. -->