bug 331 Ensure neutron port is deleted upon vm creation failed
[osm/RO.git] / osm_ro / 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-neutronclient.
26
27 For the VNF forwarding graph, The OpenStack VIM connector calls the
28 networking-sfc Neutron extension methods, whose resources are mapped
29 to the VIM connector's SFC resources as follows:
30 - Classification (OSM) -> Flow Classifier (Neutron)
31 - Service Function Instance (OSM) -> Port Pair (Neutron)
32 - Service Function (OSM) -> Port Pair Group (Neutron)
33 - Service Function Path (OSM) -> Port Chain (Neutron)
34 '''
35 __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C."
36 __date__ = "$22-sep-2017 23:59:59$"
37
38 import vimconn
39 import json
40 import logging
41 import netaddr
42 import time
43 import yaml
44 import random
45 import re
46 import copy
47
48 from novaclient import client as nClient, exceptions as nvExceptions
49 from keystoneauth1.identity import v2, v3
50 from keystoneauth1 import session
51 import keystoneclient.exceptions as ksExceptions
52 import keystoneclient.v3.client as ksClient_v3
53 import keystoneclient.v2_0.client as ksClient_v2
54 from glanceclient import client as glClient
55 import glanceclient.client as gl1Client
56 import glanceclient.exc as gl1Exceptions
57 from cinderclient import client as cClient
58 from httplib import HTTPException
59 from neutronclient.neutron import client as neClient
60 from neutronclient.common import exceptions as neExceptions
61 from requests.exceptions import ConnectionError
62
63
64 """contain the openstack virtual machine status to openmano status"""
65 vmStatus2manoFormat={'ACTIVE':'ACTIVE',
66 'PAUSED':'PAUSED',
67 'SUSPENDED': 'SUSPENDED',
68 'SHUTOFF':'INACTIVE',
69 'BUILD':'BUILD',
70 'ERROR':'ERROR','DELETED':'DELETED'
71 }
72 netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
73 }
74
75 supportedClassificationTypes = ['legacy_flow_classifier']
76
77 #global var to have a timeout creating and deleting volumes
78 volume_timeout = 60
79 server_timeout = 300
80
81 class vimconnector(vimconn.vimconnector):
82 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,
83 log_level=None, config={}, persistent_info={}):
84 '''using common constructor parameters. In this case
85 'url' is the keystone authorization url,
86 'url_admin' is not use
87 '''
88 api_version = config.get('APIversion')
89 if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'):
90 raise vimconn.vimconnException("Invalid value '{}' for config:APIversion. "
91 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version))
92 vim_type = config.get('vim_type')
93 if vim_type and vim_type not in ('vio', 'VIO'):
94 raise vimconn.vimconnException("Invalid value '{}' for config:vim_type."
95 "Allowed values are 'vio' or 'VIO'".format(vim_type))
96
97 if config.get('dataplane_net_vlan_range') is not None:
98 #validate vlan ranges provided by user
99 self._validate_vlan_ranges(config.get('dataplane_net_vlan_range'))
100
101 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
102 config)
103
104 self.insecure = self.config.get("insecure", False)
105 if not url:
106 raise TypeError('url param can not be NoneType')
107 self.persistent_info = persistent_info
108 self.availability_zone = persistent_info.get('availability_zone', None)
109 self.session = persistent_info.get('session', {'reload_client': True})
110 self.nova = self.session.get('nova')
111 self.neutron = self.session.get('neutron')
112 self.cinder = self.session.get('cinder')
113 self.glance = self.session.get('glance')
114 self.glancev1 = self.session.get('glancev1')
115 self.keystone = self.session.get('keystone')
116 self.api_version3 = self.session.get('api_version3')
117 self.vim_type = self.config.get("vim_type")
118 if self.vim_type:
119 self.vim_type = self.vim_type.upper()
120 if self.config.get("use_internal_endpoint"):
121 self.endpoint_type = "internalURL"
122 else:
123 self.endpoint_type = None
124
125 self.logger = logging.getLogger('openmano.vim.openstack')
126
127 ####### VIO Specific Changes #########
128 if self.vim_type == "VIO":
129 self.logger = logging.getLogger('openmano.vim.vio')
130
131 if log_level:
132 self.logger.setLevel( getattr(logging, log_level))
133
134 def __getitem__(self, index):
135 """Get individuals parameters.
136 Throw KeyError"""
137 if index == 'project_domain_id':
138 return self.config.get("project_domain_id")
139 elif index == 'user_domain_id':
140 return self.config.get("user_domain_id")
141 else:
142 return vimconn.vimconnector.__getitem__(self, index)
143
144 def __setitem__(self, index, value):
145 """Set individuals parameters and it is marked as dirty so to force connection reload.
146 Throw KeyError"""
147 if index == 'project_domain_id':
148 self.config["project_domain_id"] = value
149 elif index == 'user_domain_id':
150 self.config["user_domain_id"] = value
151 else:
152 vimconn.vimconnector.__setitem__(self, index, value)
153 self.session['reload_client'] = True
154
155 def _reload_connection(self):
156 '''Called before any operation, it check if credentials has changed
157 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
158 '''
159 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
160 if self.session['reload_client']:
161 if self.config.get('APIversion'):
162 self.api_version3 = self.config['APIversion'] == 'v3.3' or self.config['APIversion'] == '3'
163 else: # get from ending auth_url that end with v3 or with v2.0
164 self.api_version3 = self.url.endswith("/v3") or self.url.endswith("/v3/")
165 self.session['api_version3'] = self.api_version3
166 if self.api_version3:
167 if self.config.get('project_domain_id') or self.config.get('project_domain_name'):
168 project_domain_id_default = None
169 else:
170 project_domain_id_default = 'default'
171 if self.config.get('user_domain_id') or self.config.get('user_domain_name'):
172 user_domain_id_default = None
173 else:
174 user_domain_id_default = 'default'
175 auth = v3.Password(auth_url=self.url,
176 username=self.user,
177 password=self.passwd,
178 project_name=self.tenant_name,
179 project_id=self.tenant_id,
180 project_domain_id=self.config.get('project_domain_id', project_domain_id_default),
181 user_domain_id=self.config.get('user_domain_id', user_domain_id_default),
182 project_domain_name=self.config.get('project_domain_name'),
183 user_domain_name=self.config.get('user_domain_name'))
184 else:
185 auth = v2.Password(auth_url=self.url,
186 username=self.user,
187 password=self.passwd,
188 tenant_name=self.tenant_name,
189 tenant_id=self.tenant_id)
190 sess = session.Session(auth=auth, verify=not self.insecure)
191 if self.api_version3:
192 self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type)
193 else:
194 self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type)
195 self.session['keystone'] = self.keystone
196 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
197 # This implementation approach is due to the warning message in
198 # https://developer.openstack.org/api-guide/compute/microversions.html
199 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
200 # always require an specific microversion.
201 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
202 version = self.config.get("microversion")
203 if not version:
204 version = "2.1"
205 self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, endpoint_type=self.endpoint_type)
206 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type)
207 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type)
208 if self.endpoint_type == "internalURL":
209 glance_service_id = self.keystone.services.list(name="glance")[0].id
210 glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url
211 else:
212 glance_endpoint = None
213 self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint)
214 #using version 1 of glance client in new_image()
215 self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
216 endpoint=glance_endpoint)
217 self.session['reload_client'] = False
218 self.persistent_info['session'] = self.session
219 # add availablity zone info inside self.persistent_info
220 self._set_availablity_zones()
221 self.persistent_info['availability_zone'] = self.availability_zone
222
223 def __net_os2mano(self, net_list_dict):
224 '''Transform the net openstack format to mano format
225 net_list_dict can be a list of dict or a single dict'''
226 if type(net_list_dict) is dict:
227 net_list_=(net_list_dict,)
228 elif type(net_list_dict) is list:
229 net_list_=net_list_dict
230 else:
231 raise TypeError("param net_list_dict must be a list or a dictionary")
232 for net in net_list_:
233 if net.get('provider:network_type') == "vlan":
234 net['type']='data'
235 else:
236 net['type']='bridge'
237
238 def __classification_os2mano(self, class_list_dict):
239 """Transform the openstack format (Flow Classifier) to mano format
240 (Classification) class_list_dict can be a list of dict or a single dict
241 """
242 if isinstance(class_list_dict, dict):
243 class_list_ = [class_list_dict]
244 elif isinstance(class_list_dict, list):
245 class_list_ = class_list_dict
246 else:
247 raise TypeError(
248 "param class_list_dict must be a list or a dictionary")
249 for classification in class_list_:
250 id = classification.pop('id')
251 name = classification.pop('name')
252 description = classification.pop('description')
253 project_id = classification.pop('project_id')
254 tenant_id = classification.pop('tenant_id')
255 original_classification = copy.deepcopy(classification)
256 classification.clear()
257 classification['ctype'] = 'legacy_flow_classifier'
258 classification['definition'] = original_classification
259 classification['id'] = id
260 classification['name'] = name
261 classification['description'] = description
262 classification['project_id'] = project_id
263 classification['tenant_id'] = tenant_id
264
265 def __sfi_os2mano(self, sfi_list_dict):
266 """Transform the openstack format (Port Pair) to mano format (SFI)
267 sfi_list_dict can be a list of dict or a single dict
268 """
269 if isinstance(sfi_list_dict, dict):
270 sfi_list_ = [sfi_list_dict]
271 elif isinstance(sfi_list_dict, list):
272 sfi_list_ = sfi_list_dict
273 else:
274 raise TypeError(
275 "param sfi_list_dict must be a list or a dictionary")
276 for sfi in sfi_list_:
277 sfi['ingress_ports'] = []
278 sfi['egress_ports'] = []
279 if sfi.get('ingress'):
280 sfi['ingress_ports'].append(sfi['ingress'])
281 if sfi.get('egress'):
282 sfi['egress_ports'].append(sfi['egress'])
283 del sfi['ingress']
284 del sfi['egress']
285 params = sfi.get('service_function_parameters')
286 sfc_encap = False
287 if params:
288 correlation = params.get('correlation')
289 if correlation:
290 sfc_encap = True
291 sfi['sfc_encap'] = sfc_encap
292 del sfi['service_function_parameters']
293
294 def __sf_os2mano(self, sf_list_dict):
295 """Transform the openstack format (Port Pair Group) to mano format (SF)
296 sf_list_dict can be a list of dict or a single dict
297 """
298 if isinstance(sf_list_dict, dict):
299 sf_list_ = [sf_list_dict]
300 elif isinstance(sf_list_dict, list):
301 sf_list_ = sf_list_dict
302 else:
303 raise TypeError(
304 "param sf_list_dict must be a list or a dictionary")
305 for sf in sf_list_:
306 del sf['port_pair_group_parameters']
307 sf['sfis'] = sf['port_pairs']
308 del sf['port_pairs']
309
310 def __sfp_os2mano(self, sfp_list_dict):
311 """Transform the openstack format (Port Chain) to mano format (SFP)
312 sfp_list_dict can be a list of dict or a single dict
313 """
314 if isinstance(sfp_list_dict, dict):
315 sfp_list_ = [sfp_list_dict]
316 elif isinstance(sfp_list_dict, list):
317 sfp_list_ = sfp_list_dict
318 else:
319 raise TypeError(
320 "param sfp_list_dict must be a list or a dictionary")
321 for sfp in sfp_list_:
322 params = sfp.pop('chain_parameters')
323 sfc_encap = False
324 if params:
325 correlation = params.get('correlation')
326 if correlation:
327 sfc_encap = True
328 sfp['sfc_encap'] = sfc_encap
329 sfp['spi'] = sfp.pop('chain_id')
330 sfp['classifications'] = sfp.pop('flow_classifiers')
331 sfp['service_functions'] = sfp.pop('port_pair_groups')
332
333 # placeholder for now; read TODO note below
334 def _validate_classification(self, type, definition):
335 # only legacy_flow_classifier Type is supported at this point
336 return True
337 # TODO(igordcard): this method should be an abstract method of an
338 # abstract Classification class to be implemented by the specific
339 # Types. Also, abstract vimconnector should call the validation
340 # method before the implemented VIM connectors are called.
341
342 def _format_exception(self, exception):
343 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
344 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
345 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
346 )):
347 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
348 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
349 neExceptions.NeutronException, nvExceptions.BadRequest)):
350 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
351 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
352 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
353 elif isinstance(exception, nvExceptions.Conflict):
354 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
355 elif isinstance(exception, vimconn.vimconnException):
356 raise
357 else: # ()
358 self.logger.error("General Exception " + str(exception), exc_info=True)
359 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
360
361 def get_tenant_list(self, filter_dict={}):
362 '''Obtain tenants of VIM
363 filter_dict can contain the following keys:
364 name: filter by tenant name
365 id: filter by tenant uuid/id
366 <other VIM specific>
367 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
368 '''
369 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
370 try:
371 self._reload_connection()
372 if self.api_version3:
373 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
374 else:
375 project_class_list = self.keystone.tenants.findall(**filter_dict)
376 project_list=[]
377 for project in project_class_list:
378 if filter_dict.get('id') and filter_dict["id"] != project.id:
379 continue
380 project_list.append(project.to_dict())
381 return project_list
382 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
383 self._format_exception(e)
384
385 def new_tenant(self, tenant_name, tenant_description):
386 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
387 self.logger.debug("Adding a new tenant name: %s", tenant_name)
388 try:
389 self._reload_connection()
390 if self.api_version3:
391 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
392 description=tenant_description, is_domain=False)
393 else:
394 project = self.keystone.tenants.create(tenant_name, tenant_description)
395 return project.id
396 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
397 self._format_exception(e)
398
399 def delete_tenant(self, tenant_id):
400 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
401 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
402 try:
403 self._reload_connection()
404 if self.api_version3:
405 self.keystone.projects.delete(tenant_id)
406 else:
407 self.keystone.tenants.delete(tenant_id)
408 return tenant_id
409 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
410 self._format_exception(e)
411
412 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
413 '''Adds a tenant network to VIM. Returns the network identifier'''
414 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
415 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
416 try:
417 new_net = None
418 self._reload_connection()
419 network_dict = {'name': net_name, 'admin_state_up': True}
420 if net_type=="data" or net_type=="ptp":
421 if self.config.get('dataplane_physical_net') == None:
422 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
423 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
424 network_dict["provider:network_type"] = "vlan"
425 if vlan!=None:
426 network_dict["provider:network_type"] = vlan
427
428 ####### VIO Specific Changes #########
429 if self.vim_type == "VIO":
430 if vlan is not None:
431 network_dict["provider:segmentation_id"] = vlan
432 else:
433 if self.config.get('dataplane_net_vlan_range') is None:
434 raise vimconn.vimconnConflictException("You must provide "\
435 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
436 "at config value before creating sriov network with vlan tag")
437
438 network_dict["provider:segmentation_id"] = self._genrate_vlanID()
439
440 network_dict["shared"]=shared
441 new_net=self.neutron.create_network({'network':network_dict})
442 #print new_net
443 #create subnetwork, even if there is no profile
444 if not ip_profile:
445 ip_profile = {}
446 if 'subnet_address' not in ip_profile:
447 #Fake subnet is required
448 subnet_rand = random.randint(0, 255)
449 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
450 if 'ip_version' not in ip_profile:
451 ip_profile['ip_version'] = "IPv4"
452 subnet = {"name":net_name+"-subnet",
453 "network_id": new_net["network"]["id"],
454 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
455 "cidr": ip_profile['subnet_address']
456 }
457 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
458 subnet['gateway_ip'] = ip_profile.get('gateway_address')
459 if ip_profile.get('dns_address'):
460 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
461 if 'dhcp_enabled' in ip_profile:
462 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
463 if 'dhcp_start_address' in ip_profile:
464 subnet['allocation_pools'] = []
465 subnet['allocation_pools'].append(dict())
466 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
467 if 'dhcp_count' in ip_profile:
468 #parts = ip_profile['dhcp_start_address'].split('.')
469 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
470 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
471 ip_int += ip_profile['dhcp_count'] - 1
472 ip_str = str(netaddr.IPAddress(ip_int))
473 subnet['allocation_pools'][0]['end'] = ip_str
474 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
475 self.neutron.create_subnet({"subnet": subnet} )
476 return new_net["network"]["id"]
477 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
478 if new_net:
479 self.neutron.delete_network(new_net['network']['id'])
480 self._format_exception(e)
481
482 def get_network_list(self, filter_dict={}):
483 '''Obtain tenant networks of VIM
484 Filter_dict can be:
485 name: network name
486 id: network uuid
487 shared: boolean
488 tenant_id: tenant
489 admin_state_up: boolean
490 status: 'ACTIVE'
491 Returns the network list of dictionaries
492 '''
493 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
494 try:
495 self._reload_connection()
496 if self.api_version3 and "tenant_id" in filter_dict:
497 filter_dict['project_id'] = filter_dict.pop('tenant_id') #TODO check
498 net_dict=self.neutron.list_networks(**filter_dict)
499 net_list=net_dict["networks"]
500 self.__net_os2mano(net_list)
501 return net_list
502 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
503 self._format_exception(e)
504
505 def get_network(self, net_id):
506 '''Obtain details of network from VIM
507 Returns the network information from a network id'''
508 self.logger.debug(" Getting tenant network %s from VIM", net_id)
509 filter_dict={"id": net_id}
510 net_list = self.get_network_list(filter_dict)
511 if len(net_list)==0:
512 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
513 elif len(net_list)>1:
514 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
515 net = net_list[0]
516 subnets=[]
517 for subnet_id in net.get("subnets", () ):
518 try:
519 subnet = self.neutron.show_subnet(subnet_id)
520 except Exception as e:
521 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
522 subnet = {"id": subnet_id, "fault": str(e)}
523 subnets.append(subnet)
524 net["subnets"] = subnets
525 net["encapsulation"] = net.get('provider:network_type')
526 net["segmentation_id"] = net.get('provider:segmentation_id')
527 return net
528
529 def delete_network(self, net_id):
530 '''Deletes a tenant network from VIM. Returns the old network identifier'''
531 self.logger.debug("Deleting network '%s' from VIM", net_id)
532 try:
533 self._reload_connection()
534 #delete VM ports attached to this networks before the network
535 ports = self.neutron.list_ports(network_id=net_id)
536 for p in ports['ports']:
537 try:
538 self.neutron.delete_port(p["id"])
539 except Exception as e:
540 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
541 self.neutron.delete_network(net_id)
542 return net_id
543 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
544 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
545 self._format_exception(e)
546
547 def refresh_nets_status(self, net_list):
548 '''Get the status of the networks
549 Params: the list of network identifiers
550 Returns a dictionary with:
551 net_id: #VIM id of this network
552 status: #Mandatory. Text with one of:
553 # DELETED (not found at vim)
554 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
555 # OTHER (Vim reported other status not understood)
556 # ERROR (VIM indicates an ERROR status)
557 # ACTIVE, INACTIVE, DOWN (admin down),
558 # BUILD (on building process)
559 #
560 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
561 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
562
563 '''
564 net_dict={}
565 for net_id in net_list:
566 net = {}
567 try:
568 net_vim = self.get_network(net_id)
569 if net_vim['status'] in netStatus2manoFormat:
570 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
571 else:
572 net["status"] = "OTHER"
573 net["error_msg"] = "VIM status reported " + net_vim['status']
574
575 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
576 net['status'] = 'DOWN'
577 try:
578 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
579 except yaml.representer.RepresenterError:
580 net['vim_info'] = str(net_vim)
581 if net_vim.get('fault'): #TODO
582 net['error_msg'] = str(net_vim['fault'])
583 except vimconn.vimconnNotFoundException as e:
584 self.logger.error("Exception getting net status: %s", str(e))
585 net['status'] = "DELETED"
586 net['error_msg'] = str(e)
587 except vimconn.vimconnException as e:
588 self.logger.error("Exception getting net status: %s", str(e))
589 net['status'] = "VIM_ERROR"
590 net['error_msg'] = str(e)
591 net_dict[net_id] = net
592 return net_dict
593
594 def get_flavor(self, flavor_id):
595 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
596 self.logger.debug("Getting flavor '%s'", flavor_id)
597 try:
598 self._reload_connection()
599 flavor = self.nova.flavors.find(id=flavor_id)
600 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
601 return flavor.to_dict()
602 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
603 self._format_exception(e)
604
605 def get_flavor_id_from_data(self, flavor_dict):
606 """Obtain flavor id that match the flavor description
607 Returns the flavor_id or raises a vimconnNotFoundException
608 flavor_dict: contains the required ram, vcpus, disk
609 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
610 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
611 vimconnNotFoundException is raised
612 """
613 exact_match = False if self.config.get('use_existing_flavors') else True
614 try:
615 self._reload_connection()
616 flavor_candidate_id = None
617 flavor_candidate_data = (10000, 10000, 10000)
618 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
619 # numa=None
620 numas = flavor_dict.get("extended", {}).get("numas")
621 if numas:
622 #TODO
623 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
624 # if len(numas) > 1:
625 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
626 # numa=numas[0]
627 # numas = extended.get("numas")
628 for flavor in self.nova.flavors.list():
629 epa = flavor.get_keys()
630 if epa:
631 continue
632 # TODO
633 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
634 if flavor_data == flavor_target:
635 return flavor.id
636 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
637 flavor_candidate_id = flavor.id
638 flavor_candidate_data = flavor_data
639 if not exact_match and flavor_candidate_id:
640 return flavor_candidate_id
641 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
642 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
643 self._format_exception(e)
644
645 def new_flavor(self, flavor_data, change_name_if_used=True):
646 '''Adds a tenant flavor to openstack VIM
647 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
648 Returns the flavor identifier
649 '''
650 self.logger.debug("Adding flavor '%s'", str(flavor_data))
651 retry=0
652 max_retries=3
653 name_suffix = 0
654 name=flavor_data['name']
655 while retry<max_retries:
656 retry+=1
657 try:
658 self._reload_connection()
659 if change_name_if_used:
660 #get used names
661 fl_names=[]
662 fl=self.nova.flavors.list()
663 for f in fl:
664 fl_names.append(f.name)
665 while name in fl_names:
666 name_suffix += 1
667 name = flavor_data['name']+"-" + str(name_suffix)
668
669 ram = flavor_data.get('ram',64)
670 vcpus = flavor_data.get('vcpus',1)
671 numa_properties=None
672
673 extended = flavor_data.get("extended")
674 if extended:
675 numas=extended.get("numas")
676 if numas:
677 numa_nodes = len(numas)
678 if numa_nodes > 1:
679 return -1, "Can not add flavor with more than one numa"
680 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
681 numa_properties["hw:mem_page_size"] = "large"
682 numa_properties["hw:cpu_policy"] = "dedicated"
683 numa_properties["hw:numa_mempolicy"] = "strict"
684 if self.vim_type == "VIO":
685 numa_properties["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
686 numa_properties["vmware:latency_sensitivity_level"] = "high"
687 for numa in numas:
688 #overwrite ram and vcpus
689 ram = numa['memory']*1024
690 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
691 if 'paired-threads' in numa:
692 vcpus = numa['paired-threads']*2
693 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
694 numa_properties["hw:cpu_thread_policy"] = "require"
695 numa_properties["hw:cpu_policy"] = "dedicated"
696 elif 'cores' in numa:
697 vcpus = numa['cores']
698 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
699 numa_properties["hw:cpu_thread_policy"] = "isolate"
700 numa_properties["hw:cpu_policy"] = "dedicated"
701 elif 'threads' in numa:
702 vcpus = numa['threads']
703 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
704 numa_properties["hw:cpu_thread_policy"] = "prefer"
705 numa_properties["hw:cpu_policy"] = "dedicated"
706 # for interface in numa.get("interfaces",() ):
707 # if interface["dedicated"]=="yes":
708 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
709 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
710
711 #create flavor
712 new_flavor=self.nova.flavors.create(name,
713 ram,
714 vcpus,
715 flavor_data.get('disk',1),
716 is_public=flavor_data.get('is_public', True)
717 )
718 #add metadata
719 if numa_properties:
720 new_flavor.set_keys(numa_properties)
721 return new_flavor.id
722 except nvExceptions.Conflict as e:
723 if change_name_if_used and retry < max_retries:
724 continue
725 self._format_exception(e)
726 #except nvExceptions.BadRequest as e:
727 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
728 self._format_exception(e)
729
730 def delete_flavor(self,flavor_id):
731 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
732 '''
733 try:
734 self._reload_connection()
735 self.nova.flavors.delete(flavor_id)
736 return flavor_id
737 #except nvExceptions.BadRequest as e:
738 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
739 self._format_exception(e)
740
741 def new_image(self,image_dict):
742 '''
743 Adds a tenant image to VIM. imge_dict is a dictionary with:
744 name: name
745 disk_format: qcow2, vhd, vmdk, raw (by default), ...
746 location: path or URI
747 public: "yes" or "no"
748 metadata: metadata of the image
749 Returns the image_id
750 '''
751 retry=0
752 max_retries=3
753 while retry<max_retries:
754 retry+=1
755 try:
756 self._reload_connection()
757 #determine format http://docs.openstack.org/developer/glance/formats.html
758 if "disk_format" in image_dict:
759 disk_format=image_dict["disk_format"]
760 else: #autodiscover based on extension
761 if image_dict['location'][-6:]==".qcow2":
762 disk_format="qcow2"
763 elif image_dict['location'][-4:]==".vhd":
764 disk_format="vhd"
765 elif image_dict['location'][-5:]==".vmdk":
766 disk_format="vmdk"
767 elif image_dict['location'][-4:]==".vdi":
768 disk_format="vdi"
769 elif image_dict['location'][-4:]==".iso":
770 disk_format="iso"
771 elif image_dict['location'][-4:]==".aki":
772 disk_format="aki"
773 elif image_dict['location'][-4:]==".ari":
774 disk_format="ari"
775 elif image_dict['location'][-4:]==".ami":
776 disk_format="ami"
777 else:
778 disk_format="raw"
779 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
780 if image_dict['location'][0:4]=="http":
781 new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
782 container_format="bare", location=image_dict['location'], disk_format=disk_format)
783 else: #local path
784 with open(image_dict['location']) as fimage:
785 new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
786 container_format="bare", data=fimage, disk_format=disk_format)
787 #insert metadata. We cannot use 'new_image.properties.setdefault'
788 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
789 new_image_nova=self.nova.images.find(id=new_image.id)
790 new_image_nova.metadata.setdefault('location',image_dict['location'])
791 metadata_to_load = image_dict.get('metadata')
792 if metadata_to_load:
793 for k,v in yaml.load(metadata_to_load).iteritems():
794 new_image_nova.metadata.setdefault(k,v)
795 return new_image.id
796 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
797 self._format_exception(e)
798 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
799 if retry==max_retries:
800 continue
801 self._format_exception(e)
802 except IOError as e: #can not open the file
803 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
804 http_code=vimconn.HTTP_Bad_Request)
805
806 def delete_image(self, image_id):
807 '''Deletes a tenant image from openstack VIM. Returns the old id
808 '''
809 try:
810 self._reload_connection()
811 self.nova.images.delete(image_id)
812 return image_id
813 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
814 self._format_exception(e)
815
816 def get_image_id_from_path(self, path):
817 '''Get the image id from image path in the VIM database. Returns the image_id'''
818 try:
819 self._reload_connection()
820 images = self.nova.images.list()
821 for image in images:
822 if image.metadata.get("location")==path:
823 return image.id
824 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
825 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
826 self._format_exception(e)
827
828 def get_image_list(self, filter_dict={}):
829 '''Obtain tenant images from VIM
830 Filter_dict can be:
831 id: image id
832 name: image name
833 checksum: image checksum
834 Returns the image list of dictionaries:
835 [{<the fields at Filter_dict plus some VIM specific>}, ...]
836 List can be empty
837 '''
838 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
839 try:
840 self._reload_connection()
841 filter_dict_os=filter_dict.copy()
842 #First we filter by the available filter fields: name, id. The others are removed.
843 filter_dict_os.pop('checksum',None)
844 image_list = self.nova.images.findall(**filter_dict_os)
845 if len(image_list) == 0:
846 return []
847 #Then we filter by the rest of filter fields: checksum
848 filtered_list = []
849 for image in image_list:
850 try:
851 image_class = self.glance.images.get(image.id)
852 if 'checksum' not in filter_dict or image_class['checksum']==filter_dict.get('checksum'):
853 filtered_list.append(image_class.copy())
854 except gl1Exceptions.HTTPNotFound:
855 pass
856 return filtered_list
857 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
858 self._format_exception(e)
859
860 def __wait_for_vm(self, vm_id, status):
861 """wait until vm is in the desired status and return True.
862 If the VM gets in ERROR status, return false.
863 If the timeout is reached generate an exception"""
864 elapsed_time = 0
865 while elapsed_time < server_timeout:
866 vm_status = self.nova.servers.get(vm_id).status
867 if vm_status == status:
868 return True
869 if vm_status == 'ERROR':
870 return False
871 time.sleep(1)
872 elapsed_time += 1
873
874 # if we exceeded the timeout rollback
875 if elapsed_time >= server_timeout:
876 raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
877 http_code=vimconn.HTTP_Request_Timeout)
878
879 def _get_openstack_availablity_zones(self):
880 """
881 Get from openstack availability zones available
882 :return:
883 """
884 try:
885 openstack_availability_zone = self.nova.availability_zones.list()
886 openstack_availability_zone = [str(zone.zoneName) for zone in openstack_availability_zone
887 if zone.zoneName != 'internal']
888 return openstack_availability_zone
889 except Exception as e:
890 return None
891
892 def _set_availablity_zones(self):
893 """
894 Set vim availablity zone
895 :return:
896 """
897
898 if 'availability_zone' in self.config:
899 vim_availability_zones = self.config.get('availability_zone')
900 if isinstance(vim_availability_zones, str):
901 self.availability_zone = [vim_availability_zones]
902 elif isinstance(vim_availability_zones, list):
903 self.availability_zone = vim_availability_zones
904 else:
905 self.availability_zone = self._get_openstack_availablity_zones()
906
907 def _get_vm_availability_zone(self, availability_zone_index, availability_zone_list):
908 """
909 Return thge availability zone to be used by the created VM.
910 :return: The VIM availability zone to be used or None
911 """
912 if availability_zone_index is None:
913 if not self.config.get('availability_zone'):
914 return None
915 elif isinstance(self.config.get('availability_zone'), str):
916 return self.config['availability_zone']
917 else:
918 # TODO consider using a different parameter at config for default AV and AV list match
919 return self.config['availability_zone'][0]
920
921 vim_availability_zones = self.availability_zone
922 # check if VIM offer enough availability zones describe in the VNFD
923 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
924 # check if all the names of NFV AV match VIM AV names
925 match_by_index = False
926 for av in availability_zone_list:
927 if av not in vim_availability_zones:
928 match_by_index = True
929 break
930 if match_by_index:
931 return vim_availability_zones[availability_zone_index]
932 else:
933 return availability_zone_list[availability_zone_index]
934 else:
935 raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
936
937 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
938 availability_zone_index=None, availability_zone_list=None):
939 """Adds a VM instance to VIM
940 Params:
941 start: indicates if VM must start or boot in pause mode. Ignored
942 image_id,flavor_id: iamge and flavor uuid
943 net_list: list of interfaces, each one is a dictionary with:
944 name:
945 net_id: network uuid to connect
946 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
947 model: interface model, ignored #TODO
948 mac_address: used for SR-IOV ifaces #TODO for other types
949 use: 'data', 'bridge', 'mgmt'
950 type: 'virtual', 'PF', 'VF', 'VFnotShared'
951 vim_id: filled/added by this function
952 floating_ip: True/False (or it can be None)
953 'cloud_config': (optional) dictionary with:
954 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
955 'users': (optional) list of users to be inserted, each item is a dict with:
956 'name': (mandatory) user name,
957 'key-pairs': (optional) list of strings with the public key to be inserted to the user
958 'user-data': (optional) string is a text script to be passed directly to cloud-init
959 'config-files': (optional). List of files to be transferred. Each item is a dict with:
960 'dest': (mandatory) string with the destination absolute path
961 'encoding': (optional, by default text). Can be one of:
962 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
963 'content' (mandatory): string with the content of the file
964 'permissions': (optional) string with file permissions, typically octal notation '0644'
965 'owner': (optional) file owner, string with the format 'owner:group'
966 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
967 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
968 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
969 'size': (mandatory) string with the size of the disk in GB
970 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
971 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
972 availability_zone_index is None
973 #TODO ip, security groups
974 Returns a tuple with the instance identifier and created_items or raises an exception on error
975 created_items can be None or a dictionary where this method can include key-values that will be passed to
976 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
977 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
978 as not present.
979 """
980 self.logger.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id, flavor_id,str(net_list))
981 try:
982 server = None
983 created_items = {}
984 metadata = {}
985 net_list_vim = []
986 external_network = [] # list of external networks to be connected to instance, later on used to create floating_ip
987 no_secured_ports = [] # List of port-is with port-security disabled
988 self._reload_connection()
989 metadata_vpci = {} # For a specific neutron plugin
990 block_device_mapping = None
991 for net in net_list:
992 if not net.get("net_id"): # skip non connected iface
993 continue
994
995 port_dict={
996 "network_id": net["net_id"],
997 "name": net.get("name"),
998 "admin_state_up": True
999 }
1000 if net["type"]=="virtual":
1001 if "vpci" in net:
1002 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
1003 elif net["type"]=="VF": # for VF
1004 if "vpci" in net:
1005 if "VF" not in metadata_vpci:
1006 metadata_vpci["VF"]=[]
1007 metadata_vpci["VF"].append([ net["vpci"], "" ])
1008 port_dict["binding:vnic_type"]="direct"
1009 ########## VIO specific Changes #######
1010 if self.vim_type == "VIO":
1011 #Need to create port with port_security_enabled = False and no-security-groups
1012 port_dict["port_security_enabled"]=False
1013 port_dict["provider_security_groups"]=[]
1014 port_dict["security_groups"]=[]
1015 else: #For PT
1016 ########## VIO specific Changes #######
1017 #Current VIO release does not support port with type 'direct-physical'
1018 #So no need to create virtual port in case of PCI-device.
1019 #Will update port_dict code when support gets added in next VIO release
1020 if self.vim_type == "VIO":
1021 raise vimconn.vimconnNotSupportedException("Current VIO release does not support full passthrough (PT)")
1022 if "vpci" in net:
1023 if "PF" not in metadata_vpci:
1024 metadata_vpci["PF"]=[]
1025 metadata_vpci["PF"].append([ net["vpci"], "" ])
1026 port_dict["binding:vnic_type"]="direct-physical"
1027 if not port_dict["name"]:
1028 port_dict["name"]=name
1029 if net.get("mac_address"):
1030 port_dict["mac_address"]=net["mac_address"]
1031 new_port = self.neutron.create_port({"port": port_dict })
1032 created_items[("port", str(new_port["port"]["id"]))] = True
1033 net["mac_adress"] = new_port["port"]["mac_address"]
1034 net["vim_id"] = new_port["port"]["id"]
1035 # if try to use a network without subnetwork, it will return a emtpy list
1036 fixed_ips = new_port["port"].get("fixed_ips")
1037 if fixed_ips:
1038 net["ip"] = fixed_ips[0].get("ip_address")
1039 else:
1040 net["ip"] = None
1041
1042 port = {"port-id": new_port["port"]["id"]}
1043 if float(self.nova.api_version.get_string()) >= 2.32:
1044 port["tag"] = new_port["port"]["name"]
1045 net_list_vim.append(port)
1046
1047 if net.get('floating_ip', False):
1048 net['exit_on_floating_ip_error'] = True
1049 external_network.append(net)
1050 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
1051 net['exit_on_floating_ip_error'] = False
1052 external_network.append(net)
1053
1054 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1055 # As a workaround we wait until the VM is active and then disable the port-security
1056 if net.get("port_security") == False:
1057 no_secured_ports.append(new_port["port"]["id"])
1058
1059 if metadata_vpci:
1060 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
1061 if len(metadata["pci_assignement"]) >255:
1062 #limit the metadata size
1063 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1064 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
1065 metadata = {}
1066
1067 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
1068 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
1069
1070 security_groups = self.config.get('security_groups')
1071 if type(security_groups) is str:
1072 security_groups = ( security_groups, )
1073 # cloud config
1074 config_drive, userdata = self._create_user_data(cloud_config)
1075
1076 # Create additional volumes in case these are present in disk_list
1077 base_disk_index = ord('b')
1078 if disk_list != None:
1079 block_device_mapping = {}
1080 for disk in disk_list:
1081 if 'image_id' in disk:
1082 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
1083 chr(base_disk_index), imageRef = disk['image_id'])
1084 else:
1085 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1086 chr(base_disk_index))
1087 created_items[("volume", str(volume.id))] = True
1088 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
1089 base_disk_index += 1
1090
1091 # wait until volumes are with status available
1092 keep_waiting = True
1093 elapsed_time = 0
1094 while keep_waiting and elapsed_time < volume_timeout:
1095 keep_waiting = False
1096 for volume_id in block_device_mapping.itervalues():
1097 if self.cinder.volumes.get(volume_id).status != 'available':
1098 keep_waiting = True
1099 if keep_waiting:
1100 time.sleep(1)
1101 elapsed_time += 1
1102
1103 # if we exceeded the timeout rollback
1104 if elapsed_time >= volume_timeout:
1105 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
1106 http_code=vimconn.HTTP_Request_Timeout)
1107 # get availability Zone
1108 vm_av_zone = self._get_vm_availability_zone(availability_zone_index, availability_zone_list)
1109
1110 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}, "
1111 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
1112 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim, metadata,
1113 security_groups, vm_av_zone, self.config.get('keypair'),
1114 userdata, config_drive, block_device_mapping))
1115 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
1116 security_groups=security_groups,
1117 availability_zone=vm_av_zone,
1118 key_name=self.config.get('keypair'),
1119 userdata=userdata,
1120 config_drive=config_drive,
1121 block_device_mapping=block_device_mapping
1122 ) # , description=description)
1123
1124 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1125 if no_secured_ports:
1126 self.__wait_for_vm(server.id, 'ACTIVE')
1127
1128 for port_id in no_secured_ports:
1129 try:
1130 self.neutron.update_port(port_id, {"port": {"port_security_enabled": False, "security_groups": None} })
1131 except Exception as e:
1132 self.logger.error("It was not possible to disable port security for port {}".format(port_id))
1133 raise
1134
1135 # print "DONE :-)", server
1136
1137 pool_id = None
1138 if external_network:
1139 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
1140 self.__wait_for_vm(server.id, 'ACTIVE')
1141
1142 for floating_network in external_network:
1143 try:
1144 assigned = False
1145 while not assigned:
1146 if floating_ips:
1147 ip = floating_ips.pop(0)
1148 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
1149 free_floating_ip = ip.get("floating_ip_address")
1150 try:
1151 fix_ip = floating_network.get('ip')
1152 server.add_floating_ip(free_floating_ip, fix_ip)
1153 assigned = True
1154 except Exception as e:
1155 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
1156 else:
1157 #Find the external network
1158 external_nets = list()
1159 for net in self.neutron.list_networks()['networks']:
1160 if net['router:external']:
1161 external_nets.append(net)
1162
1163 if len(external_nets) == 0:
1164 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
1165 "network is present",
1166 http_code=vimconn.HTTP_Conflict)
1167 if len(external_nets) > 1:
1168 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
1169 "external networks are present",
1170 http_code=vimconn.HTTP_Conflict)
1171
1172 pool_id = external_nets[0].get('id')
1173 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
1174 try:
1175 #self.logger.debug("Creating floating IP")
1176 new_floating_ip = self.neutron.create_floatingip(param)
1177 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
1178 fix_ip = floating_network.get('ip')
1179 server.add_floating_ip(free_floating_ip, fix_ip)
1180 assigned=True
1181 except Exception as e:
1182 raise vimconn.vimconnException(type(e).__name__ + ": Cannot assign floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
1183 except Exception as e:
1184 if not floating_network['exit_on_floating_ip_error']:
1185 self.logger.warn("Cannot create floating_ip. %s", str(e))
1186 continue
1187 raise
1188
1189 return server.id, created_items
1190 # except nvExceptions.NotFound as e:
1191 # error_value=-vimconn.HTTP_Not_Found
1192 # error_text= "vm instance %s not found" % vm_id
1193 # except TypeError as e:
1194 # raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1195
1196 except Exception as e:
1197 server_id = None
1198 if server:
1199 server_id = server.id
1200 try:
1201 self.delete_vminstance(server_id, created_items)
1202 except Exception as e2:
1203 self.logger.error("new_vminstance rollback fail {}".format(e2))
1204
1205 self._format_exception(e)
1206
1207 def get_vminstance(self,vm_id):
1208 '''Returns the VM instance information from VIM'''
1209 #self.logger.debug("Getting VM from VIM")
1210 try:
1211 self._reload_connection()
1212 server = self.nova.servers.find(id=vm_id)
1213 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
1214 return server.to_dict()
1215 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
1216 self._format_exception(e)
1217
1218 def get_vminstance_console(self,vm_id, console_type="vnc"):
1219 '''
1220 Get a console for the virtual machine
1221 Params:
1222 vm_id: uuid of the VM
1223 console_type, can be:
1224 "novnc" (by default), "xvpvnc" for VNC types,
1225 "rdp-html5" for RDP types, "spice-html5" for SPICE types
1226 Returns dict with the console parameters:
1227 protocol: ssh, ftp, http, https, ...
1228 server: usually ip address
1229 port: the http, ssh, ... port
1230 suffix: extra text, e.g. the http path and query string
1231 '''
1232 self.logger.debug("Getting VM CONSOLE from VIM")
1233 try:
1234 self._reload_connection()
1235 server = self.nova.servers.find(id=vm_id)
1236 if console_type == None or console_type == "novnc":
1237 console_dict = server.get_vnc_console("novnc")
1238 elif console_type == "xvpvnc":
1239 console_dict = server.get_vnc_console(console_type)
1240 elif console_type == "rdp-html5":
1241 console_dict = server.get_rdp_console(console_type)
1242 elif console_type == "spice-html5":
1243 console_dict = server.get_spice_console(console_type)
1244 else:
1245 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
1246
1247 console_dict1 = console_dict.get("console")
1248 if console_dict1:
1249 console_url = console_dict1.get("url")
1250 if console_url:
1251 #parse console_url
1252 protocol_index = console_url.find("//")
1253 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1254 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1255 if protocol_index < 0 or port_index<0 or suffix_index<0:
1256 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1257 console_dict={"protocol": console_url[0:protocol_index],
1258 "server": console_url[protocol_index+2:port_index],
1259 "port": console_url[port_index:suffix_index],
1260 "suffix": console_url[suffix_index+1:]
1261 }
1262 protocol_index += 2
1263 return console_dict
1264 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
1265
1266 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
1267 self._format_exception(e)
1268
1269 def delete_vminstance(self, vm_id, created_items=None):
1270 '''Removes a VM instance from VIM. Returns the old identifier
1271 '''
1272 #print "osconnector: Getting VM from VIM"
1273 if created_items == None:
1274 created_items = {}
1275 try:
1276 self._reload_connection()
1277 # delete VM ports attached to this networks before the virtual machine
1278 for k, v in created_items.items():
1279 if not v: # skip already deleted
1280 continue
1281 try:
1282 if k[0] == "port":
1283 self.neutron.delete_port(k[1])
1284 except Exception as e:
1285 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
1286
1287 # #commented because detaching the volumes makes the servers.delete not work properly ?!?
1288 # #dettach volumes attached
1289 # server = self.nova.servers.get(vm_id)
1290 # volumes_attached_dict = server._info['os-extended-volumes:volumes_attached'] #volume['id']
1291 # #for volume in volumes_attached_dict:
1292 # # self.cinder.volumes.detach(volume['id'])
1293
1294 if vm_id:
1295 self.nova.servers.delete(vm_id)
1296
1297 # delete volumes. Although having detached, they should have in active status before deleting
1298 # we ensure in this loop
1299 keep_waiting = True
1300 elapsed_time = 0
1301 while keep_waiting and elapsed_time < volume_timeout:
1302 keep_waiting = False
1303 for k, v in created_items.items():
1304 if not v: # skip already deleted
1305 continue
1306 try:
1307 if k[0] == "volume":
1308 if self.cinder.volumes.get(k[1]).status != 'available':
1309 keep_waiting = True
1310 else:
1311 self.cinder.volumes.delete(k[1])
1312 except Exception as e:
1313 self.logger.error("Error deleting volume: " + type(e).__name__ + ": " + str(e))
1314 if keep_waiting:
1315 time.sleep(1)
1316 elapsed_time += 1
1317 return None
1318 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
1319 self._format_exception(e)
1320
1321 def refresh_vms_status(self, vm_list):
1322 '''Get the status of the virtual machines and their interfaces/ports
1323 Params: the list of VM identifiers
1324 Returns a dictionary with:
1325 vm_id: #VIM id of this Virtual Machine
1326 status: #Mandatory. Text with one of:
1327 # DELETED (not found at vim)
1328 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1329 # OTHER (Vim reported other status not understood)
1330 # ERROR (VIM indicates an ERROR status)
1331 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1332 # CREATING (on building process), ERROR
1333 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1334 #
1335 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1336 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1337 interfaces:
1338 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1339 mac_address: #Text format XX:XX:XX:XX:XX:XX
1340 vim_net_id: #network id where this interface is connected
1341 vim_interface_id: #interface/port VIM id
1342 ip_address: #null, or text with IPv4, IPv6 address
1343 compute_node: #identification of compute node where PF,VF interface is allocated
1344 pci: #PCI address of the NIC that hosts the PF,VF
1345 vlan: #physical VLAN used for VF
1346 '''
1347 vm_dict={}
1348 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1349 for vm_id in vm_list:
1350 vm={}
1351 try:
1352 vm_vim = self.get_vminstance(vm_id)
1353 if vm_vim['status'] in vmStatus2manoFormat:
1354 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
1355 else:
1356 vm['status'] = "OTHER"
1357 vm['error_msg'] = "VIM status reported " + vm_vim['status']
1358 try:
1359 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1360 except yaml.representer.RepresenterError:
1361 vm['vim_info'] = str(vm_vim)
1362 vm["interfaces"] = []
1363 if vm_vim.get('fault'):
1364 vm['error_msg'] = str(vm_vim['fault'])
1365 #get interfaces
1366 try:
1367 self._reload_connection()
1368 port_dict=self.neutron.list_ports(device_id=vm_id)
1369 for port in port_dict["ports"]:
1370 interface={}
1371 try:
1372 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1373 except yaml.representer.RepresenterError:
1374 interface['vim_info'] = str(port)
1375 interface["mac_address"] = port.get("mac_address")
1376 interface["vim_net_id"] = port["network_id"]
1377 interface["vim_interface_id"] = port["id"]
1378 # check if OS-EXT-SRV-ATTR:host is there,
1379 # in case of non-admin credentials, it will be missing
1380 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1381 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
1382 interface["pci"] = None
1383
1384 # check if binding:profile is there,
1385 # in case of non-admin credentials, it will be missing
1386 if port.get('binding:profile'):
1387 if port['binding:profile'].get('pci_slot'):
1388 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1389 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1390 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1391 pci = port['binding:profile']['pci_slot']
1392 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1393 interface["pci"] = pci
1394 interface["vlan"] = None
1395 #if network is of type vlan and port is of type direct (sr-iov) then set vlan id
1396 network = self.neutron.show_network(port["network_id"])
1397 if network['network'].get('provider:network_type') == 'vlan' and \
1398 port.get("binding:vnic_type") == "direct":
1399 interface["vlan"] = network['network'].get('provider:segmentation_id')
1400 ips=[]
1401 #look for floating ip address
1402 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1403 if floating_ip_dict.get("floatingips"):
1404 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
1405
1406 for subnet in port["fixed_ips"]:
1407 ips.append(subnet["ip_address"])
1408 interface["ip_address"] = ";".join(ips)
1409 vm["interfaces"].append(interface)
1410 except Exception as e:
1411 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1412 except vimconn.vimconnNotFoundException as e:
1413 self.logger.error("Exception getting vm status: %s", str(e))
1414 vm['status'] = "DELETED"
1415 vm['error_msg'] = str(e)
1416 except vimconn.vimconnException as e:
1417 self.logger.error("Exception getting vm status: %s", str(e))
1418 vm['status'] = "VIM_ERROR"
1419 vm['error_msg'] = str(e)
1420 vm_dict[vm_id] = vm
1421 return vm_dict
1422
1423 def action_vminstance(self, vm_id, action_dict, created_items={}):
1424 '''Send and action over a VM instance from VIM
1425 Returns None or the console dict if the action was successfully sent to the VIM'''
1426 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1427 try:
1428 self._reload_connection()
1429 server = self.nova.servers.find(id=vm_id)
1430 if "start" in action_dict:
1431 if action_dict["start"]=="rebuild":
1432 server.rebuild()
1433 else:
1434 if server.status=="PAUSED":
1435 server.unpause()
1436 elif server.status=="SUSPENDED":
1437 server.resume()
1438 elif server.status=="SHUTOFF":
1439 server.start()
1440 elif "pause" in action_dict:
1441 server.pause()
1442 elif "resume" in action_dict:
1443 server.resume()
1444 elif "shutoff" in action_dict or "shutdown" in action_dict:
1445 server.stop()
1446 elif "forceOff" in action_dict:
1447 server.stop() #TODO
1448 elif "terminate" in action_dict:
1449 server.delete()
1450 elif "createImage" in action_dict:
1451 server.create_image()
1452 #"path":path_schema,
1453 #"description":description_schema,
1454 #"name":name_schema,
1455 #"metadata":metadata_schema,
1456 #"imageRef": id_schema,
1457 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1458 elif "rebuild" in action_dict:
1459 server.rebuild(server.image['id'])
1460 elif "reboot" in action_dict:
1461 server.reboot() #reboot_type='SOFT'
1462 elif "console" in action_dict:
1463 console_type = action_dict["console"]
1464 if console_type == None or console_type == "novnc":
1465 console_dict = server.get_vnc_console("novnc")
1466 elif console_type == "xvpvnc":
1467 console_dict = server.get_vnc_console(console_type)
1468 elif console_type == "rdp-html5":
1469 console_dict = server.get_rdp_console(console_type)
1470 elif console_type == "spice-html5":
1471 console_dict = server.get_spice_console(console_type)
1472 else:
1473 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1474 http_code=vimconn.HTTP_Bad_Request)
1475 try:
1476 console_url = console_dict["console"]["url"]
1477 #parse console_url
1478 protocol_index = console_url.find("//")
1479 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1480 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1481 if protocol_index < 0 or port_index<0 or suffix_index<0:
1482 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
1483 console_dict2={"protocol": console_url[0:protocol_index],
1484 "server": console_url[protocol_index+2 : port_index],
1485 "port": int(console_url[port_index+1 : suffix_index]),
1486 "suffix": console_url[suffix_index+1:]
1487 }
1488 return console_dict2
1489 except Exception as e:
1490 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
1491
1492 return None
1493 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
1494 self._format_exception(e)
1495 #TODO insert exception vimconn.HTTP_Unauthorized
1496
1497 ####### VIO Specific Changes #########
1498 def _genrate_vlanID(self):
1499 """
1500 Method to get unused vlanID
1501 Args:
1502 None
1503 Returns:
1504 vlanID
1505 """
1506 #Get used VLAN IDs
1507 usedVlanIDs = []
1508 networks = self.get_network_list()
1509 for net in networks:
1510 if net.get('provider:segmentation_id'):
1511 usedVlanIDs.append(net.get('provider:segmentation_id'))
1512 used_vlanIDs = set(usedVlanIDs)
1513
1514 #find unused VLAN ID
1515 for vlanID_range in self.config.get('dataplane_net_vlan_range'):
1516 try:
1517 start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
1518 for vlanID in xrange(start_vlanid, end_vlanid + 1):
1519 if vlanID not in used_vlanIDs:
1520 return vlanID
1521 except Exception as exp:
1522 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1523 else:
1524 raise vimconn.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1525 " All given Vlan IDs {} are in use.".format(self.config.get('dataplane_net_vlan_range')))
1526
1527
1528 def _validate_vlan_ranges(self, dataplane_net_vlan_range):
1529 """
1530 Method to validate user given vlanID ranges
1531 Args: None
1532 Returns: None
1533 """
1534 for vlanID_range in dataplane_net_vlan_range:
1535 vlan_range = vlanID_range.replace(" ", "")
1536 #validate format
1537 vlanID_pattern = r'(\d)*-(\d)*$'
1538 match_obj = re.match(vlanID_pattern, vlan_range)
1539 if not match_obj:
1540 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1541 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range))
1542
1543 start_vlanid , end_vlanid = map(int,vlan_range.split("-"))
1544 if start_vlanid <= 0 :
1545 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1546 "Start ID can not be zero. For VLAN "\
1547 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1548 if end_vlanid > 4094 :
1549 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1550 "End VLAN ID can not be greater than 4094. For VLAN "\
1551 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1552
1553 if start_vlanid > end_vlanid:
1554 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1555 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1556 "start_ID < end_ID ".format(vlanID_range))
1557
1558 #NOT USED FUNCTIONS
1559
1560 def new_external_port(self, port_data):
1561 #TODO openstack if needed
1562 '''Adds a external port to VIM'''
1563 '''Returns the port identifier'''
1564 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1565
1566 def connect_port_network(self, port_id, network_id, admin=False):
1567 #TODO openstack if needed
1568 '''Connects a external port to a network'''
1569 '''Returns status code of the VIM response'''
1570 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1571
1572 def new_user(self, user_name, user_passwd, tenant_id=None):
1573 '''Adds a new user to openstack VIM'''
1574 '''Returns the user identifier'''
1575 self.logger.debug("osconnector: Adding a new user to VIM")
1576 try:
1577 self._reload_connection()
1578 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1579 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1580 return user.id
1581 except ksExceptions.ConnectionError as e:
1582 error_value=-vimconn.HTTP_Bad_Request
1583 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1584 except ksExceptions.ClientException as e: #TODO remove
1585 error_value=-vimconn.HTTP_Bad_Request
1586 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1587 #TODO insert exception vimconn.HTTP_Unauthorized
1588 #if reaching here is because an exception
1589 if self.debug:
1590 self.logger.debug("new_user " + error_text)
1591 return error_value, error_text
1592
1593 def delete_user(self, user_id):
1594 '''Delete a user from openstack VIM'''
1595 '''Returns the user identifier'''
1596 if self.debug:
1597 print("osconnector: Deleting a user from VIM")
1598 try:
1599 self._reload_connection()
1600 self.keystone.users.delete(user_id)
1601 return 1, user_id
1602 except ksExceptions.ConnectionError as e:
1603 error_value=-vimconn.HTTP_Bad_Request
1604 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1605 except ksExceptions.NotFound as e:
1606 error_value=-vimconn.HTTP_Not_Found
1607 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1608 except ksExceptions.ClientException as e: #TODO remove
1609 error_value=-vimconn.HTTP_Bad_Request
1610 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1611 #TODO insert exception vimconn.HTTP_Unauthorized
1612 #if reaching here is because an exception
1613 if self.debug:
1614 print("delete_tenant " + error_text)
1615 return error_value, error_text
1616
1617 def get_hosts_info(self):
1618 '''Get the information of deployed hosts
1619 Returns the hosts content'''
1620 if self.debug:
1621 print("osconnector: Getting Host info from VIM")
1622 try:
1623 h_list=[]
1624 self._reload_connection()
1625 hypervisors = self.nova.hypervisors.list()
1626 for hype in hypervisors:
1627 h_list.append( hype.to_dict() )
1628 return 1, {"hosts":h_list}
1629 except nvExceptions.NotFound as e:
1630 error_value=-vimconn.HTTP_Not_Found
1631 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1632 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1633 error_value=-vimconn.HTTP_Bad_Request
1634 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1635 #TODO insert exception vimconn.HTTP_Unauthorized
1636 #if reaching here is because an exception
1637 if self.debug:
1638 print("get_hosts_info " + error_text)
1639 return error_value, error_text
1640
1641 def get_hosts(self, vim_tenant):
1642 '''Get the hosts and deployed instances
1643 Returns the hosts content'''
1644 r, hype_dict = self.get_hosts_info()
1645 if r<0:
1646 return r, hype_dict
1647 hypervisors = hype_dict["hosts"]
1648 try:
1649 servers = self.nova.servers.list()
1650 for hype in hypervisors:
1651 for server in servers:
1652 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1653 if 'vm' in hype:
1654 hype['vm'].append(server.id)
1655 else:
1656 hype['vm'] = [server.id]
1657 return 1, hype_dict
1658 except nvExceptions.NotFound as e:
1659 error_value=-vimconn.HTTP_Not_Found
1660 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1661 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1662 error_value=-vimconn.HTTP_Bad_Request
1663 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1664 #TODO insert exception vimconn.HTTP_Unauthorized
1665 #if reaching here is because an exception
1666 if self.debug:
1667 print("get_hosts " + error_text)
1668 return error_value, error_text
1669
1670 def new_classification(self, name, ctype, definition):
1671 self.logger.debug(
1672 'Adding a new (Traffic) Classification to VIM, named %s', name)
1673 try:
1674 new_class = None
1675 self._reload_connection()
1676 if ctype not in supportedClassificationTypes:
1677 raise vimconn.vimconnNotSupportedException(
1678 'OpenStack VIM connector doesn\'t support provided '
1679 'Classification Type {}, supported ones are: '
1680 '{}'.format(ctype, supportedClassificationTypes))
1681 if not self._validate_classification(ctype, definition):
1682 raise vimconn.vimconnException(
1683 'Incorrect Classification definition '
1684 'for the type specified.')
1685 classification_dict = definition
1686 classification_dict['name'] = name
1687
1688 new_class = self.neutron.create_flow_classifier(
1689 {'flow_classifier': classification_dict})
1690 return new_class['flow_classifier']['id']
1691 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1692 neExceptions.NeutronException, ConnectionError) as e:
1693 self.logger.error(
1694 'Creation of Classification failed.')
1695 self._format_exception(e)
1696
1697 def get_classification(self, class_id):
1698 self.logger.debug(" Getting Classification %s from VIM", class_id)
1699 filter_dict = {"id": class_id}
1700 class_list = self.get_classification_list(filter_dict)
1701 if len(class_list) == 0:
1702 raise vimconn.vimconnNotFoundException(
1703 "Classification '{}' not found".format(class_id))
1704 elif len(class_list) > 1:
1705 raise vimconn.vimconnConflictException(
1706 "Found more than one Classification with this criteria")
1707 classification = class_list[0]
1708 return classification
1709
1710 def get_classification_list(self, filter_dict={}):
1711 self.logger.debug("Getting Classifications from VIM filter: '%s'",
1712 str(filter_dict))
1713 try:
1714 self._reload_connection()
1715 if self.api_version3 and "tenant_id" in filter_dict:
1716 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1717 classification_dict = self.neutron.list_flow_classifier(
1718 **filter_dict)
1719 classification_list = classification_dict["flow_classifiers"]
1720 self.__classification_os2mano(classification_list)
1721 return classification_list
1722 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1723 neExceptions.NeutronException, ConnectionError) as e:
1724 self._format_exception(e)
1725
1726 def delete_classification(self, class_id):
1727 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
1728 try:
1729 self._reload_connection()
1730 self.neutron.delete_flow_classifier(class_id)
1731 return class_id
1732 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1733 ksExceptions.ClientException, neExceptions.NeutronException,
1734 ConnectionError) as e:
1735 self._format_exception(e)
1736
1737 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
1738 self.logger.debug(
1739 "Adding a new Service Function Instance to VIM, named '%s'", name)
1740 try:
1741 new_sfi = None
1742 self._reload_connection()
1743 correlation = None
1744 if sfc_encap:
1745 # TODO(igordc): must be changed to NSH in Queens
1746 # (MPLS is a workaround)
1747 correlation = 'mpls'
1748 if len(ingress_ports) != 1:
1749 raise vimconn.vimconnNotSupportedException(
1750 "OpenStack VIM connector can only have "
1751 "1 ingress port per SFI")
1752 if len(egress_ports) != 1:
1753 raise vimconn.vimconnNotSupportedException(
1754 "OpenStack VIM connector can only have "
1755 "1 egress port per SFI")
1756 sfi_dict = {'name': name,
1757 'ingress': ingress_ports[0],
1758 'egress': egress_ports[0],
1759 'service_function_parameters': {
1760 'correlation': correlation}}
1761 new_sfi = self.neutron.create_port_pair({'port_pair': sfi_dict})
1762 return new_sfi['port_pair']['id']
1763 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1764 neExceptions.NeutronException, ConnectionError) as e:
1765 if new_sfi:
1766 try:
1767 self.neutron.delete_port_pair_group(
1768 new_sfi['port_pair']['id'])
1769 except Exception:
1770 self.logger.error(
1771 'Creation of Service Function Instance failed, with '
1772 'subsequent deletion failure as well.')
1773 self._format_exception(e)
1774
1775 def get_sfi(self, sfi_id):
1776 self.logger.debug(
1777 'Getting Service Function Instance %s from VIM', sfi_id)
1778 filter_dict = {"id": sfi_id}
1779 sfi_list = self.get_sfi_list(filter_dict)
1780 if len(sfi_list) == 0:
1781 raise vimconn.vimconnNotFoundException(
1782 "Service Function Instance '{}' not found".format(sfi_id))
1783 elif len(sfi_list) > 1:
1784 raise vimconn.vimconnConflictException(
1785 'Found more than one Service Function Instance '
1786 'with this criteria')
1787 sfi = sfi_list[0]
1788 return sfi
1789
1790 def get_sfi_list(self, filter_dict={}):
1791 self.logger.debug("Getting Service Function Instances from "
1792 "VIM filter: '%s'", str(filter_dict))
1793 try:
1794 self._reload_connection()
1795 if self.api_version3 and "tenant_id" in filter_dict:
1796 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1797 sfi_dict = self.neutron.list_port_pair(**filter_dict)
1798 sfi_list = sfi_dict["port_pairs"]
1799 self.__sfi_os2mano(sfi_list)
1800 return sfi_list
1801 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1802 neExceptions.NeutronException, ConnectionError) as e:
1803 self._format_exception(e)
1804
1805 def delete_sfi(self, sfi_id):
1806 self.logger.debug("Deleting Service Function Instance '%s' "
1807 "from VIM", sfi_id)
1808 try:
1809 self._reload_connection()
1810 self.neutron.delete_port_pair(sfi_id)
1811 return sfi_id
1812 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1813 ksExceptions.ClientException, neExceptions.NeutronException,
1814 ConnectionError) as e:
1815 self._format_exception(e)
1816
1817 def new_sf(self, name, sfis, sfc_encap=True):
1818 self.logger.debug("Adding a new Service Function to VIM, "
1819 "named '%s'", name)
1820 try:
1821 new_sf = None
1822 self._reload_connection()
1823 correlation = None
1824 if sfc_encap:
1825 # TODO(igordc): must be changed to NSH in Queens
1826 # (MPLS is a workaround)
1827 correlation = 'mpls'
1828 for instance in sfis:
1829 sfi = self.get_sfi(instance)
1830 if sfi.get('sfc_encap') != correlation:
1831 raise vimconn.vimconnNotSupportedException(
1832 "OpenStack VIM connector requires all SFIs of the "
1833 "same SF to share the same SFC Encapsulation")
1834 sf_dict = {'name': name,
1835 'port_pairs': sfis}
1836 new_sf = self.neutron.create_port_pair_group({
1837 'port_pair_group': sf_dict})
1838 return new_sf['port_pair_group']['id']
1839 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1840 neExceptions.NeutronException, ConnectionError) as e:
1841 if new_sf:
1842 try:
1843 self.neutron.delete_port_pair_group(
1844 new_sf['port_pair_group']['id'])
1845 except Exception:
1846 self.logger.error(
1847 'Creation of Service Function failed, with '
1848 'subsequent deletion failure as well.')
1849 self._format_exception(e)
1850
1851 def get_sf(self, sf_id):
1852 self.logger.debug("Getting Service Function %s from VIM", sf_id)
1853 filter_dict = {"id": sf_id}
1854 sf_list = self.get_sf_list(filter_dict)
1855 if len(sf_list) == 0:
1856 raise vimconn.vimconnNotFoundException(
1857 "Service Function '{}' not found".format(sf_id))
1858 elif len(sf_list) > 1:
1859 raise vimconn.vimconnConflictException(
1860 "Found more than one Service Function with this criteria")
1861 sf = sf_list[0]
1862 return sf
1863
1864 def get_sf_list(self, filter_dict={}):
1865 self.logger.debug("Getting Service Function from VIM filter: '%s'",
1866 str(filter_dict))
1867 try:
1868 self._reload_connection()
1869 if self.api_version3 and "tenant_id" in filter_dict:
1870 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1871 sf_dict = self.neutron.list_port_pair_group(**filter_dict)
1872 sf_list = sf_dict["port_pair_groups"]
1873 self.__sf_os2mano(sf_list)
1874 return sf_list
1875 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1876 neExceptions.NeutronException, ConnectionError) as e:
1877 self._format_exception(e)
1878
1879 def delete_sf(self, sf_id):
1880 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
1881 try:
1882 self._reload_connection()
1883 self.neutron.delete_port_pair_group(sf_id)
1884 return sf_id
1885 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1886 ksExceptions.ClientException, neExceptions.NeutronException,
1887 ConnectionError) as e:
1888 self._format_exception(e)
1889
1890 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
1891 self.logger.debug("Adding a new Service Function Path to VIM, "
1892 "named '%s'", name)
1893 try:
1894 new_sfp = None
1895 self._reload_connection()
1896 if not sfc_encap:
1897 raise vimconn.vimconnNotSupportedException(
1898 "OpenStack VIM connector only supports "
1899 "SFC-Encapsulated chains")
1900 # TODO(igordc): must be changed to NSH in Queens
1901 # (MPLS is a workaround)
1902 correlation = 'mpls'
1903 sfp_dict = {'name': name,
1904 'flow_classifiers': classifications,
1905 'port_pair_groups': sfs,
1906 'chain_parameters': {'correlation': correlation}}
1907 if spi:
1908 sfp_dict['chain_id'] = spi
1909 new_sfp = self.neutron.create_port_chain({'port_chain': sfp_dict})
1910 return new_sfp["port_chain"]["id"]
1911 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1912 neExceptions.NeutronException, ConnectionError) as e:
1913 if new_sfp:
1914 try:
1915 self.neutron.delete_port_chain(new_sfp['port_chain']['id'])
1916 except Exception:
1917 self.logger.error(
1918 'Creation of Service Function Path failed, with '
1919 'subsequent deletion failure as well.')
1920 self._format_exception(e)
1921
1922 def get_sfp(self, sfp_id):
1923 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
1924 filter_dict = {"id": sfp_id}
1925 sfp_list = self.get_sfp_list(filter_dict)
1926 if len(sfp_list) == 0:
1927 raise vimconn.vimconnNotFoundException(
1928 "Service Function Path '{}' not found".format(sfp_id))
1929 elif len(sfp_list) > 1:
1930 raise vimconn.vimconnConflictException(
1931 "Found more than one Service Function Path with this criteria")
1932 sfp = sfp_list[0]
1933 return sfp
1934
1935 def get_sfp_list(self, filter_dict={}):
1936 self.logger.debug("Getting Service Function Paths from VIM filter: "
1937 "'%s'", str(filter_dict))
1938 try:
1939 self._reload_connection()
1940 if self.api_version3 and "tenant_id" in filter_dict:
1941 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1942 sfp_dict = self.neutron.list_port_chain(**filter_dict)
1943 sfp_list = sfp_dict["port_chains"]
1944 self.__sfp_os2mano(sfp_list)
1945 return sfp_list
1946 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1947 neExceptions.NeutronException, ConnectionError) as e:
1948 self._format_exception(e)
1949
1950 def delete_sfp(self, sfp_id):
1951 self.logger.debug(
1952 "Deleting Service Function Path '%s' from VIM", sfp_id)
1953 try:
1954 self._reload_connection()
1955 self.neutron.delete_port_chain(sfp_id)
1956 return sfp_id
1957 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1958 ksExceptions.ClientException, neExceptions.NeutronException,
1959 ConnectionError) as e:
1960 self._format_exception(e)