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