1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefónica Investigación 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."
36 __date__
= "$22-sep-2017 23:59:59$"
49 from novaclient
import client
as nClient
, exceptions
as nvExceptions
50 from keystoneauth1
.identity
import v2
, v3
51 from keystoneauth1
import session
52 import keystoneclient
.exceptions
as ksExceptions
53 import keystoneclient
.v3
.client
as ksClient_v3
54 import keystoneclient
.v2_0
.client
as ksClient_v2
55 from glanceclient
import client
as glClient
56 import glanceclient
.client
as gl1Client
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
82 class vimconnector(vimconn
.vimconnector
):
83 def __init__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
=None, user
=None, passwd
=None,
84 log_level
=None, config
={}, persistent_info
={}):
85 '''using common constructor parameters. In this case
86 'url' is the keystone authorization url,
87 'url_admin' is not use
89 api_version
= config
.get('APIversion')
90 if api_version
and api_version
not in ('v3.3', 'v2.0', '2', '3'):
91 raise vimconn
.vimconnException("Invalid value '{}' for config:APIversion. "
92 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version
))
93 vim_type
= config
.get('vim_type')
94 if vim_type
and vim_type
not in ('vio', 'VIO'):
95 raise vimconn
.vimconnException("Invalid value '{}' for config:vim_type."
96 "Allowed values are 'vio' or 'VIO'".format(vim_type
))
98 if config
.get('dataplane_net_vlan_range') is not None:
99 #validate vlan ranges provided by user
100 self
._validate
_vlan
_ranges
(config
.get('dataplane_net_vlan_range'))
102 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
,
105 self
.insecure
= self
.config
.get("insecure", False)
107 raise TypeError('url param can not be NoneType')
108 self
.persistent_info
= persistent_info
109 self
.availability_zone
= persistent_info
.get('availability_zone', None)
110 self
.session
= persistent_info
.get('session', {'reload_client': True})
111 self
.nova
= self
.session
.get('nova')
112 self
.neutron
= self
.session
.get('neutron')
113 self
.cinder
= self
.session
.get('cinder')
114 self
.glance
= self
.session
.get('glance')
115 self
.glancev1
= self
.session
.get('glancev1')
116 self
.keystone
= self
.session
.get('keystone')
117 self
.api_version3
= self
.session
.get('api_version3')
118 self
.vim_type
= self
.config
.get("vim_type")
120 self
.vim_type
= self
.vim_type
.upper()
121 if self
.config
.get("use_internal_endpoint"):
122 self
.endpoint_type
= "internalURL"
124 self
.endpoint_type
= None
126 self
.logger
= logging
.getLogger('openmano.vim.openstack')
128 ####### VIO Specific Changes #########
129 if self
.vim_type
== "VIO":
130 self
.logger
= logging
.getLogger('openmano.vim.vio')
133 self
.logger
.setLevel( getattr(logging
, log_level
))
135 def __getitem__(self
, index
):
136 """Get individuals parameters.
138 if index
== 'project_domain_id':
139 return self
.config
.get("project_domain_id")
140 elif index
== 'user_domain_id':
141 return self
.config
.get("user_domain_id")
143 return vimconn
.vimconnector
.__getitem
__(self
, index
)
145 def __setitem__(self
, index
, value
):
146 """Set individuals parameters and it is marked as dirty so to force connection reload.
148 if index
== 'project_domain_id':
149 self
.config
["project_domain_id"] = value
150 elif index
== 'user_domain_id':
151 self
.config
["user_domain_id"] = value
153 vimconn
.vimconnector
.__setitem
__(self
, index
, value
)
154 self
.session
['reload_client'] = True
156 def _reload_connection(self
):
157 '''Called before any operation, it check if credentials has changed
158 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
160 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
161 if self
.session
['reload_client']:
162 if self
.config
.get('APIversion'):
163 self
.api_version3
= self
.config
['APIversion'] == 'v3.3' or self
.config
['APIversion'] == '3'
164 else: # get from ending auth_url that end with v3 or with v2.0
165 self
.api_version3
= self
.url
.split("/")[-1] == "v3"
166 self
.session
['api_version3'] = self
.api_version3
167 if self
.api_version3
:
168 auth
= v3
.Password(auth_url
=self
.url
,
170 password
=self
.passwd
,
171 project_name
=self
.tenant_name
,
172 project_id
=self
.tenant_id
,
173 project_domain_id
=self
.config
.get('project_domain_id', 'default'),
174 user_domain_id
=self
.config
.get('user_domain_id', 'default'))
176 auth
= v2
.Password(auth_url
=self
.url
,
178 password
=self
.passwd
,
179 tenant_name
=self
.tenant_name
,
180 tenant_id
=self
.tenant_id
)
181 sess
= session
.Session(auth
=auth
, verify
=not self
.insecure
)
182 if self
.api_version3
:
183 self
.keystone
= ksClient_v3
.Client(session
=sess
, endpoint_type
=self
.endpoint_type
)
185 self
.keystone
= ksClient_v2
.Client(session
=sess
, endpoint_type
=self
.endpoint_type
)
186 self
.session
['keystone'] = self
.keystone
187 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
188 # This implementation approach is due to the warning message in
189 # https://developer.openstack.org/api-guide/compute/microversions.html
190 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
191 # always require an specific microversion.
192 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
193 version
= self
.config
.get("microversion")
196 self
.nova
= self
.session
['nova'] = nClient
.Client(str(version
), session
=sess
, endpoint_type
=self
.endpoint_type
)
197 self
.neutron
= self
.session
['neutron'] = neClient
.Client('2.0', session
=sess
, endpoint_type
=self
.endpoint_type
)
198 self
.cinder
= self
.session
['cinder'] = cClient
.Client(2, session
=sess
, endpoint_type
=self
.endpoint_type
)
199 if self
.endpoint_type
== "internalURL":
200 glance_service_id
= self
.keystone
.services
.list(name
="glance")[0].id
201 glance_endpoint
= self
.keystone
.endpoints
.list(glance_service_id
, interface
="internal")[0].url
203 glance_endpoint
= None
204 self
.glance
= self
.session
['glance'] = glClient
.Client(2, session
=sess
, endpoint
=glance_endpoint
)
205 #using version 1 of glance client in new_image()
206 self
.glancev1
= self
.session
['glancev1'] = glClient
.Client('1', session
=sess
,
207 endpoint
=glance_endpoint
)
208 self
.session
['reload_client'] = False
209 self
.persistent_info
['session'] = self
.session
210 # add availablity zone info inside self.persistent_info
211 self
._set
_availablity
_zones
()
212 self
.persistent_info
['availability_zone'] = self
.availability_zone
214 def __net_os2mano(self
, net_list_dict
):
215 '''Transform the net openstack format to mano format
216 net_list_dict can be a list of dict or a single dict'''
217 if type(net_list_dict
) is dict:
218 net_list_
=(net_list_dict
,)
219 elif type(net_list_dict
) is list:
220 net_list_
=net_list_dict
222 raise TypeError("param net_list_dict must be a list or a dictionary")
223 for net
in net_list_
:
224 if net
.get('provider:network_type') == "vlan":
229 def __classification_os2mano(self
, class_list_dict
):
230 """Transform the openstack format (Flow Classifier) to mano format
231 (Classification) class_list_dict can be a list of dict or a single dict
233 if isinstance(class_list_dict
, dict):
234 class_list_
= [class_list_dict
]
235 elif isinstance(class_list_dict
, list):
236 class_list_
= class_list_dict
239 "param class_list_dict must be a list or a dictionary")
240 for classification
in class_list_
:
241 id = classification
.pop('id')
242 name
= classification
.pop('name')
243 description
= classification
.pop('description')
244 project_id
= classification
.pop('project_id')
245 tenant_id
= classification
.pop('tenant_id')
246 original_classification
= copy
.deepcopy(classification
)
247 classification
.clear()
248 classification
['ctype'] = 'legacy_flow_classifier'
249 classification
['definition'] = original_classification
250 classification
['id'] = id
251 classification
['name'] = name
252 classification
['description'] = description
253 classification
['project_id'] = project_id
254 classification
['tenant_id'] = tenant_id
256 def __sfi_os2mano(self
, sfi_list_dict
):
257 """Transform the openstack format (Port Pair) to mano format (SFI)
258 sfi_list_dict can be a list of dict or a single dict
260 if isinstance(sfi_list_dict
, dict):
261 sfi_list_
= [sfi_list_dict
]
262 elif isinstance(sfi_list_dict
, list):
263 sfi_list_
= sfi_list_dict
266 "param sfi_list_dict must be a list or a dictionary")
267 for sfi
in sfi_list_
:
268 sfi
['ingress_ports'] = []
269 sfi
['egress_ports'] = []
270 if sfi
.get('ingress'):
271 sfi
['ingress_ports'].append(sfi
['ingress'])
272 if sfi
.get('egress'):
273 sfi
['egress_ports'].append(sfi
['egress'])
276 params
= sfi
.get('service_function_parameters')
279 correlation
= params
.get('correlation')
282 sfi
['sfc_encap'] = sfc_encap
283 del sfi
['service_function_parameters']
285 def __sf_os2mano(self
, sf_list_dict
):
286 """Transform the openstack format (Port Pair Group) to mano format (SF)
287 sf_list_dict can be a list of dict or a single dict
289 if isinstance(sf_list_dict
, dict):
290 sf_list_
= [sf_list_dict
]
291 elif isinstance(sf_list_dict
, list):
292 sf_list_
= sf_list_dict
295 "param sf_list_dict must be a list or a dictionary")
297 del sf
['port_pair_group_parameters']
298 sf
['sfis'] = sf
['port_pairs']
301 def __sfp_os2mano(self
, sfp_list_dict
):
302 """Transform the openstack format (Port Chain) to mano format (SFP)
303 sfp_list_dict can be a list of dict or a single dict
305 if isinstance(sfp_list_dict
, dict):
306 sfp_list_
= [sfp_list_dict
]
307 elif isinstance(sfp_list_dict
, list):
308 sfp_list_
= sfp_list_dict
311 "param sfp_list_dict must be a list or a dictionary")
312 for sfp
in sfp_list_
:
313 params
= sfp
.pop('chain_parameters')
316 correlation
= params
.get('correlation')
319 sfp
['sfc_encap'] = sfc_encap
320 sfp
['spi'] = sfp
.pop('chain_id')
321 sfp
['classifications'] = sfp
.pop('flow_classifiers')
322 sfp
['service_functions'] = sfp
.pop('port_pair_groups')
324 # placeholder for now; read TODO note below
325 def _validate_classification(self
, type, definition
):
326 # only legacy_flow_classifier Type is supported at this point
328 # TODO(igordcard): this method should be an abstract method of an
329 # abstract Classification class to be implemented by the specific
330 # Types. Also, abstract vimconnector should call the validation
331 # method before the implemented VIM connectors are called.
333 def _format_exception(self
, exception
):
334 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
335 if isinstance(exception
, (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
,
336 ConnectionError
, ksExceptions
.ConnectionError
, neExceptions
.ConnectionFailed
338 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
339 elif isinstance(exception
, (nvExceptions
.ClientException
, ksExceptions
.ClientException
,
340 neExceptions
.NeutronException
, nvExceptions
.BadRequest
)):
341 raise vimconn
.vimconnUnexpectedResponse(type(exception
).__name
__ + ": " + str(exception
))
342 elif isinstance(exception
, (neExceptions
.NetworkNotFoundClient
, nvExceptions
.NotFound
)):
343 raise vimconn
.vimconnNotFoundException(type(exception
).__name
__ + ": " + str(exception
))
344 elif isinstance(exception
, nvExceptions
.Conflict
):
345 raise vimconn
.vimconnConflictException(type(exception
).__name
__ + ": " + str(exception
))
346 elif isinstance(exception
, vimconn
.vimconnException
):
349 self
.logger
.error("General Exception " + str(exception
), exc_info
=True)
350 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
352 def get_tenant_list(self
, filter_dict
={}):
353 '''Obtain tenants of VIM
354 filter_dict can contain the following keys:
355 name: filter by tenant name
356 id: filter by tenant uuid/id
358 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
360 self
.logger
.debug("Getting tenants from VIM filter: '%s'", str(filter_dict
))
362 self
._reload
_connection
()
363 if self
.api_version3
:
364 project_class_list
= self
.keystone
.projects
.list(name
=filter_dict
.get("name"))
366 project_class_list
= self
.keystone
.tenants
.findall(**filter_dict
)
368 for project
in project_class_list
:
369 if filter_dict
.get('id') and filter_dict
["id"] != project
.id:
371 project_list
.append(project
.to_dict())
373 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
374 self
._format
_exception
(e
)
376 def new_tenant(self
, tenant_name
, tenant_description
):
377 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
378 self
.logger
.debug("Adding a new tenant name: %s", tenant_name
)
380 self
._reload
_connection
()
381 if self
.api_version3
:
382 project
= self
.keystone
.projects
.create(tenant_name
, self
.config
.get("project_domain_id", "default"),
383 description
=tenant_description
, is_domain
=False)
385 project
= self
.keystone
.tenants
.create(tenant_name
, tenant_description
)
387 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
388 self
._format
_exception
(e
)
390 def delete_tenant(self
, tenant_id
):
391 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
392 self
.logger
.debug("Deleting tenant %s from VIM", tenant_id
)
394 self
._reload
_connection
()
395 if self
.api_version3
:
396 self
.keystone
.projects
.delete(tenant_id
)
398 self
.keystone
.tenants
.delete(tenant_id
)
400 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
401 self
._format
_exception
(e
)
403 def new_network(self
,net_name
, net_type
, ip_profile
=None, shared
=False, vlan
=None):
404 '''Adds a tenant network to VIM. Returns the network identifier'''
405 self
.logger
.debug("Adding a new network to VIM name '%s', type '%s'", net_name
, net_type
)
406 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
409 self
._reload
_connection
()
410 network_dict
= {'name': net_name
, 'admin_state_up': True}
411 if net_type
=="data" or net_type
=="ptp":
412 if self
.config
.get('dataplane_physical_net') == None:
413 raise vimconn
.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
414 network_dict
["provider:physical_network"] = self
.config
['dataplane_physical_net'] #"physnet_sriov" #TODO physical
415 network_dict
["provider:network_type"] = "vlan"
417 network_dict
["provider:network_type"] = vlan
419 ####### VIO Specific Changes #########
420 if self
.vim_type
== "VIO":
422 network_dict
["provider:segmentation_id"] = vlan
424 if self
.config
.get('dataplane_net_vlan_range') is None:
425 raise vimconn
.vimconnConflictException("You must provide "\
426 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
427 "at config value before creating sriov network with vlan tag")
429 network_dict
["provider:segmentation_id"] = self
._genrate
_vlanID
()
431 network_dict
["shared"]=shared
432 new_net
=self
.neutron
.create_network({'network':network_dict
})
434 #create subnetwork, even if there is no profile
437 if 'subnet_address' not in ip_profile
:
438 #Fake subnet is required
439 subnet_rand
= random
.randint(0, 255)
440 ip_profile
['subnet_address'] = "192.168.{}.0/24".format(subnet_rand
)
441 if 'ip_version' not in ip_profile
:
442 ip_profile
['ip_version'] = "IPv4"
443 subnet
= {"name":net_name
+"-subnet",
444 "network_id": new_net
["network"]["id"],
445 "ip_version": 4 if ip_profile
['ip_version']=="IPv4" else 6,
446 "cidr": ip_profile
['subnet_address']
448 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
449 subnet
['gateway_ip'] = ip_profile
.get('gateway_address')
450 if ip_profile
.get('dns_address'):
451 subnet
['dns_nameservers'] = ip_profile
['dns_address'].split(";")
452 if 'dhcp_enabled' in ip_profile
:
453 subnet
['enable_dhcp'] = False if ip_profile
['dhcp_enabled']=="false" else True
454 if 'dhcp_start_address' in ip_profile
:
455 subnet
['allocation_pools'] = []
456 subnet
['allocation_pools'].append(dict())
457 subnet
['allocation_pools'][0]['start'] = ip_profile
['dhcp_start_address']
458 if 'dhcp_count' in ip_profile
:
459 #parts = ip_profile['dhcp_start_address'].split('.')
460 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
461 ip_int
= int(netaddr
.IPAddress(ip_profile
['dhcp_start_address']))
462 ip_int
+= ip_profile
['dhcp_count'] - 1
463 ip_str
= str(netaddr
.IPAddress(ip_int
))
464 subnet
['allocation_pools'][0]['end'] = ip_str
465 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
466 self
.neutron
.create_subnet({"subnet": subnet
} )
467 return new_net
["network"]["id"]
468 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
470 self
.neutron
.delete_network(new_net
['network']['id'])
471 self
._format
_exception
(e
)
473 def get_network_list(self
, filter_dict
={}):
474 '''Obtain tenant networks of VIM
480 admin_state_up: boolean
482 Returns the network list of dictionaries
484 self
.logger
.debug("Getting network from VIM filter: '%s'", str(filter_dict
))
486 self
._reload
_connection
()
487 if self
.api_version3
and "tenant_id" in filter_dict
:
488 filter_dict
['project_id'] = filter_dict
.pop('tenant_id') #TODO check
489 net_dict
=self
.neutron
.list_networks(**filter_dict
)
490 net_list
=net_dict
["networks"]
491 self
.__net
_os
2mano
(net_list
)
493 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
494 self
._format
_exception
(e
)
496 def get_network(self
, net_id
):
497 '''Obtain details of network from VIM
498 Returns the network information from a network id'''
499 self
.logger
.debug(" Getting tenant network %s from VIM", net_id
)
500 filter_dict
={"id": net_id
}
501 net_list
= self
.get_network_list(filter_dict
)
503 raise vimconn
.vimconnNotFoundException("Network '{}' not found".format(net_id
))
504 elif len(net_list
)>1:
505 raise vimconn
.vimconnConflictException("Found more than one network with this criteria")
508 for subnet_id
in net
.get("subnets", () ):
510 subnet
= self
.neutron
.show_subnet(subnet_id
)
511 except Exception as e
:
512 self
.logger
.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id
, str(e
)))
513 subnet
= {"id": subnet_id
, "fault": str(e
)}
514 subnets
.append(subnet
)
515 net
["subnets"] = subnets
516 net
["encapsulation"] = net
.get('provider:network_type')
517 net
["segmentation_id"] = net
.get('provider:segmentation_id')
520 def delete_network(self
, net_id
):
521 '''Deletes a tenant network from VIM. Returns the old network identifier'''
522 self
.logger
.debug("Deleting network '%s' from VIM", net_id
)
524 self
._reload
_connection
()
525 #delete VM ports attached to this networks before the network
526 ports
= self
.neutron
.list_ports(network_id
=net_id
)
527 for p
in ports
['ports']:
529 self
.neutron
.delete_port(p
["id"])
530 except Exception as e
:
531 self
.logger
.error("Error deleting port %s: %s", p
["id"], str(e
))
532 self
.neutron
.delete_network(net_id
)
534 except (neExceptions
.ConnectionFailed
, neExceptions
.NetworkNotFoundClient
, neExceptions
.NeutronException
,
535 ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
536 self
._format
_exception
(e
)
538 def refresh_nets_status(self
, net_list
):
539 '''Get the status of the networks
540 Params: the list of network identifiers
541 Returns a dictionary with:
542 net_id: #VIM id of this network
543 status: #Mandatory. Text with one of:
544 # DELETED (not found at vim)
545 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
546 # OTHER (Vim reported other status not understood)
547 # ERROR (VIM indicates an ERROR status)
548 # ACTIVE, INACTIVE, DOWN (admin down),
549 # BUILD (on building process)
551 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
552 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
556 for net_id
in net_list
:
559 net_vim
= self
.get_network(net_id
)
560 if net_vim
['status'] in netStatus2manoFormat
:
561 net
["status"] = netStatus2manoFormat
[ net_vim
['status'] ]
563 net
["status"] = "OTHER"
564 net
["error_msg"] = "VIM status reported " + net_vim
['status']
566 if net
['status'] == "ACTIVE" and not net_vim
['admin_state_up']:
567 net
['status'] = 'DOWN'
569 net
['vim_info'] = yaml
.safe_dump(net_vim
, default_flow_style
=True, width
=256)
570 except yaml
.representer
.RepresenterError
:
571 net
['vim_info'] = str(net_vim
)
572 if net_vim
.get('fault'): #TODO
573 net
['error_msg'] = str(net_vim
['fault'])
574 except vimconn
.vimconnNotFoundException
as e
:
575 self
.logger
.error("Exception getting net status: %s", str(e
))
576 net
['status'] = "DELETED"
577 net
['error_msg'] = str(e
)
578 except vimconn
.vimconnException
as e
:
579 self
.logger
.error("Exception getting net status: %s", str(e
))
580 net
['status'] = "VIM_ERROR"
581 net
['error_msg'] = str(e
)
582 net_dict
[net_id
] = net
585 def get_flavor(self
, flavor_id
):
586 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
587 self
.logger
.debug("Getting flavor '%s'", flavor_id
)
589 self
._reload
_connection
()
590 flavor
= self
.nova
.flavors
.find(id=flavor_id
)
591 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
592 return flavor
.to_dict()
593 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
594 self
._format
_exception
(e
)
596 def get_flavor_id_from_data(self
, flavor_dict
):
597 """Obtain flavor id that match the flavor description
598 Returns the flavor_id or raises a vimconnNotFoundException
599 flavor_dict: contains the required ram, vcpus, disk
600 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
601 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
602 vimconnNotFoundException is raised
604 exact_match
= False if self
.config
.get('use_existing_flavors') else True
606 self
._reload
_connection
()
607 flavor_candidate_id
= None
608 flavor_candidate_data
= (10000, 10000, 10000)
609 flavor_target
= (flavor_dict
["ram"], flavor_dict
["vcpus"], flavor_dict
["disk"])
611 numas
= flavor_dict
.get("extended", {}).get("numas")
614 raise vimconn
.vimconnNotFoundException("Flavor with EPA still not implemted")
616 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
618 # numas = extended.get("numas")
619 for flavor
in self
.nova
.flavors
.list():
620 epa
= flavor
.get_keys()
624 flavor_data
= (flavor
.ram
, flavor
.vcpus
, flavor
.disk
)
625 if flavor_data
== flavor_target
:
627 elif not exact_match
and flavor_target
< flavor_data
< flavor_candidate_data
:
628 flavor_candidate_id
= flavor
.id
629 flavor_candidate_data
= flavor_data
630 if not exact_match
and flavor_candidate_id
:
631 return flavor_candidate_id
632 raise vimconn
.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict
)))
633 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
634 self
._format
_exception
(e
)
636 def new_flavor(self
, flavor_data
, change_name_if_used
=True):
637 '''Adds a tenant flavor to openstack VIM
638 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
639 Returns the flavor identifier
641 self
.logger
.debug("Adding flavor '%s'", str(flavor_data
))
645 name
=flavor_data
['name']
646 while retry
<max_retries
:
649 self
._reload
_connection
()
650 if change_name_if_used
:
653 fl
=self
.nova
.flavors
.list()
655 fl_names
.append(f
.name
)
656 while name
in fl_names
:
658 name
= flavor_data
['name']+"-" + str(name_suffix
)
660 ram
= flavor_data
.get('ram',64)
661 vcpus
= flavor_data
.get('vcpus',1)
664 extended
= flavor_data
.get("extended")
666 numas
=extended
.get("numas")
668 numa_nodes
= len(numas
)
670 return -1, "Can not add flavor with more than one numa"
671 numa_properties
= {"hw:numa_nodes":str(numa_nodes
)}
672 numa_properties
["hw:mem_page_size"] = "large"
673 numa_properties
["hw:cpu_policy"] = "dedicated"
674 numa_properties
["hw:numa_mempolicy"] = "strict"
675 if self
.vim_type
== "VIO":
676 numa_properties
["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
677 numa_properties
["vmware:latency_sensitivity_level"] = "high"
679 #overwrite ram and vcpus
680 ram
= numa
['memory']*1024
681 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
682 if 'paired-threads' in numa
:
683 vcpus
= numa
['paired-threads']*2
684 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
685 numa_properties
["hw:cpu_thread_policy"] = "require"
686 numa_properties
["hw:cpu_policy"] = "dedicated"
687 elif 'cores' in numa
:
688 vcpus
= numa
['cores']
689 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
690 numa_properties
["hw:cpu_thread_policy"] = "isolate"
691 numa_properties
["hw:cpu_policy"] = "dedicated"
692 elif 'threads' in numa
:
693 vcpus
= numa
['threads']
694 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
695 numa_properties
["hw:cpu_thread_policy"] = "prefer"
696 numa_properties
["hw:cpu_policy"] = "dedicated"
697 # for interface in numa.get("interfaces",() ):
698 # if interface["dedicated"]=="yes":
699 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
700 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
703 new_flavor
=self
.nova
.flavors
.create(name
,
706 flavor_data
.get('disk',1),
707 is_public
=flavor_data
.get('is_public', True)
711 new_flavor
.set_keys(numa_properties
)
713 except nvExceptions
.Conflict
as e
:
714 if change_name_if_used
and retry
< max_retries
:
716 self
._format
_exception
(e
)
717 #except nvExceptions.BadRequest as e:
718 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
719 self
._format
_exception
(e
)
721 def delete_flavor(self
,flavor_id
):
722 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
725 self
._reload
_connection
()
726 self
.nova
.flavors
.delete(flavor_id
)
728 #except nvExceptions.BadRequest as e:
729 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
730 self
._format
_exception
(e
)
732 def new_image(self
,image_dict
):
734 Adds a tenant image to VIM. imge_dict is a dictionary with:
736 disk_format: qcow2, vhd, vmdk, raw (by default), ...
737 location: path or URI
738 public: "yes" or "no"
739 metadata: metadata of the image
744 while retry
<max_retries
:
747 self
._reload
_connection
()
748 #determine format http://docs.openstack.org/developer/glance/formats.html
749 if "disk_format" in image_dict
:
750 disk_format
=image_dict
["disk_format"]
751 else: #autodiscover based on extension
752 if image_dict
['location'][-6:]==".qcow2":
754 elif image_dict
['location'][-4:]==".vhd":
756 elif image_dict
['location'][-5:]==".vmdk":
758 elif image_dict
['location'][-4:]==".vdi":
760 elif image_dict
['location'][-4:]==".iso":
762 elif image_dict
['location'][-4:]==".aki":
764 elif image_dict
['location'][-4:]==".ari":
766 elif image_dict
['location'][-4:]==".ami":
770 self
.logger
.debug("new_image: '%s' loading from '%s'", image_dict
['name'], image_dict
['location'])
771 if image_dict
['location'][0:4]=="http":
772 new_image
= self
.glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
773 container_format
="bare", location
=image_dict
['location'], disk_format
=disk_format
)
775 with
open(image_dict
['location']) as fimage
:
776 new_image
= self
.glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
777 container_format
="bare", data
=fimage
, disk_format
=disk_format
)
778 #insert metadata. We cannot use 'new_image.properties.setdefault'
779 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
780 new_image_nova
=self
.nova
.images
.find(id=new_image
.id)
781 new_image_nova
.metadata
.setdefault('location',image_dict
['location'])
782 metadata_to_load
= image_dict
.get('metadata')
784 for k
,v
in yaml
.load(metadata_to_load
).iteritems():
785 new_image_nova
.metadata
.setdefault(k
,v
)
787 except (nvExceptions
.Conflict
, ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
788 self
._format
_exception
(e
)
789 except (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
790 if retry
==max_retries
:
792 self
._format
_exception
(e
)
793 except IOError as e
: #can not open the file
794 raise vimconn
.vimconnConnectionException(type(e
).__name
__ + ": " + str(e
)+ " for " + image_dict
['location'],
795 http_code
=vimconn
.HTTP_Bad_Request
)
797 def delete_image(self
, image_id
):
798 '''Deletes a tenant image from openstack VIM. Returns the old id
801 self
._reload
_connection
()
802 self
.nova
.images
.delete(image_id
)
804 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
: #TODO remove
805 self
._format
_exception
(e
)
807 def get_image_id_from_path(self
, path
):
808 '''Get the image id from image path in the VIM database. Returns the image_id'''
810 self
._reload
_connection
()
811 images
= self
.nova
.images
.list()
813 if image
.metadata
.get("location")==path
:
815 raise vimconn
.vimconnNotFoundException("image with location '{}' not found".format( path
))
816 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
817 self
._format
_exception
(e
)
819 def get_image_list(self
, filter_dict
={}):
820 '''Obtain tenant images from VIM
824 checksum: image checksum
825 Returns the image list of dictionaries:
826 [{<the fields at Filter_dict plus some VIM specific>}, ...]
829 self
.logger
.debug("Getting image list from VIM filter: '%s'", str(filter_dict
))
831 self
._reload
_connection
()
832 filter_dict_os
=filter_dict
.copy()
833 #First we filter by the available filter fields: name, id. The others are removed.
834 filter_dict_os
.pop('checksum',None)
835 image_list
=self
.nova
.images
.findall(**filter_dict_os
)
836 if len(image_list
)==0:
838 #Then we filter by the rest of filter fields: checksum
840 for image
in image_list
:
841 image_class
=self
.glance
.images
.get(image
.id)
842 if 'checksum' not in filter_dict
or image_class
['checksum']==filter_dict
.get('checksum'):
843 filtered_list
.append(image_class
.copy())
845 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
846 self
._format
_exception
(e
)
848 def __wait_for_vm(self
, vm_id
, status
):
849 """wait until vm is in the desired status and return True.
850 If the VM gets in ERROR status, return false.
851 If the timeout is reached generate an exception"""
853 while elapsed_time
< server_timeout
:
854 vm_status
= self
.nova
.servers
.get(vm_id
).status
855 if vm_status
== status
:
857 if vm_status
== 'ERROR':
862 # if we exceeded the timeout rollback
863 if elapsed_time
>= server_timeout
:
864 raise vimconn
.vimconnException('Timeout waiting for instance ' + vm_id
+ ' to get ' + status
,
865 http_code
=vimconn
.HTTP_Request_Timeout
)
867 def _get_openstack_availablity_zones(self
):
869 Get from openstack availability zones available
873 openstack_availability_zone
= self
.nova
.availability_zones
.list()
874 openstack_availability_zone
= [str(zone
.zoneName
) for zone
in openstack_availability_zone
875 if zone
.zoneName
!= 'internal']
876 return openstack_availability_zone
877 except Exception as e
:
880 def _set_availablity_zones(self
):
882 Set vim availablity zone
886 if 'availability_zone' in self
.config
:
887 vim_availability_zones
= self
.config
.get('availability_zone')
888 if isinstance(vim_availability_zones
, str):
889 self
.availability_zone
= [vim_availability_zones
]
890 elif isinstance(vim_availability_zones
, list):
891 self
.availability_zone
= vim_availability_zones
893 self
.availability_zone
= self
._get
_openstack
_availablity
_zones
()
895 def _get_vm_availability_zone(self
, availability_zone_index
, availability_zone_list
):
897 Return thge availability zone to be used by the created VM.
898 :return: The VIM availability zone to be used or None
900 if availability_zone_index
is None:
901 if not self
.config
.get('availability_zone'):
903 elif isinstance(self
.config
.get('availability_zone'), str):
904 return self
.config
['availability_zone']
906 # TODO consider using a different parameter at config for default AV and AV list match
907 return self
.config
['availability_zone'][0]
909 vim_availability_zones
= self
.availability_zone
910 # check if VIM offer enough availability zones describe in the VNFD
911 if vim_availability_zones
and len(availability_zone_list
) <= len(vim_availability_zones
):
912 # check if all the names of NFV AV match VIM AV names
913 match_by_index
= False
914 for av
in availability_zone_list
:
915 if av
not in vim_availability_zones
:
916 match_by_index
= True
919 return vim_availability_zones
[availability_zone_index
]
921 return availability_zone_list
[availability_zone_index
]
923 raise vimconn
.vimconnConflictException("No enough availability zones at VIM for this deployment")
925 def new_vminstance(self
, name
, description
, start
, image_id
, flavor_id
, net_list
, cloud_config
=None, disk_list
=None,
926 availability_zone_index
=None, availability_zone_list
=None):
927 '''Adds a VM instance to VIM
929 start: indicates if VM must start or boot in pause mode. Ignored
930 image_id,flavor_id: iamge and flavor uuid
931 net_list: list of interfaces, each one is a dictionary with:
933 net_id: network uuid to connect
934 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
935 model: interface model, ignored #TODO
936 mac_address: used for SR-IOV ifaces #TODO for other types
937 use: 'data', 'bridge', 'mgmt'
938 type: 'virtual', 'PF', 'VF', 'VFnotShared'
939 vim_id: filled/added by this function
940 floating_ip: True/False (or it can be None)
941 'cloud_config': (optional) dictionary with:
942 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
943 'users': (optional) list of users to be inserted, each item is a dict with:
944 'name': (mandatory) user name,
945 'key-pairs': (optional) list of strings with the public key to be inserted to the user
946 'user-data': (optional) string is a text script to be passed directly to cloud-init
947 'config-files': (optional). List of files to be transferred. Each item is a dict with:
948 'dest': (mandatory) string with the destination absolute path
949 'encoding': (optional, by default text). Can be one of:
950 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
951 'content' (mandatory): string with the content of the file
952 'permissions': (optional) string with file permissions, typically octal notation '0644'
953 'owner': (optional) file owner, string with the format 'owner:group'
954 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
955 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
956 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
957 'size': (mandatory) string with the size of the disk in GB
958 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
959 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
960 availability_zone_index is None
961 #TODO ip, security groups
962 Returns the instance identifier
964 self
.logger
.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id
, flavor_id
,str(net_list
))
969 external_network
=[] # list of external networks to be connected to instance, later on used to create floating_ip
970 no_secured_ports
= [] # List of port-is with port-security disabled
971 self
._reload
_connection
()
972 metadata_vpci
={} # For a specific neutron plugin
973 block_device_mapping
= None
975 if not net
.get("net_id"): #skip non connected iface
979 "network_id": net
["net_id"],
980 "name": net
.get("name"),
981 "admin_state_up": True
983 if net
["type"]=="virtual":
985 metadata_vpci
[ net
["net_id"] ] = [[ net
["vpci"], "" ]]
986 elif net
["type"]=="VF": # for VF
988 if "VF" not in metadata_vpci
:
989 metadata_vpci
["VF"]=[]
990 metadata_vpci
["VF"].append([ net
["vpci"], "" ])
991 port_dict
["binding:vnic_type"]="direct"
992 ########## VIO specific Changes #######
993 if self
.vim_type
== "VIO":
994 #Need to create port with port_security_enabled = False and no-security-groups
995 port_dict
["port_security_enabled"]=False
996 port_dict
["provider_security_groups"]=[]
997 port_dict
["security_groups"]=[]
999 ########## VIO specific Changes #######
1000 #Current VIO release does not support port with type 'direct-physical'
1001 #So no need to create virtual port in case of PCI-device.
1002 #Will update port_dict code when support gets added in next VIO release
1003 if self
.vim_type
== "VIO":
1004 raise vimconn
.vimconnNotSupportedException("Current VIO release does not support full passthrough (PT)")
1006 if "PF" not in metadata_vpci
:
1007 metadata_vpci
["PF"]=[]
1008 metadata_vpci
["PF"].append([ net
["vpci"], "" ])
1009 port_dict
["binding:vnic_type"]="direct-physical"
1010 if not port_dict
["name"]:
1011 port_dict
["name"]=name
1012 if net
.get("mac_address"):
1013 port_dict
["mac_address"]=net
["mac_address"]
1014 new_port
= self
.neutron
.create_port({"port": port_dict
})
1015 net
["mac_adress"] = new_port
["port"]["mac_address"]
1016 net
["vim_id"] = new_port
["port"]["id"]
1017 # if try to use a network without subnetwork, it will return a emtpy list
1018 fixed_ips
= new_port
["port"].get("fixed_ips")
1020 net
["ip"] = fixed_ips
[0].get("ip_address")
1024 port
= {"port-id": new_port
["port"]["id"]}
1025 if float(self
.nova
.api_version
.get_string()) >= 2.32:
1026 port
["tag"] = new_port
["port"]["name"]
1027 net_list_vim
.append(port
)
1029 if net
.get('floating_ip', False):
1030 net
['exit_on_floating_ip_error'] = True
1031 external_network
.append(net
)
1032 elif net
['use'] == 'mgmt' and self
.config
.get('use_floating_ip'):
1033 net
['exit_on_floating_ip_error'] = False
1034 external_network
.append(net
)
1036 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1037 # As a workaround we wait until the VM is active and then disable the port-security
1038 if net
.get("port_security") == False:
1039 no_secured_ports
.append(new_port
["port"]["id"])
1042 metadata
= {"pci_assignement": json
.dumps(metadata_vpci
)}
1043 if len(metadata
["pci_assignement"]) >255:
1044 #limit the metadata size
1045 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1046 self
.logger
.warn("Metadata deleted since it exceeds the expected length (255) ")
1049 self
.logger
.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
1050 name
, image_id
, flavor_id
, str(net_list_vim
), description
, str(metadata
))
1052 security_groups
= self
.config
.get('security_groups')
1053 if type(security_groups
) is str:
1054 security_groups
= ( security_groups
, )
1056 config_drive
, userdata
= self
._create
_user
_data
(cloud_config
)
1058 #Create additional volumes in case these are present in disk_list
1059 base_disk_index
= ord('b')
1060 if disk_list
!= None:
1061 block_device_mapping
= {}
1062 for disk
in disk_list
:
1063 if 'image_id' in disk
:
1064 volume
= self
.cinder
.volumes
.create(size
= disk
['size'],name
= name
+ '_vd' +
1065 chr(base_disk_index
), imageRef
= disk
['image_id'])
1067 volume
= self
.cinder
.volumes
.create(size
=disk
['size'], name
=name
+ '_vd' +
1068 chr(base_disk_index
))
1069 block_device_mapping
['_vd' + chr(base_disk_index
)] = volume
.id
1070 base_disk_index
+= 1
1072 #wait until volumes are with status available
1075 while keep_waiting
and elapsed_time
< volume_timeout
:
1076 keep_waiting
= False
1077 for volume_id
in block_device_mapping
.itervalues():
1078 if self
.cinder
.volumes
.get(volume_id
).status
!= 'available':
1084 #if we exceeded the timeout rollback
1085 if elapsed_time
>= volume_timeout
:
1086 #delete the volumes we just created
1087 for volume_id
in block_device_mapping
.itervalues():
1088 self
.cinder
.volumes
.delete(volume_id
)
1090 #delete ports we just created
1091 for net_item
in net_list_vim
:
1092 if 'port-id' in net_item
:
1093 self
.neutron
.delete_port(net_item
['port-id'])
1095 raise vimconn
.vimconnException('Timeout creating volumes for instance ' + name
,
1096 http_code
=vimconn
.HTTP_Request_Timeout
)
1097 # get availability Zone
1098 vm_av_zone
= self
._get
_vm
_availability
_zone
(availability_zone_index
, availability_zone_list
)
1100 self
.logger
.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}, "
1101 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
1102 "block_device_mapping={})".format(name
, image_id
, flavor_id
, net_list_vim
, metadata
,
1103 security_groups
, vm_av_zone
, self
.config
.get('keypair'),
1104 userdata
, config_drive
, block_device_mapping
))
1105 server
= self
.nova
.servers
.create(name
, image_id
, flavor_id
, nics
=net_list_vim
, meta
=metadata
,
1106 security_groups
=security_groups
,
1107 availability_zone
=vm_av_zone
,
1108 key_name
=self
.config
.get('keypair'),
1110 config_drive
=config_drive
,
1111 block_device_mapping
=block_device_mapping
1112 ) # , description=description)
1114 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1115 if no_secured_ports
:
1116 self
.__wait
_for
_vm
(server
.id, 'ACTIVE')
1118 for port_id
in no_secured_ports
:
1120 self
.neutron
.update_port(port_id
, {"port": {"port_security_enabled": False, "security_groups": None} })
1122 except Exception as e
:
1123 self
.logger
.error("It was not possible to disable port security for port {}".format(port_id
))
1124 self
.delete_vminstance(server
.id)
1127 #print "DONE :-)", server
1129 floating_ips
= self
.neutron
.list_floatingips().get("floatingips", ())
1131 if external_network
:
1132 self
.__wait
_for
_vm
(server
.id, 'ACTIVE')
1134 for floating_network
in external_network
:
1137 while(assigned
== False):
1139 ip
= floating_ips
.pop(0)
1140 if not ip
.get("port_id", False) and ip
.get('tenant_id') == server
.tenant_id
:
1141 free_floating_ip
= ip
.get("floating_ip_address")
1143 fix_ip
= floating_network
.get('ip')
1144 server
.add_floating_ip(free_floating_ip
, fix_ip
)
1146 except Exception as e
:
1147 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot create floating_ip "+ str(e
), http_code
=vimconn
.HTTP_Conflict
)
1149 #Find the external network
1150 external_nets
= list()
1151 for net
in self
.neutron
.list_networks()['networks']:
1152 if net
['router:external']:
1153 external_nets
.append(net
)
1155 if len(external_nets
) == 0:
1156 raise vimconn
.vimconnException("Cannot create floating_ip automatically since no external "
1157 "network is present",
1158 http_code
=vimconn
.HTTP_Conflict
)
1159 if len(external_nets
) > 1:
1160 raise vimconn
.vimconnException("Cannot create floating_ip automatically since multiple "
1161 "external networks are present",
1162 http_code
=vimconn
.HTTP_Conflict
)
1164 pool_id
= external_nets
[0].get('id')
1165 param
= {'floatingip': {'floating_network_id': pool_id
, 'tenant_id': server
.tenant_id
}}
1167 #self.logger.debug("Creating floating IP")
1168 new_floating_ip
= self
.neutron
.create_floatingip(param
)
1169 free_floating_ip
= new_floating_ip
['floatingip']['floating_ip_address']
1170 fix_ip
= floating_network
.get('ip')
1171 server
.add_floating_ip(free_floating_ip
, fix_ip
)
1173 except Exception as e
:
1174 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot assign floating_ip "+ str(e
), http_code
=vimconn
.HTTP_Conflict
)
1175 except Exception as e
:
1176 if not floating_network
['exit_on_floating_ip_error']:
1177 self
.logger
.warn("Cannot create floating_ip. %s", str(e
))
1182 # except nvExceptions.NotFound as e:
1183 # error_value=-vimconn.HTTP_Not_Found
1184 # error_text= "vm instance %s not found" % vm_id
1185 # except TypeError as e:
1186 # raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1188 except Exception as e
:
1189 # delete the volumes we just created
1190 if block_device_mapping
:
1191 for volume_id
in block_device_mapping
.itervalues():
1192 self
.cinder
.volumes
.delete(volume_id
)
1196 self
.delete_vminstance(server
.id)
1198 # delete ports we just created
1199 for net_item
in net_list_vim
:
1200 if 'port-id' in net_item
:
1201 self
.neutron
.delete_port(net_item
['port-id'])
1203 self
._format
_exception
(e
)
1205 def get_vminstance(self
,vm_id
):
1206 '''Returns the VM instance information from VIM'''
1207 #self.logger.debug("Getting VM from VIM")
1209 self
._reload
_connection
()
1210 server
= self
.nova
.servers
.find(id=vm_id
)
1211 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
1212 return server
.to_dict()
1213 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1214 self
._format
_exception
(e
)
1216 def get_vminstance_console(self
,vm_id
, console_type
="vnc"):
1218 Get a console for the virtual machine
1220 vm_id: uuid of the VM
1221 console_type, can be:
1222 "novnc" (by default), "xvpvnc" for VNC types,
1223 "rdp-html5" for RDP types, "spice-html5" for SPICE types
1224 Returns dict with the console parameters:
1225 protocol: ssh, ftp, http, https, ...
1226 server: usually ip address
1227 port: the http, ssh, ... port
1228 suffix: extra text, e.g. the http path and query string
1230 self
.logger
.debug("Getting VM CONSOLE from VIM")
1232 self
._reload
_connection
()
1233 server
= self
.nova
.servers
.find(id=vm_id
)
1234 if console_type
== None or console_type
== "novnc":
1235 console_dict
= server
.get_vnc_console("novnc")
1236 elif console_type
== "xvpvnc":
1237 console_dict
= server
.get_vnc_console(console_type
)
1238 elif console_type
== "rdp-html5":
1239 console_dict
= server
.get_rdp_console(console_type
)
1240 elif console_type
== "spice-html5":
1241 console_dict
= server
.get_spice_console(console_type
)
1243 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
), http_code
=vimconn
.HTTP_Bad_Request
)
1245 console_dict1
= console_dict
.get("console")
1247 console_url
= console_dict1
.get("url")
1250 protocol_index
= console_url
.find("//")
1251 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1252 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1253 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1254 return -vimconn
.HTTP_Internal_Server_Error
, "Unexpected response from VIM"
1255 console_dict
={"protocol": console_url
[0:protocol_index
],
1256 "server": console_url
[protocol_index
+2:port_index
],
1257 "port": console_url
[port_index
:suffix_index
],
1258 "suffix": console_url
[suffix_index
+1:]
1262 raise vimconn
.vimconnUnexpectedResponse("Unexpected response from VIM")
1264 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.BadRequest
, ConnectionError
) as e
:
1265 self
._format
_exception
(e
)
1267 def delete_vminstance(self
, vm_id
):
1268 '''Removes a VM instance from VIM. Returns the old identifier
1270 #print "osconnector: Getting VM from VIM"
1272 self
._reload
_connection
()
1273 #delete VM ports attached to this networks before the virtual machine
1274 ports
= self
.neutron
.list_ports(device_id
=vm_id
)
1275 for p
in ports
['ports']:
1277 self
.neutron
.delete_port(p
["id"])
1278 except Exception as e
:
1279 self
.logger
.error("Error deleting port: " + type(e
).__name
__ + ": "+ str(e
))
1281 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1282 #dettach volumes attached
1283 server
= self
.nova
.servers
.get(vm_id
)
1284 volumes_attached_dict
= server
._info
['os-extended-volumes:volumes_attached']
1285 #for volume in volumes_attached_dict:
1286 # self.cinder.volumes.detach(volume['id'])
1288 self
.nova
.servers
.delete(vm_id
)
1291 #Although having detached them should have them in active status
1292 #we ensure in this loop
1295 while keep_waiting
and elapsed_time
< volume_timeout
:
1296 keep_waiting
= False
1297 for volume
in volumes_attached_dict
:
1298 if self
.cinder
.volumes
.get(volume
['id']).status
!= 'available':
1301 self
.cinder
.volumes
.delete(volume
['id'])
1307 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
1308 self
._format
_exception
(e
)
1309 #TODO insert exception vimconn.HTTP_Unauthorized
1310 #if reaching here is because an exception
1312 def refresh_vms_status(self
, vm_list
):
1313 '''Get the status of the virtual machines and their interfaces/ports
1314 Params: the list of VM identifiers
1315 Returns a dictionary with:
1316 vm_id: #VIM id of this Virtual Machine
1317 status: #Mandatory. Text with one of:
1318 # DELETED (not found at vim)
1319 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1320 # OTHER (Vim reported other status not understood)
1321 # ERROR (VIM indicates an ERROR status)
1322 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1323 # CREATING (on building process), ERROR
1324 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1326 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1327 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1329 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1330 mac_address: #Text format XX:XX:XX:XX:XX:XX
1331 vim_net_id: #network id where this interface is connected
1332 vim_interface_id: #interface/port VIM id
1333 ip_address: #null, or text with IPv4, IPv6 address
1334 compute_node: #identification of compute node where PF,VF interface is allocated
1335 pci: #PCI address of the NIC that hosts the PF,VF
1336 vlan: #physical VLAN used for VF
1339 self
.logger
.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1340 for vm_id
in vm_list
:
1343 vm_vim
= self
.get_vminstance(vm_id
)
1344 if vm_vim
['status'] in vmStatus2manoFormat
:
1345 vm
['status'] = vmStatus2manoFormat
[ vm_vim
['status'] ]
1347 vm
['status'] = "OTHER"
1348 vm
['error_msg'] = "VIM status reported " + vm_vim
['status']
1350 vm
['vim_info'] = yaml
.safe_dump(vm_vim
, default_flow_style
=True, width
=256)
1351 except yaml
.representer
.RepresenterError
:
1352 vm
['vim_info'] = str(vm_vim
)
1353 vm
["interfaces"] = []
1354 if vm_vim
.get('fault'):
1355 vm
['error_msg'] = str(vm_vim
['fault'])
1358 self
._reload
_connection
()
1359 port_dict
=self
.neutron
.list_ports(device_id
=vm_id
)
1360 for port
in port_dict
["ports"]:
1363 interface
['vim_info'] = yaml
.safe_dump(port
, default_flow_style
=True, width
=256)
1364 except yaml
.representer
.RepresenterError
:
1365 interface
['vim_info'] = str(port
)
1366 interface
["mac_address"] = port
.get("mac_address")
1367 interface
["vim_net_id"] = port
["network_id"]
1368 interface
["vim_interface_id"] = port
["id"]
1369 # check if OS-EXT-SRV-ATTR:host is there,
1370 # in case of non-admin credentials, it will be missing
1371 if vm_vim
.get('OS-EXT-SRV-ATTR:host'):
1372 interface
["compute_node"] = vm_vim
['OS-EXT-SRV-ATTR:host']
1373 interface
["pci"] = None
1375 # check if binding:profile is there,
1376 # in case of non-admin credentials, it will be missing
1377 if port
.get('binding:profile'):
1378 if port
['binding:profile'].get('pci_slot'):
1379 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1380 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1381 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1382 pci
= port
['binding:profile']['pci_slot']
1383 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1384 interface
["pci"] = pci
1385 interface
["vlan"] = None
1386 #if network is of type vlan and port is of type direct (sr-iov) then set vlan id
1387 network
= self
.neutron
.show_network(port
["network_id"])
1388 if network
['network'].get('provider:network_type') == 'vlan' and \
1389 port
.get("binding:vnic_type") == "direct":
1390 interface
["vlan"] = network
['network'].get('provider:segmentation_id')
1392 #look for floating ip address
1393 floating_ip_dict
= self
.neutron
.list_floatingips(port_id
=port
["id"])
1394 if floating_ip_dict
.get("floatingips"):
1395 ips
.append(floating_ip_dict
["floatingips"][0].get("floating_ip_address") )
1397 for subnet
in port
["fixed_ips"]:
1398 ips
.append(subnet
["ip_address"])
1399 interface
["ip_address"] = ";".join(ips
)
1400 vm
["interfaces"].append(interface
)
1401 except Exception as e
:
1402 self
.logger
.error("Error getting vm interface information " + type(e
).__name
__ + ": "+ str(e
))
1403 except vimconn
.vimconnNotFoundException
as e
:
1404 self
.logger
.error("Exception getting vm status: %s", str(e
))
1405 vm
['status'] = "DELETED"
1406 vm
['error_msg'] = str(e
)
1407 except vimconn
.vimconnException
as e
:
1408 self
.logger
.error("Exception getting vm status: %s", str(e
))
1409 vm
['status'] = "VIM_ERROR"
1410 vm
['error_msg'] = str(e
)
1414 def action_vminstance(self
, vm_id
, action_dict
):
1415 '''Send and action over a VM instance from VIM
1416 Returns the vm_id if the action was successfully sent to the VIM'''
1417 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1419 self
._reload
_connection
()
1420 server
= self
.nova
.servers
.find(id=vm_id
)
1421 if "start" in action_dict
:
1422 if action_dict
["start"]=="rebuild":
1425 if server
.status
=="PAUSED":
1427 elif server
.status
=="SUSPENDED":
1429 elif server
.status
=="SHUTOFF":
1431 elif "pause" in action_dict
:
1433 elif "resume" in action_dict
:
1435 elif "shutoff" in action_dict
or "shutdown" in action_dict
:
1437 elif "forceOff" in action_dict
:
1439 elif "terminate" in action_dict
:
1441 elif "createImage" in action_dict
:
1442 server
.create_image()
1443 #"path":path_schema,
1444 #"description":description_schema,
1445 #"name":name_schema,
1446 #"metadata":metadata_schema,
1447 #"imageRef": id_schema,
1448 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1449 elif "rebuild" in action_dict
:
1450 server
.rebuild(server
.image
['id'])
1451 elif "reboot" in action_dict
:
1452 server
.reboot() #reboot_type='SOFT'
1453 elif "console" in action_dict
:
1454 console_type
= action_dict
["console"]
1455 if console_type
== None or console_type
== "novnc":
1456 console_dict
= server
.get_vnc_console("novnc")
1457 elif console_type
== "xvpvnc":
1458 console_dict
= server
.get_vnc_console(console_type
)
1459 elif console_type
== "rdp-html5":
1460 console_dict
= server
.get_rdp_console(console_type
)
1461 elif console_type
== "spice-html5":
1462 console_dict
= server
.get_spice_console(console_type
)
1464 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
),
1465 http_code
=vimconn
.HTTP_Bad_Request
)
1467 console_url
= console_dict
["console"]["url"]
1469 protocol_index
= console_url
.find("//")
1470 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1471 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1472 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1473 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1474 console_dict2
={"protocol": console_url
[0:protocol_index
],
1475 "server": console_url
[protocol_index
+2 : port_index
],
1476 "port": int(console_url
[port_index
+1 : suffix_index
]),
1477 "suffix": console_url
[suffix_index
+1:]
1479 return console_dict2
1480 except Exception as e
:
1481 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1484 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1485 self
._format
_exception
(e
)
1486 #TODO insert exception vimconn.HTTP_Unauthorized
1488 ####### VIO Specific Changes #########
1489 def _genrate_vlanID(self
):
1491 Method to get unused vlanID
1499 networks
= self
.get_network_list()
1500 for net
in networks
:
1501 if net
.get('provider:segmentation_id'):
1502 usedVlanIDs
.append(net
.get('provider:segmentation_id'))
1503 used_vlanIDs
= set(usedVlanIDs
)
1505 #find unused VLAN ID
1506 for vlanID_range
in self
.config
.get('dataplane_net_vlan_range'):
1508 start_vlanid
, end_vlanid
= map(int, vlanID_range
.replace(" ", "").split("-"))
1509 for vlanID
in xrange(start_vlanid
, end_vlanid
+ 1):
1510 if vlanID
not in used_vlanIDs
:
1512 except Exception as exp
:
1513 raise vimconn
.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp
))
1515 raise vimconn
.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1516 " All given Vlan IDs {} are in use.".format(self
.config
.get('dataplane_net_vlan_range')))
1519 def _validate_vlan_ranges(self
, dataplane_net_vlan_range
):
1521 Method to validate user given vlanID ranges
1525 for vlanID_range
in dataplane_net_vlan_range
:
1526 vlan_range
= vlanID_range
.replace(" ", "")
1528 vlanID_pattern
= r
'(\d)*-(\d)*$'
1529 match_obj
= re
.match(vlanID_pattern
, vlan_range
)
1531 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1532 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range
))
1534 start_vlanid
, end_vlanid
= map(int,vlan_range
.split("-"))
1535 if start_vlanid
<= 0 :
1536 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1537 "Start ID can not be zero. For VLAN "\
1538 "networks valid IDs are 1 to 4094 ".format(vlanID_range
))
1539 if end_vlanid
> 4094 :
1540 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1541 "End VLAN ID can not be greater than 4094. For VLAN "\
1542 "networks valid IDs are 1 to 4094 ".format(vlanID_range
))
1544 if start_vlanid
> end_vlanid
:
1545 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1546 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1547 "start_ID < end_ID ".format(vlanID_range
))
1551 def new_external_port(self
, port_data
):
1552 #TODO openstack if needed
1553 '''Adds a external port to VIM'''
1554 '''Returns the port identifier'''
1555 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.new_external_port() not implemented"
1557 def connect_port_network(self
, port_id
, network_id
, admin
=False):
1558 #TODO openstack if needed
1559 '''Connects a external port to a network'''
1560 '''Returns status code of the VIM response'''
1561 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.connect_port_network() not implemented"
1563 def new_user(self
, user_name
, user_passwd
, tenant_id
=None):
1564 '''Adds a new user to openstack VIM'''
1565 '''Returns the user identifier'''
1566 self
.logger
.debug("osconnector: Adding a new user to VIM")
1568 self
._reload
_connection
()
1569 user
=self
.keystone
.users
.create(user_name
, user_passwd
, tenant_id
=tenant_id
)
1570 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1572 except ksExceptions
.ConnectionError
as e
:
1573 error_value
=-vimconn
.HTTP_Bad_Request
1574 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1575 except ksExceptions
.ClientException
as e
: #TODO remove
1576 error_value
=-vimconn
.HTTP_Bad_Request
1577 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1578 #TODO insert exception vimconn.HTTP_Unauthorized
1579 #if reaching here is because an exception
1581 self
.logger
.debug("new_user " + error_text
)
1582 return error_value
, error_text
1584 def delete_user(self
, user_id
):
1585 '''Delete a user from openstack VIM'''
1586 '''Returns the user identifier'''
1588 print("osconnector: Deleting a user from VIM")
1590 self
._reload
_connection
()
1591 self
.keystone
.users
.delete(user_id
)
1593 except ksExceptions
.ConnectionError
as e
:
1594 error_value
=-vimconn
.HTTP_Bad_Request
1595 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1596 except ksExceptions
.NotFound
as e
:
1597 error_value
=-vimconn
.HTTP_Not_Found
1598 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1599 except ksExceptions
.ClientException
as e
: #TODO remove
1600 error_value
=-vimconn
.HTTP_Bad_Request
1601 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1602 #TODO insert exception vimconn.HTTP_Unauthorized
1603 #if reaching here is because an exception
1605 print("delete_tenant " + error_text
)
1606 return error_value
, error_text
1608 def get_hosts_info(self
):
1609 '''Get the information of deployed hosts
1610 Returns the hosts content'''
1612 print("osconnector: Getting Host info from VIM")
1615 self
._reload
_connection
()
1616 hypervisors
= self
.nova
.hypervisors
.list()
1617 for hype
in hypervisors
:
1618 h_list
.append( hype
.to_dict() )
1619 return 1, {"hosts":h_list
}
1620 except nvExceptions
.NotFound
as e
:
1621 error_value
=-vimconn
.HTTP_Not_Found
1622 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1623 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1624 error_value
=-vimconn
.HTTP_Bad_Request
1625 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1626 #TODO insert exception vimconn.HTTP_Unauthorized
1627 #if reaching here is because an exception
1629 print("get_hosts_info " + error_text
)
1630 return error_value
, error_text
1632 def get_hosts(self
, vim_tenant
):
1633 '''Get the hosts and deployed instances
1634 Returns the hosts content'''
1635 r
, hype_dict
= self
.get_hosts_info()
1638 hypervisors
= hype_dict
["hosts"]
1640 servers
= self
.nova
.servers
.list()
1641 for hype
in hypervisors
:
1642 for server
in servers
:
1643 if server
.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype
['hypervisor_hostname']:
1645 hype
['vm'].append(server
.id)
1647 hype
['vm'] = [server
.id]
1649 except nvExceptions
.NotFound
as e
:
1650 error_value
=-vimconn
.HTTP_Not_Found
1651 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1652 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1653 error_value
=-vimconn
.HTTP_Bad_Request
1654 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1655 #TODO insert exception vimconn.HTTP_Unauthorized
1656 #if reaching here is because an exception
1658 print("get_hosts " + error_text
)
1659 return error_value
, error_text
1661 def new_classification(self
, name
, ctype
, definition
):
1663 'Adding a new (Traffic) Classification to VIM, named %s', name
)
1666 self
._reload
_connection
()
1667 if ctype
not in supportedClassificationTypes
:
1668 raise vimconn
.vimconnNotSupportedException(
1669 'OpenStack VIM connector doesn\'t support provided '
1670 'Classification Type {}, supported ones are: '
1671 '{}'.format(ctype
, supportedClassificationTypes
))
1672 if not self
._validate
_classification
(ctype
, definition
):
1673 raise vimconn
.vimconnException(
1674 'Incorrect Classification definition '
1675 'for the type specified.')
1676 classification_dict
= definition
1677 classification_dict
['name'] = name
1679 new_class
= self
.neutron
.create_flow_classifier(
1680 {'flow_classifier': classification_dict
})
1681 return new_class
['flow_classifier']['id']
1682 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1683 neExceptions
.NeutronException
, ConnectionError
) as e
:
1685 'Creation of Classification failed.')
1686 self
._format
_exception
(e
)
1688 def get_classification(self
, class_id
):
1689 self
.logger
.debug(" Getting Classification %s from VIM", class_id
)
1690 filter_dict
= {"id": class_id
}
1691 class_list
= self
.get_classification_list(filter_dict
)
1692 if len(class_list
) == 0:
1693 raise vimconn
.vimconnNotFoundException(
1694 "Classification '{}' not found".format(class_id
))
1695 elif len(class_list
) > 1:
1696 raise vimconn
.vimconnConflictException(
1697 "Found more than one Classification with this criteria")
1698 classification
= class_list
[0]
1699 return classification
1701 def get_classification_list(self
, filter_dict
={}):
1702 self
.logger
.debug("Getting Classifications from VIM filter: '%s'",
1705 self
._reload
_connection
()
1706 if self
.api_version3
and "tenant_id" in filter_dict
:
1707 filter_dict
['project_id'] = filter_dict
.pop('tenant_id')
1708 classification_dict
= self
.neutron
.list_flow_classifier(
1710 classification_list
= classification_dict
["flow_classifiers"]
1711 self
.__classification
_os
2mano
(classification_list
)
1712 return classification_list
1713 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1714 neExceptions
.NeutronException
, ConnectionError
) as e
:
1715 self
._format
_exception
(e
)
1717 def delete_classification(self
, class_id
):
1718 self
.logger
.debug("Deleting Classification '%s' from VIM", class_id
)
1720 self
._reload
_connection
()
1721 self
.neutron
.delete_flow_classifier(class_id
)
1723 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1724 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1725 ConnectionError
) as e
:
1726 self
._format
_exception
(e
)
1728 def new_sfi(self
, name
, ingress_ports
, egress_ports
, sfc_encap
=True):
1730 "Adding a new Service Function Instance to VIM, named '%s'", name
)
1733 self
._reload
_connection
()
1736 # TODO(igordc): must be changed to NSH in Queens
1737 # (MPLS is a workaround)
1738 correlation
= 'mpls'
1739 if len(ingress_ports
) != 1:
1740 raise vimconn
.vimconnNotSupportedException(
1741 "OpenStack VIM connector can only have "
1742 "1 ingress port per SFI")
1743 if len(egress_ports
) != 1:
1744 raise vimconn
.vimconnNotSupportedException(
1745 "OpenStack VIM connector can only have "
1746 "1 egress port per SFI")
1747 sfi_dict
= {'name': name
,
1748 'ingress': ingress_ports
[0],
1749 'egress': egress_ports
[0],
1750 'service_function_parameters': {
1751 'correlation': correlation
}}
1752 new_sfi
= self
.neutron
.create_port_pair({'port_pair': sfi_dict
})
1753 return new_sfi
['port_pair']['id']
1754 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1755 neExceptions
.NeutronException
, ConnectionError
) as e
:
1758 self
.neutron
.delete_port_pair_group(
1759 new_sfi
['port_pair']['id'])
1762 'Creation of Service Function Instance failed, with '
1763 'subsequent deletion failure as well.')
1764 self
._format
_exception
(e
)
1766 def get_sfi(self
, sfi_id
):
1768 'Getting Service Function Instance %s from VIM', sfi_id
)
1769 filter_dict
= {"id": sfi_id
}
1770 sfi_list
= self
.get_sfi_list(filter_dict
)
1771 if len(sfi_list
) == 0:
1772 raise vimconn
.vimconnNotFoundException(
1773 "Service Function Instance '{}' not found".format(sfi_id
))
1774 elif len(sfi_list
) > 1:
1775 raise vimconn
.vimconnConflictException(
1776 'Found more than one Service Function Instance '
1777 'with this criteria')
1781 def get_sfi_list(self
, filter_dict
={}):
1782 self
.logger
.debug("Getting Service Function Instances from "
1783 "VIM filter: '%s'", str(filter_dict
))
1785 self
._reload
_connection
()
1786 if self
.api_version3
and "tenant_id" in filter_dict
:
1787 filter_dict
['project_id'] = filter_dict
.pop('tenant_id')
1788 sfi_dict
= self
.neutron
.list_port_pair(**filter_dict
)
1789 sfi_list
= sfi_dict
["port_pairs"]
1790 self
.__sfi
_os
2mano
(sfi_list
)
1792 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1793 neExceptions
.NeutronException
, ConnectionError
) as e
:
1794 self
._format
_exception
(e
)
1796 def delete_sfi(self
, sfi_id
):
1797 self
.logger
.debug("Deleting Service Function Instance '%s' "
1800 self
._reload
_connection
()
1801 self
.neutron
.delete_port_pair(sfi_id
)
1803 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1804 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1805 ConnectionError
) as e
:
1806 self
._format
_exception
(e
)
1808 def new_sf(self
, name
, sfis
, sfc_encap
=True):
1809 self
.logger
.debug("Adding a new Service Function to VIM, "
1813 self
._reload
_connection
()
1816 # TODO(igordc): must be changed to NSH in Queens
1817 # (MPLS is a workaround)
1818 correlation
= 'mpls'
1819 for instance
in sfis
:
1820 sfi
= self
.get_sfi(instance
)
1821 if sfi
.get('sfc_encap') != correlation
:
1822 raise vimconn
.vimconnNotSupportedException(
1823 "OpenStack VIM connector requires all SFIs of the "
1824 "same SF to share the same SFC Encapsulation")
1825 sf_dict
= {'name': name
,
1827 new_sf
= self
.neutron
.create_port_pair_group({
1828 'port_pair_group': sf_dict
})
1829 return new_sf
['port_pair_group']['id']
1830 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1831 neExceptions
.NeutronException
, ConnectionError
) as e
:
1834 self
.neutron
.delete_port_pair_group(
1835 new_sf
['port_pair_group']['id'])
1838 'Creation of Service Function failed, with '
1839 'subsequent deletion failure as well.')
1840 self
._format
_exception
(e
)
1842 def get_sf(self
, sf_id
):
1843 self
.logger
.debug("Getting Service Function %s from VIM", sf_id
)
1844 filter_dict
= {"id": sf_id
}
1845 sf_list
= self
.get_sf_list(filter_dict
)
1846 if len(sf_list
) == 0:
1847 raise vimconn
.vimconnNotFoundException(
1848 "Service Function '{}' not found".format(sf_id
))
1849 elif len(sf_list
) > 1:
1850 raise vimconn
.vimconnConflictException(
1851 "Found more than one Service Function with this criteria")
1855 def get_sf_list(self
, filter_dict
={}):
1856 self
.logger
.debug("Getting Service Function from VIM filter: '%s'",
1859 self
._reload
_connection
()
1860 if self
.api_version3
and "tenant_id" in filter_dict
:
1861 filter_dict
['project_id'] = filter_dict
.pop('tenant_id')
1862 sf_dict
= self
.neutron
.list_port_pair_group(**filter_dict
)
1863 sf_list
= sf_dict
["port_pair_groups"]
1864 self
.__sf
_os
2mano
(sf_list
)
1866 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1867 neExceptions
.NeutronException
, ConnectionError
) as e
:
1868 self
._format
_exception
(e
)
1870 def delete_sf(self
, sf_id
):
1871 self
.logger
.debug("Deleting Service Function '%s' from VIM", sf_id
)
1873 self
._reload
_connection
()
1874 self
.neutron
.delete_port_pair_group(sf_id
)
1876 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1877 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1878 ConnectionError
) as e
:
1879 self
._format
_exception
(e
)
1881 def new_sfp(self
, name
, classifications
, sfs
, sfc_encap
=True, spi
=None):
1882 self
.logger
.debug("Adding a new Service Function Path to VIM, "
1886 self
._reload
_connection
()
1888 raise vimconn
.vimconnNotSupportedException(
1889 "OpenStack VIM connector only supports "
1890 "SFC-Encapsulated chains")
1891 # TODO(igordc): must be changed to NSH in Queens
1892 # (MPLS is a workaround)
1893 correlation
= 'mpls'
1894 sfp_dict
= {'name': name
,
1895 'flow_classifiers': classifications
,
1896 'port_pair_groups': sfs
,
1897 'chain_parameters': {'correlation': correlation
}}
1899 sfp_dict
['chain_id'] = spi
1900 new_sfp
= self
.neutron
.create_port_chain({'port_chain': sfp_dict
})
1901 return new_sfp
["port_chain"]["id"]
1902 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1903 neExceptions
.NeutronException
, ConnectionError
) as e
:
1906 self
.neutron
.delete_port_chain(new_sfp
['port_chain']['id'])
1909 'Creation of Service Function Path failed, with '
1910 'subsequent deletion failure as well.')
1911 self
._format
_exception
(e
)
1913 def get_sfp(self
, sfp_id
):
1914 self
.logger
.debug(" Getting Service Function Path %s from VIM", sfp_id
)
1915 filter_dict
= {"id": sfp_id
}
1916 sfp_list
= self
.get_sfp_list(filter_dict
)
1917 if len(sfp_list
) == 0:
1918 raise vimconn
.vimconnNotFoundException(
1919 "Service Function Path '{}' not found".format(sfp_id
))
1920 elif len(sfp_list
) > 1:
1921 raise vimconn
.vimconnConflictException(
1922 "Found more than one Service Function Path with this criteria")
1926 def get_sfp_list(self
, filter_dict
={}):
1927 self
.logger
.debug("Getting Service Function Paths from VIM filter: "
1928 "'%s'", str(filter_dict
))
1930 self
._reload
_connection
()
1931 if self
.api_version3
and "tenant_id" in filter_dict
:
1932 filter_dict
['project_id'] = filter_dict
.pop('tenant_id')
1933 sfp_dict
= self
.neutron
.list_port_chain(**filter_dict
)
1934 sfp_list
= sfp_dict
["port_chains"]
1935 self
.__sfp
_os
2mano
(sfp_list
)
1937 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1938 neExceptions
.NeutronException
, ConnectionError
) as e
:
1939 self
._format
_exception
(e
)
1941 def delete_sfp(self
, sfp_id
):
1943 "Deleting Service Function Path '%s' from VIM", sfp_id
)
1945 self
._reload
_connection
()
1946 self
.neutron
.delete_port_chain(sfp_id
)
1948 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1949 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1950 ConnectionError
) as e
:
1951 self
._format
_exception
(e
)