openmano first code upload
[osm/RO.git] / vimconn_openstack.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5 # This file is part of openmano
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
18 # under the License.
19 #
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
22 ##
23
24 '''
25 osconnector implements all the methods to interact with openstack using the python-client.
26 '''
27 __author__="Alfonso Tierno, Gerardo Garcia"
28 __date__ ="$22-jun-2014 11:19:29$"
29
30 import vimconn
31 import json
32 import yaml
33
34 from novaclient import client as nClient, exceptions as nvExceptions
35 import keystoneclient.v2_0.client as ksClient
36 import keystoneclient.exceptions as ksExceptions
37 import glanceclient.v2.client as glClient
38 import glanceclient.client as gl1Client
39 import glanceclient.exc as gl1Exceptions
40 from httplib import HTTPException
41 from neutronclient.neutron import client as neClient
42 from neutronclient.common import exceptions as neExceptions
43 from requests.exceptions import ConnectionError
44
45 '''contain the openstack virtual machine status to openmano status'''
46 vmStatus2manoFormat={'ACTIVE':'ACTIVE',
47 'PAUSED':'PAUSED',
48 'SUSPENDED': 'SUSPENDED',
49 'SHUTOFF':'INACTIVE',
50 'BUILD':'BUILD',
51 'ERROR':'ERROR','DELETED':'DELETED'
52 }
53 netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
54 }
55
56 class vimconnector(vimconn.vimconnector):
57 def __init__(self, uuid, name, tenant, url, url_admin=None, user=None, passwd=None, debug=True, config={}):
58 '''using common constructor parameters. In this case
59 'url' is the keystone authorization url,
60 'url_admin' is not use
61 '''
62 vimconn.vimconnector.__init__(self, uuid, name, tenant, url, url_admin, user, passwd, debug, config)
63
64 self.k_creds={}
65 self.n_creds={}
66 if not url:
67 raise TypeError, 'url param can not be NoneType'
68 self.k_creds['auth_url'] = url
69 self.n_creds['auth_url'] = url
70 if tenant:
71 self.k_creds['tenant_name'] = tenant
72 self.n_creds['project_id'] = tenant
73 if user:
74 self.k_creds['username'] = user
75 self.n_creds['username'] = user
76 if passwd:
77 self.k_creds['password'] = passwd
78 self.n_creds['api_key'] = passwd
79 self.reload_client = True
80
81 def __setitem__(self,index, value):
82 '''Set individuals parameters
83 Throw TypeError, KeyError
84 '''
85 if index=='tenant':
86 self.reload_client=True
87 self.tenant = value
88 if value:
89 self.k_creds['tenant_name'] = value
90 self.n_creds['project_id'] = value
91 else:
92 del self.k_creds['tenant_name']
93 del self.n_creds['project_id']
94 elif index=='user':
95 self.reload_client=True
96 self.user = value
97 if value:
98 self.k_creds['username'] = value
99 self.n_creds['username'] = value
100 else:
101 del self.k_creds['username']
102 del self.n_creds['username']
103 elif index=='passwd':
104 self.reload_client=True
105 self.passwd = value
106 if value:
107 self.k_creds['password'] = value
108 self.n_creds['api_key'] = value
109 else:
110 del self.k_creds['password']
111 del self.n_creds['api_key']
112 elif index=='url':
113 self.reload_client=True
114 self.url = value
115 if value:
116 self.k_creds['auth_url'] = value
117 self.n_creds['auth_url'] = value
118 else:
119 raise TypeError, 'url param can not be NoneType'
120 else:
121 vimconn.vimconnector.__setitem__(self,index, value)
122
123 def _reload_connection(self):
124 '''Called before any operation, it check if credentials has changed
125 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
126 '''
127 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
128 if self.reload_client:
129 #test valid params
130 if len(self.n_creds) <4:
131 raise ksExceptions.ClientException("Not enough parameters to connect to openstack")
132 self.nova = nClient.Client(2, **self.n_creds)
133 self.keystone = ksClient.Client(**self.k_creds)
134 self.glance_endpoint = self.keystone.service_catalog.url_for(service_type='image', endpoint_type='publicURL')
135 self.glance = glClient.Client(self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds
136 self.ne_endpoint=self.keystone.service_catalog.url_for(service_type='network', endpoint_type='publicURL')
137 self.neutron = neClient.Client('2.0', endpoint_url=self.ne_endpoint, token=self.keystone.auth_token, **self.k_creds)
138 self.reload_client = False
139
140 def new_external_port(self, port_data):
141 #TODO openstack if needed
142 '''Adds a external port to VIM'''
143 '''Returns the port identifier'''
144 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
145
146 def connect_port_network(self, port_id, network_id, admin=False):
147 #TODO openstack if needed
148 '''Connects a external port to a network'''
149 '''Returns status code of the VIM response'''
150 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
151
152 def new_user(self, user_name, user_passwd, tenant_id=None):
153 '''Adds a new user to openstack VIM'''
154 '''Returns the user identifier'''
155 if self.debug:
156 print "osconnector: Adding a new user to VIM"
157 try:
158 self._reload_connection()
159 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
160 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
161 return 1, user.id
162 except ksExceptions.ConnectionError as e:
163 error_value=-vimconn.HTTP_Bad_Request
164 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
165 except ksExceptions.ClientException as e: #TODO remove
166 error_value=-vimconn.HTTP_Bad_Request
167 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
168 #TODO insert exception vimconn.HTTP_Unauthorized
169 #if reaching here is because an exception
170 if self.debug:
171 print "new_tenant " + error_text
172 return error_value, error_text
173
174 def delete_user(self, user_id):
175 '''Delete a user from openstack VIM'''
176 '''Returns the user identifier'''
177 if self.debug:
178 print "osconnector: Deleting a user from VIM"
179 try:
180 self._reload_connection()
181 self.keystone.users.delete(user_id)
182 return 1, user_id
183 except ksExceptions.ConnectionError as e:
184 error_value=-vimconn.HTTP_Bad_Request
185 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
186 except ksExceptions.NotFound as e:
187 error_value=-vimconn.HTTP_Not_Found
188 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
189 except ksExceptions.ClientException as e: #TODO remove
190 error_value=-vimconn.HTTP_Bad_Request
191 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
192 #TODO insert exception vimconn.HTTP_Unauthorized
193 #if reaching here is because an exception
194 if self.debug:
195 print "delete_tenant " + error_text
196 return error_value, error_text
197
198 def new_tenant(self,tenant_name,tenant_description):
199 '''Adds a new tenant to openstack VIM'''
200 '''Returns the tenant identifier'''
201 if self.debug:
202 print "osconnector: Adding a new tenant to VIM"
203 try:
204 self._reload_connection()
205 tenant=self.keystone.tenants.create(tenant_name, tenant_description)
206 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
207 return 1, tenant.id
208 except ksExceptions.ConnectionError as e:
209 error_value=-vimconn.HTTP_Bad_Request
210 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
211 except ksExceptions.ClientException as e: #TODO remove
212 error_value=-vimconn.HTTP_Bad_Request
213 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
214 #TODO insert exception vimconn.HTTP_Unauthorized
215 #if reaching here is because an exception
216 if self.debug:
217 print "new_tenant " + error_text
218 return error_value, error_text
219
220 def delete_tenant(self,tenant_id):
221 '''Delete a tenant from openstack VIM'''
222 '''Returns the tenant identifier'''
223 if self.debug:
224 print "osconnector: Deleting a tenant from VIM"
225 try:
226 self._reload_connection()
227 self.keystone.tenants.delete(tenant_id)
228 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
229 return 1, tenant_id
230 except ksExceptions.ConnectionError as e:
231 error_value=-vimconn.HTTP_Bad_Request
232 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
233 except ksExceptions.ClientException as e: #TODO remove
234 error_value=-vimconn.HTTP_Bad_Request
235 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
236 #TODO insert exception vimconn.HTTP_Unauthorized
237 #if reaching here is because an exception
238 if self.debug:
239 print "delete_tenant " + error_text
240 return error_value, error_text
241
242 def __net_os2mano(self, net_list_dict):
243 '''Transform the net openstack format to mano format
244 net_list_dict can be a list of dict or a single dict'''
245 if type(net_list_dict) is dict:
246 net_list_=(net_list_dict,)
247 elif type(net_list_dict) is list:
248 net_list_=net_list_dict
249 else:
250 raise TypeError("param net_list_dict must be a list or a dictionary")
251 for net in net_list_:
252 if net.get('provider:network_type') == "vlan":
253 net['type']='data'
254 else:
255 net['type']='bridge'
256
257 def new_tenant_network(self,net_name,net_type,public=False,cidr=None,vlan=None):
258 '''Adds a tenant network to VIM'''
259 '''Returns the network identifier'''
260 if self.debug:
261 print "osconnector: Adding a new tenant network to VIM (tenant: " + self.tenant + ", type: " + net_type + "): "+ net_name
262 try:
263 self._reload_connection()
264 network_dict = {'name': net_name, 'admin_state_up': True}
265 if net_type=="data" or net_type=="ptp":
266 if self.config.get('dataplane_physical_net') == None:
267 return -vimconn.HTTP_Bad_Request, "You must provide a 'dataplane_physical_net' at config value before creating sriov network "
268
269 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
270 network_dict["provider:network_type"] = "vlan"
271 if vlan!=None:
272 network_dict["provider:network_type"] = vlan
273 network_dict["shared"]=public
274 new_net=self.neutron.create_network({'network':network_dict})
275 #print new_net
276 #create fake subnetwork
277 if not cidr:
278 cidr="192.168.111.0/24"
279 subnet={"name":net_name+"-subnet",
280 "network_id": new_net["network"]["id"],
281 "ip_version": 4,
282 "cidr": cidr
283 }
284 self.neutron.create_subnet({"subnet": subnet} )
285 return 1, new_net["network"]["id"]
286 except neExceptions.ConnectionFailed as e:
287 error_value=-vimconn.HTTP_Bad_Request
288 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
289 except (ksExceptions.ClientException, neExceptions.NeutronException) as e:
290 error_value=-vimconn.HTTP_Bad_Request
291 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
292 #TODO insert exception vimconn.HTTP_Unauthorized
293 #if reaching here is because an exception
294 if self.debug:
295 print "new_tenant_network " + error_text
296 return error_value, error_text
297
298 def get_network_list(self, filter_dict={}):
299 '''Obtain tenant networks of VIM
300 Filter_dict can be:
301 name: network name
302 id: network uuid
303 shared: boolean
304 tenant_id: tenant
305 admin_state_up: boolean
306 status: 'ACTIVE'
307 Returns the network list of dictionaries
308 '''
309 if self.debug:
310 print "osconnector.get_network_list(): Getting network from VIM (filter: " + str(filter_dict) + "): "
311 try:
312 self._reload_connection()
313 net_dict=self.neutron.list_networks(**filter_dict)
314 net_list=net_dict["networks"]
315 self.__net_os2mano(net_list)
316 return 1, net_list
317 except neClient.exceptions.ConnectionFailed as e:
318 error_value=-vimconn.HTTP_Bad_Request
319 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
320 except (ksExceptions.ClientException, neExceptions.NeutronException) as e:
321 error_value=-vimconn.HTTP_Bad_Request
322 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
323 #TODO insert exception vimconn.HTTP_Unauthorized
324 #if reaching here is because an exception
325 if self.debug:
326 print "get_network_list " + error_text
327 return error_value, error_text
328
329 def get_tenant_network(self, net_id, tenant_id=None):
330 '''Obtain tenant networks of VIM'''
331 '''Returns the network information from a network id'''
332 if self.debug:
333 print "osconnector.get_tenant_network(): Getting tenant network %s from VIM" % net_id
334 filter_dict={"id": net_id}
335 if tenant_id:
336 filter_dict["tenant_id"] = tenant_id
337 r, net_list = self.get_network_list(filter_dict)
338 if r<0:
339 return r, net_list
340 if len(net_list)==0:
341 return -vimconn.HTTP_Not_Found, "Network '%s' not found" % net_id
342 elif len(net_list)>1:
343 return -vimconn.HTTP_Conflict, "Found more than one network with this criteria"
344 net = net_list[0]
345 subnets=[]
346 for subnet_id in net.get("subnets", () ):
347 try:
348 subnet = self.neutron.show_subnet(subnet_id)
349 except Exception as e:
350 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
351 print "osconnector.get_tenant_network(): Error getting subnet %s %s" % (net_id, error_text)
352 subnet = {"id": subnet_id, "fault": error_text}
353 subnets.append(subnet)
354 net["subnets"] = subnets
355 return 1, net
356
357
358 def delete_tenant_network(self, net_id):
359 '''Deletes a tenant network from VIM'''
360 '''Returns the network identifier'''
361 if self.debug:
362 print "osconnector: Deleting a new tenant network from VIM tenant: " + self.tenant + ", id: " + net_id
363 try:
364 self._reload_connection()
365 #delete VM ports attached to this networks before the network
366 ports = self.neutron.list_ports(network_id=net_id)
367 for p in ports['ports']:
368 try:
369 self.neutron.delete_port(p["id"])
370 except Exception as e:
371 print "Error deleting port: " + type(e).__name__ + ": "+ str(e)
372 self.neutron.delete_network(net_id)
373 return 1, net_id
374 except neClient.exceptions.ConnectionFailed as e:
375 error_value=-vimconn.HTTP_Bad_Request
376 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
377 except neExceptions.NetworkNotFoundClient as e:
378 error_value=-vimconn.HTTP_Not_Found
379 error_text= type(e).__name__ + ": "+ str(e.args[0])
380 except (ksExceptions.ClientException, neExceptions.NeutronException) as e:
381 error_value=-vimconn.HTTP_Bad_Request
382 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
383 #TODO insert exception vimconn.HTTP_Unauthorized
384 #if reaching here is because an exception
385 if self.debug:
386 print "delete_tenant_network " + error_text
387 return error_value, error_text
388
389 def get_tenant_flavor(self, flavor_id):
390 '''Obtain flavor details from the VIM
391 Returns the flavor dict details
392 '''
393 print "VIMConnector: Getting flavor from VIM"
394 try:
395 self._reload_connection()
396 flavor = self.nova.flavors.find(id=flavor_id)
397 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
398 return 1, {"flavor": flavor.to_dict()}
399 except nvExceptions.NotFound as e:
400 error_value=-vimconn.HTTP_Not_Found
401 error_text= "flavor instance %s not found" % flavor_id
402 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
403 error_value=-vimconn.HTTP_Bad_Request
404 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
405 #TODO insert exception vimconn.HTTP_Unauthorized
406 #if reaching here is because an exception
407 if self.debug:
408 print "get_tenant_flavor " + error_text
409 return error_value, error_text
410
411 def new_tenant_flavor(self, flavor_dict, change_name_if_used=True):
412 '''Adds a tenant flavor to openstack VIM
413 if change_name_if_used is True, it will change name in case of conflict
414 Returns the flavor identifier
415 '''
416 retry=0
417 name_suffix = 0
418 name=flavor_dict['name']
419 while retry<2:
420 retry+=1
421 try:
422 self._reload_connection()
423 if change_name_if_used:
424 #get used names
425 fl_names=[]
426 fl=self.nova.flavors.list()
427 for f in fl:
428 fl_names.append(f.name)
429 while name in fl_names:
430 name_suffix += 1
431 name = flavor_dict['name']+"-" + str(name_suffix)
432
433 ram = flavor_dict.get('ram',64)
434 vcpus = flavor_dict.get('vcpus',1)
435 numa_properties=None
436
437 extended=flavor_dict.get("extended")
438 if extended:
439 numas=extended.get("numas")
440 if numas:
441 numa_nodes = len(numas)
442 if numa_nodes > 1:
443 return -1, "Can not add flavor with more than one numa"
444 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
445 numa_properties["hw:mem_page_size"] = "large"
446 numa_properties["hw:cpu_policy"] = "dedicated"
447 numa_properties["hw:numa_mempolicy"] = "strict"
448 for numa in numas:
449 #overwrite ram and vcpus
450 ram = numa['memory']*1024
451 if 'paired-threads' in numa:
452 vcpus = numa['paired-threads']*2
453 numa_properties["hw:cpu_threads_policy"] = "prefer"
454 elif 'cores' in numa:
455 vcpus = numa['cores']
456 #numa_properties["hw:cpu_threads_policy"] = "prefer"
457 elif 'threads' in numa:
458 vcpus = numa['threads']
459 numa_properties["hw:cpu_policy"] = "isolated"
460 for interface in numa.get("interfaces",() ):
461 if interface["dedicated"]=="yes":
462 error_value=-vimconn.HTTP_Bad_Request
463 error_text= "Passthrough interfaces are not supported for the openstack connector"
464 break
465 #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
466
467 #create flavor
468 new_flavor=self.nova.flavors.create(name,
469 ram,
470 vcpus,
471 flavor_dict.get('disk',1),
472 is_public=flavor_dict.get('is_public', True)
473 )
474 #add metadata
475 if numa_properties:
476 new_flavor.set_keys(numa_properties)
477 return 1, new_flavor.id
478 except nvExceptions.Conflict as e:
479 error_value=-vimconn.HTTP_Conflict
480 error_text= str(e)
481 if change_name_if_used:
482 continue
483 break
484 #except nvExceptions.BadRequest as e:
485 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
486 error_value=-vimconn.HTTP_Bad_Request
487 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
488 break
489 #TODO insert exception vimconn.HTTP_Unauthorized
490 #if reaching here is because an exception
491 if self.debug:
492 print "new_tenant_flavor " + error_text
493 return error_value, error_text
494
495 def delete_tenant_flavor(self,flavor_id):
496 '''Deletes a tenant flavor from openstack VIM
497 Returns >0,id if ok; or <0,error_text if error
498 '''
499 retry=0
500 while retry<2:
501 retry+=1
502 try:
503 self._reload_connection()
504 self.nova.flavors.delete(flavor_id)
505 return 1, flavor_id
506 except nvExceptions.NotFound as e:
507 error_value = -vimconn.HTTP_Not_Found
508 error_text = "flavor '%s' not found" % flavor_id
509 break
510 #except nvExceptions.BadRequest as e:
511 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
512 error_value=-vimconn.HTTP_Bad_Request
513 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
514 break
515 if self.debug:
516 print "delete_tenant_flavor " + error_text
517 #if reaching here is because an exception
518 return error_value, error_text
519
520 def new_tenant_image(self,image_dict):
521 '''
522 Adds a tenant image to VIM
523 if change_name_if_used is True, it will change name in case of conflict
524 Returns:
525 >1, image-id if the image is created
526 <0, message if there is an error
527 '''
528 retry=0
529 #using version 1 of glance client
530 glancev1 = gl1Client.Client('1',self.glance_endpoint, token=self.keystone.auth_token, **self.k_creds) #TODO check k_creds vs n_creds
531 while retry<2:
532 retry+=1
533 try:
534 self._reload_connection()
535 #determine format http://docs.openstack.org/developer/glance/formats.html
536 if "disk_format" in image_dict:
537 disk_format=image_dict["disk_format"]
538 else: #autodiscover base on extention
539 if image_dict['location'][-6:]==".qcow2":
540 disk_format="qcow2"
541 elif image_dict['location'][-4:]==".vhd":
542 disk_format="vhd"
543 elif image_dict['location'][-5:]==".vmdk":
544 disk_format="vmdk"
545 elif image_dict['location'][-4:]==".vdi":
546 disk_format="vdi"
547 elif image_dict['location'][-4:]==".iso":
548 disk_format="iso"
549 elif image_dict['location'][-4:]==".aki":
550 disk_format="aki"
551 elif image_dict['location'][-4:]==".ari":
552 disk_format="ari"
553 elif image_dict['location'][-4:]==".ami":
554 disk_format="ami"
555 else:
556 disk_format="raw"
557 print "new_tenant_image: '%s' loading from '%s'" % (image_dict['name'], image_dict['location'])
558 if image_dict['location'][0:4]=="http":
559 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
560 container_format="bare", location=image_dict['location'], disk_format=disk_format)
561 else: #local path
562 with open(image_dict['location']) as fimage:
563 new_image = glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
564 container_format="bare", data=fimage, disk_format=disk_format)
565 #insert metadata. We cannot use 'new_image.properties.setdefault'
566 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
567 new_image_nova=self.nova.images.find(id=new_image.id)
568 new_image_nova.metadata.setdefault('location',image_dict['location'])
569 metadata_to_load = image_dict.get('metadata')
570 if metadata_to_load:
571 for k,v in yaml.load(metadata_to_load).iteritems():
572 new_image_nova.metadata.setdefault(k,v)
573 return 1, new_image.id
574 except nvExceptions.Conflict as e:
575 error_value=-vimconn.HTTP_Conflict
576 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
577 break
578 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError) as e:
579 error_value=-vimconn.HTTP_Bad_Request
580 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
581 continue
582 except IOError as e: #can not open the file
583 error_value=-vimconn.HTTP_Bad_Request
584 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0])) + " for " + image_dict['location']
585 break
586 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
587 error_value=-vimconn.HTTP_Bad_Request
588 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
589 break
590 #TODO insert exception vimconn.HTTP_Unauthorized
591 #if reaching here is because an exception
592 if self.debug:
593 print "new_tenant_image " + error_text
594 return error_value, error_text
595
596 def delete_tenant_image(self, image_id):
597 '''Deletes a tenant image from openstack VIM
598 Returns >0,id if ok; or <0,error_text if error
599 '''
600 retry=0
601 while retry<2:
602 retry+=1
603 try:
604 self._reload_connection()
605 self.nova.images.delete(image_id)
606 return 1, image_id
607 except nvExceptions.NotFound as e:
608 error_value = -vimconn.HTTP_Not_Found
609 error_text = "flavor '%s' not found" % image_id
610 break
611 #except nvExceptions.BadRequest as e:
612 except (ksExceptions.ClientException, nvExceptions.ClientException) as e: #TODO remove
613 error_value=-vimconn.HTTP_Bad_Request
614 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
615 break
616 if self.debug:
617 print "delete_tenant_image " + error_text
618 #if reaching here is because an exception
619 return error_value, error_text
620
621 def new_tenant_vminstance(self,name,description,start,image_id,flavor_id,net_list):
622 '''Adds a VM instance to VIM
623 Params:
624 start: indicates if VM must start or boot in pause mode. Ignored
625 image_id,flavor_id: iamge and flavor uuid
626 net_list: list of interfaces, each one is a dictionary with:
627 name:
628 net_id: network uuid to connect
629 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
630 model: interface model, ignored #TODO
631 mac_address: used for SR-IOV ifaces #TODO for other types
632 use: 'data', 'bridge', 'mgmt'
633 type: 'virtual', 'PF', 'VF', 'VFnotShared'
634 vim_id: filled/added by this function
635 #TODO ip, security groups
636 Returns >=0, the instance identifier
637 <0, error_text
638 '''
639 if self.debug:
640 print "osconnector: Creating VM into VIM"
641 print " image %s flavor %s nics=%s" %(image_id, flavor_id,net_list)
642 try:
643 metadata=[]
644 net_list_vim=[]
645 self._reload_connection()
646 metadata_vpci={}
647 for net in net_list:
648 if not net.get("net_id"): #skip non connected iface
649 continue
650 if net["type"]=="virtual":
651 net_list_vim.append({'net-id': net["net_id"]})
652 if "vpci" in net:
653 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
654 elif net["type"]=="PF":
655 print "new_tenant_vminstance: Warning, can not connect a passthrough interface "
656 #TODO insert this when openstack consider passthrough ports as openstack neutron ports
657 else: #VF
658 if "vpci" in net:
659 if "VF" not in metadata_vpci:
660 metadata_vpci["VF"]=[]
661 metadata_vpci["VF"].append([ net["vpci"], "" ])
662 port_dict={
663 "network_id": net["net_id"],
664 "name": net.get("name"),
665 "binding:vnic_type": "direct",
666 "admin_state_up": True
667 }
668 if not port_dict["name"]:
669 port_dict["name"] = name
670 if net.get("mac_address"):
671 port_dict["mac_address"]=net["mac_address"]
672 #TODO: manage having SRIOV without vlan tag
673 #if net["type"] == "VFnotShared"
674 # port_dict["vlan"]=0
675 new_port = self.neutron.create_port({"port": port_dict })
676 net["mac_adress"] = new_port["port"]["mac_address"]
677 net["vim_id"] = new_port["port"]["id"]
678 net["ip"] = new_port["port"].get("fixed_ips",[{}])[0].get("ip_address")
679 net_list_vim.append({"port-id": new_port["port"]["id"]})
680 if metadata_vpci:
681 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
682
683 print "name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s"\
684 % (name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
685
686 security_groups = self.config.get('security_groups')
687 if type(security_groups) is str:
688 security_groups = ( security_groups, )
689 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
690 security_groups = security_groups,
691 availability_zone = self.config.get('availability_zone'),
692 key_name = self.config.get('keypair'),
693 ) #, description=description)
694
695
696 print "DONE :-)", server
697
698 # #TODO server.add_floating_ip("10.95.87.209")
699 # #To look for a free floating_ip
700 # free_floating_ip = None
701 # for floating_ip in self.neutron.list_floatingips().get("floatingips", () ):
702 # if not floating_ip["port_id"]:
703 # free_floating_ip = floating_ip["floating_ip_address"]
704 # break
705 # if free_floating_ip:
706 # server.add_floating_ip(free_floating_ip)
707
708
709 return 1, server.id
710 # except nvExceptions.NotFound as e:
711 # error_value=-vimconn.HTTP_Not_Found
712 # error_text= "vm instance %s not found" % vm_id
713 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError, TypeError) as e:
714 error_value=-vimconn.HTTP_Bad_Request
715 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
716 except neClient.exceptions.ConnectionFailed as e:
717 error_value=-vimconn.HTTP_Bad_Request
718 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
719 #TODO insert exception vimconn.HTTP_Unauthorized
720 #if reaching here is because an exception
721 if self.debug:
722 print "new_tenant_vminstance Exception",e, error_text
723 return error_value, error_text
724
725 def get_tenant_vminstance(self,vm_id):
726 '''Returns the VM instance information from VIM'''
727 if self.debug:
728 print "osconnector: Getting VM from VIM"
729 try:
730 self._reload_connection()
731 server = self.nova.servers.find(id=vm_id)
732 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
733 return 1, {"server": server.to_dict()}
734 except nvExceptions.NotFound as e:
735 error_value=-vimconn.HTTP_Not_Found
736 error_text= "vm instance %s not found" % vm_id
737 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
738 error_value=-vimconn.HTTP_Bad_Request
739 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
740 #TODO insert exception vimconn.HTTP_Unauthorized
741 #if reaching here is because an exception
742 if self.debug:
743 print "get_tenant_vminstance " + error_text
744 return error_value, error_text
745
746 def get_tenant_vminstance_console(self,vm_id, console_type="vnc"):
747 '''
748 Get a console for the virtual machine
749 Params:
750 vm_id: uuid of the VM
751 console_type, can be:
752 "novnc" (by default), "xvpvnc" for VNC types,
753 "rdp-html5" for RDP types, "spice-html5" for SPICE types
754 Returns <0, text on error, for example not available
755 1, URL/text with the console parameters
756 '''
757 if self.debug:
758 print "osconnector: Getting VM CONSOLE from VIM"
759 try:
760 self._reload_connection()
761 server = self.nova.servers.find(id=vm_id)
762 if console_type == None or console_type == "novnc":
763 console_dict = server.get_vnc_console("novnc")
764 elif console_type == "xvpvnc":
765 console_dict = server.get_vnc_console(console_type)
766 elif console_type == "rdp-html5":
767 console_dict = server.get_rdp_console(console_type)
768 elif console_type == "spice-html5":
769 console_dict = server.get_spice_console(console_type)
770 else:
771 return -vimconn.HTTP_Bad_Request, "console type '%s' not allowed" % console_type
772
773 console_dict1 = console_dict.get("console")
774 if console_dict1:
775 console_url = console_dict1.get("url")
776 if console_url:
777 #parse console_url
778 protocol_index = console_url.find("//")
779 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
780 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
781 if protocol_index < 0 or port_index<0 or suffix_index<0:
782 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
783 console_dict={"protocol": console_url[0:protocol_index],
784 "server": console_url[protocol_index+2:port_index],
785 "port": console_url[port_index:suffix_index],
786 "suffix": console_url[suffix_index+1:]
787 }
788 protocol_index += 2
789 return 1, console_dict
790 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
791
792 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
793 return 1, {"server": server.to_dict()}
794 except nvExceptions.NotFound as e:
795 error_value=-vimconn.HTTP_Not_Found
796 error_text= "vm instance %s not found" % vm_id
797 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest) as e:
798 error_value=-vimconn.HTTP_Bad_Request
799 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
800 #TODO insert exception vimconn.HTTP_Unauthorized
801 #if reaching here is because an exception
802 if self.debug:
803 print "get_tenant_vminstance " + error_text
804 return error_value, error_text
805
806
807 def delete_tenant_vminstance(self, vm_id):
808 '''Removes a VM instance from VIM
809 Returns >0, the instance identifier
810 <0, error_text
811 '''
812 if self.debug:
813 print "osconnector: Getting VM from VIM"
814 try:
815 self._reload_connection()
816 #delete VM ports attached to this networks before the virtual machine
817 ports = self.neutron.list_ports(device_id=vm_id)
818 for p in ports['ports']:
819 try:
820 self.neutron.delete_port(p["id"])
821 except Exception as e:
822 print "Error deleting port: " + type(e).__name__ + ": "+ str(e)
823 self.nova.servers.delete(vm_id)
824 return 1, vm_id
825 except nvExceptions.NotFound as e:
826 error_value=-vimconn.HTTP_Not_Found
827 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
828 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
829 error_value=-vimconn.HTTP_Bad_Request
830 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
831 #TODO insert exception vimconn.HTTP_Unauthorized
832 #if reaching here is because an exception
833 if self.debug:
834 print "get_tenant_vminstance " + error_text
835 return error_value, error_text
836
837 def refresh_tenant_vms_and_nets(self, vmDict, netDict):
838 '''Refreshes the status of the dictionaries of VM instances and nets passed as arguments. It modifies the dictionaries
839 Returns:
840 - result: 0 if all elements could be refreshed (even if its status didn't change)
841 n>0, the number of elements that couldn't be refreshed,
842 <0 if error (foreseen)
843 - error_msg: text with reference to possible errors
844 '''
845 #vms_refreshed = []
846 #nets_refreshed = []
847 vms_unrefreshed = []
848 nets_unrefreshed = []
849 if self.debug:
850 print "osconnector refresh_tenant_vms and nets: Getting tenant VM instance information from VIM"
851 for vm_id in vmDict:
852 vmDict[vm_id] = {'error_msg':None, 'vim_info':None}
853 r,c = self.get_tenant_vminstance(vm_id)
854 if r<0:
855 print "osconnector refresh_tenant_vm. Error getting vm_id '%s' status: %s" % (vm_id, c)
856 if r==-vimconn.HTTP_Not_Found:
857 vmDict[vm_id]['status'] = "DELETED"
858 else:
859 vmDict[vm_id]['status'] = "VIM_ERROR"
860 vmDict[vm_id]['error_msg'] = c
861 vms_unrefreshed.append(vm_id)
862 else:
863 try:
864 vmDict[vm_id]['status'] = vmStatus2manoFormat[ c['server']['status'] ]
865 vmDict[vm_id]['vim_info'] = yaml.safe_dump(c['server'])
866 vmDict[vm_id]["interfaces"] = []
867 if c['server'].get('fault'):
868 vmDict[vm_id]['error_msg'] = str(c['server']['fault'])
869 #get interfaces
870 try:
871 self._reload_connection()
872 port_dict=self.neutron.list_ports(device_id=vm_id)
873 for port in port_dict["ports"]:
874 interface={}
875 interface['vim_info'] = yaml.safe_dump(port)
876 interface["mac_address"] = port.get("mac_address")
877 interface["vim_net_id"] = port["network_id"]
878 interface["vim_interface_id"] = port["id"]
879 ips=[]
880 #look for floating ip address
881 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
882 if floating_ip_dict.get("floatingips"):
883 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
884
885 for subnet in port["fixed_ips"]:
886 ips.append(subnet["ip_address"])
887 interface["ip_address"] = ";".join(ips)
888 vmDict[vm_id]["interfaces"].append(interface)
889 except Exception as e:
890 print type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
891
892
893 #error message at server.fault["message"]
894 except KeyError as e:
895 print "osconnector refresh_tenant_elements KeyError %s getting vm_id '%s' status %s" % (str(e), vm_id, c['server']['status'])
896 vmDict[vm_id]['status'] = "VIM_ERROR"
897 vmDict[vm_id]['error_msg'] = str(e)
898 vms_unrefreshed.append(vm_id)
899
900 #print "VMs refreshed: %s" % str(vms_refreshed)
901 for net_id in netDict:
902 netDict[net_id]={'error_msg':None, 'vim_info':None}
903 r,c = self.get_tenant_network(net_id)
904 if r<0:
905 print "osconnector refresh_tenant_network. Error getting net_id '%s' status: %s" % (net_id, c)
906 if r==-vimconn.HTTP_Not_Found:
907 netDict[net_id]['status'] = "DELETED" #TODO check exit status
908 else:
909 netDict[vm_id]['status'] = "VIM_ERROR"
910 netDict[vm_id]['error_msg'] = c
911 nets_unrefreshed.append(net_id)
912 else:
913 try:
914 netDict[net_id]['status'] = netStatus2manoFormat[ c['status'] ]
915 if c['status'] == "ACIVE" and not c['admin_state_up']:
916 netDict[net_id]['status'] = 'DOWN'
917 netDict[net_id]['vim_info'] = yaml.safe_dump(c)
918 if c.get('fault'): #TODO
919 netDict[net_id]['error_msg'] = str(c['fault'])
920 except KeyError as e:
921 print "osconnector refresh_tenant_elements KeyError %s getting vm_id '%s' status %s" % (str(e), vm_id, c['network']['status'])
922 netDict[net_id]['status'] = "VIM_ERROR"
923 netDict[net_id]['error_msg'] = str(e)
924 nets_unrefreshed.append(net_id)
925
926 #print "Nets refreshed: %s" % str(nets_refreshed)
927
928 error_msg=""
929 if len(vms_unrefreshed)+len(nets_unrefreshed)>0:
930 error_msg += "VMs unrefreshed: " + str(vms_unrefreshed) + "; nets unrefreshed: " + str(nets_unrefreshed)
931 print error_msg
932
933 #return len(vms_unrefreshed)+len(nets_unrefreshed), error_msg, vms_refreshed, nets_refreshed
934 return len(vms_unrefreshed)+len(nets_unrefreshed), error_msg
935
936 def action_tenant_vminstance(self, vm_id, action_dict):
937 '''Send and action over a VM instance from VIM
938 Returns the status'''
939 if self.debug:
940 print "osconnector: Action over VM instance from VIM " + vm_id
941 try:
942 self._reload_connection()
943 server = self.nova.servers.find(id=vm_id)
944 if "start" in action_dict:
945 if action_dict["start"]=="rebuild":
946 server.rebuild()
947 else:
948 if server.status=="PAUSED":
949 server.unpause()
950 elif server.status=="SUSPENDED":
951 server.resume()
952 elif server.status=="SHUTOFF":
953 server.start()
954 elif "pause" in action_dict:
955 server.pause()
956 elif "resume" in action_dict:
957 server.resume()
958 elif "shutoff" in action_dict or "shutdown" in action_dict:
959 server.stop()
960 elif "forceOff" in action_dict:
961 server.stop() #TODO
962 elif "terminate" in action_dict:
963 server.delete()
964 elif "createImage" in action_dict:
965 server.create_image()
966 #"path":path_schema,
967 #"description":description_schema,
968 #"name":name_schema,
969 #"metadata":metadata_schema,
970 #"imageRef": id_schema,
971 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
972 elif "rebuild" in action_dict:
973 server.rebuild(server.image['id'])
974 elif "reboot" in action_dict:
975 server.reboot() #reboot_type='SOFT'
976 elif "console" in action_dict:
977 console_type = action_dict["console"]
978 if console_type == None or console_type == "novnc":
979 console_dict = server.get_vnc_console("novnc")
980 elif console_type == "xvpvnc":
981 console_dict = server.get_vnc_console(console_type)
982 elif console_type == "rdp-html5":
983 console_dict = server.get_rdp_console(console_type)
984 elif console_type == "spice-html5":
985 console_dict = server.get_spice_console(console_type)
986 else:
987 return -vimconn.HTTP_Bad_Request, "console type '%s' not allowed" % console_type
988
989 try:
990 console_url = console_dict["console"]["url"]
991 #parse console_url
992 protocol_index = console_url.find("//")
993 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
994 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
995 if protocol_index < 0 or port_index<0 or suffix_index<0:
996 print "action_tenant_vminstance, console: response", str(console_dict)
997 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
998 console_dict2={"protocol": console_url[0:protocol_index],
999 "server": console_url[protocol_index+2 : port_index],
1000 "port": int(console_url[port_index+1 : suffix_index]),
1001 "suffix": console_url[suffix_index+1:]
1002 }
1003 protocol_index += 2
1004 return 1, console_dict2
1005 except:
1006 print "action_tenant_vminstance, console: response", str(console_dict)
1007 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1008
1009 return 1, vm_id
1010 except nvExceptions.NotFound as e:
1011 error_value=-vimconn.HTTP_Not_Found
1012 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1013 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1014 error_value=-vimconn.HTTP_Bad_Request
1015 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1016 #TODO insert exception vimconn.HTTP_Unauthorized
1017 #if reaching here is because an exception
1018 if self.debug:
1019 print "action_tenant_vminstance " + error_text
1020 return error_value, error_text
1021
1022 def get_hosts_info(self):
1023 '''Get the information of deployed hosts
1024 Returns the hosts content'''
1025 if self.debug:
1026 print "osconnector: Getting Host info from VIM"
1027 try:
1028 h_list=[]
1029 self._reload_connection()
1030 hypervisors = self.nova.hypervisors.list()
1031 for hype in hypervisors:
1032 h_list.append( hype.to_dict() )
1033 return 1, {"hosts":h_list}
1034 except nvExceptions.NotFound as e:
1035 error_value=-vimconn.HTTP_Not_Found
1036 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1037 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1038 error_value=-vimconn.HTTP_Bad_Request
1039 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1040 #TODO insert exception vimconn.HTTP_Unauthorized
1041 #if reaching here is because an exception
1042 if self.debug:
1043 print "get_hosts_info " + error_text
1044 return error_value, error_text
1045
1046 def get_hosts(self, vim_tenant):
1047 '''Get the hosts and deployed instances
1048 Returns the hosts content'''
1049 r, hype_dict = self.get_hosts_info()
1050 if r<0:
1051 return r, hype_dict
1052 hypervisors = hype_dict["hosts"]
1053 try:
1054 servers = self.nova.servers.list()
1055 for hype in hypervisors:
1056 for server in servers:
1057 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1058 if 'vm' in hype:
1059 hype['vm'].append(server.id)
1060 else:
1061 hype['vm'] = [server.id]
1062 return 1, hype_dict
1063 except nvExceptions.NotFound as e:
1064 error_value=-vimconn.HTTP_Not_Found
1065 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1066 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1067 error_value=-vimconn.HTTP_Bad_Request
1068 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1069 #TODO insert exception vimconn.HTTP_Unauthorized
1070 #if reaching here is because an exception
1071 if self.debug:
1072 print "get_hosts " + error_text
1073 return error_value, error_text
1074
1075 def get_image_id_from_path(self, path):
1076 '''Get the image id from image path in the VIM database'''
1077 '''Returns:
1078 0,"Image not found" if there are no images with that path
1079 1,image-id if there is one image with that path
1080 <0,message if there was an error (Image not found, error contacting VIM, more than 1 image with that path, etc.)
1081 '''
1082 try:
1083 self._reload_connection()
1084 images = self.nova.images.list()
1085 for image in images:
1086 if image.metadata.get("location")==path:
1087 return 1, image.id
1088 return 0, "image with location '%s' not found" % path
1089 except (ksExceptions.ClientException, nvExceptions.ClientException) as e: #TODO remove
1090 error_value=-vimconn.HTTP_Bad_Request
1091 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1092 if self.debug:
1093 print "get_image_id_from_path " + error_text
1094 #if reaching here is because an exception
1095 return error_value, error_text
1096
1097