diff options
| author | Sacheendra talluri <sacheendra.t@gmail.com> | 2017-04-04 15:34:01 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-04-04 15:34:01 +0200 |
| commit | 693a23d17c8783e51a97fd5a2439b5d1ceaa003c (patch) | |
| tree | fdad42c44a147e03101925ffd4d62a869bac4490 | |
| parent | 59b05ac8425e34cdf41179506cdda357c59b4b12 (diff) | |
| parent | fd0376c5a8f018457a11bb0f9602bda366571955 (diff) | |
Merge pull request #6 from atlarge-research/api-over-http
Access API over HTTP
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | OpenDC.postman_collection.json | 61 | ||||
| -rw-r--r-- | README.md | 26 | ||||
| -rw-r--r-- | main.py | 103 | ||||
| -rw-r--r-- | opendc/api/v1/paths.json | 48 | ||||
| -rw-r--r-- | opendc/api/v1/users/endpoint.py | 2 | ||||
| -rw-r--r-- | opendc/util/path_parser.py | 39 | ||||
| -rw-r--r-- | opendc/util/rest.py | 5 | ||||
| -rw-r--r-- | static/index.html | 32 |
9 files changed, 255 insertions, 63 deletions
@@ -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 @@ -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. @@ -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 |
