summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSacheendra talluri <sacheendra.t@gmail.com>2017-04-04 15:34:01 +0200
committerGitHub <noreply@github.com>2017-04-04 15:34:01 +0200
commit693a23d17c8783e51a97fd5a2439b5d1ceaa003c (patch)
treefdad42c44a147e03101925ffd4d62a869bac4490
parent59b05ac8425e34cdf41179506cdda357c59b4b12 (diff)
parentfd0376c5a8f018457a11bb0f9602bda366571955 (diff)
Merge pull request #6 from atlarge-research/api-over-http
Access API over HTTP
-rw-r--r--.gitignore2
-rw-r--r--OpenDC.postman_collection.json61
-rw-r--r--README.md26
-rw-r--r--main.py103
-rw-r--r--opendc/api/v1/paths.json48
-rw-r--r--opendc/api/v1/users/endpoint.py2
-rw-r--r--opendc/util/path_parser.py39
-rw-r--r--opendc/util/rest.py5
-rw-r--r--static/index.html32
9 files changed, 255 insertions, 63 deletions
diff --git a/.gitignore b/.gitignore
index e7a8a360..14b2f25e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,4 @@ _mailinglist
.tox
.cache/
.idea/
-*.json
+config.json
diff --git a/OpenDC.postman_collection.json b/OpenDC.postman_collection.json
new file mode 100644
index 00000000..2fa03f58
--- /dev/null
+++ b/OpenDC.postman_collection.json
@@ -0,0 +1,61 @@
+{
+ "variables": [],
+ "info": {
+ "name": "OpenDC",
+ "_postman_id": "e8b68f59-29cb-71d0-6237-b22932c40f9c",
+ "description": "Sample requests for developing the OpenDC API",
+ "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "Create New Simulation",
+ "request": {
+ "url": "localhost:8081/api/v1/simulations",
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "description": ""
+ },
+ {
+ "key": "auth-token",
+ "value": "PUT YOUR AUTH TOKEN HERE",
+ "description": ""
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"simulation\": {\r\n \"name\": \"Simulation Name\"\r\n }\r\n}"
+ },
+ "description": ""
+ },
+ "response": []
+ },
+ {
+ "name": "Create New User",
+ "request": {
+ "url": "localhost:8081/api/v1/users",
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "description": ""
+ },
+ {
+ "key": "auth-token",
+ "value": "PUT YOUR AUTH TOKEN HERE",
+ "description": ""
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"user\": {\r\n \"email\": \"email@example.com\"\r\n }\r\n}"
+ },
+ "description": ""
+ },
+ "response": []
+ }
+ ]
+} \ No newline at end of file
diff --git a/README.md b/README.md
index 187d4133..5cd95019 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,8 @@ The following steps will guide you through setting up the OpenDC web server loca
### Local Setup
+#### Install requirements
+
Make sure you have Python 2.7 installed (if not, get it [here](https://www.python.org/)), as well as pip (if not, get it [here](https://pip.pypa.io/en/stable/installing/)). Then run the following to install the requirements.
```bash
@@ -50,6 +52,8 @@ pip install oauth2client
pip install eventlet
```
+#### Get the code
+
Clone both this repository and the main OpenDC repository, from the same base directory.
```bash
@@ -57,6 +61,8 @@ git clone https://github.com/atlarge-research/opendc-web-server.git
git clone https://github.com/atlarge-research/opendc.git
```
+#### Set up the database
+
Set up the database, replacing `PATH_TO_DATABASE` with where you'd like to create the SQLite database. (This will replace any file named `opendc.db` at the location `PATH_TO_DATABASE`.)
```bash
@@ -64,6 +70,8 @@ cd opendc/database
python rebuild-database.py "PATH_TO_DATABASE"
```
+#### Configure OpenDC
+
Create a file `config.json` in `opendc-web-server`, containing:
```json
@@ -81,7 +89,19 @@ Make the following replacements:
* Replace `PATH_TO_DATABASE` with where you created the database.
* Replace `FLASK_SECRET`, come up with some string.
-In `opendc-web-server/static/index.html`, add your own `OAUTH_CLIENT_ID` in `content=""`.
+In `opendc-web-server/static/index.html`, add your own `OAUTH_CLIENT_ID` in `content=` on line `2`.
+
+#### Set up Postman and OpenDC account
+
+To easily make HTTP requests to the web server, we recommend Postman (get it [here](https://www.getpostman.com/)).
+
+Once Postman is installed and set up, `Import` the OpenDC requests collection (`OpenDC.postman_collection.json`). In the `Collections` tab, expand `OpenDC` and click `Create New User`. This should open the request in the `Builder` pane.
+
+Navigate to `http://localhost:8081/my-auth-token` and copy the authentication token on this page to your clipboard. In the Postman `Builder` pane, navigate to the `Headers (2)` tab, and paste the authentication token as value for the `auth-token` header. (This token expires every hour - refresh the auth token page to get a new token.)
+
+(Optional: navigate to the `Body` tab and change the email address to the gmail address you used to get an authentication token.)
+
+Click `Send` in Postman to send your request and see the server's response. If it's a `200`, your account is set up!
### Local Development
@@ -92,8 +112,6 @@ cd opendc-web-server
python main.py config.json
```
-Navigate to `http://localhost:8081/web-server-test` in a web browser (Chrome recommended), and open the console to see the server's response to the query in `static/index.html`.
-
-To try a different query, edit the `path`, `method`, and/or `parameters` in `static/index.html`.
+To try a different query, use the Postman `Builder` to edit the method, path, body, query parameters, etc. `Create New Simulation` is provided as an additional example.
When editing the web server code, restart the server (`CTRL` + `c` followed by `python main.py config.json` in the console running the server) to see the result of your changes.
diff --git a/main.py b/main.py
index 56f8e7c1..3dbb2a53 100644
--- a/main.py
+++ b/main.py
@@ -10,7 +10,7 @@ import flask_socketio
from oauth2client import client, crypt
from opendc.models.user import User
-from opendc.util import exceptions, rest
+from opendc.util import exceptions, rest, path_parser
if len(sys.argv) < 2:
print "config file path not given as argument"
@@ -55,7 +55,7 @@ def serve_projects():
return send_from_directory(STATIC_ROOT, 'projects.html')
-@FLASK_CORE_APP.route('/web-server-test')
+@FLASK_CORE_APP.route('/my-auth-token')
def serve_web_server_test():
"""Serve the web server test."""
@@ -102,51 +102,98 @@ def sign_in():
return jsonify(**data)
+@FLASK_CORE_APP.route('/api/<string:version>/<path:endpoint_path>', methods = ['GET', 'POST', 'PUT', 'DELETE'])
+def api_call(version, endpoint_path):
+ """Call an API endpoint directly over HTTP"""
+
+ # Get path and parameters
+ (path, path_parameters) = path_parser.parse(version, endpoint_path)
+
+ query_parameters = request.args.to_dict()
+ for param in query_parameters:
+ try:
+ query_parameters[param] = int(query_parameters[param])
+ except:
+ pass
+
+ try:
+ body_parameters = json.loads(request.get_data())
+ except:
+ body_parameters = {}
+
+ # Create and call request
+ (req, response) = _process_message({
+ 'id': 0,
+ 'method': request.method,
+ 'parameters': {
+ 'body': body_parameters,
+ 'path': path_parameters,
+ 'query': query_parameters
+ },
+ 'path': path,
+ 'token': request.headers.get('auth-token')
+ })
+
+ print 'HTTP:\t{} to `/{}` resulted in {}: {}'.format(
+ req.method,
+ req.path,
+ response.status['code'],
+ response.status['description']
+ )
+ sys.stdout.flush()
+
+ flask_response = jsonify(json.loads(response.to_JSON()))
+ flask_response.status_code = response.status['code']
+ return flask_response
+
@SOCKET_IO_CORE.on('request')
def receive_message(message):
""""Receive a SocketIO request"""
+ (request, response) = _process_message(message)
+
+ print 'Socket:\t{} to `/{}` resulted in {}: {}'.format(
+ request.method,
+ request.path,
+ response.status['code'],
+ response.status['description']
+ )
+ sys.stdout.flush()
+
+ flask_socketio.emit('response', response.to_JSON(), json=True)
+
+def _process_message(message):
+ """Process a request message and return the response."""
+
try:
- request = rest.Request(message)
+ request = rest.Request(message)
response = request.process()
-
- flask_socketio.emit('response', response.to_JSON(), json=True)
-
- print 'Socket: {} to `/{}` resulted in {}: {}'.format(
- request.method,
- request.path,
- response.status['code'],
- response.status['description']
- )
- return
+ return (request, response)
except exceptions.AuthorizationTokenError as e:
response = rest.Response(401, 'Authorization error')
response.id = message['id']
- flask_socketio.emit('response', response.to_JSON(), json=True)
-
except exceptions.RequestInitializationError as e:
response = rest.Response(400, e.message)
response.id = message['id']
- flask_socketio.emit('response', response.to_JSON(), json=True)
-
+ if not 'method' in message:
+ message['method'] = 'UNSPECIFIED'
+ if not 'path' in message:
+ message['path'] = 'UNSPECIFIED'
+
except Exception as e:
response = rest.Response(500, 'Internal server error')
- response.id = message['id']
-
- flask_socketio.emit('response', response.to_JSON(), json=True)
+ if 'id' in message:
+ response.id = message['id']
traceback.print_exc()
+
+ request = rest.Request()
+ request.method = message['method']
+ request.path = message['path']
- print 'Socket: {} to `{}` resulted in {}: {}'.format(
- message['method'],
- message['path'],
- response.status['code'],
- response.status['description']
- )
-
+ return (request, response)
SOCKET_IO_CORE.run(FLASK_CORE_APP, host='0.0.0.0', port=8081)
-
diff --git a/opendc/api/v1/paths.json b/opendc/api/v1/paths.json
new file mode 100644
index 00000000..08908c25
--- /dev/null
+++ b/opendc/api/v1/paths.json
@@ -0,0 +1,48 @@
+[
+ "room-types",
+ "room-types/{name}",
+ "room-types/{name}/allowed-objects",
+ "schedulers",
+ "simulations",
+ "simulations/{simulationId}",
+ "simulations/{simulationId}/authorizations",
+ "simulations/{simulationId}/authorizations/{userId}",
+ "simulations/{simulationId}/datacenters",
+ "simulations/{simulationId}/datacenters/{datacenterId}",
+ "simulations/{simulationId}/datacenters/{datacenterId}/rooms",
+ "simulations/{simulationId}/datacenters/{datacenterId}/rooms/{roomId}",
+ "simulations/{simulationId}/datacenters/{datacenterId}/rooms/{roomId}/tiles",
+ "simulations/{simulationId}/datacenters/{datacenterId}/rooms/{roomId}/tiles/{tileId}",
+ "simulations/{simulationId}/datacenters/{datacenterId}/rooms/{roomId}/tiles/{tileId}/rack",
+ "simulations/{simulationId}/datacenters/{datacenterId}/rooms/{roomId}/tiles/{tileId}/rack/machines",
+ "simulations/{simulationId}/datacenters/{datacenterId}/rooms/{roomId}/tiles/{tileId}/rack/machines/{position}",
+ "simulations/{simulationId}/experiments/",
+ "simulations/{simulationId}/experiments/{experimentId}",
+ "simulations/{simulationId}/experiments/{experimentId}/last-simulated-tick",
+ "simulations/{simulationId}/experiments/{experimentId}/machine-states",
+ "simulations/{simulationId}/experiments/{experimentId}/rack-states",
+ "simulations/{simulationId}/experiments/{experimentId}/room-states",
+ "simulations/{simulationId}/experiments/{experimentId}/statistics/task-durations",
+ "simulations/{simulationId}/experiments/{experimentId}/task-states",
+ "simulations/{simulationId}/paths/",
+ "simulations/{simulationId}/paths/{pathId}",
+ "simulations/{simulationId}/paths/{pathId}/branches",
+ "simulations/{simulationId}/paths/{pathId}/sections",
+ "simulations/{simulationId}/paths/{pathId}/sections/{sectionId}",
+ "specifications/cpus",
+ "specifications/cpus/{id}",
+ "specifications/failure-models",
+ "specifications/failure-models/{id}",
+ "specifications/gpus",
+ "specifications/gpus/{id}",
+ "specifications/memories",
+ "specifications/memories/{id}",
+ "specifications/storages",
+ "specifications/storages/{id}",
+ "specifications/traces",
+ "specifications/traces/{traceId}",
+ "specifications/traces/{traceId}/tasks",
+ "users",
+ "users/{userId}",
+ "users/{userId}/authorizations"
+] \ No newline at end of file
diff --git a/opendc/api/v1/users/endpoint.py b/opendc/api/v1/users/endpoint.py
index 1f43f665..7e381ed5 100644
--- a/opendc/api/v1/users/endpoint.py
+++ b/opendc/api/v1/users/endpoint.py
@@ -43,7 +43,6 @@ def POST(request):
request.check_required_parameters(
body = {
'user': {
- 'googleId': 'string',
'email': 'string'
}
}
@@ -54,6 +53,7 @@ def POST(request):
# Instantiate a User
+ request.params_body['user']['googleId'] = request.google_id
user = User.from_JSON(request.params_body['user'])
# Make sure a User with this Google ID does not already exist
diff --git a/opendc/util/path_parser.py b/opendc/util/path_parser.py
new file mode 100644
index 00000000..2f9528b1
--- /dev/null
+++ b/opendc/util/path_parser.py
@@ -0,0 +1,39 @@
+import json
+import sys
+import re
+
+def parse(version, endpoint_path):
+ """Map an HTTP endpoint path to an API path"""
+
+ # Get possible paths
+ with open('opendc/api/{}/paths.json'.format(version)) as paths_file:
+ paths = json.load(paths_file)
+
+ # Find API path that matches endpoint_path
+ endpoint_path_parts = endpoint_path.strip('/').split('/')
+ paths_parts = [x.split('/') for x in paths if len(x.split('/')) == len(endpoint_path_parts)]
+ path = None
+
+ for path_parts in paths_parts:
+ found = True
+ for (endpoint_part, part) in zip(endpoint_path_parts, path_parts):
+ if not part.startswith('{') and endpoint_part != part:
+ found = False
+ break
+ if found:
+ path = path_parts
+
+ if path is None:
+ return None
+
+ # Extract path parameters
+ parameters = {}
+
+ for (name, value) in zip(path, endpoint_path_parts):
+ if name.startswith('{'):
+ try:
+ parameters[name.strip('{}')] = int(value)
+ except:
+ parameters[name.strip('{}')] = value
+
+ return ('{}/{}'.format(version, '/'.join(path)), parameters)
diff --git a/opendc/util/rest.py b/opendc/util/rest.py
index 65747762..ad53f084 100644
--- a/opendc/util/rest.py
+++ b/opendc/util/rest.py
@@ -13,11 +13,14 @@ with open(sys.argv[1]) as file:
class Request(object):
"""WebSocket message to REST request mapping."""
- def __init__(self, message):
+ def __init__(self, message=None):
""""Initialize a Request from a socket message."""
# Get the Request parameters from the message
+ if message is None:
+ return
+
try:
self.message = message
diff --git a/static/index.html b/static/index.html
index 744f7d27..3fc1c2d0 100644
--- a/static/index.html
+++ b/static/index.html
@@ -3,36 +3,9 @@
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script>
<script>
function onSignIn(googleUser) {
- var id_token = googleUser.getAuthResponse().id_token;
-
- var socket = io.connect('http://' + document.domain + ':' + location.port);
- socket.on('connect', function() {
- socket.emit('request', {
- id: 234,
- path: "/v1/simulations/{simulationId}",
- method: "GET",
- parameters: {
- body: {
-
- },
- path: {
- simulationId: 1
- },
- query: {
- email:'l.overweel@gmail.com'
- }
- },
- token: id_token
- });
- })
-
- socket.on('response', function(response) {
- console.log(JSON.parse(response));
- });
+ document.getElementById('token').innerText = googleUser.getAuthResponse().id_token;
}
-
</script>
-
<script>
function signOut() {
var auth2 = gapi.auth2.getAuthInstance();
@@ -44,3 +17,6 @@ function onSignIn(googleUser) {
<div class="g-signin2" data-onsuccess="onSignIn"></div>
<a href="#" onclick="signOut();">Sign out</a>
+
+<p>Your auth token:</p>
+<p id="token" style="word-wrap:break-word;">Loading...</p> \ No newline at end of file