Statistics
| Branch: | Revision:

root / snfOCCI / APIserver.py @ fe35958e

History | View | Annotate | Download (17.3 kB)

1
#!/usr/bin/env python
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

    
13
from snfOCCI.registry import snfRegistry
14
from snfOCCI.compute import ComputeBackend
15
from snfOCCI.config import SERVER_CONFIG, KAMAKI_CONFIG, VOMS_CONFIG
16
import snf_voms
17
from snfOCCI.network import NetworkBackend, IpNetworkBackend, IpNetworkInterfaceBackend, NetworkInterfaceBackend
18

    
19

    
20
from kamaki.clients.compute import ComputeClient
21
from kamaki.clients.cyclades import CycladesClient
22
from kamaki.clients import astakos
23
from kamaki.clients import ClientError
24
from kamaki.cli import config as kamaki_config
25

    
26
from occi.core_model import Mixin, Resource
27
from occi.backend import MixinBackend
28
from occi.extensions.infrastructure import COMPUTE, START, STOP, SUSPEND, RESTART, RESOURCE_TEMPLATE, OS_TEMPLATE, NETWORK, IPNETWORK, NETWORKINTERFACE,IPNETWORKINTERFACE 
29
from occi import wsgi
30
from occi.exceptions import HTTPError
31
from occi import core_model
32

    
33
from wsgiref.simple_server import make_server
34
from wsgiref.validate import validator
35
from webob import Request
36
from pprint import pprint
37

    
38

    
39
class MyAPP(wsgi.Application):
40
    '''
41
    An OCCI WSGI application.
42
    '''
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
        
77
    def refresh_images(self, snf, client):
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
      
87
    def refresh_flavors(self, snf, client):
88
        
89
        flavors = snf.list_flavors()
90
        for flavor in flavors:
91
            details = snf.get_flavor_details(flavor['id'])
92
            FLAVOR_ATTRIBUTES = {'occi.core.id': flavor['id'],
93
                                 'occi.compute.cores': str(details['vcpus']),
94
                                 'occi.compute.memory': str(details['ram']),
95
                                 'occi.storage.size': str(details['disk']),
96
                                 }
97
            FLAVOR = Mixin("http://schemas.ogf.org/occi/resource_tpl#", str(flavor['name']), [RESOURCE_TEMPLATE], attributes = FLAVOR_ATTRIBUTES)
98
            self.register_backend(FLAVOR, MixinBackend())
99
            
100
            
101
    def refresh_flavors_norecursive(self, snf, client):
102
        flavors = snf.list_flavors(True)
103
        print "Retrieving details for each image id"
104
        for flavor in flavors:
105
            FLAVOR_ATTRIBUTES = {'occi.core.id': flavor['id'],
106
                                 'occi.compute.cores': str(flavor['vcpus']),
107
                                 'occi.compute.memory': str(flavor['ram']),
108
                                 'occi.storage.size': str(flavor['disk']),
109
                                 }
110
             
111
            FLAVOR = Mixin("http://schemas.ogf.org/occi/resource_tpl#", occify_terms(str(flavor['name'])), [RESOURCE_TEMPLATE], title='FLAVOR',attributes = FLAVOR_ATTRIBUTES)
112
            self.register_backend(FLAVOR, MixinBackend())
113
            
114
    def refresh_network_instances(self,client):
115
        networks =client.networks_get(command = 'detail')
116
        network_details = networks.json['networks']
117
        resources = self.registry.resources
118
        occi_keys = resources.keys()
119
         
120
        for network in network_details:
121
            if '/network/'+str(network['id']) not in occi_keys:
122
                netID = '/network/'+str(network['id'])   
123
                snf_net = core_model.Resource(netID,
124
                                           NETWORK,
125
                                           [IPNETWORK])
126
                
127
                snf_net.attributes['occi.core.id'] = str(network['id']) 
128
               
129
                #This info comes from the network details
130
                snf_net.attributes['occi.network.state'] = str(network['status'])
131
                snf_net.attributes['occi.network.gateway'] = str(network['gateway'])
132
               
133
                if network['public'] == True:
134
                    snf_net.attributes['occi.network.type'] = "Public = True"
135
                else:
136
                    snf_net.attributes['occi.network.type'] = "Public = False"
137
                    
138
                self.registry.add_resource(netID, snf_net, None)       
139
            
140
        
141
    
142
    def refresh_compute_instances(self, snf, client):
143
        '''Syncing registry with cyclades resources'''
144
        
145
        servers = snf.list_servers()
146
        snf_keys = []
147
        for server in servers:
148
            snf_keys.append(str(server['id']))
149

    
150
        resources = self.registry.resources
151
        occi_keys = resources.keys()
152
        
153
        print occi_keys
154
        for serverID in occi_keys:
155
            if '/compute/' in serverID and resources[serverID].attributes['occi.compute.hostname'] == "":
156
                self.registry.delete_resource(serverID, None)
157
        
158
        occi_keys = resources.keys()
159
        
160
            
161
        #Compute instances in synnefo not available in registry
