4 from optparse import OptionParser, OptionValueError
8 from eventlet import wsgi
13 from snfOCCI.registry import snfRegistry
14 from snfOCCI.compute import ComputeBackend
15 from snfOCCI.config import SERVER_CONFIG, KAMAKI_CONFIG, VOMS_CONFIG
17 from snfOCCI.network import NetworkBackend, IpNetworkBackend, IpNetworkInterfaceBackend, NetworkInterfaceBackend
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
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
30 from occi.exceptions import HTTPError
31 from occi import core_model
33 from wsgiref.simple_server import make_server
34 from wsgiref.validate import validator
35 from webob import Request
36 from pprint import pprint
39 class MyAPP(wsgi.Application):
41 An OCCI WSGI application.
46 Initialization of the WSGI OCCI application for synnefo
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)
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()
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())
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)
77 def refresh_images(self, snf, client):
79 images = snf.list_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())
85 raise HTTPError(404, "Unauthorized access")
87 def refresh_flavors(self, snf, client):
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']),
97 FLAVOR = Mixin("http://schemas.ogf.org/occi/resource_tpl#", str(flavor['name']), [RESOURCE_TEMPLATE], attributes = FLAVOR_ATTRIBUTES)
98 self.register_backend(FLAVOR, MixinBackend())
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']),
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())
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()
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,
127 snf_net.attributes['occi.core.id'] = str(network['id'])
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'])
133 if network['public'] == True:
134 snf_net.attributes['occi.network.type'] = "Public = True"
136 snf_net.attributes['occi.network.type'] = "Public = False"
138 self.registry.add_resource(netID, snf_net, None)
142 def refresh_compute_instances(self, snf, client):
143 '''Syncing registry with cyclades resources'''
145 servers = snf.list_servers()
147 for server in servers:
148 snf_keys.append(str(server['id']))
150 resources = self.registry.resources
151 occi_keys = resources.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)
158 occi_keys = resources.keys()
161 #Compute instances in synnefo not available in registry
162 diff = [x for x in snf_keys if '/compute/'+x not in occi_keys]
166 details = snf.get_server_details(int(key))
167 flavor = snf.get_flavor_details(details['flavor']['id'])
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'])
173 for i in self.registry.backends:
174 if i.term == occify_terms(str(image['name'])):
176 if i.term == occify_terms(str(flavor['name'])):
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'])
192 resource.attributes['occi.compute.hostname'] = ""
194 self.registry.add_resource(key, resource, None)
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,
200 [IPNETWORKINTERFACE], resource,
201 self.registry.resources['/network/'+str(netKey)])
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'])
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
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
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' : '' }
236 resource.links.append(NET_LINK)
237 self.registry.add_resource(link_id, NET_LINK, None)
240 except ClientError as ce:
242 print('Image not found (probably older version')
247 #Compute instances in registry not available in synnefo
248 diff = [x for x in occi_keys if x[9:] not in snf_keys]
250 if '/network/' not in key:
251 self.registry.delete_resource(key, None)
254 def __call__(self, environ, response):
256 # Enable VOMS Authorization
257 print "snf-occi application has been called!"
259 req = Request(environ)
260 auth_endpoint = 'snf-auth uri=\'https://'+SERVER_CONFIG['hostname']+':5000/main\''
262 if not req.environ.has_key('HTTP_X_AUTH_TOKEN'):
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)]
273 if req.environ.has_key('HTTP_X_AUTH_TOKEN'):
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'])
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)
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)]
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)]
303 compClient = ComputeClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN'])
304 cyclClient = CycladesClient(KAMAKI_CONFIG['compute_url'], environ['HTTP_AUTH_TOKEN'])
306 #Up-to-date flavors and images
308 self.refresh_images(compClient,cyclClient)
310 self.refresh_flavors_norecursive(compClient,cyclClient)
311 self.refresh_network_instances(cyclClient)
312 self.refresh_compute_instances(compClient,cyclClient)
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)
317 def application(env, start_response):
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)
324 env['HTTP_AUTH_TOKEN'] = get_user_token(user_dn)
326 # Get user authentication details
327 astakosClient = astakos.AstakosClient(KAMAKI_CONFIG['astakos_url'], env['HTTP_AUTH_TOKEN'])
328 user_details = astakosClient.authenticate()
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']}}}
337 headers = [('Content-Type', 'application/json')]
338 start_response(status,headers)
340 body = json.dumps(response)
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"""
350 def tenant_application(env, start_response):
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']
357 if req.environ.has_key('HTTP_X_AUTH_TOKEN'):
358 env['HTTP_AUTH_TOKEN']= req.environ['HTTP_X_AUTH_TOKEN']
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()
366 response = {'tenants_links': [], 'tenants':[{'description':'Instances of EGI Federated Clouds TF','enabled': True, 'id':user_details['access']['user']['id'],'name':'EGI_FCTF'}]}
369 headers = [('Content-Type', 'application/json')]
370 start_response(status,headers)
372 body = json.dumps(response)
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
384 def occify_terms(term_name):
386 Occifies a term_name so that it is compliant with GFD 185.
388 term = term_name.strip().replace(' ', '_').replace('.', '-').lower()
389 term=term.replace('(','_').replace(')','_').replace('@','_').replace('+','-_')
392 def get_user_token(user_dn):
393 config = kamaki_config.Config()
394 return config.get_cloud("default", "token")