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
63 from email
.mime
.multipart
import MIMEMultipart
64 from email
.mime
.text
import MIMEText
67 """contain the openstack virtual machine status to openmano status"""
68 vmStatus2manoFormat
={'ACTIVE':'ACTIVE',
70 'SUSPENDED': 'SUSPENDED',
73 'ERROR':'ERROR','DELETED':'DELETED'
75 netStatus2manoFormat
={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
78 supportedClassificationTypes
= ['legacy_flow_classifier']
80 #global var to have a timeout creating and deleting volumes
84 class vimconnector(vimconn
.vimconnector
):
85 def __init__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
=None, user
=None, passwd
=None,
86 log_level
=None, config
={}, persistent_info
={}):
87 '''using common constructor parameters. In this case
88 'url' is the keystone authorization url,
89 'url_admin' is not use
91 api_version
= config
.get('APIversion')
92 if api_version
and api_version
not in ('v3.3', 'v2.0', '2', '3'):
93 raise vimconn
.vimconnException("Invalid value '{}' for config:APIversion. "
94 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version
))
95 vim_type
= config
.get('vim_type')
96 if vim_type
and vim_type
not in ('vio', 'VIO'):
97 raise vimconn
.vimconnException("Invalid value '{}' for config:vim_type."
98 "Allowed values are 'vio' or 'VIO'".format(vim_type
))
100 if config
.get('dataplane_net_vlan_range') is not None:
101 #validate vlan ranges provided by user
102 self
._validate
_vlan
_ranges
(config
.get('dataplane_net_vlan_range'))
104 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
,
107 self
.insecure
= self
.config
.get("insecure", False)
109 raise TypeError('url param can not be NoneType')
110 self
.persistent_info
= persistent_info
111 self
.availability_zone
= persistent_info
.get('availability_zone', None)
112 self
.session
= persistent_info
.get('session', {'reload_client': True})
113 self
.nova
= self
.session
.get('nova')
114 self
.neutron
= self
.session
.get('neutron')
115 self
.cinder
= self
.session
.get('cinder')
116 self
.glance
= self
.session
.get('glance')
117 self
.glancev1
= self
.session
.get('glancev1')
118 self
.keystone
= self
.session
.get('keystone')
119 self
.api_version3
= self
.session
.get('api_version3')
120 self
.vim_type
= self
.config
.get("vim_type")
122 self
.vim_type
= self
.vim_type
.upper()
123 if self
.config
.get("use_internal_endpoint"):
124 self
.endpoint_type
= "internalURL"
126 self
.endpoint_type
= None
128 self
.logger
= logging
.getLogger('openmano.vim.openstack')
130 ####### VIO Specific Changes #########
131 if self
.vim_type
== "VIO":
132 self
.logger
= logging
.getLogger('openmano.vim.vio')
135 self
.logger
.setLevel( getattr(logging
, log_level
))
137 def __getitem__(self
, index
):
138 """Get individuals parameters.
140 if index
== 'project_domain_id':
141 return self
.config
.get("project_domain_id")
142 elif index
== 'user_domain_id':
143 return self
.config
.get("user_domain_id")
145 return vimconn
.vimconnector
.__getitem
__(self
, index
)
147 def __setitem__(self
, index
, value
):
148 """Set individuals parameters and it is marked as dirty so to force connection reload.
150 if index
== 'project_domain_id':
151 self
.config
["project_domain_id"] = value
152 elif index
== 'user_domain_id':
153 self
.config
["user_domain_id"] = value
155 vimconn
.vimconnector
.__setitem
__(self
, index
, value
)
156 self
.session
['reload_client'] = True
158 def _reload_connection(self
):
159 '''Called before any operation, it check if credentials has changed
160 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
162 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
163 if self
.session
['reload_client']:
164 if self
.config
.get('APIversion'):
165 self
.api_version3
= self
.config
['APIversion'] == 'v3.3' or self
.config
['APIversion'] == '3'
166 else: # get from ending auth_url that end with v3 or with v2.0
167 self
.api_version3
= self
.url
.split("/")[-1] == "v3"
168 self
.session
['api_version3'] = self
.api_version3
169 if self
.api_version3
:
170 auth
= v3
.Password(auth_url
=self
.url
,
172 password
=self
.passwd
,
173 project_name
=self
.tenant_name
,
174 project_id
=self
.tenant_id
,
175 project_domain_id
=self
.config
.get('project_domain_id', 'default'),
176 user_domain_id
=self
.config
.get('user_domain_id', 'default'))
178 auth
= v2
.Password(auth_url
=self
.url
,
180 password
=self
.passwd
,
181 tenant_name
=self
.tenant_name
,
182 tenant_id
=self
.tenant_id
)
183 sess
= session
.Session(auth
=auth
, verify
=not self
.insecure
)
184 if self
.api_version3
:
185 self
.keystone
= ksClient_v3
.Client(session
=sess
, endpoint_type
=self
.endpoint_type
)
187 self
.keystone
= ksClient_v2
.Client(session
=sess
, endpoint_type
=self
.endpoint_type
)
188 self
.session
['keystone'] = self
.keystone
189 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
190 # This implementation approach is due to the warning message in
191 # https://developer.openstack.org/api-guide/compute/microversions.html
192 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
193 # always require an specific microversion.
194 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
195 version
= self
.config
.get("microversion")
198 self
.nova
= self
.session
['nova'] = nClient
.Client(str(version
), session
=sess
, endpoint_type
=self
.endpoint_type
)
199 self
.neutron
= self
.session
['neutron'] = neClient
.Client('2.0', session
=sess
, endpoint_type
=self
.endpoint_type
)
200 self
.cinder
= self
.session
['cinder'] = cClient
.Client(2, session
=sess
, endpoint_type
=self
.endpoint_type
)
201 if self
.endpoint_type
== "internalURL":
202 glance_service_id
= self
.keystone
.services
.list(name
="glance")[0].id
203 glance_endpoint
= self
.keystone
.endpoints
.list(glance_service_id
, interface
="internal")[0].url
205 glance_endpoint
= None
206 self
.glance
= self
.session
['glance'] = glClient
.Client(2, session
=sess
, endpoint
=glance_endpoint
)
207 #using version 1 of glance client in new_image()
208 self
.glancev1
= self
.session
['glancev1'] = glClient
.Client('1', session
=sess
,
209 endpoint
=glance_endpoint
)
210 self
.session
['reload_client'] = False
211 self
.persistent_info
['session'] = self
.session
212 # add availablity zone info inside self.persistent_info
213 self
._set
_availablity
_zones
()
214 self
.persistent_info
['availability_zone'] = self
.availability_zone
216 def __net_os2mano(self
, net_list_dict
):
217 '''Transform the net openstack format to mano format
218 net_list_dict can be a list of dict or a single dict'''
219 if type(net_list_dict
) is dict:
220 net_list_
=(net_list_dict
,)
221 elif type(net_list_dict
) is list:
222 net_list_
=net_list_dict
224 raise TypeError("param net_list_dict must be a list or a dictionary")
225 for net
in net_list_
:
226 if net
.get('provider:network_type') == "vlan":
231 def __classification_os2mano(self
, class_list_dict
):
232 """Transform the openstack format (Flow Classifier) to mano format
233 (Classification) class_list_dict can be a list of dict or a single dict
235 if isinstance(class_list_dict
, dict):
236 class_list_
= [class_list_dict
]
237 elif isinstance(class_list_dict
, list):
238 class_list_
= class_list_dict
241 "param class_list_dict must be a list or a dictionary")
242 for classification
in class_list_
:
243 id = classification
.pop('id')
244 name
= classification
.pop('name')
245 description
= classification
.pop('description')
246 project_id
= classification
.pop('project_id')
247 tenant_id
= classification
.pop('tenant_id')
248 original_classification
= copy
.deepcopy(classification
)
249 classification
.clear()
250 classification
['ctype'] = 'legacy_flow_classifier'
251 classification
['definition'] = original_classification
252 classification
['id'] = id
253 classification
['name'] = name
254 classification
['description'] = description
255 classification
['project_id'] = project_id
256 classification
['tenant_id'] = tenant_id
258 def __sfi_os2mano(self
, sfi_list_dict
):
259 """Transform the openstack format (Port Pair) to mano format (SFI)
260 sfi_list_dict can be a list of dict or a single dict
262 if isinstance(sfi_list_dict
, dict):
263 sfi_list_
= [sfi_list_dict
]
264 elif isinstance(sfi_list_dict
, list):
265 sfi_list_
= sfi_list_dict
268 "param sfi_list_dict must be a list or a dictionary")
269 for sfi
in sfi_list_
:
270 sfi
['ingress_ports'] = []
271 sfi
['egress_ports'] = []
272 if sfi
.get('ingress'):
273 sfi
['ingress_ports'].append(sfi
['ingress'])
274 if sfi
.get('egress'):
275 sfi
['egress_ports'].append(sfi
['egress'])
278 params
= sfi
.get('service_function_parameters')
281 correlation
= params
.get('correlation')
284 sfi
['sfc_encap'] = sfc_encap
285 del sfi
['service_function_parameters']
287 def __sf_os2mano(self
, sf_list_dict
):
288 """Transform the openstack format (Port Pair Group) to mano format (SF)
289 sf_list_dict can be a list of dict or a single dict
291 if isinstance(sf_list_dict
, dict):
292 sf_list_
= [sf_list_dict
]
293 elif isinstance(sf_list_dict
, list):
294 sf_list_
= sf_list_dict
297 "param sf_list_dict must be a list or a dictionary")
299 del sf
['port_pair_group_parameters']
300 sf
['sfis'] = sf
['port_pairs']
303 def __sfp_os2mano(self
, sfp_list_dict
):
304 """Transform the openstack format (Port Chain) to mano format (SFP)
305 sfp_list_dict can be a list of dict or a single dict
307 if isinstance(sfp_list_dict
, dict):
308 sfp_list_
= [sfp_list_dict
]
309 elif isinstance(sfp_list_dict
, list):
310 sfp_list_
= sfp_list_dict
313 "param sfp_list_dict must be a list or a dictionary")
314 for sfp
in sfp_list_
:
315 params
= sfp
.pop('chain_parameters')
318 correlation
= params
.get('correlation')
321 sfp
['sfc_encap'] = sfc_encap
322 sfp
['spi'] = sfp
.pop('chain_id')
323 sfp
['classifications'] = sfp
.pop('flow_classifiers')
324 sfp
['service_functions'] = sfp
.pop('port_pair_groups')
326 # placeholder for now; read TODO note below
327 def _validate_classification(self
, type, definition
):
328 # only legacy_flow_classifier Type is supported at this point
330 # TODO(igordcard): this method should be an abstract method of an
331 # abstract Classification class to be implemented by the specific
332 # Types. Also, abstract vimconnector should call the validation
333 # method before the implemented VIM connectors are called.
335 def _format_exception(self
, exception
):
336 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
337 if isinstance(exception
, (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
,
338 ConnectionError
, ksExceptions
.ConnectionError
, neExceptions
.ConnectionFailed
340 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
341 elif isinstance(exception
, (nvExceptions
.ClientException
, ksExceptions
.ClientException
,
342 neExceptions
.NeutronException
, nvExceptions
.BadRequest
)):
343 raise vimconn
.vimconnUnexpectedResponse(type(exception
).__name
__ + ": " + str(exception
))
344 elif isinstance(exception
, (neExceptions
.NetworkNotFoundClient
, nvExceptions
.NotFound
)):
345 raise vimconn
.vimconnNotFoundException(type(exception
).__name
__ + ": " + str(exception
))
346 elif isinstance(exception
, nvExceptions
.Conflict
):
347 raise vimconn
.vimconnConflictException(type(exception
).__name
__ + ": " + str(exception
))
348 elif isinstance(exception
, vimconn
.vimconnException
):
351 self
.logger
.error("General Exception " + str(exception
), exc_info
=True)
352 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
354 def get_tenant_list(self
, filter_dict
={}):
355 '''Obtain tenants of VIM
356 filter_dict can contain the following keys:
357 name: filter by tenant name
358 id: filter by tenant uuid/id
360 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
362 self
.logger
.debug("Getting tenants from VIM filter: '%s'", str(filter_dict
))
364 self
._reload
_connection
()
365 if self
.api_version3
:
366 project_class_list
= self
.keystone
.projects
.list(name
=filter_dict
.get("name"))
368 project_class_list
= self
.keystone
.tenants
.findall(**filter_dict
)
370 for project
in project_class_list
:
371 if filter_dict
.get('id') and filter_dict
["id"] != project
.id:
373 project_list
.append(project
.to_dict())
375 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
376 self
._format
_exception
(e
)
378 def new_tenant(self
, tenant_name
, tenant_description
):
379 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
380 self
.logger
.debug("Adding a new tenant name: %s", tenant_name
)
382 self
._reload
_connection
()
383 if self
.api_version3
:
384 project
= self
.keystone
.projects
.create(tenant_name
, self
.config
.get("project_domain_id", "default"),
385 description
=tenant_description
, is_domain
=False)
387 project
= self
.keystone
.tenants
.create(tenant_name
, tenant_description
)
389 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
390 self
._format
_exception
(e
)
392 def delete_tenant(self
, tenant_id
):
393 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
394 self
.logger
.debug("Deleting tenant %s from VIM", tenant_id
)
396 self
._reload
_connection
()
397 if self
.api_version3
:
398 self
.keystone
.projects
.delete(tenant_id
)
400 self
.keystone
.tenants
.delete(tenant_id
)
402 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
403 self
._format
_exception
(e
)
405 def new_network(self
,net_name
, net_type
, ip_profile
=None, shared
=False, vlan
=None):
406 '''Adds a tenant network to VIM. Returns the network identifier'''
407 self
.logger
.debug("Adding a new network to VIM name '%s', type '%s'", net_name
, net_type
)
408 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
411 self
._reload
_connection
()
412 network_dict
= {'name': net_name
, 'admin_state_up': True}
413 if net_type
=="data" or net_type
=="ptp":
414 if self
.config
.get('dataplane_physical_net') == None:
415 raise vimconn
.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
416 network_dict
["provider:physical_network"] = self
.config
['dataplane_physical_net'] #"physnet_sriov" #TODO physical
417 network_dict
["provider:network_type"] = "vlan"
419 network_dict
["provider:network_type"] = vlan
421 ####### VIO Specific Changes #########
422 if self
.vim_type
== "VIO":
424 network_dict
["provider:segmentation_id"] = vlan
426 if self
.config
.get('dataplane_net_vlan_range') is None:
427 raise vimconn
.vimconnConflictException("You must provide "\
428 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
429 "at config value before creating sriov network with vlan tag")
431 network_dict
["provider:segmentation_id"] = self
._genrate
_vlanID
()
433 network_dict
["shared"]=shared
434 new_net
=self
.neutron
.create_network({'network':network_dict
})
436 #create subnetwork, even if there is no profile
439 if 'subnet_address' not in ip_profile
:
440 #Fake subnet is required
441 subnet_rand
= random
.randint(0, 255)
442 ip_profile
['subnet_address'] = "192.168.{}.0/24".format(subnet_rand
)
443 if 'ip_version' not in ip_profile
:
444 ip_profile
['ip_version'] = "IPv4"
445 subnet
= {"name":net_name
+"-subnet",
446 "network_id": new_net
["network"]["id"],
447 "ip_version": 4 if ip_profile
['ip_version']=="IPv4" else 6,
448 "cidr": ip_profile
['subnet_address']
450 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
451 subnet
['gateway_ip'] = ip_profile
.get('gateway_address')
452 if ip_profile
.get('dns_address'):
453 subnet
['dns_nameservers'] = ip_profile
['dns_address'].split(";")
454 if 'dhcp_enabled' in ip_profile
:
455 subnet
['enable_dhcp'] = False if ip_profile
['dhcp_enabled']=="false" else True
456 if 'dhcp_start_address' in ip_profile
:
457 subnet
['allocation_pools'] = []
458 subnet
['allocation_pools'].append(dict())
459 subnet
['allocation_pools'][0]['start'] = ip_profile
['dhcp_start_address']
460 if 'dhcp_count' in ip_profile
:
461 #parts = ip_profile['dhcp_start_address'].split('.')
462 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
463 ip_int
= int(netaddr
.IPAddress(ip_profile
['dhcp_start_address']))
464 ip_int
+= ip_profile
['dhcp_count'] - 1
465 ip_str
= str(netaddr
.IPAddress(ip_int
))
466 subnet
['allocation_pools'][0]['end'] = ip_str
467 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
468 self
.neutron
.create_subnet({"subnet": subnet
} )
469 return new_net
["network"]["id"]
470 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
472 self
.neutron
.delete_network(new_net
['network']['id'])
473 self
._format
_exception
(e
)
475 def get_network_list(self
, filter_dict
={}):
476 '''Obtain tenant networks of VIM
482 admin_state_up: boolean
484 Returns the network list of dictionaries
486 self
.logger
.debug("Getting network from VIM filter: '%s'", str(filter_dict
))
488 self
._reload
_connection
()
489 if self
.api_version3
and "tenant_id" in filter_dict
:
490 filter_dict
['project_id'] = filter_dict
.pop('tenant_id') #TODO check
491 net_dict
=self
.neutron
.list_networks(**filter_dict
)
492 net_list
=net_dict
["networks"]
493 self
.__net
_os
2mano
(net_list
)
495 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
496 self
._format
_exception
(e
)
498 def get_network(self
, net_id
):
499 '''Obtain details of network from VIM
500 Returns the network information from a network id'''
501 self
.logger
.debug(" Getting tenant network %s from VIM", net_id
)
502 filter_dict
={"id": net_id
}
503 net_list
= self
.get_network_list(filter_dict
)
505 raise vimconn
.vimconnNotFoundException("Network '{}' not found".format(net_id
))
506 elif len(net_list
)>1:
507 raise vimconn
.vimconnConflictException("Found more than one network with this criteria")
510 for subnet_id
in net
.get("subnets", () ):
512 subnet
= self
.neutron
.show_subnet(subnet_id
)
513 except Exception as e
:
514 self
.logger
.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id
, str(e
)))
515 subnet
= {"id": subnet_id
, "fault": str(e
)}
516 subnets
.append(subnet
)
517 net
["subnets"] = subnets
518 net
["encapsulation"] = net
.get('provider:network_type')
519 net
["segmentation_id"] = net
.get('provider:segmentation_id')
522 def delete_network(self
, net_id
):
523 '''Deletes a tenant network from VIM. Returns the old network identifier'''
524 self
.logger
.debug("Deleting network '%s' from VIM", net_id
)
526 self
._reload
_connection
()
527 #delete VM ports attached to this networks before the network
528 ports
= self
.neutron
.list_ports(network_id
=net_id
)
529 for p
in ports
['ports']:
531 self
.neutron
.delete_port(p
["id"])
532 except Exception as e
:
533 self
.logger
.error("Error deleting port %s: %s", p
["id"], str(e
))
534 self
.neutron
.delete_network(net_id
)
536 except (neExceptions
.ConnectionFailed
, neExceptions
.NetworkNotFoundClient
, neExceptions
.NeutronException
,
537 ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
538 self
._format
_exception
(e
)
540 def refresh_nets_status(self
, net_list
):
541 '''Get the status of the networks
542 Params: the list of network identifiers
543 Returns a dictionary with:
544 net_id: #VIM id of this network
545 status: #Mandatory. Text with one of:
546 # DELETED (not found at vim)
547 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
548 # OTHER (Vim reported other status not understood)
549 # ERROR (VIM indicates an ERROR status)
550 # ACTIVE, INACTIVE, DOWN (admin down),
551 # BUILD (on building process)
553 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
554 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
558 for net_id
in net_list
:
561 net_vim
= self
.get_network(net_id
)
562 if net_vim
['status'] in netStatus2manoFormat
:
563 net
["status"] = netStatus2manoFormat
[ net_vim
['status'] ]
565 net
["status"] = "OTHER"
566 net
["error_msg"] = "VIM status reported " + net_vim
['status']
568 if net
['status'] == "ACTIVE" and not net_vim
['admin_state_up']:
569 net
['status'] = 'DOWN'
571 net
['vim_info'] = yaml
.safe_dump(net_vim
, default_flow_style
=True, width
=256)
572 except yaml
.representer
.RepresenterError
:
573 net
['vim_info'] = str(net_vim
)
574 if net_vim
.get('fault'): #TODO
575 net
['error_msg'] = str(net_vim
['fault'])
576 except vimconn
.vimconnNotFoundException
as e
:
577 self
.logger
.error("Exception getting net status: %s", str(e
))
578 net
['status'] = "DELETED"
579 net
['error_msg'] = str(e
)
580 except vimconn
.vimconnException
as e
:
581 self
.logger
.error("Exception getting net status: %s", str(e
))
582 net
['status'] = "VIM_ERROR"
583 net
['error_msg'] = str(e
)
584 net_dict
[net_id
] = net
587 def get_flavor(self
, flavor_id
):
588 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
589 self
.logger
.debug("Getting flavor '%s'", flavor_id
)
591 self
._reload
_connection
()
592 flavor
= self
.nova
.flavors
.find(id=flavor_id
)
593 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
594 return flavor
.to_dict()
595 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
596 self
._format
_exception
(e
)
598 def get_flavor_id_from_data(self
, flavor_dict
):
599 """Obtain flavor id that match the flavor description
600 Returns the flavor_id or raises a vimconnNotFoundException
601 flavor_dict: contains the required ram, vcpus, disk
602 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
603 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
604 vimconnNotFoundException is raised
606 exact_match
= False if self
.config
.get('use_existing_flavors') else True
608 self
._reload
_connection
()
609 flavor_candidate_id
= None
610 flavor_candidate_data
= (10000, 10000, 10000)
611 flavor_target
= (flavor_dict
["ram"], flavor_dict
["vcpus"], flavor_dict
["disk"])
613 numas
= flavor_dict
.get("extended", {}).get("numas")
616 raise vimconn
.vimconnNotFoundException("Flavor with EPA still not implemted")
618 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
620 # numas = extended.get("numas")
621 for flavor
in self
.nova
.flavors
.list():
622 epa
= flavor
.get_keys()
626 flavor_data
= (flavor
.ram
, flavor
.vcpus
, flavor
.disk
)
627 if flavor_data
== flavor_target
:
629 elif not exact_match
and flavor_target
< flavor_data
< flavor_candidate_data
:
630 flavor_candidate_id
= flavor
.id
631 flavor_candidate_data
= flavor_data
632 if not exact_match
and flavor_candidate_id
:
633 return flavor_candidate_id
634 raise vimconn
.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict
)))
635 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
636 self
._format
_exception
(e
)
639 def new_flavor(self
, flavor_data
, change_name_if_used
=True):
640 '''Adds a tenant flavor to openstack VIM
641 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
642 Returns the flavor identifier
644 self
.logger
.debug("Adding flavor '%s'", str(flavor_data
))
648 name
=flavor_data
['name']
649 while retry
<max_retries
:
652 self
._reload
_connection
()
653 if change_name_if_used
:
656 fl
=self
.nova
.flavors
.list()
658 fl_names
.append(f
.name
)
659 while name
in fl_names
:
661 name
= flavor_data
['name']+"-" + str(name_suffix
)
663 ram
= flavor_data
.get('ram',64)
664 vcpus
= flavor_data
.get('vcpus',1)
667 extended
= flavor_data
.get("extended")
669 numas
=extended
.get("numas")
671 numa_nodes
= len(numas
)
673 return -1, "Can not add flavor with more than one numa"
674 numa_properties
= {"hw:numa_nodes":str(numa_nodes
)}
675 numa_properties
["hw:mem_page_size"] = "large"
676 numa_properties
["hw:cpu_policy"] = "dedicated"
677 numa_properties
["hw:numa_mempolicy"] = "strict"
678 if self
.vim_type
== "VIO":
679 numa_properties
["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
680 numa_properties
["vmware:latency_sensitivity_level"] = "high"
682 #overwrite ram and vcpus
683 ram
= numa
['memory']*1024
684 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
685 if 'paired-threads' in numa
:
686 vcpus
= numa
['paired-threads']*2
687 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
688 numa_properties
["hw:cpu_thread_policy"] = "require"
689 numa_properties
["hw:cpu_policy"] = "dedicated"
690 elif 'cores' in numa
:
691 vcpus
= numa
['cores']
692 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
693 numa_properties
["hw:cpu_thread_policy"] = "isolate"
694 numa_properties
["hw:cpu_policy"] = "dedicated"
695 elif 'threads' in numa
:
696 vcpus
= numa
['threads']
697 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
698 numa_properties
["hw:cpu_thread_policy"] = "prefer"
699 numa_properties
["hw:cpu_policy"] = "dedicated"
700 # for interface in numa.get("interfaces",() ):
701 # if interface["dedicated"]=="yes":
702 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
703 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
706 new_flavor
=self
.nova
.flavors
.create(name
,
709 flavor_data
.get('disk',1),
710 is_public
=flavor_data
.get('is_public', True)
714 new_flavor
.set_keys(numa_properties
)
716 except nvExceptions
.Conflict
as e
:
717 if change_name_if_used
and retry
< max_retries
:
719 self
._format
_exception
(e
)
720 #except nvExceptions.BadRequest as e:
721 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
722 self
._format
_exception
(e
)
724 def delete_flavor(self
,flavor_id
):
725 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
728 self
._reload
_connection
()
729 self
.nova
.flavors
.delete(flavor_id
)
731 #except nvExceptions.BadRequest as e:
732 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
733 self
._format
_exception
(e
)
735 def new_image(self
,image_dict
):
737 Adds a tenant image to VIM. imge_dict is a dictionary with:
739 disk_format: qcow2, vhd, vmdk, raw (by default), ...
740 location: path or URI
741 public: "yes" or "no"
742 metadata: metadata of the image
747 while retry
<max_retries
:
750 self
._reload
_connection
()
751 #determine format http://docs.openstack.org/developer/glance/formats.html
752 if "disk_format" in image_dict
:
753 disk_format
=image_dict
["disk_format"]
754 else: #autodiscover based on extension
755 if image_dict
['location'][-6:]==".qcow2":
757 elif image_dict
['location'][-4:]==".vhd":
759 elif image_dict
['location'][-5:]==".vmdk":
761 elif image_dict
['location'][-4:]==".vdi":
763 elif image_dict
['location'][-4:]==".iso":
765 elif image_dict
['location'][-4:]==".aki":
767 elif image_dict
['location'][-4:]==".ari":
769 elif image_dict
['location'][-4:]==".ami":
773 self
.logger
.debug("new_image: '%s' loading from '%s'", image_dict
['name'], image_dict
['location'])
774 if image_dict
['location'][0:4]=="http":
775 new_image
= self
.glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
776 container_format
="bare", location
=image_dict
['location'], disk_format
=disk_format
)
778 with
open(image_dict
['location']) as fimage
:
779 new_image
= self
.glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
780 container_format
="bare", data
=fimage
, disk_format
=disk_format
)
781 #insert metadata. We cannot use 'new_image.properties.setdefault'
782 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
783 new_image_nova
=self
.nova
.images
.find(id=new_image
.id)
784 new_image_nova
.metadata
.setdefault('location',image_dict
['location'])
785 metadata_to_load
= image_dict
.get('metadata')
787 for k
,v
in yaml
.load(metadata_to_load
).iteritems():
788 new_image_nova
.metadata
.setdefault(k
,v
)
790 except (nvExceptions
.Conflict
, ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
791 self
._format
_exception
(e
)
792 except (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
793 if retry
==max_retries
:
795 self
._format
_exception
(e
)
796 except IOError as e
: #can not open the file
797 raise vimconn
.vimconnConnectionException(type(e
).__name
__ + ": " + str(e
)+ " for " + image_dict
['location'],
798 http_code
=vimconn
.HTTP_Bad_Request
)
800 def delete_image(self
, image_id
):
801 '''Deletes a tenant image from openstack VIM. Returns the old id
804 self
._reload
_connection
()
805 self
.nova
.images
.delete(image_id
)
807 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
: #TODO remove
808 self
._format
_exception
(e
)
810 def get_image_id_from_path(self
, path
):
811 '''Get the image id from image path in the VIM database. Returns the image_id'''
813 self
._reload
_connection
()
814 images
= self
.nova
.images
.list()
816 if image
.metadata
.get("location")==path
:
818 raise vimconn
.vimconnNotFoundException("image with location '{}' not found".format( path
))
819 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
820 self
._format
_exception
(e
)
822 def get_image_list(self
, filter_dict
={}):
823 '''Obtain tenant images from VIM
827 checksum: image checksum
828 Returns the image list of dictionaries:
829 [{<the fields at Filter_dict plus some VIM specific>}, ...]
832 self
.logger
.debug("Getting image list from VIM filter: '%s'", str(filter_dict
))
834 self
._reload
_connection
()
835 filter_dict_os
=filter_dict
.copy()
836 #First we filter by the available filter fields: name, id. The others are removed.
837 filter_dict_os
.pop('checksum',None)
838 image_list
=self
.nova
.images
.findall(**filter_dict_os
)
839 if len(image_list
)==0:
841 #Then we filter by the rest of filter fields: checksum
843 for image
in image_list
:
844 image_class
=self
.glance
.images
.get(image
.id)
845 if 'checksum' not in filter_dict
or image_class
['checksum']==filter_dict
.get('checksum'):
846 filtered_list
.append(image_class
.copy())
848 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
849 self
._format
_exception
(e
)
852 def _create_mimemultipart(content_list
):
853 """Creates a MIMEmultipart text combining the content_list
854 :param content_list: list of text scripts to be combined
855 :return: str of the created MIMEmultipart. If the list is empty returns None, if the list contains only one
856 element MIMEmultipart is not created and this content is returned
860 elif len(content_list
) == 1:
861 return content_list
[0]
862 combined_message
= MIMEMultipart()
863 for content
in content_list
:
864 if content
.startswith('#include'):
865 format
= 'text/x-include-url'
866 elif content
.startswith('#include-once'):
867 format
= 'text/x-include-once-url'
868 elif content
.startswith('#!'):
869 format
= 'text/x-shellscript'
870 elif content
.startswith('#cloud-config'):
871 format
= 'text/cloud-config'
872 elif content
.startswith('#cloud-config-archive'):
873 format
= 'text/cloud-config-archive'
874 elif content
.startswith('#upstart-job'):
875 format
= 'text/upstart-job'
876 elif content
.startswith('#part-handler'):
877 format
= 'text/part-handler'
878 elif content
.startswith('#cloud-boothook'):
879 format
= 'text/cloud-boothook'
881 format
= 'text/x-shellscript'
882 sub_message
= MIMEText(content
, format
, sys
.getdefaultencoding())
883 combined_message
.attach(sub_message
)
884 return combined_message
.as_string()
886 def __wait_for_vm(self
, vm_id
, status
):
887 """wait until vm is in the desired status and return True.
888 If the VM gets in ERROR status, return false.
889 If the timeout is reached generate an exception"""
891 while elapsed_time
< server_timeout
:
892 vm_status
= self
.nova
.servers
.get(vm_id
).status
893 if vm_status
== status
:
895 if vm_status
== 'ERROR':
900 # if we exceeded the timeout rollback
901 if elapsed_time
>= server_timeout
:
902 raise vimconn
.vimconnException('Timeout waiting for instance ' + vm_id
+ ' to get ' + status
,
903 http_code
=vimconn
.HTTP_Request_Timeout
)
905 def _get_openstack_availablity_zones(self
):
907 Get from openstack availability zones available
911 openstack_availability_zone
= self
.nova
.availability_zones
.list()
912 openstack_availability_zone
= [str(zone
.zoneName
) for zone
in openstack_availability_zone
913 if zone
.zoneName
!= 'internal']
914 return openstack_availability_zone
915 except Exception as e
:
918 def _set_availablity_zones(self
):
920 Set vim availablity zone
924 if 'availability_zone' in self
.config
:
925 vim_availability_zones
= self
.config
.get('availability_zone')
926 if isinstance(vim_availability_zones
, str):
927 self
.availability_zone
= [vim_availability_zones
]
928 elif isinstance(vim_availability_zones
, list):
929 self
.availability_zone
= vim_availability_zones
931 self
.availability_zone
= self
._get
_openstack
_availablity
_zones
()
933 def _get_vm_availability_zone(self
, availability_zone_index
, availability_zone_list
):
935 Return thge availability zone to be used by the created VM.
936 :return: The VIM availability zone to be used or None
938 if availability_zone_index
is None:
939 if not self
.config
.get('availability_zone'):
941 elif isinstance(self
.config
.get('availability_zone'), str):
942 return self
.config
['availability_zone']
944 # TODO consider using a different parameter at config for default AV and AV list match
945 return self
.config
['availability_zone'][0]
947 vim_availability_zones
= self
.availability_zone
948 # check if VIM offer enough availability zones describe in the VNFD
949 if vim_availability_zones
and len(availability_zone_list
) <= len(vim_availability_zones
):
950 # check if all the names of NFV AV match VIM AV names
951 match_by_index
= False
952 for av
in availability_zone_list
:
953 if av
not in vim_availability_zones
:
954 match_by_index
= True
957 return vim_availability_zones
[availability_zone_index
]
959 return availability_zone_list
[availability_zone_index
]
961 raise vimconn
.vimconnConflictException("No enough availability zones at VIM for this deployment")
963 def new_vminstance(self
, name
, description
, start
, image_id
, flavor_id
, net_list
, cloud_config
=None, disk_list
=None,
964 availability_zone_index
=None, availability_zone_list
=None):
965 '''Adds a VM instance to VIM
967 start: indicates if VM must start or boot in pause mode. Ignored
968 image_id,flavor_id: iamge and flavor uuid
969 net_list: list of interfaces, each one is a dictionary with:
971 net_id: network uuid to connect
972 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
973 model: interface model, ignored #TODO
974 mac_address: used for SR-IOV ifaces #TODO for other types
975 use: 'data', 'bridge', 'mgmt'
976 type: 'virtual', 'PF', 'VF', 'VFnotShared'
977 vim_id: filled/added by this function
978 floating_ip: True/False (or it can be None)
979 'cloud_config': (optional) dictionary with:
980 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
981 'users': (optional) list of users to be inserted, each item is a dict with:
982 'name': (mandatory) user name,
983 'key-pairs': (optional) list of strings with the public key to be inserted to the user
984 'user-data': (optional) string is a text script to be passed directly to cloud-init
985 'config-files': (optional). List of files to be transferred. Each item is a dict with:
986 'dest': (mandatory) string with the destination absolute path
987 'encoding': (optional, by default text). Can be one of:
988 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
989 'content' (mandatory): string with the content of the file
990 'permissions': (optional) string with file permissions, typically octal notation '0644'
991 'owner': (optional) file owner, string with the format 'owner:group'
992 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
993 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
994 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
995 'size': (mandatory) string with the size of the disk in GB
996 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
997 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
998 availability_zone_index is None
999 #TODO ip, security groups
1000 Returns the instance identifier
1002 self
.logger
.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id
, flavor_id
,str(net_list
))
1007 external_network
=[] # list of external networks to be connected to instance, later on used to create floating_ip
1008 no_secured_ports
= [] # List of port-is with port-security disabled
1009 self
._reload
_connection
()
1010 metadata_vpci
={} # For a specific neutron plugin
1011 block_device_mapping
= None
1012 for net
in net_list
:
1013 if not net
.get("net_id"): #skip non connected iface
1017 "network_id": net
["net_id"],
1018 "name": net
.get("name"),
1019 "admin_state_up": True
1021 if net
["type"]=="virtual":
1023 metadata_vpci
[ net
["net_id"] ] = [[ net
["vpci"], "" ]]
1024 elif net
["type"]=="VF": # for VF
1026 if "VF" not in metadata_vpci
:
1027 metadata_vpci
["VF"]=[]
1028 metadata_vpci
["VF"].append([ net
["vpci"], "" ])
1029 port_dict
["binding:vnic_type"]="direct"
1030 ########## VIO specific Changes #######
1031 if self
.vim_type
== "VIO":
1032 #Need to create port with port_security_enabled = False and no-security-groups
1033 port_dict
["port_security_enabled"]=False
1034 port_dict
["provider_security_groups"]=[]
1035 port_dict
["security_groups"]=[]
1037 ########## VIO specific Changes #######
1038 #Current VIO release does not support port with type 'direct-physical'
1039 #So no need to create virtual port in case of PCI-device.
1040 #Will update port_dict code when support gets added in next VIO release
1041 if self
.vim_type
== "VIO":
1042 raise vimconn
.vimconnNotSupportedException("Current VIO release does not support full passthrough (PT)")
1044 if "PF" not in metadata_vpci
:
1045 metadata_vpci
["PF"]=[]
1046 metadata_vpci
["PF"].append([ net
["vpci"], "" ])
1047 port_dict
["binding:vnic_type"]="direct-physical"
1048 if not port_dict
["name"]:
1049 port_dict
["name"]=name
1050 if net
.get("mac_address"):
1051 port_dict
["mac_address"]=net
["mac_address"]
1052 new_port
= self
.neutron
.create_port({"port": port_dict
})
1053 net
["mac_adress"] = new_port
["port"]["mac_address"]
1054 net
["vim_id"] = new_port
["port"]["id"]
1055 # if try to use a network without subnetwork, it will return a emtpy list
1056 fixed_ips
= new_port
["port"].get("fixed_ips")
1058 net
["ip"] = fixed_ips
[0].get("ip_address")
1062 port
= {"port-id": new_port
["port"]["id"]}
1063 if float(self
.nova
.api_version
.get_string()) >= 2.32:
1064 port
["tag"] = new_port
["port"]["name"]
1065 net_list_vim
.append(port
)
1067 if net
.get('floating_ip', False):
1068 net
['exit_on_floating_ip_error'] = True
1069 external_network
.append(net
)
1070 elif net
['use'] == 'mgmt' and self
.config
.get('use_floating_ip'):
1071 net
['exit_on_floating_ip_error'] = False
1072 external_network
.append(net
)
1074 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1075 # As a workaround we wait until the VM is active and then disable the port-security
1076 if net
.get("port_security") == False:
1077 no_secured_ports
.append(new_port
["port"]["id"])
1080 metadata
= {"pci_assignement": json
.dumps(metadata_vpci
)}
1081 if len(metadata
["pci_assignement"]) >255:
1082 #limit the metadata size
1083 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1084 self
.logger
.warn("Metadata deleted since it exceeds the expected length (255) ")
1087 self
.logger
.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
1088 name
, image_id
, flavor_id
, str(net_list_vim
), description
, str(metadata
))
1090 security_groups
= self
.config
.get('security_groups')
1091 if type(security_groups
) is str:
1092 security_groups
= ( security_groups
, )
1097 if isinstance(cloud_config
, dict):
1098 if cloud_config
.get("user-data"):
1099 if isinstance(cloud_config
["user-data"], str):
1100 userdata_list
.append(cloud_config
["user-data"])
1102 for u
in cloud_config
["user-data"]:
1103 userdata_list
.append(u
)
1104 if cloud_config
.get("boot-data-drive") != None:
1105 config_drive
= cloud_config
["boot-data-drive"]
1106 if cloud_config
.get("config-files") or cloud_config
.get("users") or cloud_config
.get("key-pairs"):
1109 if cloud_config
.get("key-pairs"):
1110 userdata_dict
["ssh-authorized-keys"] = cloud_config
["key-pairs"]
1111 userdata_dict
["users"] = [{"default": None, "ssh-authorized-keys": cloud_config
["key-pairs"] }]
1112 if cloud_config
.get("users"):
1113 if "users" not in userdata_dict
:
1114 userdata_dict
["users"] = [ "default" ]
1115 for user
in cloud_config
["users"]:
1117 "name" : user
["name"],
1118 "sudo": "ALL = (ALL)NOPASSWD:ALL"
1120 if "user-info" in user
:
1121 user_info
["gecos"] = user
["user-info"]
1122 if user
.get("key-pairs"):
1123 user_info
["ssh-authorized-keys"] = user
["key-pairs"]
1124 userdata_dict
["users"].append(user_info
)
1126 if cloud_config
.get("config-files"):
1127 userdata_dict
["write_files"] = []
1128 for file in cloud_config
["config-files"]:
1130 "path" : file["dest"],
1131 "content": file["content"]
1133 if file.get("encoding"):
1134 file_info
["encoding"] = file["encoding"]
1135 if file.get("permissions"):
1136 file_info
["permissions"] = file["permissions"]
1137 if file.get("owner"):
1138 file_info
["owner"] = file["owner"]
1139 userdata_dict
["write_files"].append(file_info
)
1140 userdata_list
.append("#cloud-config\n" + yaml
.safe_dump(userdata_dict
, indent
=4,
1141 default_flow_style
=False))
1142 userdata
= self
._create
_mimemultipart
(userdata_list
)
1143 self
.logger
.debug("userdata: %s", userdata
)
1144 elif isinstance(cloud_config
, str):
1145 userdata
= cloud_config
1147 #Create additional volumes in case these are present in disk_list
1148 base_disk_index
= ord('b')
1149 if disk_list
!= None:
1150 block_device_mapping
= {}
1151 for disk
in disk_list
:
1152 if 'image_id' in disk
:
1153 volume
= self
.cinder
.volumes
.create(size
= disk
['size'],name
= name
+ '_vd' +
1154 chr(base_disk_index
), imageRef
= disk
['image_id'])
1156 volume
= self
.cinder
.volumes
.create(size
=disk
['size'], name
=name
+ '_vd' +
1157 chr(base_disk_index
))
1158 block_device_mapping
['_vd' + chr(base_disk_index
)] = volume
.id
1159 base_disk_index
+= 1
1161 #wait until volumes are with status available
1164 while keep_waiting
and elapsed_time
< volume_timeout
:
1165 keep_waiting
= False
1166 for volume_id
in block_device_mapping
.itervalues():
1167 if self
.cinder
.volumes
.get(volume_id
).status
!= 'available':
1173 #if we exceeded the timeout rollback
1174 if elapsed_time
>= volume_timeout
:
1175 #delete the volumes we just created
1176 for volume_id
in block_device_mapping
.itervalues():
1177 self
.cinder
.volumes
.delete(volume_id
)
1179 #delete ports we just created
1180 for net_item
in net_list_vim
:
1181 if 'port-id' in net_item
:
1182 self
.neutron
.delete_port(net_item
['port-id'])
1184 raise vimconn
.vimconnException('Timeout creating volumes for instance ' + name
,
1185 http_code
=vimconn
.HTTP_Request_Timeout
)
1186 # get availability Zone
1187 vm_av_zone
= self
._get
_vm
_availability
_zone
(availability_zone_index
, availability_zone_list
)
1189 self
.logger
.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}, "
1190 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
1191 "block_device_mapping={})".format(name
, image_id
, flavor_id
, net_list_vim
, metadata
,
1192 security_groups
, vm_av_zone
, self
.config
.get('keypair'),
1193 userdata
, config_drive
, block_device_mapping
))
1194 server
= self
.nova
.servers
.create(name
, image_id
, flavor_id
, nics
=net_list_vim
, meta
=metadata
,
1195 security_groups
=security_groups
,
1196 availability_zone
=vm_av_zone
,
1197 key_name
=self
.config
.get('keypair'),
1199 config_drive
=config_drive
,
1200 block_device_mapping
=block_device_mapping
1201 ) # , description=description)
1203 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1204 if no_secured_ports
:
1205 self
.__wait
_for
_vm
(server
.id, 'ACTIVE')
1207 for port_id
in no_secured_ports
:
1209 self
.neutron
.update_port(port_id
, {"port": {"port_security_enabled": False, "security_groups": None} })
1211 except Exception as e
:
1212 self
.logger
.error("It was not possible to disable port security for port {}".format(port_id
))
1213 self
.delete_vminstance(server
.id)
1216 #print "DONE :-)", server
1218 floating_ips
= self
.neutron
.list_floatingips().get("floatingips", ())
1220 if external_network
:
1221 self
.__wait
_for
_vm
(server
.id, 'ACTIVE')
1223 for floating_network
in external_network
:
1226 while(assigned
== False):
1228 ip
= floating_ips
.pop(0)
1229 if not ip
.get("port_id", False) and ip
.get('tenant_id') == server
.tenant_id
:
1230 free_floating_ip
= ip
.get("floating_ip_address")
1232 fix_ip
= floating_network
.get('ip')
1233 server
.add_floating_ip(free_floating_ip
, fix_ip
)
1235 except Exception as e
:
1236 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot create floating_ip "+ str(e
), http_code
=vimconn
.HTTP_Conflict
)
1238 #Find the external network
1239 external_nets
= list()
1240 for net
in self
.neutron
.list_networks()['networks']:
1241 if net
['router:external']:
1242 external_nets
.append(net
)
1244 if len(external_nets
) == 0:
1245 raise vimconn
.vimconnException("Cannot create floating_ip automatically since no external "
1246 "network is present",
1247 http_code
=vimconn
.HTTP_Conflict
)
1248 if len(external_nets
) > 1:
1249 raise vimconn
.vimconnException("Cannot create floating_ip automatically since multiple "
1250 "external networks are present",
1251 http_code
=vimconn
.HTTP_Conflict
)
1253 pool_id
= external_nets
[0].get('id')
1254 param
= {'floatingip': {'floating_network_id': pool_id
, 'tenant_id': server
.tenant_id
}}
1256 #self.logger.debug("Creating floating IP")
1257 new_floating_ip
= self
.neutron
.create_floatingip(param
)
1258 free_floating_ip
= new_floating_ip
['floatingip']['floating_ip_address']
1259 fix_ip
= floating_network
.get('ip')
1260 server
.add_floating_ip(free_floating_ip
, fix_ip
)
1262 except Exception as e
:
1263 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot assign floating_ip "+ str(e
), http_code
=vimconn
.HTTP_Conflict
)
1264 except Exception as e
:
1265 if not floating_network
['exit_on_floating_ip_error']:
1266 self
.logger
.warn("Cannot create floating_ip. %s", str(e
))
1271 # except nvExceptions.NotFound as e:
1272 # error_value=-vimconn.HTTP_Not_Found
1273 # error_text= "vm instance %s not found" % vm_id
1274 # except TypeError as e:
1275 # raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1277 except Exception as e
:
1278 # delete the volumes we just created
1279 if block_device_mapping
:
1280 for volume_id
in block_device_mapping
.itervalues():
1281 self
.cinder
.volumes
.delete(volume_id
)
1285 self
.delete_vminstance(server
.id)
1287 # delete ports we just created
1288 for net_item
in net_list_vim
:
1289 if 'port-id' in net_item
:
1290 self
.neutron
.delete_port(net_item
['port-id'])
1292 self
._format
_exception
(e
)
1294 def get_vminstance(self
,vm_id
):
1295 '''Returns the VM instance information from VIM'''
1296 #self.logger.debug("Getting VM from VIM")
1298 self
._reload
_connection
()
1299 server
= self
.nova
.servers
.find(id=vm_id
)
1300 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
1301 return server
.to_dict()
1302 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1303 self
._format
_exception
(e
)
1305 def get_vminstance_console(self
,vm_id
, console_type
="vnc"):
1307 Get a console for the virtual machine
1309 vm_id: uuid of the VM
1310 console_type, can be:
1311 "novnc" (by default), "xvpvnc" for VNC types,
1312 "rdp-html5" for RDP types, "spice-html5" for SPICE types
1313 Returns dict with the console parameters:
1314 protocol: ssh, ftp, http, https, ...
1315 server: usually ip address
1316 port: the http, ssh, ... port
1317 suffix: extra text, e.g. the http path and query string
1319 self
.logger
.debug("Getting VM CONSOLE from VIM")
1321 self
._reload
_connection
()
1322 server
= self
.nova
.servers
.find(id=vm_id
)
1323 if console_type
== None or console_type
== "novnc":
1324 console_dict
= server
.get_vnc_console("novnc")
1325 elif console_type
== "xvpvnc":
1326 console_dict
= server
.get_vnc_console(console_type
)
1327 elif console_type
== "rdp-html5":
1328 console_dict
= server
.get_rdp_console(console_type
)
1329 elif console_type
== "spice-html5":
1330 console_dict
= server
.get_spice_console(console_type
)
1332 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
), http_code
=vimconn
.HTTP_Bad_Request
)
1334 console_dict1
= console_dict
.get("console")
1336 console_url
= console_dict1
.get("url")
1339 protocol_index
= console_url
.find("//")
1340 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1341 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1342 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1343 return -vimconn
.HTTP_Internal_Server_Error
, "Unexpected response from VIM"
1344 console_dict
={"protocol": console_url
[0:protocol_index
],
1345 "server": console_url
[protocol_index
+2:port_index
],
1346 "port": console_url
[port_index
:suffix_index
],
1347 "suffix": console_url
[suffix_index
+1:]
1351 raise vimconn
.vimconnUnexpectedResponse("Unexpected response from VIM")
1353 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.BadRequest
, ConnectionError
) as e
:
1354 self
._format
_exception
(e
)
1356 def delete_vminstance(self
, vm_id
):
1357 '''Removes a VM instance from VIM. Returns the old identifier
1359 #print "osconnector: Getting VM from VIM"
1361 self
._reload
_connection
()
1362 #delete VM ports attached to this networks before the virtual machine
1363 ports
= self
.neutron
.list_ports(device_id
=vm_id
)
1364 for p
in ports
['ports']:
1366 self
.neutron
.delete_port(p
["id"])
1367 except Exception as e
:
1368 self
.logger
.error("Error deleting port: " + type(e
).__name
__ + ": "+ str(e
))
1370 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1371 #dettach volumes attached
1372 server
= self
.nova
.servers
.get(vm_id
)
1373 volumes_attached_dict
= server
._info
['os-extended-volumes:volumes_attached']
1374 #for volume in volumes_attached_dict:
1375 # self.cinder.volumes.detach(volume['id'])
1377 self
.nova
.servers
.delete(vm_id
)
1380 #Although having detached them should have them in active status
1381 #we ensure in this loop
1384 while keep_waiting
and elapsed_time
< volume_timeout
:
1385 keep_waiting
= False
1386 for volume
in volumes_attached_dict
:
1387 if self
.cinder
.volumes
.get(volume
['id']).status
!= 'available':
1390 self
.cinder
.volumes
.delete(volume
['id'])
1396 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
1397 self
._format
_exception
(e
)
1398 #TODO insert exception vimconn.HTTP_Unauthorized
1399 #if reaching here is because an exception
1401 def refresh_vms_status(self
, vm_list
):
1402 '''Get the status of the virtual machines and their interfaces/ports
1403 Params: the list of VM identifiers
1404 Returns a dictionary with:
1405 vm_id: #VIM id of this Virtual Machine
1406 status: #Mandatory. Text with one of:
1407 # DELETED (not found at vim)
1408 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1409 # OTHER (Vim reported other status not understood)
1410 # ERROR (VIM indicates an ERROR status)
1411 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1412 # CREATING (on building process), ERROR
1413 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1415 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1416 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1418 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1419 mac_address: #Text format XX:XX:XX:XX:XX:XX
1420 vim_net_id: #network id where this interface is connected
1421 vim_interface_id: #interface/port VIM id
1422 ip_address: #null, or text with IPv4, IPv6 address
1423 compute_node: #identification of compute node where PF,VF interface is allocated
1424 pci: #PCI address of the NIC that hosts the PF,VF
1425 vlan: #physical VLAN used for VF
1428 self
.logger
.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1429 for vm_id
in vm_list
:
1432 vm_vim
= self
.get_vminstance(vm_id
)
1433 if vm_vim
['status'] in vmStatus2manoFormat
:
1434 vm
['status'] = vmStatus2manoFormat
[ vm_vim
['status'] ]
1436 vm
['status'] = "OTHER"
1437 vm
['error_msg'] = "VIM status reported " + vm_vim
['status']
1439 vm
['vim_info'] = yaml
.safe_dump(vm_vim
, default_flow_style
=True, width
=256)
1440 except yaml
.representer
.RepresenterError
:
1441 vm
['vim_info'] = str(vm_vim
)
1442 vm
["interfaces"] = []
1443 if vm_vim
.get('fault'):
1444 vm
['error_msg'] = str(vm_vim
['fault'])
1447 self
._reload
_connection
()
1448 port_dict
=self
.neutron
.list_ports(device_id
=vm_id
)
1449 for port
in port_dict
["ports"]:
1452 interface
['vim_info'] = yaml
.safe_dump(port
, default_flow_style
=True, width
=256)
1453 except yaml
.representer
.RepresenterError
:
1454 interface
['vim_info'] = str(port
)
1455 interface
["mac_address"] = port
.get("mac_address")
1456 interface
["vim_net_id"] = port
["network_id"]
1457 interface
["vim_interface_id"] = port
["id"]
1458 # check if OS-EXT-SRV-ATTR:host is there,
1459 # in case of non-admin credentials, it will be missing
1460 if vm_vim
.get('OS-EXT-SRV-ATTR:host'):
1461 interface
["compute_node"] = vm_vim
['OS-EXT-SRV-ATTR:host']
1462 interface
["pci"] = None
1464 # check if binding:profile is there,
1465 # in case of non-admin credentials, it will be missing
1466 if port
.get('binding:profile'):
1467 if port
['binding:profile'].get('pci_slot'):
1468 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1469 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1470 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1471 pci
= port
['binding:profile']['pci_slot']
1472 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1473 interface
["pci"] = pci
1474 interface
["vlan"] = None
1475 #if network is of type vlan and port is of type direct (sr-iov) then set vlan id
1476 network
= self
.neutron
.show_network(port
["network_id"])
1477 if network
['network'].get('provider:network_type') == 'vlan' and \
1478 port
.get("binding:vnic_type") == "direct":
1479 interface
["vlan"] = network
['network'].get('provider:segmentation_id')
1481 #look for floating ip address
1482 floating_ip_dict
= self
.neutron
.list_floatingips(port_id
=port
["id"])
1483 if floating_ip_dict
.get("floatingips"):
1484 ips
.append(floating_ip_dict
["floatingips"][0].get("floating_ip_address") )
1486 for subnet
in port
["fixed_ips"]:
1487 ips
.append(subnet
["ip_address"])
1488 interface
["ip_address"] = ";".join(ips
)
1489 vm
["interfaces"].append(interface
)
1490 except Exception as e
:
1491 self
.logger
.error("Error getting vm interface information " + type(e
).__name
__ + ": "+ str(e
))
1492 except vimconn
.vimconnNotFoundException
as e
:
1493 self
.logger
.error("Exception getting vm status: %s", str(e
))
1494 vm
['status'] = "DELETED"
1495 vm
['error_msg'] = str(e
)
1496 except vimconn
.vimconnException
as e
:
1497 self
.logger
.error("Exception getting vm status: %s", str(e
))
1498 vm
['status'] = "VIM_ERROR"
1499 vm
['error_msg'] = str(e
)
1503 def action_vminstance(self
, vm_id
, action_dict
):
1504 '''Send and action over a VM instance from VIM
1505 Returns the vm_id if the action was successfully sent to the VIM'''
1506 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1508 self
._reload
_connection
()
1509 server
= self
.nova
.servers
.find(id=vm_id
)
1510 if "start" in action_dict
:
1511 if action_dict
["start"]=="rebuild":
1514 if server
.status
=="PAUSED":
1516 elif server
.status
=="SUSPENDED":
1518 elif server
.status
=="SHUTOFF":
1520 elif "pause" in action_dict
:
1522 elif "resume" in action_dict
:
1524 elif "shutoff" in action_dict
or "shutdown" in action_dict
:
1526 elif "forceOff" in action_dict
:
1528 elif "terminate" in action_dict
:
1530 elif "createImage" in action_dict
:
1531 server
.create_image()
1532 #"path":path_schema,
1533 #"description":description_schema,
1534 #"name":name_schema,
1535 #"metadata":metadata_schema,
1536 #"imageRef": id_schema,
1537 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1538 elif "rebuild" in action_dict
:
1539 server
.rebuild(server
.image
['id'])
1540 elif "reboot" in action_dict
:
1541 server
.reboot() #reboot_type='SOFT'
1542 elif "console" in action_dict
:
1543 console_type
= action_dict
["console"]
1544 if console_type
== None or console_type
== "novnc":
1545 console_dict
= server
.get_vnc_console("novnc")
1546 elif console_type
== "xvpvnc":
1547 console_dict
= server
.get_vnc_console(console_type
)
1548 elif console_type
== "rdp-html5":
1549 console_dict
= server
.get_rdp_console(console_type
)
1550 elif console_type
== "spice-html5":
1551 console_dict
= server
.get_spice_console(console_type
)
1553 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
),
1554 http_code
=vimconn
.HTTP_Bad_Request
)
1556 console_url
= console_dict
["console"]["url"]
1558 protocol_index
= console_url
.find("//")
1559 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1560 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1561 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1562 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1563 console_dict2
={"protocol": console_url
[0:protocol_index
],
1564 "server": console_url
[protocol_index
+2 : port_index
],
1565 "port": int(console_url
[port_index
+1 : suffix_index
]),
1566 "suffix": console_url
[suffix_index
+1:]
1568 return console_dict2
1569 except Exception as e
:
1570 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1573 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1574 self
._format
_exception
(e
)
1575 #TODO insert exception vimconn.HTTP_Unauthorized
1577 ####### VIO Specific Changes #########
1578 def _genrate_vlanID(self
):
1580 Method to get unused vlanID
1588 networks
= self
.get_network_list()
1589 for net
in networks
:
1590 if net
.get('provider:segmentation_id'):
1591 usedVlanIDs
.append(net
.get('provider:segmentation_id'))
1592 used_vlanIDs
= set(usedVlanIDs
)
1594 #find unused VLAN ID
1595 for vlanID_range
in self
.config
.get('dataplane_net_vlan_range'):
1597 start_vlanid
, end_vlanid
= map(int, vlanID_range
.replace(" ", "").split("-"))
1598 for vlanID
in xrange(start_vlanid
, end_vlanid
+ 1):
1599 if vlanID
not in used_vlanIDs
:
1601 except Exception as exp
:
1602 raise vimconn
.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp
))
1604 raise vimconn
.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1605 " All given Vlan IDs {} are in use.".format(self
.config
.get('dataplane_net_vlan_range')))
1608 def _validate_vlan_ranges(self
, dataplane_net_vlan_range
):
1610 Method to validate user given vlanID ranges
1614 for vlanID_range
in dataplane_net_vlan_range
:
1615 vlan_range
= vlanID_range
.replace(" ", "")
1617 vlanID_pattern
= r
'(\d)*-(\d)*$'
1618 match_obj
= re
.match(vlanID_pattern
, vlan_range
)
1620 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1621 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range
))
1623 start_vlanid
, end_vlanid
= map(int,vlan_range
.split("-"))
1624 if start_vlanid
<= 0 :
1625 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1626 "Start ID can not be zero. For VLAN "\
1627 "networks valid IDs are 1 to 4094 ".format(vlanID_range
))
1628 if end_vlanid
> 4094 :
1629 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1630 "End VLAN ID can not be greater than 4094. For VLAN "\
1631 "networks valid IDs are 1 to 4094 ".format(vlanID_range
))
1633 if start_vlanid
> end_vlanid
:
1634 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1635 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1636 "start_ID < end_ID ".format(vlanID_range
))
1640 def new_external_port(self
, port_data
):
1641 #TODO openstack if needed
1642 '''Adds a external port to VIM'''
1643 '''Returns the port identifier'''
1644 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.new_external_port() not implemented"
1646 def connect_port_network(self
, port_id
, network_id
, admin
=False):
1647 #TODO openstack if needed
1648 '''Connects a external port to a network'''
1649 '''Returns status code of the VIM response'''
1650 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.connect_port_network() not implemented"
1652 def new_user(self
, user_name
, user_passwd
, tenant_id
=None):
1653 '''Adds a new user to openstack VIM'''
1654 '''Returns the user identifier'''
1655 self
.logger
.debug("osconnector: Adding a new user to VIM")
1657 self
._reload
_connection
()
1658 user
=self
.keystone
.users
.create(user_name
, user_passwd
, tenant_id
=tenant_id
)
1659 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1661 except ksExceptions
.ConnectionError
as e
:
1662 error_value
=-vimconn
.HTTP_Bad_Request
1663 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1664 except ksExceptions
.ClientException
as e
: #TODO remove
1665 error_value
=-vimconn
.HTTP_Bad_Request
1666 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1667 #TODO insert exception vimconn.HTTP_Unauthorized
1668 #if reaching here is because an exception
1670 self
.logger
.debug("new_user " + error_text
)
1671 return error_value
, error_text
1673 def delete_user(self
, user_id
):
1674 '''Delete a user from openstack VIM'''
1675 '''Returns the user identifier'''
1677 print("osconnector: Deleting a user from VIM")
1679 self
._reload
_connection
()
1680 self
.keystone
.users
.delete(user_id
)
1682 except ksExceptions
.ConnectionError
as e
:
1683 error_value
=-vimconn
.HTTP_Bad_Request
1684 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1685 except ksExceptions
.NotFound
as e
:
1686 error_value
=-vimconn
.HTTP_Not_Found
1687 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1688 except ksExceptions
.ClientException
as e
: #TODO remove
1689 error_value
=-vimconn
.HTTP_Bad_Request
1690 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1691 #TODO insert exception vimconn.HTTP_Unauthorized
1692 #if reaching here is because an exception
1694 print("delete_tenant " + error_text
)
1695 return error_value
, error_text
1697 def get_hosts_info(self
):
1698 '''Get the information of deployed hosts
1699 Returns the hosts content'''
1701 print("osconnector: Getting Host info from VIM")
1704 self
._reload
_connection
()
1705 hypervisors
= self
.nova
.hypervisors
.list()
1706 for hype
in hypervisors
:
1707 h_list
.append( hype
.to_dict() )
1708 return 1, {"hosts":h_list
}
1709 except nvExceptions
.NotFound
as e
:
1710 error_value
=-vimconn
.HTTP_Not_Found
1711 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1712 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1713 error_value
=-vimconn
.HTTP_Bad_Request
1714 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1715 #TODO insert exception vimconn.HTTP_Unauthorized
1716 #if reaching here is because an exception
1718 print("get_hosts_info " + error_text
)
1719 return error_value
, error_text
1721 def get_hosts(self
, vim_tenant
):
1722 '''Get the hosts and deployed instances
1723 Returns the hosts content'''
1724 r
, hype_dict
= self
.get_hosts_info()
1727 hypervisors
= hype_dict
["hosts"]
1729 servers
= self
.nova
.servers
.list()
1730 for hype
in hypervisors
:
1731 for server
in servers
:
1732 if server
.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype
['hypervisor_hostname']:
1734 hype
['vm'].append(server
.id)
1736 hype
['vm'] = [server
.id]
1738 except nvExceptions
.NotFound
as e
:
1739 error_value
=-vimconn
.HTTP_Not_Found
1740 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1741 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1742 error_value
=-vimconn
.HTTP_Bad_Request
1743 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1744 #TODO insert exception vimconn.HTTP_Unauthorized
1745 #if reaching here is because an exception
1747 print("get_hosts " + error_text
)
1748 return error_value
, error_text
1750 def new_classification(self
, name
, ctype
, definition
):
1752 'Adding a new (Traffic) Classification to VIM, named %s', name
)
1755 self
._reload
_connection
()
1756 if ctype
not in supportedClassificationTypes
:
1757 raise vimconn
.vimconnNotSupportedException(
1758 'OpenStack VIM connector doesn\'t support provided '
1759 'Classification Type {}, supported ones are: '
1760 '{}'.format(ctype
, supportedClassificationTypes
))
1761 if not self
._validate
_classification
(ctype
, definition
):
1762 raise vimconn
.vimconnException(
1763 'Incorrect Classification definition '
1764 'for the type specified.')
1765 classification_dict
= definition
1766 classification_dict
['name'] = name
1768 new_class
= self
.neutron
.create_flow_classifier(
1769 {'flow_classifier': classification_dict
})
1770 return new_class
['flow_classifier']['id']
1771 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1772 neExceptions
.NeutronException
, ConnectionError
) as e
:
1774 'Creation of Classification failed.')
1775 self
._format
_exception
(e
)
1777 def get_classification(self
, class_id
):
1778 self
.logger
.debug(" Getting Classification %s from VIM", class_id
)
1779 filter_dict
= {"id": class_id
}
1780 class_list
= self
.get_classification_list(filter_dict
)
1781 if len(class_list
) == 0:
1782 raise vimconn
.vimconnNotFoundException(
1783 "Classification '{}' not found".format(class_id
))
1784 elif len(class_list
) > 1:
1785 raise vimconn
.vimconnConflictException(
1786 "Found more than one Classification with this criteria")
1787 classification
= class_list
[0]
1788 return classification
1790 def get_classification_list(self
, filter_dict
={}):
1791 self
.logger
.debug("Getting Classifications from VIM filter: '%s'",
1794 self
._reload
_connection
()
1795 if self
.api_version3
and "tenant_id" in filter_dict
:
1796 filter_dict
['project_id'] = filter_dict
.pop('tenant_id')
1797 classification_dict
= self
.neutron
.list_flow_classifier(
1799 classification_list
= classification_dict
["flow_classifiers"]
1800 self
.__classification
_os
2mano
(classification_list
)
1801 return classification_list
1802 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1803 neExceptions
.NeutronException
, ConnectionError
) as e
:
1804 self
._format
_exception
(e
)
1806 def delete_classification(self
, class_id
):
1807 self
.logger
.debug("Deleting Classification '%s' from VIM", class_id
)
1809 self
._reload
_connection
()
1810 self
.neutron
.delete_flow_classifier(class_id
)
1812 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1813 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1814 ConnectionError
) as e
:
1815 self
._format
_exception
(e
)
1817 def new_sfi(self
, name
, ingress_ports
, egress_ports
, sfc_encap
=True):
1819 "Adding a new Service Function Instance to VIM, named '%s'", name
)
1822 self
._reload
_connection
()
1825 # TODO(igordc): must be changed to NSH in Queens
1826 # (MPLS is a workaround)
1827 correlation
= 'mpls'
1828 if len(ingress_ports
) != 1:
1829 raise vimconn
.vimconnNotSupportedException(
1830 "OpenStack VIM connector can only have "
1831 "1 ingress port per SFI")
1832 if len(egress_ports
) != 1:
1833 raise vimconn
.vimconnNotSupportedException(
1834 "OpenStack VIM connector can only have "
1835 "1 egress port per SFI")
1836 sfi_dict
= {'name': name
,
1837 'ingress': ingress_ports
[0],
1838 'egress': egress_ports
[0],
1839 'service_function_parameters': {
1840 'correlation': correlation
}}
1841 new_sfi
= self
.neutron
.create_port_pair({'port_pair': sfi_dict
})
1842 return new_sfi
['port_pair']['id']
1843 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1844 neExceptions
.NeutronException
, ConnectionError
) as e
:
1847 self
.neutron
.delete_port_pair_group(
1848 new_sfi
['port_pair']['id'])
1851 'Creation of Service Function Instance failed, with '
1852 'subsequent deletion failure as well.')
1853 self
._format
_exception
(e
)
1855 def get_sfi(self
, sfi_id
):
1857 'Getting Service Function Instance %s from VIM', sfi_id
)
1858 filter_dict
= {"id": sfi_id
}
1859 sfi_list
= self
.get_sfi_list(filter_dict
)
1860 if len(sfi_list
) == 0:
1861 raise vimconn
.vimconnNotFoundException(
1862 "Service Function Instance '{}' not found".format(sfi_id
))
1863 elif len(sfi_list
) > 1:
1864 raise vimconn
.vimconnConflictException(
1865 'Found more than one Service Function Instance '
1866 'with this criteria')
1870 def get_sfi_list(self
, filter_dict
={}):
1871 self
.logger
.debug("Getting Service Function Instances from "
1872 "VIM filter: '%s'", str(filter_dict
))
1874 self
._reload
_connection
()
1875 if self
.api_version3
and "tenant_id" in filter_dict
:
1876 filter_dict
['project_id'] = filter_dict
.pop('tenant_id')
1877 sfi_dict
= self
.neutron
.list_port_pair(**filter_dict
)
1878 sfi_list
= sfi_dict
["port_pairs"]
1879 self
.__sfi
_os
2mano
(sfi_list
)
1881 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1882 neExceptions
.NeutronException
, ConnectionError
) as e
:
1883 self
._format
_exception
(e
)
1885 def delete_sfi(self
, sfi_id
):
1886 self
.logger
.debug("Deleting Service Function Instance '%s' "
1889 self
._reload
_connection
()
1890 self
.neutron
.delete_port_pair(sfi_id
)
1892 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1893 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1894 ConnectionError
) as e
:
1895 self
._format
_exception
(e
)
1897 def new_sf(self
, name
, sfis
, sfc_encap
=True):
1898 self
.logger
.debug("Adding a new Service Function to VIM, "
1902 self
._reload
_connection
()
1905 # TODO(igordc): must be changed to NSH in Queens
1906 # (MPLS is a workaround)
1907 correlation
= 'mpls'
1908 for instance
in sfis
:
1909 sfi
= self
.get_sfi(instance
)
1910 if sfi
.get('sfc_encap') != correlation
:
1911 raise vimconn
.vimconnNotSupportedException(
1912 "OpenStack VIM connector requires all SFIs of the "
1913 "same SF to share the same SFC Encapsulation")
1914 sf_dict
= {'name': name
,
1916 new_sf
= self
.neutron
.create_port_pair_group({
1917 'port_pair_group': sf_dict
})
1918 return new_sf
['port_pair_group']['id']
1919 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1920 neExceptions
.NeutronException
, ConnectionError
) as e
:
1923 self
.neutron
.delete_port_pair_group(
1924 new_sf
['port_pair_group']['id'])
1927 'Creation of Service Function failed, with '
1928 'subsequent deletion failure as well.')
1929 self
._format
_exception
(e
)
1931 def get_sf(self
, sf_id
):
1932 self
.logger
.debug("Getting Service Function %s from VIM", sf_id
)
1933 filter_dict
= {"id": sf_id
}
1934 sf_list
= self
.get_sf_list(filter_dict
)
1935 if len(sf_list
) == 0:
1936 raise vimconn
.vimconnNotFoundException(
1937 "Service Function '{}' not found".format(sf_id
))
1938 elif len(sf_list
) > 1:
1939 raise vimconn
.vimconnConflictException(
1940 "Found more than one Service Function with this criteria")
1944 def get_sf_list(self
, filter_dict
={}):
1945 self
.logger
.debug("Getting Service Function from VIM filter: '%s'",
1948 self
._reload
_connection
()
1949 if self
.api_version3
and "tenant_id" in filter_dict
:
1950 filter_dict
['project_id'] = filter_dict
.pop('tenant_id')
1951 sf_dict
= self
.neutron
.list_port_pair_group(**filter_dict
)
1952 sf_list
= sf_dict
["port_pair_groups"]
1953 self
.__sf
_os
2mano
(sf_list
)
1955 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1956 neExceptions
.NeutronException
, ConnectionError
) as e
:
1957 self
._format
_exception
(e
)
1959 def delete_sf(self
, sf_id
):
1960 self
.logger
.debug("Deleting Service Function '%s' from VIM", sf_id
)
1962 self
._reload
_connection
()
1963 self
.neutron
.delete_port_pair_group(sf_id
)
1965 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1966 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1967 ConnectionError
) as e
:
1968 self
._format
_exception
(e
)
1970 def new_sfp(self
, name
, classifications
, sfs
, sfc_encap
=True, spi
=None):
1971 self
.logger
.debug("Adding a new Service Function Path to VIM, "
1975 self
._reload
_connection
()
1977 raise vimconn
.vimconnNotSupportedException(
1978 "OpenStack VIM connector only supports "
1979 "SFC-Encapsulated chains")
1980 # TODO(igordc): must be changed to NSH in Queens
1981 # (MPLS is a workaround)
1982 correlation
= 'mpls'
1983 sfp_dict
= {'name': name
,
1984 'flow_classifiers': classifications
,
1985 'port_pair_groups': sfs
,
1986 'chain_parameters': {'correlation': correlation
}}
1988 sfp_dict
['chain_id'] = spi
1989 new_sfp
= self
.neutron
.create_port_chain({'port_chain': sfp_dict
})
1990 return new_sfp
["port_chain"]["id"]
1991 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1992 neExceptions
.NeutronException
, ConnectionError
) as e
:
1995 self
.neutron
.delete_port_chain(new_sfp
['port_chain']['id'])
1998 'Creation of Service Function Path failed, with '
1999 'subsequent deletion failure as well.')
2000 self
._format
_exception
(e
)
2002 def get_sfp(self
, sfp_id
):
2003 self
.logger
.debug(" Getting Service Function Path %s from VIM", sfp_id
)
2004 filter_dict
= {"id": sfp_id
}
2005 sfp_list
= self
.get_sfp_list(filter_dict
)
2006 if len(sfp_list
) == 0:
2007 raise vimconn
.vimconnNotFoundException(
2008 "Service Function Path '{}' not found".format(sfp_id
))
2009 elif len(sfp_list
) > 1:
2010 raise vimconn
.vimconnConflictException(
2011 "Found more than one Service Function Path with this criteria")
2015 def get_sfp_list(self
, filter_dict
={}):
2016 self
.logger
.debug("Getting Service Function Paths from VIM filter: "
2017 "'%s'", str(filter_dict
))
2019 self
._reload
_connection
()
2020 if self
.api_version3
and "tenant_id" in filter_dict
:
2021 filter_dict
['project_id'] = filter_dict
.pop('tenant_id')
2022 sfp_dict
= self
.neutron
.list_port_chain(**filter_dict
)
2023 sfp_list
= sfp_dict
["port_chains"]
2024 self
.__sfp
_os
2mano
(sfp_list
)
2026 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
2027 neExceptions
.NeutronException
, ConnectionError
) as e
:
2028 self
._format
_exception
(e
)
2030 def delete_sfp(self
, sfp_id
):
2032 "Deleting Service Function Path '%s' from VIM", sfp_id
)
2034 self
._reload
_connection
()
2035 self
.neutron
.delete_port_chain(sfp_id
)
2037 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
2038 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
2039 ConnectionError
) as e
:
2040 self
._format
_exception
(e
)