162
        diff = [x for x in snf_keys if '/compute/'+x not in occi_keys]
163
        
164
        for key in diff:
165

    
166
            details = snf.get_server_details(int(key))
167
            flavor = snf.get_flavor_details(details['flavor']['id'])
168
            
169
            try:
170
                print "line 65:Finished getting image details for VM "+key+" with ID" + str(details['flavor']['id'])
171
                image = snf.get_image_details(details['image']['id'])
172
                
173
                for i in self.registry.backends:
174
                    if i.term ==  occify_terms(str(image['name'])):
175
                        rel_image = i
176
                    if i.term ==  occify_terms(str(flavor['name'])):
177
                        rel_flavor = i
178

    
179
                        
180
                resource = Resource(key, COMPUTE, [rel_flavor, rel_image])
181
                resource.actions = [START]
182
                resource.attributes['occi.core.id'] = key
183
                resource.attributes['occi.compute.state'] = 'inactive'
184
                resource.attributes['occi.compute.architecture'] = SERVER_CONFIG['compute_arch']
185
                resource.attributes['occi.compute.cores'] = str(flavor['vcpus'])
186
                resource.attributes['occi.compute.memory'] = str(flavor['ram'])
187
                resource.attributes['occi.core.title'] = str(details['name'])
188
                networkIDs = details['addresses'].keys()
189
                if len(networkIDs)>0: 
190
                    resource.attributes['occi.compute.hostname'] =  str(details['addresses'][networkIDs[0]][0]['addr'])
191
                else:
192
                    resource.attributes['occi.compute.hostname'] = ""
193
                    
194
                self.registry.add_resource(key, resource, None)  
195
                
196
                for netKey in networkIDs:
197
                    link_id = str(uuid.uuid4())
198
                    NET_LINK = core_model.Link("http://schemas.ogf.org/occi/infrastructure#networkinterface" + link_id,
199
                                               NETWORKINTERFACE,
200
                                               [IPNETWORKINTERFACE], resource,
201
                                               self.registry.resources['/network/'+str(netKey)])
202
                    
203
                    for version in details['addresses'][netKey]:
204
                        if version['version']==4:
205
                            ip4address = str(version['addr'])
206
                            allocheme = str(version['OS-EXT-IPS:type'])
207
                        elif version['version']==6:
208
                            ip6address = str(version['addr'])
209
                   
210
                    if 'attachments' in details.keys():
211
                        for item in details['attachments']:
212
                            NET_LINK.attributes ={'occi.core.id':link_id,
213
                                          'occi.networkinterface.allocation' : allocheme,
214
                                          'occi.networking.interface': str(item['id']),
215
                                          'occi.networkinterface.mac' : str(item['mac_address']),
216
                                          'occi.networkinterface.address' : ip4address,
217
                                          'occi.networkinterface.ip6' :  ip6address                      
218
                                      }
219
                    elif  len(details['addresses'][netKey])>0:
220
                        NET_LINK.attributes ={'occi.core.id':link_id,
221
                                          'occi.networkinterface.allocation' : allocheme,
222
                                          'occi.networking.interface': '',
223
                                          'occi.networkinterface.mac' : '',
224
                                          'occi.networkinterface.address' : ip4address,
225
                                          'occi.networkinterface.ip6' :  ip6address                      
226
                                      }
227
    
228
                    else:
229
                        NET_LINK.attributes ={'occi.core.id':link_id,
230
                                          'occi.networkinterface.allocation' : '',
231
                                          'occi.networking.interface': '',
232
                                          'occi.networkinterface.mac' : '',
233
                                          'occi.networkinterface.address' :'',
234
                                          'occi.networkinterface.ip6' : '' }
235
                                      
236
                    resource.links.append(NET_LINK)
237
                    self.registry.add_resource(link_id, NET_LINK, None)
238
                     
239
                
240
            except ClientError as ce:
241
                if ce.status == 404:
242
                    print('Image not found (probably older version')
243
                    continue
244
                else:
245
                    raise ce
246
                  
247
        #Compute instances in registry not available in synnefo
248
        diff = [x for x in occi_keys if x[9:] not in snf_keys]
249
        for key in diff:
250
            if '/network/' not in key:
251
                self.registry.delete_resource(key, None)
252

    
253

    
254
    def __call__(self, environ, response):
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:  
303
            compClient = ComputeClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN'])
304
            cyclClient = CycladesClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN'])
305

    
306
            #Up-to-date flavors and images
307
           
308
            self.refresh_images(compClient,cyclClient)
309
            
310
            self.refresh_flavors_norecursive(compClient,cyclClient)
311
            self.refresh_network_instances(cyclClient)
312
            self.refresh_compute_instances(compClient,cyclClient)
313
            
314
            # token will be represented in self.extras
315
            return self._call_occi(environ, response, security = None, token = environ['HTTP_AUTH_TOKEN'], snf = compClient, client = cyclClient)
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)
339

    
340
    body = json.dumps(response)
341
    print body
342
    return [body]
343

    
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']    
355
 
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

    
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")