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