1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
5 # This file is part of openmano
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
12 # http://www.apache.org/licenses/LICENSE-2.0
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
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
25 osconnector implements all the methods to interact with openstack using the python-neutronclient.
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)
35 __author__
= "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C., Eduardo Sousa"
36 __date__
= "$22-sep-2017 23:59:59$"
47 from pprint
import pformat
48 from types
import StringTypes
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
65 """contain the openstack virtual machine status to openmano status"""
66 vmStatus2manoFormat
={'ACTIVE':'ACTIVE',
68 'SUSPENDED': 'SUSPENDED',
71 'ERROR':'ERROR','DELETED':'DELETED'
73 netStatus2manoFormat
={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
76 supportedClassificationTypes
= ['legacy_flow_classifier']
78 #global var to have a timeout creating and deleting volumes
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())
91 return super(SafeDumper
, self
).represent_data(data
)
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
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
))
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'))
114 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
,
117 if self
.config
.get("insecure") and self
.config
.get("ca_cert"):
118 raise vimconn
.vimconnException("options insecure and ca_cert are mutually exclusive")
120 if self
.config
.get("insecure"):
122 if self
.config
.get("ca_cert"):
123 self
.verify
= self
.config
.get("ca_cert")
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")
139 self
.vim_type
= self
.vim_type
.upper()
140 if self
.config
.get("use_internal_endpoint"):
141 self
.endpoint_type
= "internalURL"
143 self
.endpoint_type
= None
145 self
.logger
= logging
.getLogger('openmano.vim.openstack')
147 ####### VIO Specific Changes #########
148 if self
.vim_type
== "VIO":
149 self
.logger
= logging
.getLogger('openmano.vim.vio')
152 self
.logger
.setLevel( getattr(logging
, log_level
))
154 def __getitem__(self
, index
):
155 """Get individuals parameters.
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")
162 return vimconn
.vimconnector
.__getitem
__(self
, index
)
164 def __setitem__(self
, index
, value
):
165 """Set individuals parameters and it is marked as dirty so to force connection reload.
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
172 vimconn
.vimconnector
.__setitem
__(self
, index
, value
)
173 self
.session
['reload_client'] = True
175 def serialize(self
, value
):
176 """Serialization of python basic types.
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
182 if isinstance(value
, StringTypes
):
186 return yaml
.dump(value
, Dumper
=SafeDumper
,
187 default_flow_style
=True, width
=256)
188 except yaml
.representer
.RepresenterError
:
190 'The following entity cannot be serialized in YAML:'
191 '\n\n%s\n\n', pformat(value
), exc_info
=True)
194 def _reload_connection(self
):
195 '''Called before any operation, it check if credentials has changed
196 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
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
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
213 user_domain_id_default
= 'default'
214 auth
= v3
.Password(auth_url
=self
.url
,
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'))
224 auth
= v2
.Password(auth_url
=self
.url
,
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'])
234 self
.keystone
= ksClient_v3
.Client(session
=sess
, endpoint_type
=self
.endpoint_type
)
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")
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'])
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
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
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
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":
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
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
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
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
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
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'])
333 params
= sfi
.get('service_function_parameters')
336 correlation
= params
.get('correlation')
339 sfi
['sfc_encap'] = sfc_encap
340 del sfi
['service_function_parameters']
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
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
352 "param sf_list_dict must be a list or a dictionary")
354 del sf
['port_pair_group_parameters']
355 sf
['sfis'] = sf
['port_pairs']
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
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
368 "param sfp_list_dict must be a list or a dictionary")
369 for sfp
in sfp_list_
:
370 params
= sfp
.pop('chain_parameters')
373 correlation
= params
.get('correlation')
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')
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
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.
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
):
407 self
.logger
.error("General Exception " + str(exception
), exc_info
=True)
408 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
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
416 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
418 self
.logger
.debug("Getting tenants from VIM filter: '%s'", str(filter_dict
))
420 self
._reload
_connection
()
421 if self
.api_version3
:
422 project_class_list
= self
.keystone
.projects
.list(name
=filter_dict
.get("name"))
424 project_class_list
= self
.keystone
.tenants
.findall(**filter_dict
)
426 for project
in project_class_list
:
427 if filter_dict
.get('id') and filter_dict
["id"] != project
.id:
429 project_list
.append(project
.to_dict())
431 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
432 self
._format
_exception
(e
)
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
)
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)
443 project
= self
.keystone
.tenants
.create(tenant_name
, tenant_description
)
445 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ksExceptions
.BadRequest
, ConnectionError
) as e
:
446 self
._format
_exception
(e
)
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
)
452 self
._reload
_connection
()
453 if self
.api_version3
:
454 self
.keystone
.projects
.delete(tenant_id
)
456 self
.keystone
.tenants
.delete(tenant_id
)
458 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ksExceptions
.NotFound
, ConnectionError
) as e
:
459 self
._format
_exception
(e
)
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))
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"
475 network_dict
["provider:network_type"] = vlan
477 ####### VIO Specific Changes #########
478 if self
.vim_type
== "VIO":
480 network_dict
["provider:segmentation_id"] = vlan
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")
487 network_dict
["provider:segmentation_id"] = self
._genrate
_vlanID
()
489 network_dict
["shared"]=shared
490 new_net
=self
.neutron
.create_network({'network':network_dict
})
492 #create subnetwork, even if there is no 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']
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']
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
:
532 self
.neutron
.delete_network(new_net
['network']['id'])
533 self
._format
_exception
(e
)
535 def get_network_list(self
, filter_dict
={}):
536 '''Obtain tenant networks of VIM
542 admin_state_up: boolean
544 Returns the network list of dictionaries
546 self
.logger
.debug("Getting network from VIM filter: '%s'", str(filter_dict
))
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
_os
2mano
(net_list
)
556 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
557 self
._format
_exception
(e
)
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
)
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")
571 for subnet_id
in net
.get("subnets", () ):
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')
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
)
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']:
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
)
599 except (neExceptions
.ConnectionFailed
, neExceptions
.NetworkNotFoundClient
, neExceptions
.NeutronException
,
600 ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
601 self
._format
_exception
(e
)
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)
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)
621 for net_id
in net_list
:
624 net_vim
= self
.get_network(net_id
)
625 if net_vim
['status'] in netStatus2manoFormat
:
626 net
["status"] = netStatus2manoFormat
[ net_vim
['status'] ]
628 net
["status"] = "OTHER"
629 net
["error_msg"] = "VIM status reported " + net_vim
['status']
631 if net
['status'] == "ACTIVE" and not net_vim
['admin_state_up']:
632 net
['status'] = 'DOWN'
634 net
['vim_info'] = self
.serialize(net_vim
)
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
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
)
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
)
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
668 exact_match
= False if self
.config
.get('use_existing_flavors') else True
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"])
675 numas
= flavor_dict
.get("extended", {}).get("numas")
678 raise vimconn
.vimconnNotFoundException("Flavor with EPA still not implemted")
680 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
682 # numas = extended.get("numas")
683 for flavor
in self
.nova
.flavors
.list():
684 epa
= flavor
.get_keys()
688 flavor_data
= (flavor
.ram
, flavor
.vcpus
, flavor
.disk
)
689 if flavor_data
== flavor_target
:
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
)
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
705 self
.logger
.debug("Adding flavor '%s'", str(flavor_data
))
710 name
=flavor_data
['name']
711 while retry
<max_retries
:
714 self
._reload
_connection
()
715 if change_name_if_used
:
718 fl
=self
.nova
.flavors
.list()
720 fl_names
.append(f
.name
)
721 while name
in fl_names
:
723 name
= flavor_data
['name']+"-" + str(name_suffix
)
725 ram
= flavor_data
.get('ram',64)
726 vcpus
= flavor_data
.get('vcpus',1)
729 extended
= flavor_data
.get("extended")
731 numas
=extended
.get("numas")
733 numa_nodes
= len(numas
)
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"
744 #overwrite ram and vcpus
745 #check if key 'memory' is present in numa else use ram value at flavor
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
770 new_flavor
=self
.nova
.flavors
.create(name
,
773 flavor_data
.get('disk',0),
774 is_public
=flavor_data
.get('is_public', True)
778 new_flavor
.set_keys(numa_properties
)
780 except nvExceptions
.Conflict
as e
:
781 if change_name_if_used
and retry
< max_retries
:
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
)
788 def delete_flavor(self
,flavor_id
):
789 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
792 self
._reload
_connection
()
793 self
.nova
.flavors
.delete(flavor_id
)
795 #except nvExceptions.BadRequest as e:
796 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
797 self
._format
_exception
(e
)
799 def new_image(self
,image_dict
):
801 Adds a tenant image to VIM. imge_dict is a dictionary with:
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
811 while retry
<max_retries
:
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"):
821 elif image_dict
['location'].endswith(".vhd"):
823 elif image_dict
['location'].endswith(".vmdk"):
825 elif image_dict
['location'].endswith(".vdi"):
827 elif image_dict
['location'].endswith(".iso"):
829 elif image_dict
['location'].endswith(".aki"):
831 elif image_dict
['location'].endswith(".ari"):
833 elif image_dict
['location'].endswith(".ami"):
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
)
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")
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']
859 metadata_to_load
['location'] = image_dict
['location']
860 self
.glance
.images
.update(new_image
.id, **metadata_to_load
)
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
:
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
)
872 def delete_image(self
, image_id
):
873 '''Deletes a tenant image from openstack VIM. Returns the old id
876 self
._reload
_connection
()
877 self
.glance
.images
.delete(image_id
)
879 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, gl1Exceptions
.HTTPNotFound
, ConnectionError
) as e
: #TODO remove
880 self
._format
_exception
(e
)
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'''
885 self
._reload
_connection
()
886 images
= self
.glance
.images
.list()
888 if image
.metadata
.get("location")==path
:
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
)
894 def get_image_list(self
, filter_dict
={}):
895 '''Obtain tenant images from VIM
899 checksum: image checksum
900 Returns the image list of dictionaries:
901 [{<the fields at Filter_dict plus some VIM specific>}, ...]
904 self
.logger
.debug("Getting image list from VIM filter: '%s'", str(filter_dict
))
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()
911 for image
in image_list
:
913 if filter_dict
.get("name") and image
["name"] != filter_dict
["name"]:
915 if filter_dict
.get("id") and image
["id"] != filter_dict
["id"]:
917 if filter_dict
.get("checksum") and image
["checksum"] != filter_dict
["checksum"]:
920 filtered_list
.append(image
.copy())
921 except gl1Exceptions
.HTTPNotFound
:
924 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
925 self
._format
_exception
(e
)
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"""
932 while elapsed_time
< server_timeout
:
933 vm_status
= self
.nova
.servers
.get(vm_id
).status
934 if vm_status
== status
:
936 if vm_status
== 'ERROR':
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
)
946 def _get_openstack_availablity_zones(self
):
948 Get from openstack availability zones available
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
:
959 def _set_availablity_zones(self
):
961 Set vim availablity zone
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
972 self
.availability_zone
= self
._get
_openstack
_availablity
_zones
()
974 def _get_vm_availability_zone(self
, availability_zone_index
, availability_zone_list
):
976 Return thge availability zone to be used by the created VM.
977 :return: The VIM availability zone to be used or None
979 if availability_zone_index
is None:
980 if not self
.config
.get('availability_zone'):
982 elif isinstance(self
.config
.get('availability_zone'), str):
983 return self
.config
['availability_zone']
985 # TODO consider using a different parameter at config for default AV and AV list match
986 return self
.config
['availability_zone'][0]
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
998 return vim_availability_zones
[availability_zone_index
]
1000 return availability_zone_list
[availability_zone_index
]
1002 raise vimconn
.vimconnConflictException("No enough availability zones at VIM for this deployment")
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
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:
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
1048 self
.logger
.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id
, flavor_id
,str(net_list
))
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
1064 "network_id": net
["net_id"],
1065 "name": net
.get("name"),
1066 "admin_state_up": True
1068 if net
["type"]=="virtual":
1071 # metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
1072 elif net
["type"] == "VF" or net
["type"] == "SR-IOV": # for VF
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)")
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")
1111 net
["ip"] = fixed_ips
[0].get("ip_address")
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
)
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')
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"])
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) ")
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
)
1144 security_groups
= self
.config
.get('security_groups')
1145 if type(security_groups
) is str:
1146 security_groups
= ( security_groups
, )
1148 config_drive
, userdata
= self
._create
_user
_data
(cloud_config
)
1150 # Create additional volumes in case these are present in disk_list
1151 base_disk_index
= ord('b')
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']
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'])
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
1168 # Wait until created volumes are with status available
1170 while elapsed_time
< volume_timeout
:
1171 for created_item
in created_items
:
1172 v
, _
, volume_id
= created_item
.partition(":")
1174 if self
.cinder
.volumes
.get(volume_id
).status
!= 'available':
1176 else: # all ready: break from while
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
)
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'),
1197 config_drive
=config_drive
,
1198 block_device_mapping
=block_device_mapping
1199 ) # , description=description)
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')
1206 for port_id
in no_secured_ports
:
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(
1213 # print "DONE :-)", server
1216 if external_network
:
1217 floating_ips
= self
.neutron
.list_floatingips().get("floatingips", ())
1218 for floating_network
in external_network
:
1223 ip
= floating_ips
.pop(0)
1224 if ip
.get("port_id", False) or ip
.get('tenant_id') != server
.tenant_id
:
1226 if isinstance(floating_network
['floating_ip'], str):
1227 if ip
.get("floating_network_id") != floating_network
['floating_ip']:
1229 free_floating_ip
= ip
.get("floating_ip_address")
1231 if isinstance(floating_network
['floating_ip'], str) and \
1232 floating_network
['floating_ip'].lower() != "true":
1233 pool_id
= floating_network
['floating_ip']
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
)
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
)
1250 pool_id
= external_nets
[0].get('id')
1251 param
= {'floatingip': {'floating_network_id': pool_id
, 'tenant_id': server
.tenant_id
}}
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
)
1260 fix_ip
= floating_network
.get('ip')
1263 server
.add_floating_ip(free_floating_ip
, fix_ip
)
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
:
1272 raise vimconn
.vimconnException(
1273 "Cannot create floating_ip: {} {}".format(type(e
).__name
__, e
),
1274 http_code
=vimconn
.HTTP_Conflict
)
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
))
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)
1289 except Exception as e
:
1292 server_id
= server
.id
1294 self
.delete_vminstance(server_id
, created_items
)
1295 except Exception as e2
:
1296 self
.logger
.error("new_vminstance rollback fail {}".format(e2
))
1298 self
._format
_exception
(e
)
1300 def get_vminstance(self
,vm_id
):
1301 '''Returns the VM instance information from VIM'''
1302 #self.logger.debug("Getting VM from VIM")
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
)
1311 def get_vminstance_console(self
,vm_id
, console_type
="vnc"):
1313 Get a console for the virtual machine
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
1325 self
.logger
.debug("Getting VM CONSOLE from VIM")
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
)
1338 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
), http_code
=vimconn
.HTTP_Bad_Request
)
1340 console_dict1
= console_dict
.get("console")
1342 console_url
= console_dict1
.get("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:]
1357 raise vimconn
.vimconnUnexpectedResponse("Unexpected response from VIM")
1359 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.BadRequest
, ConnectionError
) as e
:
1360 self
._format
_exception
(e
)
1362 def delete_vminstance(self
, vm_id
, created_items
=None):
1363 '''Removes a VM instance from VIM. Returns the old identifier
1365 #print "osconnector: Getting VM from VIM"
1366 if created_items
== None:
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
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
))
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'])
1389 self
.nova
.servers
.delete(vm_id
)
1391 # delete volumes. Although having detached, they should have in active status before deleting
1392 # we ensure in this loop
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
1401 k_item
, _
, k_id
= k
.partition(":")
1402 if k_item
== "volume":
1403 if self
.cinder
.volumes
.get(k_id
).status
!= 'available':
1406 self
.cinder
.volumes
.delete(k_id
)
1407 except Exception as e
:
1408 self
.logger
.error("Error deleting volume: {}: {}".format(type(e
).__name
__, e
))
1413 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
1414 self
._format
_exception
(e
)
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
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)
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
1443 self
.logger
.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1444 for vm_id
in vm_list
:
1447 vm_vim
= self
.get_vminstance(vm_id
)
1448 if vm_vim
['status'] in vmStatus2manoFormat
:
1449 vm
['status'] = vmStatus2manoFormat
[ vm_vim
['status'] ]
1451 vm
['status'] = "OTHER"
1452 vm
['error_msg'] = "VIM status reported " + vm_vim
['status']
1454 vm
['vim_info'] = self
.serialize(vm_vim
)
1456 vm
["interfaces"] = []
1457 if vm_vim
.get('fault'):
1458 vm
['error_msg'] = str(vm_vim
['fault'])
1461 self
._reload
_connection
()
1462 port_dict
= self
.neutron
.list_ports(device_id
=vm_id
)
1463 for port
in port_dict
["ports"]:
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
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')
1492 #look for floating ip address
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") )
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
),
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
)
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
))
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":
1529 if server
.status
=="PAUSED":
1531 elif server
.status
=="SUSPENDED":
1533 elif server
.status
=="SHUTOFF":
1535 elif "pause" in action_dict
:
1537 elif "resume" in action_dict
:
1539 elif "shutoff" in action_dict
or "shutdown" in action_dict
:
1541 elif "forceOff" in action_dict
:
1543 elif "terminate" in action_dict
:
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
)
1568 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
),
1569 http_code
=vimconn
.HTTP_Bad_Request
)
1571 console_url
= console_dict
["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:]
1583 return console_dict2
1584 except Exception as e
:
1585 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1588 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1589 self
._format
_exception
(e
)
1590 #TODO insert exception vimconn.HTTP_Unauthorized
1592 ####### VIO Specific Changes #########
1593 def _genrate_vlanID(self
):
1595 Method to get unused vlanID
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
)
1609 #find unused VLAN ID
1610 for vlanID_range
in self
.config
.get('dataplane_net_vlan_range'):
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
:
1616 except Exception as exp
:
1617 raise vimconn
.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp
))
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')))
1623 def _validate_vlan_ranges(self
, dataplane_net_vlan_range
):
1625 Method to validate user given vlanID ranges
1629 for vlanID_range
in dataplane_net_vlan_range
:
1630 vlan_range
= vlanID_range
.replace(" ", "")
1632 vlanID_pattern
= r
'(\d)*-(\d)*$'
1633 match_obj
= re
.match(vlanID_pattern
, vlan_range
)
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
))
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
))
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
))
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"
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"
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")
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)
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
1687 def delete_user(self
, user_id
):
1688 '''Delete a user from openstack VIM'''
1689 '''Returns the user identifier'''
1691 print("osconnector: Deleting a user from VIM")
1693 self
._reload
_connection
()
1694 self
.keystone
.users
.delete(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
1710 def get_hosts_info(self
):
1711 '''Get the information of deployed hosts
1712 Returns the hosts content'''
1714 print("osconnector: Getting Host info from VIM")
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
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()
1739 hypervisors
= hype_dict
["hosts"]
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']:
1746 hype
['vm'].append(server
.id)
1748 hype
['vm'] = [server
.id]
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
1761 def new_classification(self
, name
, ctype
, definition
):
1763 'Adding a new (Traffic) Classification to VIM, named %s', name
)
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
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
:
1785 'Creation of Classification failed.')
1786 self
._format
_exception
(e
)
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
1801 def get_classification_list(self
, filter_dict
={}):
1802 self
.logger
.debug("Getting Classifications from VIM filter: '%s'",
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(
1811 classification_list
= classification_dict
["flow_classifiers"]
1812 self
.__classification
_os
2mano
(classification_list
)
1813 return classification_list
1814 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1815 neExceptions
.NeutronException
, ConnectionError
) as e
:
1816 self
._format
_exception
(e
)
1818 def delete_classification(self
, class_id
):
1819 self
.logger
.debug("Deleting Classification '%s' from VIM", class_id
)
1821 self
._reload
_connection
()
1822 self
.neutron
.delete_sfc_flow_classifier(class_id
)
1824 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1825 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1826 ConnectionError
) as e
:
1827 self
._format
_exception
(e
)
1829 def new_sfi(self
, name
, ingress_ports
, egress_ports
, sfc_encap
=True):
1831 "Adding a new Service Function Instance to VIM, named '%s'", name
)
1834 self
._reload
_connection
()
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
:
1857 self
.neutron
.delete_sfc_port_pair(
1858 new_sfi
['port_pair']['id'])
1861 'Creation of Service Function Instance failed, with '
1862 'subsequent deletion failure as well.')
1863 self
._format
_exception
(e
)
1865 def get_sfi(self
, sfi_id
):
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')
1880 def get_sfi_list(self
, filter_dict
={}):
1881 self
.logger
.debug("Getting Service Function Instances from "
1882 "VIM filter: '%s'", str(filter_dict
))
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
_os
2mano
(sfi_list
)
1892 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1893 neExceptions
.NeutronException
, ConnectionError
) as e
:
1894 self
._format
_exception
(e
)
1896 def delete_sfi(self
, sfi_id
):
1897 self
.logger
.debug("Deleting Service Function Instance '%s' "
1900 self
._reload
_connection
()
1901 self
.neutron
.delete_sfc_port_pair(sfi_id
)
1903 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1904 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1905 ConnectionError
) as e
:
1906 self
._format
_exception
(e
)
1908 def new_sf(self
, name
, sfis
, sfc_encap
=True):
1909 self
.logger
.debug("Adding a new Service Function to VIM, "
1913 self
._reload
_connection
()
1914 # correlation = None
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
,
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
:
1932 self
.neutron
.delete_sfc_port_pair_group(
1933 new_sf
['port_pair_group']['id'])
1936 'Creation of Service Function failed, with '
1937 'subsequent deletion failure as well.')
1938 self
._format
_exception
(e
)
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")
1953 def get_sf_list(self
, filter_dict
={}):
1954 self
.logger
.debug("Getting Service Function from VIM filter: '%s'",
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
_os
2mano
(sf_list
)
1965 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1966 neExceptions
.NeutronException
, ConnectionError
) as e
:
1967 self
._format
_exception
(e
)
1969 def delete_sf(self
, sf_id
):
1970 self
.logger
.debug("Deleting Service Function '%s' from VIM", sf_id
)
1972 self
._reload
_connection
()
1973 self
.neutron
.delete_sfc_port_pair_group(sf_id
)
1975 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1976 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1977 ConnectionError
) as e
:
1978 self
._format
_exception
(e
)
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, "
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'
1991 sfp_dict
= {'name': name
,
1992 'flow_classifiers': classifications
,
1993 'port_pair_groups': sfs
,
1994 'chain_parameters': {'correlation': correlation
}}
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
:
2003 self
.neutron
.delete_sfc_port_chain(new_sfp
['port_chain']['id'])
2006 'Creation of Service Function Path failed, with '
2007 'subsequent deletion failure as well.')
2008 self
._format
_exception
(e
)
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")
2023 def get_sfp_list(self
, filter_dict
={}):
2024 self
.logger
.debug("Getting Service Function Paths from VIM filter: "
2025 "'%s'", str(filter_dict
))
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
_os
2mano
(sfp_list
)
2035 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
2036 neExceptions
.NeutronException
, ConnectionError
) as e
:
2037 self
._format
_exception
(e
)
2039 def delete_sfp(self
, sfp_id
):
2041 "Deleting Service Function Path '%s' from VIM", sfp_id
)
2043 self
._reload
_connection
()
2044 self
.neutron
.delete_sfc_port_chain(sfp_id
)
2046 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
2047 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
2048 ConnectionError
) as e
:
2049 self
._format
_exception
(e
)