Browse Source

Merge "Provide simple RESTful API + WSGI Server"

Jenkins 5 years ago
parent
commit
9346b6af1d

+ 25
- 0
INSTALL.md View File

@@ -0,0 +1,25 @@
1
+Installation
2
+============
3
+
4
+API Server
5
+----------
6
+
7
+SECURITY NOTE: Not for production use!  This server does not provide sufficient
8
+security to operate in a production environment.
9
+
10
+1. Ensure the server's dependencies are installed:
11
+
12
+    + anyjson
13
+    + inception itself (oslo.config, ipython)
14
+    + pastescript
15
+    + routes
16
+    + sqlalchemy
17
+
18
+2. Create the database
19
+
20
+        $ python inception/api/server.py # needed first time only
21
+
22
+3. Run server
23
+
24
+        $ paster serve ./etc/inception/paste-config.ini
25
+

+ 11
- 0
etc/inception/paste-config.ini View File

@@ -0,0 +1,11 @@
1
+# Paste Configuration for running the Inception Cloud API Server
2
+
3
+[app:main]
4
+paste.app_factory = inception.api.server:app_factory
5
+
6
+[server:main]
7
+# egg:PasteScript#wsgiutils does not support HTTP 'DELETE' method so
8
+# we provide our own server_factory
9
+paste.server_factory = inception.api.server:server_factory
10
+host = localhost
11
+port = 7653

+ 0
- 0
inception/api/__init__.py View File


+ 39
- 0
inception/api/samples/create.sh View File

@@ -0,0 +1,39 @@
1
+#!/bin/bash
2
+
3
+TENANT_ID=4d0046aff188439d8aeb5cdf3033492d
4
+API_PORT=${API_PORT-7653}
5
+
6
+
7
+# Cloud A:
8
+#IMAGE="u1204-130531-gv"
9
+#KEY_NAME='af-keypair' or 'af2'
10
+
11
+# Cloud B:
12
+#IMAGE="u1204-130531-gv"
13
+#KEY_NAME='af'
14
+
15
+curl -k -X 'POST' \
16
+     -H 'Content-type: application/json' \
17
+     -v http://localhost:${API_PORT}/${TENANT_ID}/att-inception-clouds \
18
+     -d '
19
+{
20
+        "OS_AUTH_URL":"'$OS_AUTH_URL'",
21
+        "OS_PASSWORD":"'$OS_PASSWORD'",
22
+        "OS_TENANT_ID":"'$OS_TENANT_ID'",
23
+        "OS_TENANT_NAME":"'$OS_TENANT_NAME'",
24
+        "OS_USERNAME":"'$OS_USERNAME'",
25
+        "prefix": "af4",
26
+        "num_workers":2,
27
+        "flavor":"m1.medium",
28
+        "gateway_flavor":"m1.small",
29
+        "image":"u1204-130621-gv",
30
+        "user":"ubuntu",
31
+        "pool":"research",
32
+        "key_name":"af-keypair",
33
+        "security_groups":["default"],
34
+        "chef_repo": "git://github.com/att/inception-chef-repo.git",
35
+        "chef_repo_branch": "master",
36
+        "chefserver_image": "u1204-130716-gvc",
37
+        "dst_dir":"/home/ubuntu/",
38
+        "userdata":""
39
+}'

+ 17
- 0
inception/api/samples/delete.sh View File

@@ -0,0 +1,17 @@
1
+#!/bin/bash
2
+
3
+TENANT_ID=4d0046aff188439d8aeb5cdf3033492d
4
+CLOUD=${1-0ec5e84d4fe54bbd93d149bdb27695e5}
5
+API_PORT=${API_PORT-7653}
6
+
7
+
8
+curl -k -X 'DELETE' \
9
+     -H 'Content-type: application/json' \
10
+     -v http://localhost:${API_PORT}/${TENANT_ID}/att-inception-clouds/${CLOUD} \
11
+     -d '{
12
+    "OS_AUTH_URL":"'$OS_AUTH_URL'",
13
+    "OS_PASSWORD":"'$OS_PASSWORD'",
14
+    "OS_TENANT_ID":"'$OS_TENANT_ID'",
15
+    "OS_TENANT_NAME":"'$OS_TENANT_NAME'",
16
+    "OS_USERNAME":"'$OS_USERNAME'"
17
+}'

