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