Add option to choose voms authentication or native okeanos tokens
[snf-occi] / snfOCCI / APIserver.py
1 #!/usr/bin/env python
2
3 import re
4 import sys
5 from optparse import OptionParser, OptionValueError
6 import string
7 import sqlite3
8
9 from snfOCCI.registry import snfRegistry
10 from snfOCCI.compute import ComputeBackend
11 from snfOCCI.config import SERVER_CONFIG, KAMAKI_CONFIG
12
13 from kamaki.clients.compute import ComputeClient
14 from kamaki.clients.cyclades import CycladesClient
15 from kamaki.config  import Config
16
17 from occi.core_model import Mixin, Resource
18 from occi.backend import MixinBackend
19 from occi.extensions.infrastructure import COMPUTE, START, STOP, SUSPEND, RESTART, RESOURCE_TEMPLATE, OS_TEMPLATE
20 from occi.wsgi import Application
21 from occi.exceptions import HTTPError
22
23 from wsgiref.simple_server import make_server
24 from wsgiref.validate import validator
25
26 import voms
27
28 def parse_arguments(args):
29
30     kw = {}
31     kw["usage"] = "%prog [options]"
32     kw["description"] = "OCCI interface to synnefo API"
33
34     parser = OptionParser(**kw)
35     parser.disable_interspersed_args()
36
37     parser.add_option("--enable_voms", action="store_true", dest="enable_voms", default=False, help="Enable voms authorization")
38     parser.add_option("--voms_db", action="store", type="string", dest="voms_db", help="Path to sqlite database file")
39
40     (opts, args) = parser.parse_args(args)
41
42     if opts.enable_voms and not opts.voms_db:
43         print "--voms_db option required"
44         parser.print_help()
45
46     return (opts, args)
47
48 class MyAPP(Application):
49     '''
50     An OCCI WSGI application.
51     '''
52
53     def refresh_images(self, snf, client):
54
55         images = snf.list_images()
56         for image in images:
57             IMAGE_ATTRIBUTES = {'occi.core.id': str(image['id'])}
58             IMAGE = Mixin("http://schemas.ogf.org/occi/infrastructure#", str(image['name']), [OS_TEMPLATE], attributes = IMAGE_ATTRIBUTES)
59             self.register_backend(IMAGE, MixinBackend())
60
61     def refresh_flavors(self, snf, client):
62         
63         flavors = snf.list_flavors()
64         for flavor in flavors:
65             details = snf.get_flavor_details(flavor['id'])
66             FLAVOR_ATTRIBUTES = {'occi.core.id': flavor['id'],
67                                  'occi.compute.cores': details['cpu'],
68                                  'occi.compute.memory': details['ram'],
69                                  'occi.storage.size': details['disk'],
70                                  }
71             FLAVOR = Mixin("http://schemas.ogf.org/occi/infrastructure#", str(flavor['name']), [RESOURCE_TEMPLATE], attributes = FLAVOR_ATTRIBUTES)
72             self.register_backend(FLAVOR, MixinBackend())
73
74
75     def refresh_compute_instances(self, snf):
76         '''Syncing registry with cyclades resources'''
77
78         servers = snf.list_servers()
79         snf_keys = []
80         for server in servers:
81             snf_keys.append(str(server['id']))
82
83         resources = self.registry.resources
84         occi_keys = resources.keys()
85         
86         #Compute instances in synnefo not available in registry
87         diff = [x for x in snf_keys if '/compute/'+x not in occi_keys]
88         for key in diff:
89
90             details = snf.get_server_details(int(key))
91             flavor = snf.get_flavor_details(details['flavorRef'])
92             image = snf.get_image_details(details['imageRef'])
93
94             for i in self.registry.backends:
95                 if i.term == str(image['name']):
96                     rel_image = i
97                 if i.term == str(flavor['name']):
98                     rel_flavor = i
99
100             resource = Resource(key, COMPUTE, [rel_flavor, rel_image])
101             resource.actions = [START]
102             resource.attributes['occi.core.id'] = key
103             resource.attributes['occi.compute.state'] = 'inactive'
104             resource.attributes['occi.compute.architecture'] = SERVER_CONFIG['compute_arch']
105             resource.attributes['occi.compute.cores'] = flavor['cpu']
106             resource.attributes['occi.compute.memory'] = flavor['ram']
107             resource.attributes['occi.compute.hostname'] = SERVER_CONFIG['hostname'] % {'id':int(key)}
108
109             self.registry.add_resource(key, resource, None)
110
111         #Compute instances in registry not available in synnefo
112         diff = [x for x in occi_keys if x[9:] not in snf_keys]
113         for key in diff:
114             self.registry.delete_resource(key, None)
115
116
117     def __call__(self, environ, response):
118
119         #Authorization
120
121         if ENABLE_VOMS:
122
123             global VOMS_DB
124             conn = sqlite3.connect(VOMS_DB)
125
126             ssl_dict = dict()
127             
128             #Regular expression in HTTP headers
129             #raw environ[HTTP_SSL] contains PEM certificates in wrong format
130         
131             pem_re = r'^(-----BEGIN CERTIFICATE----- )(.*|\s]*)( -----END CERTIFICATE-----)'
132
133             client_cert = re.search(pem_re, environ["HTTP_SSL_CLIENT_CERT"])
134             client_chain = re.search(pem_re, environ["HTTP_SSL_CLIENT_CERT_CHAIN_0"])
135
136             client_cert_list=[]
137             client_chain_list=[]
138
139             for i in range(1,4):
140                 client_cert_list.append(string.strip(client_cert.group(i)))
141
142             for i in range(1,4):
143                 client_chain_list.append(string.strip(client_chain.group(i)))
144
145
146             cert = client_cert_list[0]+"\n"+client_cert_list[1].replace(" "," \n")+"\n"+client_cert_list[2]
147             chain = client_chain_list[0]+"\n"+client_chain_list[1].replace(" "," \n")+"\n"+client_chain_list[2]
148
149             ssl_dict["SSL_CLIENT_S_DN"] = environ["HTTP_SSL_CLIENT_S_DN"]
150             ssl_dict["SSL_CLIENT_CERT"] = cert
151             ssl_dict["SSL_CLIENT_CERT_CHAIN_0"] = chain
152
153             (user_dn, user_vo, user_fqans) = voms.authenticate(ssl_dict)
154             print (user_dn, user_vo, user_fqans)
155
156             cursor = conn.cursor()
157             query = "SELECT token FROM vo_map WHERE vo_name=?"
158             cursor.execute(query,[(user_vo)])
159
160             (token,) = cursor.fetchone()
161
162             if token:
163                 compClient = ComputeClient(KAMAKI_CONFIG['compute_url'], token)
164                 cyclClient = CycladesClient(KAMAKI_CONFIG['compute_url'], token)
165
166                 self.refresh_images(compClient,cyclClient)
167                 self.refresh_flavors(compClient,cyclClient)
168                 self.refresh_compute_instances(compClient)
169
170
171                 return self._call_occi(environ, response, security = None, token = token, snf = compClient, client = cyclClient)
172             else:
173                 raise HTTPError(404, "Unauthorized access")
174
175         else:
176             #Authorize with user token
177             compClient = ComputeClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN'])
178             cyclClient = CycladesClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN'])
179             
180             return self._call_occi(environ, response, security = None, token = environ['HTTP_AUTH_TOKEN'], snf = compClient, client = cyclClient)
181
182
183 def main():
184
185     global ENABLE_VOMS, VOMS_DB
186     (opts, args) = parse_arguments(sys.argv[1:])
187
188     ENABLE_VOMS = opts.enable_voms
189     VOMS_DB = opts.voms_db
190
191     APP = MyAPP(registry = snfRegistry())
192
193     COMPUTE_BACKEND = ComputeBackend()
194     APP.register_backend(COMPUTE, COMPUTE_BACKEND)
195     APP.register_backend(START, COMPUTE_BACKEND)
196     APP.register_backend(STOP, COMPUTE_BACKEND)
197     APP.register_backend(RESTART, COMPUTE_BACKEND)
198     APP.register_backend(SUSPEND, COMPUTE_BACKEND)
199     APP.register_backend(RESOURCE_TEMPLATE, MixinBackend())
200     APP.register_backend(OS_TEMPLATE, MixinBackend())
201  
202     VALIDATOR_APP = validator(APP)
203     HTTPD = make_server('', SERVER_CONFIG['port'], VALIDATOR_APP)
204     HTTPD.serve_forever()
205