+ 8
- 0
inception/api/samples/index.sh View File

@@ -0,0 +1,8 @@
1
+#!/bin/bash
2
+
3
+TENANT_ID=4d0046aff188439d8aeb5cdf3033492d
4
+API_PORT=${API_PORT-7653}
5
+
6
+curl -k -X 'GET' \
7
+     -H 'Content-type: application/json' \
8
+     -v http://localhost:${API_PORT}/${TENANT_ID}/att-inception-clouds

+ 9
- 0
inception/api/samples/notfound.sh View File

@@ -0,0 +1,9 @@
1
+#!/bin/bash
2
+
3
+TENANT_ID=4d0046aff188439d8aeb5cdf3033492d
4
+API_PORT=${API_PORT-7653}
5
+
6
+curl -k -X 'GET' \
7
+     -H 'Content-type: application/json' \
8
+     -v http://localhost:${API_PORT}/${TENANT_ID}/att-inception-clouds/0ec5e84d4fe54bbd93d149bdb27695e6 \
9
+     -d '{}'

+ 11
- 0
inception/api/samples/show.sh View File

@@ -0,0 +1,11 @@
1
+#!/bin/bash
2
+
3
+TENANT_ID=4d0046aff188439d8aeb5cdf3033492d
4
+CLOUD_ID=${1-77cb85c70437489183dec645474558f7}
5
+API_PORT=${API_PORT-7653}
6
+
7
+
8
+curl -k -X 'GET' \
9
+     -H 'Content-type: application/json' \
10
+     -v http://localhost:${API_PORT}/${TENANT_ID}/att-inception-clouds/${CLOUD_ID} \
11
+     -d '{}'

+ 42
- 0
inception/api/samples/unauth.sh View File

@@ -0,0 +1,42 @@
1
+#!/bin/bash
2
+
3
+TENANT_ID=4d0046aff188439d8aeb5cdf3033492d
4
+API_PORT=${API_PORT-7653}
5
+
6
+# Cause server to fail
7
+unset OS_AUTH_URL
8
+unset OS_PASSWORD
9
+unset OS_TENANT_ID
10
+unset OS_TENANT_NAME
11
+unset OS_USERNAME
12
+
13
+# Cloud A:
14
+#IMAGE="8848d4cd-1bdf-4627-ae31-ce9bf61440a4"
15
+#KEY_NAME='af-keypair' or 'af2'
16
+
17
+# Cloud B:
18
+#IMAGE="2fe7633c-85bd-42b8-a857-80d5efa78d9f"
19
+#KEY_NAME='af'
20
+
21
+curl -k -X 'POST' \
22
+     -H 'Content-type: application/json' \
23
+     -v http://localhost:${API_PORT}/${TENANT_ID}/att-inception-clouds \
24
+     -d '
25
+{
26
+    "prefix": "af4",
27
+    "num_workers":2,
28
+    "flavor":2,
29
+    "gateway_flavor":1,
30
+    "image":"8848d4cd-1bdf-4627-ae31-ce9bf61440a4",
31
+    "user":"ubuntu",
32
+    "pool":"research",
33
+    "key_name":"af2",
34
+    "security_groups":["default"],
35
+    "chef_repo": "git://github.com/att/inception-chef-repo.git",
36
+    "chef_repo_branch": "master",
37
+    "chefserver_image": "8848d4cd-1bdf-4627-ae31-ce9bf61440a4",
38
+    "dst_dir":"/home/ubuntu/",
39
+    "userdata":""
40
+}'
41
+
42
+# Expect exception in response.

+ 504
- 0
inception/api/server.py View File

