Revision fe35958e snfOCCI/APIserver.py
b/snfOCCI/APIserver.py | ||
---|---|---|
1 | 1 |
#!/usr/bin/env python |
2 | 2 |
|
3 |
import sys |
|
4 |
from optparse import OptionParser, OptionValueError |
|
5 |
import string |
|
6 |
import sqlite3 |
|
7 |
import eventlet |
|
8 |
from eventlet import wsgi |
|
9 |
import os |
|
10 |
import json |
|
11 |
import uuid |
|
12 |
|
|
3 | 13 |
from snfOCCI.registry import snfRegistry |
4 | 14 |
from snfOCCI.compute import ComputeBackend |
15 |
from snfOCCI.config import SERVER_CONFIG, KAMAKI_CONFIG, VOMS_CONFIG |
|
16 |
import snf_voms |
|
5 | 17 |
from snfOCCI.network import NetworkBackend, IpNetworkBackend, IpNetworkInterfaceBackend, NetworkInterfaceBackend |
6 | 18 |
|
7 |
from snfOCCI.config import SERVER_CONFIG, KAMAKI_CONFIG |
|
8 | 19 |
|
9 | 20 |
from kamaki.clients.compute import ComputeClient |
10 | 21 |
from kamaki.clients.cyclades import CycladesClient |
22 |
from kamaki.clients import astakos |
|
11 | 23 |
from kamaki.clients import ClientError |
24 |
from kamaki.cli import config as kamaki_config |
|
12 | 25 |
|
13 | 26 |
from occi.core_model import Mixin, Resource |
14 |
from occi.backend import MixinBackend, KindBackend
|
|
27 |
from occi.backend import MixinBackend |
|
15 | 28 |
from occi.extensions.infrastructure import COMPUTE, START, STOP, SUSPEND, RESTART, RESOURCE_TEMPLATE, OS_TEMPLATE, NETWORK, IPNETWORK, NETWORKINTERFACE,IPNETWORKINTERFACE |
16 |
from occi.wsgi import Application
|
|
29 |
from occi import wsgi
|
|
17 | 30 |
from occi.exceptions import HTTPError |
18 | 31 |
from occi import core_model |
19 | 32 |
|
20 | 33 |
from wsgiref.simple_server import make_server |
21 | 34 |
from wsgiref.validate import validator |
22 |
import uuid |
|
35 |
from webob import Request |
|
36 |
from pprint import pprint |
|
37 |
|
|
23 | 38 |
|
24 |
class MyAPP(Application): |
|
39 |
class MyAPP(wsgi.Application):
|
|
25 | 40 |
''' |
26 | 41 |
An OCCI WSGI application. |
27 | 42 |
''' |
28 | 43 |
|
44 |
def __init__(self): |
|
45 |
""" |
|
46 |
Initialization of the WSGI OCCI application for synnefo |
|
47 |
""" |
|
48 |
global ENABLE_VOMS, VOMS_DB |
|
49 |
ENABLE_VOMS = VOMS_CONFIG['enable_voms'] |
|
50 |
super(MyAPP,self).__init__(registry=snfRegistry()) |
|
51 |
self._register_backends() |
|
52 |
VALIDATOR_APP = validator(self) |
|
53 |
|
|
54 |
|
|
55 |
def _register_backends(self): |
|
56 |
COMPUTE_BACKEND = ComputeBackend() |
|
57 |
NETWORK_BACKEND = NetworkBackend() |
|
58 |
NETWORKINTERFACE_BACKEND = NetworkInterfaceBackend() |
|
59 |
IPNETWORK_BACKEND = IpNetworkBackend() |
|
60 |
IPNETWORKINTERFACE_BACKEND = IpNetworkInterfaceBackend() |
|
61 |
|
|
62 |
self.register_backend(COMPUTE, COMPUTE_BACKEND) |
|
63 |
self.register_backend(START, COMPUTE_BACKEND) |
|
64 |
self.register_backend(STOP, COMPUTE_BACKEND) |
|
65 |
self.register_backend(RESTART, COMPUTE_BACKEND) |
|
66 |
self.register_backend(SUSPEND, COMPUTE_BACKEND) |
|
67 |
self.register_backend(RESOURCE_TEMPLATE, MixinBackend()) |
|
68 |
self.register_backend(OS_TEMPLATE, MixinBackend()) |
|
69 |
|
|
70 |
# Network related backends |
|
71 |
self.register_backend(NETWORK, NETWORK_BACKEND) |
|
72 |
self.register_backend(IPNETWORK, IPNETWORK_BACKEND) |
|
73 |
self.register_backend(NETWORKINTERFACE,NETWORKINTERFACE_BACKEND) |
|
74 |
self.register_backend(IPNETWORKINTERFACE, IPNETWORKINTERFACE_BACKEND) |
|
75 |
|
|
76 |
|
|
29 | 77 |
def refresh_images(self, snf, client): |
30 |
|
|
31 |
images = snf.list_images() |
|
32 |
for image in images: |
|
33 |
IMAGE_ATTRIBUTES = {'occi.core.id': str(image['id'])} |
|
34 |
IMAGE = Mixin("http://schemas.ogf.org/occi/infrastructure#", str(image['name']), [OS_TEMPLATE], attributes = IMAGE_ATTRIBUTES) |
|
35 |
self.register_backend(IMAGE, MixinBackend()) |
|
36 |
|
|
78 |
try: |
|
79 |
images = snf.list_images() |
|
80 |
for image in images: |
|
81 |
IMAGE_ATTRIBUTES = {'occi.core.id': str(image['id'])} |
|
82 |
IMAGE = Mixin("http://schemas.ogf.org/occi/os_tpl#", occify_terms(str(image['name'])), [OS_TEMPLATE],title='IMAGE' ,attributes = IMAGE_ATTRIBUTES) |
|
83 |
self.register_backend(IMAGE, MixinBackend()) |
|
84 |
except: |
|
85 |
raise HTTPError(404, "Unauthorized access") |
|
86 |
|
|
37 | 87 |
def refresh_flavors(self, snf, client): |
38 | 88 |
|
39 | 89 |
flavors = snf.list_flavors() |
40 |
print "Retrieving details for each image id" |
|
41 | 90 |
for flavor in flavors: |
42 | 91 |
details = snf.get_flavor_details(flavor['id']) |
43 | 92 |
FLAVOR_ATTRIBUTES = {'occi.core.id': flavor['id'], |
... | ... | |
45 | 94 |
'occi.compute.memory': str(details['ram']), |
46 | 95 |
'occi.storage.size': str(details['disk']), |
47 | 96 |
} |
48 |
FLAVOR = Mixin("http://schemas.ogf.org/occi/infrastructure#", str(flavor['name']), [RESOURCE_TEMPLATE], attributes = FLAVOR_ATTRIBUTES)
|
|
97 |
FLAVOR = Mixin("http://schemas.ogf.org/occi/resource_tpl#", str(flavor['name']), [RESOURCE_TEMPLATE], attributes = FLAVOR_ATTRIBUTES)
|
|
49 | 98 |
self.register_backend(FLAVOR, MixinBackend()) |
50 | 99 |
|
100 |
|
|
51 | 101 |
def refresh_flavors_norecursive(self, snf, client): |
52 | 102 |
flavors = snf.list_flavors(True) |
53 | 103 |
print "Retrieving details for each image id" |
54 | 104 |
for flavor in flavors: |
55 |
# details = snf.get_flavor_details(flavor['id']) |
|
56 | 105 |
FLAVOR_ATTRIBUTES = {'occi.core.id': flavor['id'], |
57 | 106 |
'occi.compute.cores': str(flavor['vcpus']), |
58 | 107 |
'occi.compute.memory': str(flavor['ram']), |
59 | 108 |
'occi.storage.size': str(flavor['disk']), |
60 | 109 |
} |
61 | 110 |
|
62 |
FLAVOR = Mixin("http://schemas.ogf.org/occi/infrastructure#", str(flavor['name']), [RESOURCE_TEMPLATE], attributes = FLAVOR_ATTRIBUTES)
|
|
111 |
FLAVOR = Mixin("http://schemas.ogf.org/occi/resource_tpl#", occify_terms(str(flavor['name'])), [RESOURCE_TEMPLATE], title='FLAVOR',attributes = FLAVOR_ATTRIBUTES)
|
|
63 | 112 |
self.register_backend(FLAVOR, MixinBackend()) |
64 | 113 |
|
65 |
|
|
66 | 114 |
def refresh_network_instances(self,client): |
67 | 115 |
networks =client.networks_get(command = 'detail') |
68 | 116 |
network_details = networks.json['networks'] |
... | ... | |
90 | 138 |
self.registry.add_resource(netID, snf_net, None) |
91 | 139 |
|
92 | 140 |
|
93 |
|
|
141 |
|
|
94 | 142 |
def refresh_compute_instances(self, snf, client): |
95 | 143 |
'''Syncing registry with cyclades resources''' |
96 | 144 |
|
... | ... | |
102 | 150 |
resources = self.registry.resources |
103 | 151 |
occi_keys = resources.keys() |
104 | 152 |
|
105 |
print resources.keys() |
|
106 |
|
|
153 |
print occi_keys |
|
107 | 154 |
for serverID in occi_keys: |
108 | 155 |
if '/compute/' in serverID and resources[serverID].attributes['occi.compute.hostname'] == "": |
109 | 156 |
self.registry.delete_resource(serverID, None) |
... | ... | |
113 | 160 |
|
114 | 161 |
#Compute instances in synnefo not available in registry |
115 | 162 |
diff = [x for x in snf_keys if '/compute/'+x not in occi_keys] |
163 |
|
|
116 | 164 |
for key in diff: |
117 | 165 |
|
118 | 166 |
details = snf.get_server_details(int(key)) |
119 | 167 |
flavor = snf.get_flavor_details(details['flavor']['id']) |
120 | 168 |
|
121 | 169 |
try: |
122 |
print "line 65:Finished getting image details for VM with ID" + str(details['flavor']['id']) |
|
170 |
print "line 65:Finished getting image details for VM "+key+" with ID" + str(details['flavor']['id'])
|
|
123 | 171 |
image = snf.get_image_details(details['image']['id']) |
124 | 172 |
|
125 | 173 |
for i in self.registry.backends: |
126 |
if i.term == str(image['name']):
|
|
174 |
if i.term == occify_terms(str(image['name'])):
|
|
127 | 175 |
rel_image = i |
128 |
if i.term == str(flavor['name']):
|
|
176 |
if i.term == occify_terms(str(flavor['name'])):
|
|
129 | 177 |
rel_flavor = i |
178 |
|
|
130 | 179 |
|
131 | 180 |
resource = Resource(key, COMPUTE, [rel_flavor, rel_image]) |
132 | 181 |
resource.actions = [START] |
... | ... | |
138 | 187 |
resource.attributes['occi.core.title'] = str(details['name']) |
139 | 188 |
networkIDs = details['addresses'].keys() |
140 | 189 |
if len(networkIDs)>0: |
141 |
#resource.attributes['occi.compute.hostname'] = SERVER_CONFIG['hostname'] % {'id':int(key)} |
|
142 | 190 |
resource.attributes['occi.compute.hostname'] = str(details['addresses'][networkIDs[0]][0]['addr']) |
143 |
#resource.attributes['occi.networkinterface.address'] = str(details['addresses'][networkIDs[0]][0]['addr']) |
|
144 | 191 |
else: |
145 | 192 |
resource.attributes['occi.compute.hostname'] = "" |
146 | 193 |
|
... | ... | |
166 | 213 |
'occi.networkinterface.allocation' : allocheme, |
167 | 214 |
'occi.networking.interface': str(item['id']), |
168 | 215 |
'occi.networkinterface.mac' : str(item['mac_address']), |
169 |
'occi.networkinterface.ip4' : ip4address,
|
|
216 |
'occi.networkinterface.address' : ip4address,
|
|
170 | 217 |
'occi.networkinterface.ip6' : ip6address |
171 | 218 |
} |
172 | 219 |
elif len(details['addresses'][netKey])>0: |
... | ... | |
174 | 221 |
'occi.networkinterface.allocation' : allocheme, |
175 | 222 |
'occi.networking.interface': '', |
176 | 223 |
'occi.networkinterface.mac' : '', |
177 |
'occi.networkinterface.ip4' : ip4address,
|
|
224 |
'occi.networkinterface.address' : ip4address,
|
|
178 | 225 |
'occi.networkinterface.ip6' : ip6address |
179 | 226 |
} |
180 | 227 |
|
... | ... | |
183 | 230 |
'occi.networkinterface.allocation' : '', |
184 | 231 |
'occi.networking.interface': '', |
185 | 232 |
'occi.networkinterface.mac' : '', |
186 |
'occi.networkinterface.ip4' :'',
|
|
233 |
'occi.networkinterface.address' :'',
|
|
187 | 234 |
'occi.networkinterface.ip6' : '' } |
188 | 235 |
|
189 | 236 |
resource.links.append(NET_LINK) |
... | ... | |
192 | 239 |
|
193 | 240 |
except ClientError as ce: |
194 | 241 |
if ce.status == 404: |
195 |
print('Image not found, sorry!!!')
|
|
242 |
print('Image not found (probably older version')
|
|
196 | 243 |
continue |
197 | 244 |
else: |
198 | 245 |
raise ce |
... | ... | |
203 | 250 |
if '/network/' not in key: |
204 | 251 |
self.registry.delete_resource(key, None) |
205 | 252 |
|
206 |
|
|
253 |
|
|
207 | 254 |
def __call__(self, environ, response): |
208 | 255 |
|
256 |
# Enable VOMS Authorization |
|
257 |
print "snf-occi application has been called!" |
|
258 |
|
|
259 |
req = Request(environ) |
|
260 |
auth_endpoint = 'snf-auth uri=\'https://'+SERVER_CONFIG['hostname']+':5000/main\'' |
|
261 |
|
|
262 |
if not req.environ.has_key('HTTP_X_AUTH_TOKEN'): |
|
263 |
|
|
264 |
print "Error: An authentication token has not been provided!" |
|
265 |
status = '401 Not Authorized' |
|
266 |
headers = [('Content-Type', 'text/html'),('Www-Authenticate',auth_endpoint)] |
|
267 |
response(status,headers) |
|
268 |
return [str(response)] |
|
269 |
|
|
270 |
|
|
271 |
if ENABLE_VOMS: |
|
272 |
|
|
273 |
if req.environ.has_key('HTTP_X_AUTH_TOKEN'): |
|
274 |
|
|
275 |
environ['HTTP_AUTH_TOKEN']= req.environ['HTTP_X_AUTH_TOKEN'] |
|
276 |
compClient = ComputeClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN']) |
|
277 |
cyclClient = CycladesClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN']) |
|
278 |
|
|
279 |
try: |
|
280 |
#Up-to-date flavors and images |
|
281 |
self.refresh_images(compClient,cyclClient) |
|
282 |
self.refresh_flavors_norecursive(compClient,cyclClient) |
|
283 |
self.refresh_network_instances(cyclClient) |
|
284 |
self.refresh_compute_instances(compClient,cyclClient) |
|
285 |
# token will be represented in self.extras |
|
286 |
return self._call_occi(environ, response, security = None, token = environ['HTTP_AUTH_TOKEN'], snf = compClient, client = cyclClient) |
|
287 |
except HTTPError: |
|
288 |
print "Exception from unauthorized access!" |
|
289 |
status = '401 Not Authorized' |
|
290 |
headers = [('Content-Type', 'text/html'),('Www-Authenticate',auth_endpoint)] |
|
291 |
response(status,headers) |
|
292 |
return [str(response)] |
|
293 |
|
|
294 |
else: |
|
295 |
|
|
296 |
#raise HTTPError(404, "Unauthorized access") |
|
297 |
status = '401 Not Authorized' |
|
298 |
headers = [('Content-Type', 'text/html'),('Www-Authenticate',auth_endpoint)] |
|
299 |
response(status,headers) |
|
300 |
return [str(response)] |
|
301 |
|
|
302 |
else: |
|
209 | 303 |
compClient = ComputeClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN']) |
210 | 304 |
cyclClient = CycladesClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN']) |
211 | 305 |
|
212 | 306 |
#Up-to-date flavors and images |
213 |
print "@refresh_images" |
|
307 |
|
|
214 | 308 |
self.refresh_images(compClient,cyclClient) |
215 |
print "@refresh_flavors" |
|
309 |
|
|
216 | 310 |
self.refresh_flavors_norecursive(compClient,cyclClient) |
217 | 311 |
self.refresh_network_instances(cyclClient) |
218 |
print "@refresh_compute_instances" |
|
219 | 312 |
self.refresh_compute_instances(compClient,cyclClient) |
220 |
|
|
313 |
|
|
221 | 314 |
# token will be represented in self.extras |
222 | 315 |
return self._call_occi(environ, response, security = None, token = environ['HTTP_AUTH_TOKEN'], snf = compClient, client = cyclClient) |
223 | 316 |
|
317 |
def application(env, start_response): |
|
318 |
|
|
319 |
print "snf-occi will execute voms authentication" |
|
320 |
t =snf_voms.VomsAuthN() |
|
321 |
(user_dn, user_vo, user_fqans) = t.process_request(env) |
|
322 |
print (user_dn, user_vo, user_fqans) |
|
323 |
|
|
324 |
env['HTTP_AUTH_TOKEN'] = get_user_token(user_dn) |
|
325 |
|
|
326 |
# Get user authentication details |
|
327 |
astakosClient = astakos.AstakosClient(KAMAKI_CONFIG['astakos_url'], env['HTTP_AUTH_TOKEN']) |
|
328 |
user_details = astakosClient.authenticate() |
|
329 |
|
|
330 |
response = {'access': {'token':{'issued_at':'','expires': user_details['access']['token']['expires'] , 'id':env['HTTP_AUTH_TOKEN']}, |
|
331 |
'serviceCatalog': [], |
|
332 |
'user':{'username': user_dn,'roles_links':user_details['access']['user']['roles_links'],'id': user_details['access']['user']['id'], 'roles':[], 'name':user_dn }, |
|
333 |
'metadata': {'is_admin': 0, 'roles': user_details['access']['user']['roles']}}} |
|
334 |
|
|
335 |
|
|
336 |
status = '200 OK' |
|
337 |
headers = [('Content-Type', 'application/json')] |
|
338 |
start_response(status,headers) |
|
224 | 339 |
|
225 |
def main(): |
|
340 |
body = json.dumps(response) |
|
341 |
print body |
|
342 |
return [body] |
|
226 | 343 |
|
227 |
APP = MyAPP(registry = snfRegistry()) |
|
228 |
COMPUTE_BACKEND = ComputeBackend() |
|
229 |
NETWORK_BACKEND = NetworkBackend() |
|
230 |
NETWORKINTERFACE_BACKEND = NetworkInterfaceBackend() |
|
231 |
IPNETWORK_BACKEND = IpNetworkBackend() |
|
232 |
IPNETWORKINTERFACE_BACKEND = IpNetworkInterfaceBackend() |
|
233 |
|
|
234 |
APP.register_backend(COMPUTE, COMPUTE_BACKEND) |
|
235 |
APP.register_backend(START, COMPUTE_BACKEND) |
|
236 |
APP.register_backend(STOP, COMPUTE_BACKEND) |
|
237 |
APP.register_backend(RESTART, COMPUTE_BACKEND) |
|
238 |
APP.register_backend(SUSPEND, COMPUTE_BACKEND) |
|
239 |
APP.register_backend(RESOURCE_TEMPLATE, MixinBackend()) |
|
240 |
APP.register_backend(OS_TEMPLATE, MixinBackend()) |
|
344 |
|
|
345 |
def app_factory(global_config, **local_config): |
|
346 |
"""This function wraps our simple WSGI app so it |
|
347 |
can be used with paste.deploy""" |
|
348 |
return application |
|
349 |
|
|
350 |
def tenant_application(env, start_response): |
|
351 |
|
|
352 |
print "snf-occi will return tenant information" |
|
353 |
if env.has_key('SSL_CLIENT_S_DN_ENV'): |
|
354 |
print env['SSL_CLIENT_S_DN_ENV'], env['SSL_CLIENT_CERT_ENV'] |
|
241 | 355 |
|
242 |
# Network related backends |
|
243 |
APP.register_backend(NETWORK, NETWORK_BACKEND) |
|
244 |
APP.register_backend(IPNETWORK, IPNETWORK_BACKEND) |
|
245 |
APP.register_backend(NETWORKINTERFACE,NETWORKINTERFACE_BACKEND) |
|
246 |
APP.register_backend(IPNETWORKINTERFACE, IPNETWORKINTERFACE_BACKEND) |
|
247 |
|
|
248 |
VALIDATOR_APP = validator(APP) |
|
249 |
|
|
250 |
HTTPD = make_server('', SERVER_CONFIG['port'], VALIDATOR_APP) |
|
251 |
HTTPD.serve_forever() |
|
356 |
req = Request(env) |
|
357 |
if req.environ.has_key('HTTP_X_AUTH_TOKEN'): |
|
358 |
env['HTTP_AUTH_TOKEN']= req.environ['HTTP_X_AUTH_TOKEN'] |
|
359 |
else: |
|
360 |
raise HTTPError(404, "Unauthorized access") |
|
361 |
# Get user authentication details |
|
362 |
print "@ refresh_user authentication details" |
|
363 |
astakosClient = astakos.AstakosClient(KAMAKI_CONFIG['astakos_url'], env['HTTP_AUTH_TOKEN']) |
|
364 |
user_details = astakosClient.authenticate() |
|
365 |
|
|
366 |
response = {'tenants_links': [], 'tenants':[{'description':'Instances of EGI Federated Clouds TF','enabled': True, 'id':user_details['access']['user']['id'],'name':'EGI_FCTF'}]} |
|
367 |
|
|
368 |
status = '200 OK' |
|
369 |
headers = [('Content-Type', 'application/json')] |
|
370 |
start_response(status,headers) |
|
371 |
|
|
372 |
body = json.dumps(response) |
|
373 |
print body |
|
374 |
return [body] |
|
375 |
|
|
376 |
|
|
377 |
def tenant_app_factory(global_config, **local_config): |
|
378 |
"""This function wraps our simple WSGI app so it |
|
379 |
can be used with paste.deploy""" |
|
380 |
return tenant_application |
|
381 |
|
|
382 |
|
|
252 | 383 |
|
384 |
def occify_terms(term_name): |
|
385 |
''' |
|
386 |
Occifies a term_name so that it is compliant with GFD 185. |
|
387 |
''' |
|
388 |
term = term_name.strip().replace(' ', '_').replace('.', '-').lower() |
|
389 |
term=term.replace('(','_').replace(')','_').replace('@','_').replace('+','-_') |
|
390 |
return term |
|
391 |
|
|
392 |
def get_user_token(user_dn): |
|
393 |
config = kamaki_config.Config() |
|
394 |
return config.get_cloud("default", "token") |
Also available in: Unified diff