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