@@ -0,0 +1,504 @@
1
+#!/usr/bin/env python
2
+
3
+#    Copyright (C) 2013 AT&T Labs Inc. All Rights Reserved.
4
+#
5
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6
+#    not use this file except in compliance with the License. You may obtain
7
+#    a copy of the License at
8
+#
9
+#         http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+#    Unless required by applicable law or agreed to in writing, software
12
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+#    License for the specific language governing permissions and limitations
15
+#    under the License.
16
+
17
+# TODO(forrest-r): consider splitting module (e.g. WSGI v. SqlAlchemy, etc)
18
+# TODO(forrest-r): add configuration, noted throughout
19
+# TODO(forrest-r): add database scrubbing code for when it gets out of sync i
20
+#                  with reality (e.g. due to out-of-band Inception Cloud
21
+#                  manipulation)
22
+
23
+import anyjson
24
+import logging
25
+import os
26
+import threading
27
+import sys
28
+import traceback
29
+import uuid
30
+from wsgiref.simple_server import make_server
31
+
32
+from routes import Mapper
33
+from sqlalchemy import create_engine
34
+from sqlalchemy import Column
35
+from sqlalchemy import ForeignKey
36
+from sqlalchemy import Integer
37
+from sqlalchemy import String
38
+from sqlalchemy.dialects.postgresql import UUID
39
+from sqlalchemy.ext.declarative import declarative_base
40
+from sqlalchemy.orm import backref
41
+from sqlalchemy.orm import relationship
42
+from sqlalchemy.orm import sessionmaker
43
+from sqlalchemy.types import TypeDecorator, CHAR
44
+
45
+from inception.orchestrator import Orchestrator
46
+
47
+
48
+# TODO(forrest-r) root-cause the lack of logging from Orchestrator
49
+logging.basicConfig()
50
+LOG = logging.getLogger(__name__)
51
+
52
+# TODO(forrest-r): make db name/loc config item
53
+_ENGINE = create_engine('sqlite:///./inception.db', echo=True)
54
+_SESSION = sessionmaker(bind=_ENGINE)
55
+
56
+_BASE = declarative_base()
57
+
58
+#
59
+# Route Mappping
60
+#
61
+
62
+_MAPPER = Mapper()
63
+
64
+# URLs contain project_id for future use.
65
+
66
+_MAPPER.connect("create", "/{project_id}/att-inception-clouds",
67
+                controller="OrchestratorAdapter", action="create",
68
+                conditions=dict(method=["POST"]))
69
+_MAPPER.connect("index", "/{project_id}/att-inception-clouds",
70
+                controller="OrchestratorAdapter", action="index",
71
+                conditions=dict(method=["GET"]))
72
+_MAPPER.connect("delete", "/{project_id}/att-inception-clouds/{id}",
73
+                controller="OrchestratorAdapter", action="delete",
74
+                conditions=dict(method=["DELETE"]))
75
+_MAPPER.connect("show", "/{project_id}/att-inception-clouds/{id}",
76
+                controller="OrchestratorAdapter", action="show",
77
+                conditions=dict(method=["GET"]))
78
+
79
+
80
+class OrchestratorThread(threading.Thread):
81
+    """Thread sub-class for invoking (long running) Orchestrator.start() or
82
+       Orchestrator.cleanup() methods."""
83
+
84
+    def __init__(self, orch_opts, cmd, id=None):
85
+        threading.Thread.__init__(self)
86
+        self.opts = orch_opts           # Capture Orchestrator (config) options
87
+        self.cmd = cmd                  # cmd = create|destroy
88
+        self.id = id
89
+        self.session = _SESSION()        # SQLAlchemy session obj
90
+
91
+        if id is None:
92
+            self.ic = InceptionCloud()  # Create new IC obj (create case)
93
+            # Create Orchestrator object from opts originating in request JSON
94
+            self.orchestrator = self._make_orchestrator(self.opts)
95
+            self.ic.save(self.orchestrator)
96
+
97
+    def _make_orchestrator(self, opt_dict):
98
+        """Create an Orchestrator Object from a dictionary of parameters."""
99
+        # NB: certain values are required even for Orchestrator.cleanup()
100
+        # to function.  Those are not allowed a default value here.  Values
101
+        # that aren't required for cleanup() (and probably not stored in the
102
+        # database) are permitted a default since they don't matter.
103
+        return Orchestrator(
104
+            prefix=opt_dict['prefix'],
105
+            num_workers=opt_dict['num_workers'],
106
+            atomic=False,
107
+            parallel=True,
108
+            sdn=False,
109
+            chef_repo=opt_dict.get('chef_repo', ''),
110
+            chef_repo_branch=opt_dict.get('chef_repo_branch', ''),
111
+            # TODO(forrest-r): key
112
+            ssh_keyfile=None,
113
+            pool=opt_dict.get('pool', ''),
114
+            user=opt_dict.get('user', ''),
115
+            image=opt_dict['image'],
116
+            chefserver_image=opt_dict.get('chefserver_image', ''),
117
+            flavor=opt_dict.get('flavor', ''),
118
+            gateway_flavor=opt_dict.get('gateway_flavor', ''),
119
+            key_name=opt_dict.get('key_name', ''),
120
+            security_groups=opt_dict.get('security_groups', ''),
121
+            src_dir='../bin/',
122
+            dst_dir=opt_dict.get('dst_dir', ''),
123
+            # TODO(forrest-r): end-to-end transport of userdata
124
+            # ("customization scripts") from horizon requires more work.
125
+            userdata='../bin/userdata.sh.template',
126
+            timeout=25 * 60,
127
+            poll_interval=5)
128
+
129
+    def _create(self):
130
+        """Create an Orchestrator object and use it to create an
131
+           Inception Cloud."""
132
+
133
+        #  Start initializing InceptionCloud object to persist data of interest
134
+        self.ic.status = 'Active'
135
+        self.ic.power_state = 'Building'
136
+        self.session.add(self.ic)
137
+        self.session.commit()
138
+
139
+        # Run Orchestrator.start()
140
+        try:
141
+            self.orchestrator.start(re_raise=True)
142
+        except Exception:
143
+            self.ic.status = 'Error'
144
+            self.ic.power_state = 'Failed'
145
+            self.session.commit()
146
+            raise
147
+
148
+        LOG.info("Orchestrator.start() completed. "
149
+                 "Inception cloud %s created." % self.ic.id)
150
+
151
+        # Copy info generated by running Orchestrator.start() into database
152
+        self.ic.save(self.orchestrator)
153
+        self.ic.status = 'Active'
154
+        self.ic.power_state = 'Running'
155
+        self.session.commit()
156
+
157
+    def _destroy(self):
158
+        """Create an Orchestrator object from an InceptionCloud object
159
+           retrieved from the database and use it to destroy an actual
160
+           Inception Cloud."""
161
+
162
+        # Repeating db query is easiest way to get obj into db session
163
+        # for this thread.
164
+        self.ic = self.session.query(InceptionCloud).get(self.id)
165
+        self.orchestrator = self._make_orchestrator(self.ic.__dict__)
166
+
167
+        # Run orchestrator
168
+        self.ic.task = 'Deleting'
169
+        self.session.commit()
170
+
171
+        try:
172
+            self.orchestrator.cleanup(re_raise=True)
173
+        except Exception:
174
+            self.ic.status = 'Error'
175
+            self.ic.power_state = 'Failed'
176
+            self.session.commit()
177
+            LOG.exception("Orchestrator.cleanup() failed")
178
+            raise
179
+
180
+        LOG.info("Orchestrator.cleanup() completed.  "
181
+                 "Inception cloud %s destroyed." % self.ic.id)
182
+
183
+        self.session.delete(self.ic)
184
+        self.session.commit()
185
+
186
+    def run(self):
187
+        if self.cmd == 'create':
188
+            self._create()
189
+        elif self.cmd == 'destroy':
190
+            self._destroy()
191
+        else:
192
+            LOG.critical("Unrecognized command: %s", self.cmd)
193
+
194
+#
195
+# Database
196
+#
197
+
198
+
199
+class GUID(TypeDecorator):
200
+    """Backend-neutral GUID type from Type Decorator Recipes
201
+       http://docs.sqlalchemy.org/en/rel_0_8/core/types.html"""
202
+
203
+    impl = CHAR
204
+
205
+    def load_dialect_impl(self, dialect):
206
+        td = UUID() if dialect.name == 'postgresql' else CHAR(32)
207
+        return dialect.type_descriptor(td)
208
+
209
+    def process_bind_param(self, value, dialect):
210
+        if value is None:
211
+            return value
212
+        elif dialect.name == 'postgresql':
213
+            return str(value)
214
+        else:
215
+            if not isinstance(value, uuid.UUID):
216
+                return "%.32x" % uuid.UUID(value)
217
+            else:   # hexstring
218
+                return "%.32x" % value
219
+
220
+    def process_result_value(self, value, dialect):
221
+        return value if value is None else uuid.UUID(value)
222
+
223
+
224
+class Worker(_BASE):
225
+    """Models Worker Nodes in the Inception Cloud to persist information about
226
+       them.  Required since IC:worker cardinality is 1:N"""
227
+    __tablename__ = 'inception_workers'
228
+
229
+    id = Column(GUID, primary_key=True)     # OpenStack worker VM id
230
+    ic_id = Column(GUID, ForeignKey("inception_clouds.id"))
231
+    cloud = relationship("InceptionCloud",
232
+                         backref=backref("worker_ids", order_by=id),
233
+                         cascade="all, delete, delete-orphan, merge",
234
+                         single_parent=True)
235
+
236
+    def __init__(self, id):
237
+        self.id = id
238
+
239
+    def __repr__(self):
240
+        return ("<Workers('%s', from_ic='%s' )>" % (self.id, self.ic_id,))
241
+
242
+
243
+class InceptionCloud(_BASE):
244
+    """Models Inception Clouds to persist information about them.
245
+    Not all data that is an input to the creation of an Orchestrator object
246
+    is worthy of persistence and some data which is an output of running
247
+    Orchestrator.start() is worthy of persistence."""
248
+    __tablename__ = 'inception_clouds'
249
+
250
+    id = Column(GUID, primary_key=True)
251
+    prefix = Column(String)
252
+    num_workers = Column(Integer)
253
+    image = Column(String)
254
+    _gateway_id = Column('gateway_id', String)
255
+    _gateway_floating_ip = Column('gateway_floating_ip', String)
256
+    _chefserver_id = Column('chefserver_id', String)
257
+    _chefserver_ip = Column('chefserver_ip', String)
258
+    _controller_id = Column('controller_id', String)
259
+    _controller_ip = Column('controller_ip', String)
260
+    size = Column(String)
261
+    keypair = Column(String)
262
+    status = Column(String)
263
+    task = Column(String)
264
+    power_state = Column(String)
265
+
266
+    def __init__(self):
267
+        self.id = uuid.uuid4()      # Generate our own IC id
268
+        self.worker_ids = []
269
+
270
+    def __repr__(self):
271
+        return "<InceptionCloud('%s')>" % (self.id,)
272
+
273
+    def save(self, orch):
274
+        """Save information contained in the orchestrator obj that was
275
+           obtained by the successful completion of the start() method."""
276
+        self.prefix = orch.prefix
277
+        self.num_workers = orch.num_workers
278
+        self.image = orch.image
279
+        self._gateway_id = orch._gateway_id
280
+        if orch._gateway_floating_ip is not None:
281
+            self._gateway_floating_ip = orch._gateway_floating_ip.ip
282
+        else:
283
+            self._gateway_floating_ip = None
284
+        self._chefserver_id = orch._chefserver_id
285
+        self._chefserver_ip = orch._chefserver_ip
286
+        self._controller_id = orch._controller_id
287
+        self._controller_ip = orch._controller_ip
288
+        self.worker_ids = [Worker(wid) for wid in orch._worker_ids]
289
+        # self.size =
290
+        # self.keypair =
291
+
292
+    def to_dict(self):
293
+        """Returns InceptionCloud attributes of interest in dict form."""
294
+
295
+        def hex_or_none(id):
296
+            # TODO(forrest-r): be more careful with db input so that all ids
297
+            # are of same type and this function can be simplified/eliminated.
298
+            if id is None:
299
+                return id
300
+            if type(id) is unicode:
301
+                return uuid.UUID(id).hex
302
+            return id.hex
303
+
304
+        return {
305
+            'id': hex_or_none(self.id),
306
+            'prefix': self.prefix,
307
+            'num_workers': self.num_workers,
308
+            'image': self.image,
309
+            'gw_id': hex_or_none(self._gateway_id),
310
+            'gw_floating_ip': self._gateway_floating_ip,
311
+            'chef_id': hex_or_none(self._chefserver_id),
312
+            'chef_ip': self._chefserver_ip,
313
+            'controller_id': hex_or_none(self._controller_id),
314
+            'controller_ip': self._controller_ip,
315
+            'worker_ids': [wid.id.hex for wid in self.worker_ids],
316
+            'size': self.size,
317
+            'keypair': self.keypair,
318
+            'status': self.status,
319
+            'task': self.task,
320
+            'power_state': self.power_state,
321
+        }
322
+
323
+#
324
+# WSGI to Orchestrator Adaptor
325
+#
326
+
327
+
328
+def _read_request_body(environ):
329
+    """Read and return the request body from a WSGI environment."""
330
+    content_length = int(environ['CONTENT_LENGTH'])
331
+    return environ['wsgi.input'].read(content_length)
332
+
333
+
334
+OS_AUTH_KEYWORDS = ['OS_AUTH_URL', 'OS_PASSWORD', 'OS_TENANT_ID',
335
+                    'OS_TENANT_NAME', 'OS_USERNAME', ]
336
+
337
+
338
+class OrchestratorAdapter(object):
339
+    """A class to implement the Restful API for Orchestrator.
340
+       Each method implements an individual API call and is itself a WSGI
341
+       application."""
342
+
343
+    def index(self, environ, start_response, route_vars):
344
+        """GET /: All Inception Clouds known to system."""
345
+
346
+        session = _SESSION()
347
+        inception_clouds = session.query(InceptionCloud).all()
348
+
349
+        status = '200 OK'
350
+        response_headers = [('Content-type', 'text/json')]
351
+        result = {}
352
+
353
+        if inception_clouds is not None:
354
+            result = dict(clouds=[ic.to_dict() for ic in inception_clouds])
355
+
356
+        session.close()
357
+
358
+        start_response(status, response_headers)
359
+        return [anyjson.serialize(result)]
360
+
361
+    def create(self, environ, start_response, route_vars):
362
+        """POST /: Create new Inception Cloud."""
363
+
364
+        request_body = _read_request_body(environ)
365
+        opt_dict = anyjson.deserialize(request_body)
366
+
367
+        response_headers = [('Content-type', 'text/json')]
368
+        status = '200 OK'
369
+        result = {}
370
+
371
+        try:
372
+            # Copy request authorization environment to local environment
373
+            for kw in OS_AUTH_KEYWORDS:
374
+                os.environ[kw] = opt_dict[kw]
375
+
376
+            ao = OrchestratorThread(opt_dict, 'create')
377
+            ao.start()
378
+            result = {'cloud': {'id': ao.ic.id.hex, }}
379
+        except KeyError as ke:
380
+            # KeyError almost certainly means the OpenStack authorization
381
+            # environment (OS_*) wasn't provided making this a bad request
382
+            t, v, tb = sys.exc_info()   # type, value, traceback
383
+            status = '400 Bad Request'
384
+            result = {'exception': {'type': t.__name__, 'value': v.args, }, }
385
+        except Exception:
386
+            t, v, tb = sys.exc_info()   # type, value, traceback
387
+            print traceback.format_tb(tb)
388
+            status = '500 Internal Server Error'
389
+            result = {'exception': {'type': t.__name__, 'value': v.args, }, }
390
+        finally:
391
+            start_response(status, response_headers)
392
+            return [anyjson.serialize(result)]
393
+
394
+    def delete(self, environ, start_response, route_vars):
395
+        """DELETE /id: Delete specific Inception Cloud."""
396
+        id = route_vars['id']
397
+        session = _SESSION()
398
+        inception_cloud = session.query(InceptionCloud).get(id)
399
+
400
+        if inception_cloud is None:
401
+            status = '404 Not Found'
402
+            response_headers = [('Content-type', 'text/json')]
403
+            start_response(status, response_headers)
404
+            return [anyjson.serialize({})]
405
+
406
+        request_body = _read_request_body(environ)
407
+        auth_env = anyjson.deserialize(request_body)
408
+        opt_dict = inception_cloud.to_dict()
409
+
410
+        response_headers = [('Content-type', 'text/json')]
411
+        status = '200 OK'
412
+        result = {}
413
+
414
+        try:
415
+            # Copy request authorization environment to local environment
416
+            for kw in OS_AUTH_KEYWORDS:
417
+                os.environ[kw] = auth_env[kw]
418
+
419
+            # detach inception_cloud from our session
420
+            ao = OrchestratorThread(opt_dict, 'destroy', inception_cloud.id)
421
+            ao.start()
422
+            result = {'action': 'delete', 'id': opt_dict['id'], 'prefix':
423
+                      opt_dict['prefix']}
424
+        except KeyError as ke:
425
+            # KeyError almost certainly means the OpenStack authorization
426
+            # environment (OS_*) wasn't provided making this a bad request
427
+            t, v, tb = sys.exc_info()   # type, value, traceback
428
+            status = '400 Bad Request'
429
+            result = {'exception': {'type': t.__name__, 'value': v.args, }, }
430
+        except Exception:
431
+            t, v, tb = sys.exc_info()   # type, value, traceback
432
+            print traceback.format_tb(tb)
433
+            status = '500 Internal Server Error'
434
+        finally:
435
+            start_response(status, response_headers)
436
+            return [anyjson.serialize(result)]
437
+
438
+    def show(self, environ, start_response, route_vars):
439
+        """GET /id: Show Inception Cloud details for specific cloud."""
440
+        id = route_vars['id']
441
+        session = _SESSION()
442
+        inception_cloud = session.query(InceptionCloud).get(id)
443
+
444
+        status = '200 OK'
445
+        response_headers = [('Content-type', 'text/json')]
446
+        result = {}
447
+
448
+        if inception_cloud is None:
449
+            status = '404 Not Found'
450
+        else:
451
+            opt_dict = inception_cloud.to_dict()
452
+            result = dict(cloud=opt_dict)
453
+
454
+        session.close()
455
+
456
+        start_response(status, response_headers)
457
+        return [anyjson.serialize(result)]
458
+
459
+#
460
+# WSGI Application
461
+#
462
+
463
+controllers = {'OrchestratorAdapter': OrchestratorAdapter()}
464
+
465
+
466
+def application(environ, start_response):
467
+    """Simple top-level WSGI application"""
468
+    route_vars = _MAPPER.match(environ['PATH_INFO'], environ)
469
+    if route_vars is None:
470
+        status = '404 Not Found'
471
+        response_headers = [('Content-type', 'text/plain')]
472
+        start_response(status, response_headers)
473
+        return []
474
+
475
+    controller = controllers[route_vars['controller']]
476
+    action = getattr(controller, route_vars['action'])
477
+
478
+    return action(environ, start_response, route_vars)
479
+
480
+
481
+def app_factory(global_config, **local_config):
482
+    """This function wraps the simple app above so that
483
+       paste.deploy can use it."""
484
+    return application
485
+
486
+
487
+def server_factory(global_conf, host, port):
488
+    """Paste's example server factory.
489
+       Use wsgiref's server because it supports HTTP's DELETE method
490
+       whereas paste.deploy's wsgiutils one does not."""
491
+    port = int(port)
492
+
493
+    def serve(app):
494
+        s = make_server(host=host, port=port, app=app)
495
+        s.serve_forever()
496
+
497
+    return serve
498
+
499
+#
500
+# Main Program
501
+#
502
+
503
+if __name__ == '__main__':
504
+    _BASE.metadata.create_all(_ENGINE)    # Create db if not already

Loading…
Cancel
Save