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