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$"
48 from novaclient
import client
as nClient
, exceptions
as nvExceptions
49 from keystoneauth1
.identity
import v2
, v3
50 from keystoneauth1
import session
51 import keystoneclient
.exceptions
as ksExceptions
52 import keystoneclient
.v3
.client
as ksClient_v3
53 import keystoneclient
.v2_0
.client
as ksClient_v2
54 from glanceclient
import client
as glClient
55 import glanceclient
.client
as gl1Client
56 import glanceclient
.exc
as gl1Exceptions
57 from cinderclient
import client
as cClient
58 from httplib
import HTTPException
59 from neutronclient
.neutron
import client
as neClient
60 from neutronclient
.common
import exceptions
as neExceptions
61 from requests
.exceptions
import ConnectionError
64 """contain the openstack virtual machine status to openmano status"""
65 vmStatus2manoFormat
={'ACTIVE':'ACTIVE',
67 'SUSPENDED': 'SUSPENDED',
70 'ERROR':'ERROR','DELETED':'DELETED'
72 netStatus2manoFormat
={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
75 supportedClassificationTypes
= ['legacy_flow_classifier']
77 #global var to have a timeout creating and deleting volumes
81 class vimconnector(vimconn
.vimconnector
):
82 def __init__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
=None, user
=None, passwd
=None,
83 log_level
=None, config
={}, persistent_info
={}):
84 '''using common constructor parameters. In this case
85 'url' is the keystone authorization url,
86 'url_admin' is not use
88 api_version
= config
.get('APIversion')
89 if api_version
and api_version
not in ('v3.3', 'v2.0', '2', '3'):
90 raise vimconn
.vimconnException("Invalid value '{}' for config:APIversion. "
91 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version
))
92 vim_type
= config
.get('vim_type')
93 if vim_type
and vim_type
not in ('vio', 'VIO'):
94 raise vimconn
.vimconnException("Invalid value '{}' for config:vim_type."
95 "Allowed values are 'vio' or 'VIO'".format(vim_type
))
97 if config
.get('dataplane_net_vlan_range') is not None:
98 #validate vlan ranges provided by user
99 self
._validate
_vlan
_ranges
(config
.get('dataplane_net_vlan_range'))
101 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
,
104 if self
.config
.get("insecure") and self
.config
.get("ca_cert"):
105 raise vimconn
.vimconnException("options insecure and ca_cert are mutually exclusive")
107 if self
.config
.get("insecure"):
109 if self
.config
.get("ca_cert"):
110 self
.verify
= self
.config
.get("ca_cert")
113 raise TypeError('url param can not be NoneType')
114 self
.persistent_info
= persistent_info
115 self
.availability_zone
= persistent_info
.get('availability_zone', None)
116 self
.session
= persistent_info
.get('session', {'reload_client': True})
117 self
.nova
= self
.session
.get('nova')
118 self
.neutron
= self
.session
.get('neutron')
119 self
.cinder
= self
.session
.get('cinder')
120 self
.glance
= self
.session
.get('glance')
121 self
.glancev1
= self
.session
.get('glancev1')
122 self
.keystone
= self
.session
.get('keystone')
123 self
.api_version3
= self
.session
.get('api_version3')
124 self
.vim_type
= self
.config
.get("vim_type")
126 self
.vim_type
= self
.vim_type
.upper()
127 if self
.config
.get("use_internal_endpoint"):
128 self
.endpoint_type
= "internalURL"
130 self
.endpoint_type
= None
132 self
.logger
= logging
.getLogger('openmano.vim.openstack')
134 ####### VIO Specific Changes #########
135 if self
.vim_type
== "VIO":
136 self
.logger
= logging
.getLogger('openmano.vim.vio')
139 self
.logger
.setLevel( getattr(logging
, log_level
))
141 def __getitem__(self
, index
):
142 """Get individuals parameters.
144 if index
== 'project_domain_id':
145 return self
.config
.get("project_domain_id")
146 elif index
== 'user_domain_id':
147 return self
.config
.get("user_domain_id")
149 return vimconn
.vimconnector
.__getitem
__(self
, index
)
151 def __setitem__(self
, index
, value
):
152 """Set individuals parameters and it is marked as dirty so to force connection reload.
154 if index
== 'project_domain_id':
155 self
.config
["project_domain_id"] = value
156 elif index
== 'user_domain_id':
157 self
.config
["user_domain_id"] = value
159 vimconn
.vimconnector
.__setitem
__(self
, index
, value
)
160 self
.session
['reload_client'] = True
162 def _reload_connection(self
):
163 '''Called before any operation, it check if credentials has changed
164 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
166 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
167 if self
.session
['reload_client']:
168 if self
.config
.get('APIversion'):
169 self
.api_version3
= self
.config
['APIversion'] == 'v3.3' or self
.config
['APIversion'] == '3'
170 else: # get from ending auth_url that end with v3 or with v2.0
171 self
.api_version3
= self
.url
.endswith("/v3") or self
.url
.endswith("/v3/")
172 self
.session
['api_version3'] = self
.api_version3
173 if self
.api_version3
:
174 if self
.config
.get('project_domain_id') or self
.config
.get('project_domain_name'):
175 project_domain_id_default
= None
177 project_domain_id_default
= 'default'
178 if self
.config
.get('user_domain_id') or self
.config
.get('user_domain_name'):
179 user_domain_id_default
= None
181 user_domain_id_default
= 'default'
182 auth
= v3
.Password(auth_url
=self
.url
,
184 password
=self
.passwd
,
185 project_name
=self
.tenant_name
,
186 project_id
=self
.tenant_id
,
187 project_domain_id
=self
.config
.get('project_domain_id', project_domain_id_default
),
188 user_domain_id
=self
.config
.get('user_domain_id', user_domain_id_default
),
189 project_domain_name
=self
.config
.get('project_domain_name'),
190 user_domain_name
=self
.config
.get('user_domain_name'))
192 auth
= v2
.Password(auth_url
=self
.url
,
194 password
=self
.passwd
,
195 tenant_name
=self
.tenant_name
,
196 tenant_id
=self
.tenant_id
)
197 sess
= session
.Session(auth
=auth
, verify
=self
.verify
)
198 if self
.api_version3
:
199 self
.keystone
= ksClient_v3
.Client(session
=sess
, endpoint_type
=self
.endpoint_type
)
201 self
.keystone
= ksClient_v2
.Client(session
=sess
, endpoint_type
=self
.endpoint_type
)
202 self
.session
['keystone'] = self
.keystone
203 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
204 # This implementation approach is due to the warning message in
205 # https://developer.openstack.org/api-guide/compute/microversions.html
206 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
207 # always require an specific microversion.
208 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
209 version
= self
.config
.get("microversion")
212 self
.nova
= self
.session
['nova'] = nClient
.Client(str(version
), session
=sess
, endpoint_type
=self
.endpoint_type
)
213 self
.neutron
= self
.session
['neutron'] = neClient
.Client('2.0', session
=sess
, endpoint_type
=self
.endpoint_type
)
214 self
.cinder
= self
.session
['cinder'] = cClient
.Client(2, session
=sess
, endpoint_type
=self
.endpoint_type
)
215 if self
.endpoint_type
== "internalURL":
216 glance_service_id
= self
.keystone
.services
.list(name
="glance")[0].id
217 glance_endpoint
= self
.keystone
.endpoints
.list(glance_service_id
, interface
="internal")[0].url
219 glance_endpoint
= None
220 self
.glance
= self
.session
['glance'] = glClient
.Client(2, session
=sess
, endpoint
=glance_endpoint
)
221 #using version 1 of glance client in new_image()
222 self
.glancev1
= self
.session
['glancev1'] = glClient
.Client('1', session
=sess
,
223 endpoint
=glance_endpoint
)
224 self
.session
['reload_client'] = False
225 self
.persistent_info
['session'] = self
.session
226 # add availablity zone info inside self.persistent_info
227 self
._set
_availablity
_zones
()
228 self
.persistent_info
['availability_zone'] = self
.availability_zone
230 def __net_os2mano(self
, net_list_dict
):
231 '''Transform the net openstack format to mano format
232 net_list_dict can be a list of dict or a single dict'''
233 if type(net_list_dict
) is dict:
234 net_list_
=(net_list_dict
,)
235 elif type(net_list_dict
) is list:
236 net_list_
=net_list_dict
238 raise TypeError("param net_list_dict must be a list or a dictionary")
239 for net
in net_list_
:
240 if net
.get('provider:network_type') == "vlan":
245 def __classification_os2mano(self
, class_list_dict
):
246 """Transform the openstack format (Flow Classifier) to mano format
247 (Classification) class_list_dict can be a list of dict or a single dict
249 if isinstance(class_list_dict
, dict):
250 class_list_
= [class_list_dict
]
251 elif isinstance(class_list_dict
, list):
252 class_list_
= class_list_dict
255 "param class_list_dict must be a list or a dictionary")
256 for classification
in class_list_
:
257 id = classification
.pop('id')
258 name
= classification
.pop('name')
259 description
= classification
.pop('description')
260 project_id
= classification
.pop('project_id')
261 tenant_id
= classification
.pop('tenant_id')
262 original_classification
= copy
.deepcopy(classification
)
263 classification
.clear()
264 classification
['ctype'] = 'legacy_flow_classifier'
265 classification
['definition'] = original_classification
266 classification
['id'] = id
267 classification
['name'] = name
268 classification
['description'] = description
269 classification
['project_id'] = project_id
270 classification
['tenant_id'] = tenant_id
272 def __sfi_os2mano(self
, sfi_list_dict
):
273 """Transform the openstack format (Port Pair) to mano format (SFI)
274 sfi_list_dict can be a list of dict or a single dict
276 if isinstance(sfi_list_dict
, dict):
277 sfi_list_
= [sfi_list_dict
]
278 elif isinstance(sfi_list_dict
, list):
279 sfi_list_
= sfi_list_dict
282 "param sfi_list_dict must be a list or a dictionary")
283 for sfi
in sfi_list_
:
284 sfi
['ingress_ports'] = []
285 sfi
['egress_ports'] = []
286 if sfi
.get('ingress'):
287 sfi
['ingress_ports'].append(sfi
['ingress'])
288 if sfi
.get('egress'):
289 sfi
['egress_ports'].append(sfi
['egress'])
292 params
= sfi
.get('service_function_parameters')
295 correlation
= params
.get('correlation')
298 sfi
['sfc_encap'] = sfc_encap
299 del sfi
['service_function_parameters']
301 def __sf_os2mano(self
, sf_list_dict
):
302 """Transform the openstack format (Port Pair Group) to mano format (SF)
303 sf_list_dict can be a list of dict or a single dict
305 if isinstance(sf_list_dict
, dict):
306 sf_list_
= [sf_list_dict
]
307 elif isinstance(sf_list_dict
, list):
308 sf_list_
= sf_list_dict
311 "param sf_list_dict must be a list or a dictionary")
313 del sf
['port_pair_group_parameters']
314 sf
['sfis'] = sf
['port_pairs']
317 def __sfp_os2mano(self
, sfp_list_dict
):
318 """Transform the openstack format (Port Chain) to mano format (SFP)
319 sfp_list_dict can be a list of dict or a single dict
321 if isinstance(sfp_list_dict
, dict):
322 sfp_list_
= [sfp_list_dict
]
323 elif isinstance(sfp_list_dict
, list):
324 sfp_list_
= sfp_list_dict
327 "param sfp_list_dict must be a list or a dictionary")
328 for sfp
in sfp_list_
:
329 params
= sfp
.pop('chain_parameters')
332 correlation
= params
.get('correlation')
335 sfp
['sfc_encap'] = sfc_encap
336 sfp
['spi'] = sfp
.pop('chain_id')
337 sfp
['classifications'] = sfp
.pop('flow_classifiers')
338 sfp
['service_functions'] = sfp
.pop('port_pair_groups')
340 # placeholder for now; read TODO note below
341 def _validate_classification(self
, type, definition
):
342 # only legacy_flow_classifier Type is supported at this point
344 # TODO(igordcard): this method should be an abstract method of an
345 # abstract Classification class to be implemented by the specific
346 # Types. Also, abstract vimconnector should call the validation
347 # method before the implemented VIM connectors are called.
349 def _format_exception(self
, exception
):
350 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
351 if isinstance(exception
, (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
,
352 ConnectionError
, ksExceptions
.ConnectionError
, neExceptions
.ConnectionFailed
354 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
355 elif isinstance(exception
, (nvExceptions
.ClientException
, ksExceptions
.ClientException
,
356 neExceptions
.NeutronException
, nvExceptions
.BadRequest
)):
357 raise vimconn
.vimconnUnexpectedResponse(type(exception
).__name
__ + ": " + str(exception
))
358 elif isinstance(exception
, (neExceptions
.NetworkNotFoundClient
, nvExceptions
.NotFound
)):
359 raise vimconn
.vimconnNotFoundException(type(exception
).__name
__ + ": " + str(exception
))
360 elif isinstance(exception
, nvExceptions
.Conflict
):
361 raise vimconn
.vimconnConflictException(type(exception
).__name
__ + ": " + str(exception
))
362 elif isinstance(exception
, vimconn
.vimconnException
):
365 self
.logger
.error("General Exception " + str(exception
), exc_info
=True)
366 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
368 def get_tenant_list(self
, filter_dict
={}):
369 '''Obtain tenants of VIM
370 filter_dict can contain the following keys:
371 name: filter by tenant name
372 id: filter by tenant uuid/id
374 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
376 self
.logger
.debug("Getting tenants from VIM filter: '%s'", str(filter_dict
))
378 self
._reload
_connection
()
379 if self
.api_version3
:
380 project_class_list
= self
.keystone
.projects
.list(name
=filter_dict
.get("name"))
382 project_class_list
= self
.keystone
.tenants
.findall(**filter_dict
)
384 for project
in project_class_list
:
385 if filter_dict
.get('id') and filter_dict
["id"] != project
.id:
387 project_list
.append(project
.to_dict())
389 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
390 self
._format
_exception
(e
)
392 def new_tenant(self
, tenant_name
, tenant_description
):
393 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
394 self
.logger
.debug("Adding a new tenant name: %s", tenant_name
)
396 self
._reload
_connection
()
397 if self
.api_version3
:
398 project
= self
.keystone
.projects
.create(tenant_name
, self
.config
.get("project_domain_id", "default"),
399 description
=tenant_description
, is_domain
=False)
401 project
= self
.keystone
.tenants
.create(tenant_name
, tenant_description
)
403 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
404 self
._format
_exception
(e
)
406 def delete_tenant(self
, tenant_id
):
407 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
408 self
.logger
.debug("Deleting tenant %s from VIM", tenant_id
)
410 self
._reload
_connection
()
411 if self
.api_version3
:
412 self
.keystone
.projects
.delete(tenant_id
)
414 self
.keystone
.tenants
.delete(tenant_id
)
416 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
417 self
._format
_exception
(e
)
419 def new_network(self
,net_name
, net_type
, ip_profile
=None, shared
=False, vlan
=None):
420 '''Adds a tenant network to VIM. Returns the network identifier'''
421 self
.logger
.debug("Adding a new network to VIM name '%s', type '%s'", net_name
, net_type
)
422 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
425 self
._reload
_connection
()
426 network_dict
= {'name': net_name
, 'admin_state_up': True}
427 if net_type
=="data" or net_type
=="ptp":
428 if self
.config
.get('dataplane_physical_net') == None:
429 raise vimconn
.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
430 network_dict
["provider:physical_network"] = self
.config
['dataplane_physical_net'] #"physnet_sriov" #TODO physical
431 network_dict
["provider:network_type"] = "vlan"
433 network_dict
["provider:network_type"] = vlan
435 ####### VIO Specific Changes #########
436 if self
.vim_type
== "VIO":
438 network_dict
["provider:segmentation_id"] = vlan
440 if self
.config
.get('dataplane_net_vlan_range') is None:
441 raise vimconn
.vimconnConflictException("You must provide "\
442 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
443 "at config value before creating sriov network with vlan tag")
445 network_dict
["provider:segmentation_id"] = self
._genrate
_vlanID
()
447 network_dict
["shared"]=shared
448 new_net
=self
.neutron
.create_network({'network':network_dict
})
450 #create subnetwork, even if there is no profile
453 if not ip_profile
.get('subnet_address'):
454 #Fake subnet is required
455 subnet_rand
= random
.randint(0, 255)
456 ip_profile
['subnet_address'] = "192.168.{}.0/24".format(subnet_rand
)
457 if 'ip_version' not in ip_profile
:
458 ip_profile
['ip_version'] = "IPv4"
459 subnet
= {"name":net_name
+"-subnet",
460 "network_id": new_net
["network"]["id"],
461 "ip_version": 4 if ip_profile
['ip_version']=="IPv4" else 6,
462 "cidr": ip_profile
['subnet_address']
464 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
465 if ip_profile
.get('gateway_address'):
466 subnet
['gateway_ip'] = ip_profile
['gateway_address']
468 subnet
['gateway_ip'] = None
469 if ip_profile
.get('dns_address'):
470 subnet
['dns_nameservers'] = ip_profile
['dns_address'].split(";")
471 if 'dhcp_enabled' in ip_profile
:
472 subnet
['enable_dhcp'] = False if \
473 ip_profile
['dhcp_enabled']=="false" or ip_profile
['dhcp_enabled']==False else True
474 if ip_profile
.get('dhcp_start_address'):
475 subnet
['allocation_pools'] = []
476 subnet
['allocation_pools'].append(dict())
477 subnet
['allocation_pools'][0]['start'] = ip_profile
['dhcp_start_address']
478 if ip_profile
.get('dhcp_count'):
479 #parts = ip_profile['dhcp_start_address'].split('.')
480 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
481 ip_int
= int(netaddr
.IPAddress(ip_profile
['dhcp_start_address']))
482 ip_int
+= ip_profile
['dhcp_count'] - 1
483 ip_str
= str(netaddr
.IPAddress(ip_int
))
484 subnet
['allocation_pools'][0]['end'] = ip_str
485 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
486 self
.neutron
.create_subnet({"subnet": subnet
} )
487 return new_net
["network"]["id"]
488 except Exception as e
:
490 self
.neutron
.delete_network(new_net
['network']['id'])
491 self
._format
_exception
(e
)
493 def get_network_list(self
, filter_dict
={}):
494 '''Obtain tenant networks of VIM
500 admin_state_up: boolean
502 Returns the network list of dictionaries
504 self
.logger
.debug("Getting network from VIM filter: '%s'", str(filter_dict
))
506 self
._reload
_connection
()
507 filter_dict_os
= filter_dict
.copy()
508 if self
.api_version3
and "tenant_id" in filter_dict_os
:
509 filter_dict_os
['project_id'] = filter_dict_os
.pop('tenant_id') #T ODO check
510 net_dict
= self
.neutron
.list_networks(**filter_dict_os
)
511 net_list
= net_dict
["networks"]
512 self
.__net
_os
2mano
(net_list
)
514 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
515 self
._format
_exception
(e
)
517 def get_network(self
, net_id
):
518 '''Obtain details of network from VIM
519 Returns the network information from a network id'''
520 self
.logger
.debug(" Getting tenant network %s from VIM", net_id
)
521 filter_dict
={"id": net_id
}
522 net_list
= self
.get_network_list(filter_dict
)
524 raise vimconn
.vimconnNotFoundException("Network '{}' not found".format(net_id
))
525 elif len(net_list
)>1:
526 raise vimconn
.vimconnConflictException("Found more than one network with this criteria")
529 for subnet_id
in net
.get("subnets", () ):
531 subnet
= self
.neutron
.show_subnet(subnet_id
)
532 except Exception as e
:
533 self
.logger
.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id
, str(e
)))
534 subnet
= {"id": subnet_id
, "fault": str(e
)}
535 subnets
.append(subnet
)
536 net
["subnets"] = subnets
537 net
["encapsulation"] = net
.get('provider:network_type')
538 net
["segmentation_id"] = net
.get('provider:segmentation_id')
541 def delete_network(self
, net_id
):
542 '''Deletes a tenant network from VIM. Returns the old network identifier'''
543 self
.logger
.debug("Deleting network '%s' from VIM", net_id
)
545 self
._reload
_connection
()
546 #delete VM ports attached to this networks before the network
547 ports
= self
.neutron
.list_ports(network_id
=net_id
)
548 for p
in ports
['ports']:
550 self
.neutron
.delete_port(p
["id"])
551 except Exception as e
:
552 self
.logger
.error("Error deleting port %s: %s", p
["id"], str(e
))
553 self
.neutron
.delete_network(net_id
)
555 except (neExceptions
.ConnectionFailed
, neExceptions
.NetworkNotFoundClient
, neExceptions
.NeutronException
,
556 ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
557 self
._format
_exception
(e
)
559 def refresh_nets_status(self
, net_list
):
560 '''Get the status of the networks
561 Params: the list of network identifiers
562 Returns a dictionary with:
563 net_id: #VIM id of this network
564 status: #Mandatory. Text with one of:
565 # DELETED (not found at vim)
566 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
567 # OTHER (Vim reported other status not understood)
568 # ERROR (VIM indicates an ERROR status)
569 # ACTIVE, INACTIVE, DOWN (admin down),
570 # BUILD (on building process)
572 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
573 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
577 for net_id
in net_list
:
580 net_vim
= self
.get_network(net_id
)
581 if net_vim
['status'] in netStatus2manoFormat
:
582 net
["status"] = netStatus2manoFormat
[ net_vim
['status'] ]
584 net
["status"] = "OTHER"
585 net
["error_msg"] = "VIM status reported " + net_vim
['status']
587 if net
['status'] == "ACTIVE" and not net_vim
['admin_state_up']:
588 net
['status'] = 'DOWN'
590 net
['vim_info'] = yaml
.safe_dump(net_vim
, default_flow_style
=True, width
=256)
591 except yaml
.representer
.RepresenterError
:
592 net
['vim_info'] = str(net_vim
)
593 if net_vim
.get('fault'): #TODO
594 net
['error_msg'] = str(net_vim
['fault'])
595 except vimconn
.vimconnNotFoundException
as e
:
596 self
.logger
.error("Exception getting net status: %s", str(e
))
597 net
['status'] = "DELETED"
598 net
['error_msg'] = str(e
)
599 except vimconn
.vimconnException
as e
:
600 self
.logger
.error("Exception getting net status: %s", str(e
))
601 net
['status'] = "VIM_ERROR"
602 net
['error_msg'] = str(e
)
603 net_dict
[net_id
] = net
606 def get_flavor(self
, flavor_id
):
607 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
608 self
.logger
.debug("Getting flavor '%s'", flavor_id
)
610 self
._reload
_connection
()
611 flavor
= self
.nova
.flavors
.find(id=flavor_id
)
612 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
613 return flavor
.to_dict()
614 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
615 self
._format
_exception
(e
)
617 def get_flavor_id_from_data(self
, flavor_dict
):
618 """Obtain flavor id that match the flavor description
619 Returns the flavor_id or raises a vimconnNotFoundException
620 flavor_dict: contains the required ram, vcpus, disk
621 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
622 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
623 vimconnNotFoundException is raised
625 exact_match
= False if self
.config
.get('use_existing_flavors') else True
627 self
._reload
_connection
()
628 flavor_candidate_id
= None
629 flavor_candidate_data
= (10000, 10000, 10000)
630 flavor_target
= (flavor_dict
["ram"], flavor_dict
["vcpus"], flavor_dict
["disk"])
632 numas
= flavor_dict
.get("extended", {}).get("numas")
635 raise vimconn
.vimconnNotFoundException("Flavor with EPA still not implemted")
637 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
639 # numas = extended.get("numas")
640 for flavor
in self
.nova
.flavors
.list():
641 epa
= flavor
.get_keys()
645 flavor_data
= (flavor
.ram
, flavor
.vcpus
, flavor
.disk
)
646 if flavor_data
== flavor_target
:
648 elif not exact_match
and flavor_target
< flavor_data
< flavor_candidate_data
:
649 flavor_candidate_id
= flavor
.id
650 flavor_candidate_data
= flavor_data
651 if not exact_match
and flavor_candidate_id
:
652 return flavor_candidate_id
653 raise vimconn
.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict
)))
654 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
655 self
._format
_exception
(e
)
657 def new_flavor(self
, flavor_data
, change_name_if_used
=True):
658 '''Adds a tenant flavor to openstack VIM
659 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
660 Returns the flavor identifier
662 self
.logger
.debug("Adding flavor '%s'", str(flavor_data
))
666 name
=flavor_data
['name']
667 while retry
<max_retries
:
670 self
._reload
_connection
()
671 if change_name_if_used
:
674 fl
=self
.nova
.flavors
.list()
676 fl_names
.append(f
.name
)
677 while name
in fl_names
:
679 name
= flavor_data
['name']+"-" + str(name_suffix
)
681 ram
= flavor_data
.get('ram',64)
682 vcpus
= flavor_data
.get('vcpus',1)
685 extended
= flavor_data
.get("extended")
687 numas
=extended
.get("numas")
689 numa_nodes
= len(numas
)
691 return -1, "Can not add flavor with more than one numa"
692 numa_properties
= {"hw:numa_nodes":str(numa_nodes
)}
693 numa_properties
["hw:mem_page_size"] = "large"
694 numa_properties
["hw:cpu_policy"] = "dedicated"
695 numa_properties
["hw:numa_mempolicy"] = "strict"
696 if self
.vim_type
== "VIO":
697 numa_properties
["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
698 numa_properties
["vmware:latency_sensitivity_level"] = "high"
700 #overwrite ram and vcpus
701 #check if key 'memory' is present in numa else use ram value at flavor
703 ram
= numa
['memory']*1024
704 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
705 if 'paired-threads' in numa
:
706 vcpus
= numa
['paired-threads']*2
707 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
708 numa_properties
["hw:cpu_thread_policy"] = "require"
709 numa_properties
["hw:cpu_policy"] = "dedicated"
710 elif 'cores' in numa
:
711 vcpus
= numa
['cores']
712 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
713 numa_properties
["hw:cpu_thread_policy"] = "isolate"
714 numa_properties
["hw:cpu_policy"] = "dedicated"
715 elif 'threads' in numa
:
716 vcpus
= numa
['threads']
717 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
718 numa_properties
["hw:cpu_thread_policy"] = "prefer"
719 numa_properties
["hw:cpu_policy"] = "dedicated"
720 # for interface in numa.get("interfaces",() ):
721 # if interface["dedicated"]=="yes":
722 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
723 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
726 new_flavor
=self
.nova
.flavors
.create(name
,
729 flavor_data
.get('disk',0),
730 is_public
=flavor_data
.get('is_public', True)
734 new_flavor
.set_keys(numa_properties
)
736 except nvExceptions
.Conflict
as e
:
737 if change_name_if_used
and retry
< max_retries
:
739 self
._format
_exception
(e
)
740 #except nvExceptions.BadRequest as e:
741 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
742 self
._format
_exception
(e
)
744 def delete_flavor(self
,flavor_id
):
745 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
748 self
._reload
_connection
()
749 self
.nova
.flavors
.delete(flavor_id
)
751 #except nvExceptions.BadRequest as e:
752 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
753 self
._format
_exception
(e
)
755 def new_image(self
,image_dict
):
757 Adds a tenant image to VIM. imge_dict is a dictionary with:
759 disk_format: qcow2, vhd, vmdk, raw (by default), ...
760 location: path or URI
761 public: "yes" or "no"
762 metadata: metadata of the image
767 while retry
<max_retries
:
770 self
._reload
_connection
()
771 #determine format http://docs.openstack.org/developer/glance/formats.html
772 if "disk_format" in image_dict
:
773 disk_format
=image_dict
["disk_format"]
774 else: #autodiscover based on extension
775 if image_dict
['location'][-6:]==".qcow2":
777 elif image_dict
['location'][-4:]==".vhd":
779 elif image_dict
['location'][-5:]==".vmdk":
781 elif image_dict
['location'][-4:]==".vdi":
783 elif image_dict
['location'][-4:]==".iso":
785 elif image_dict
['location'][-4:]==".aki":
787 elif image_dict
['location'][-4:]==".ari":
789 elif image_dict
['location'][-4:]==".ami":
793 self
.logger
.debug("new_image: '%s' loading from '%s'", image_dict
['name'], image_dict
['location'])
794 if image_dict
['location'][0:4]=="http":
795 new_image
= self
.glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
796 container_format
="bare", location
=image_dict
['location'], disk_format
=disk_format
)
798 with
open(image_dict
['location']) as fimage
:
799 new_image
= self
.glancev1
.images
.create(name
=image_dict
['name'], is_public
=image_dict
.get('public',"yes")=="yes",
800 container_format
="bare", data
=fimage
, disk_format
=disk_format
)
801 #insert metadata. We cannot use 'new_image.properties.setdefault'
802 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
803 new_image_nova
=self
.nova
.images
.find(id=new_image
.id)
804 new_image_nova
.metadata
.setdefault('location',image_dict
['location'])
805 metadata_to_load
= image_dict
.get('metadata')
807 for k
,v
in yaml
.load(metadata_to_load
).iteritems():
808 new_image_nova
.metadata
.setdefault(k
,v
)
810 except (nvExceptions
.Conflict
, ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
811 self
._format
_exception
(e
)
812 except (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
813 if retry
==max_retries
:
815 self
._format
_exception
(e
)
816 except IOError as e
: #can not open the file
817 raise vimconn
.vimconnConnectionException(type(e
).__name
__ + ": " + str(e
)+ " for " + image_dict
['location'],
818 http_code
=vimconn
.HTTP_Bad_Request
)
820 def delete_image(self
, image_id
):
821 '''Deletes a tenant image from openstack VIM. Returns the old id
824 self
._reload
_connection
()
825 self
.nova
.images
.delete(image_id
)
827 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
: #TODO remove
828 self
._format
_exception
(e
)
830 def get_image_id_from_path(self
, path
):
831 '''Get the image id from image path in the VIM database. Returns the image_id'''
833 self
._reload
_connection
()
834 images
= self
.nova
.images
.list()
836 if image
.metadata
.get("location")==path
:
838 raise vimconn
.vimconnNotFoundException("image with location '{}' not found".format( path
))
839 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
840 self
._format
_exception
(e
)
842 def get_image_list(self
, filter_dict
={}):
843 '''Obtain tenant images from VIM
847 checksum: image checksum
848 Returns the image list of dictionaries:
849 [{<the fields at Filter_dict plus some VIM specific>}, ...]
852 self
.logger
.debug("Getting image list from VIM filter: '%s'", str(filter_dict
))
854 self
._reload
_connection
()
855 filter_dict_os
= filter_dict
.copy()
856 #First we filter by the available filter fields: name, id. The others are removed.
857 filter_dict_os
.pop('checksum', None)
858 image_list
= self
.nova
.images
.findall(**filter_dict_os
)
859 if len(image_list
) == 0:
861 #Then we filter by the rest of filter fields: checksum
863 for image
in image_list
:
865 image_class
= self
.glance
.images
.get(image
.id)
866 if 'checksum' not in filter_dict
or image_class
['checksum'] == filter_dict
.get('checksum'):
867 filtered_list
.append(image_class
.copy())
868 except gl1Exceptions
.HTTPNotFound
:
871 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
872 self
._format
_exception
(e
)
874 def __wait_for_vm(self
, vm_id
, status
):
875 """wait until vm is in the desired status and return True.
876 If the VM gets in ERROR status, return false.
877 If the timeout is reached generate an exception"""
879 while elapsed_time
< server_timeout
:
880 vm_status
= self
.nova
.servers
.get(vm_id
).status
881 if vm_status
== status
:
883 if vm_status
== 'ERROR':
888 # if we exceeded the timeout rollback
889 if elapsed_time
>= server_timeout
:
890 raise vimconn
.vimconnException('Timeout waiting for instance ' + vm_id
+ ' to get ' + status
,
891 http_code
=vimconn
.HTTP_Request_Timeout
)
893 def _get_openstack_availablity_zones(self
):
895 Get from openstack availability zones available
899 openstack_availability_zone
= self
.nova
.availability_zones
.list()
900 openstack_availability_zone
= [str(zone
.zoneName
) for zone
in openstack_availability_zone
901 if zone
.zoneName
!= 'internal']
902 return openstack_availability_zone
903 except Exception as e
:
906 def _set_availablity_zones(self
):
908 Set vim availablity zone
912 if 'availability_zone' in self
.config
:
913 vim_availability_zones
= self
.config
.get('availability_zone')
914 if isinstance(vim_availability_zones
, str):
915 self
.availability_zone
= [vim_availability_zones
]
916 elif isinstance(vim_availability_zones
, list):
917 self
.availability_zone
= vim_availability_zones
919 self
.availability_zone
= self
._get
_openstack
_availablity
_zones
()
921 def _get_vm_availability_zone(self
, availability_zone_index
, availability_zone_list
):
923 Return thge availability zone to be used by the created VM.
924 :return: The VIM availability zone to be used or None
926 if availability_zone_index
is None:
927 if not self
.config
.get('availability_zone'):
929 elif isinstance(self
.config
.get('availability_zone'), str):
930 return self
.config
['availability_zone']
932 # TODO consider using a different parameter at config for default AV and AV list match
933 return self
.config
['availability_zone'][0]
935 vim_availability_zones
= self
.availability_zone
936 # check if VIM offer enough availability zones describe in the VNFD
937 if vim_availability_zones
and len(availability_zone_list
) <= len(vim_availability_zones
):
938 # check if all the names of NFV AV match VIM AV names
939 match_by_index
= False
940 for av
in availability_zone_list
:
941 if av
not in vim_availability_zones
:
942 match_by_index
= True
945 return vim_availability_zones
[availability_zone_index
]
947 return availability_zone_list
[availability_zone_index
]
949 raise vimconn
.vimconnConflictException("No enough availability zones at VIM for this deployment")
951 def new_vminstance(self
, name
, description
, start
, image_id
, flavor_id
, net_list
, cloud_config
=None, disk_list
=None,
952 availability_zone_index
=None, availability_zone_list
=None):
953 """Adds a VM instance to VIM
955 start: indicates if VM must start or boot in pause mode. Ignored
956 image_id,flavor_id: iamge and flavor uuid
957 net_list: list of interfaces, each one is a dictionary with:
959 net_id: network uuid to connect
960 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
961 model: interface model, ignored #TODO
962 mac_address: used for SR-IOV ifaces #TODO for other types
963 use: 'data', 'bridge', 'mgmt'
964 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
965 vim_id: filled/added by this function
966 floating_ip: True/False (or it can be None)
967 'cloud_config': (optional) dictionary with:
968 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
969 'users': (optional) list of users to be inserted, each item is a dict with:
970 'name': (mandatory) user name,
971 'key-pairs': (optional) list of strings with the public key to be inserted to the user
972 'user-data': (optional) string is a text script to be passed directly to cloud-init
973 'config-files': (optional). List of files to be transferred. Each item is a dict with:
974 'dest': (mandatory) string with the destination absolute path
975 'encoding': (optional, by default text). Can be one of:
976 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
977 'content' (mandatory): string with the content of the file
978 'permissions': (optional) string with file permissions, typically octal notation '0644'
979 'owner': (optional) file owner, string with the format 'owner:group'
980 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
981 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
982 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
983 'size': (mandatory) string with the size of the disk in GB
984 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
985 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
986 availability_zone_index is None
987 #TODO ip, security groups
988 Returns a tuple with the instance identifier and created_items or raises an exception on error
989 created_items can be None or a dictionary where this method can include key-values that will be passed to
990 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
991 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
994 self
.logger
.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id
, flavor_id
,str(net_list
))
1000 external_network
= [] # list of external networks to be connected to instance, later on used to create floating_ip
1001 no_secured_ports
= [] # List of port-is with port-security disabled
1002 self
._reload
_connection
()
1003 # metadata_vpci = {} # For a specific neutron plugin
1004 block_device_mapping
= None
1005 for net
in net_list
:
1006 if not net
.get("net_id"): # skip non connected iface
1010 "network_id": net
["net_id"],
1011 "name": net
.get("name"),
1012 "admin_state_up": True
1014 if net
["type"]=="virtual":
1017 # metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
1018 elif net
["type"] == "VF" or net
["type"] == "SR-IOV": # for VF
1020 # if "VF" not in metadata_vpci:
1021 # metadata_vpci["VF"]=[]
1022 # metadata_vpci["VF"].append([ net["vpci"], "" ])
1023 port_dict
["binding:vnic_type"]="direct"
1024 # VIO specific Changes
1025 if self
.vim_type
== "VIO":
1026 # Need to create port with port_security_enabled = False and no-security-groups
1027 port_dict
["port_security_enabled"]=False
1028 port_dict
["provider_security_groups"]=[]
1029 port_dict
["security_groups"]=[]
1030 else: # For PT PCI-PASSTHROUGH
1031 # VIO specific Changes
1032 # Current VIO release does not support port with type 'direct-physical'
1033 # So no need to create virtual port in case of PCI-device.
1034 # Will update port_dict code when support gets added in next VIO release
1035 if self
.vim_type
== "VIO":
1036 raise vimconn
.vimconnNotSupportedException(
1037 "Current VIO release does not support full passthrough (PT)")
1039 # if "PF" not in metadata_vpci:
1040 # metadata_vpci["PF"]=[]
1041 # metadata_vpci["PF"].append([ net["vpci"], "" ])
1042 port_dict
["binding:vnic_type"]="direct-physical"
1043 if not port_dict
["name"]:
1044 port_dict
["name"]=name
1045 if net
.get("mac_address"):
1046 port_dict
["mac_address"]=net
["mac_address"]
1047 if net
.get("ip_address"):
1048 port_dict
["fixed_ips"] = [{'ip_address': net
["ip_address"]}]
1049 # TODO add 'subnet_id': <subnet_id>
1050 new_port
= self
.neutron
.create_port({"port": port_dict
})
1051 created_items
["port:" + str(new_port
["port"]["id"])] = True
1052 net
["mac_adress"] = new_port
["port"]["mac_address"]
1053 net
["vim_id"] = new_port
["port"]["id"]
1054 # if try to use a network without subnetwork, it will return a emtpy list
1055 fixed_ips
= new_port
["port"].get("fixed_ips")
1057 net
["ip"] = fixed_ips
[0].get("ip_address")
1061 port
= {"port-id": new_port
["port"]["id"]}
1062 if float(self
.nova
.api_version
.get_string()) >= 2.32:
1063 port
["tag"] = new_port
["port"]["name"]
1064 net_list_vim
.append(port
)
1066 if net
.get('floating_ip', False):
1067 net
['exit_on_floating_ip_error'] = True
1068 external_network
.append(net
)
1069 elif net
['use'] == 'mgmt' and self
.config
.get('use_floating_ip'):
1070 net
['exit_on_floating_ip_error'] = False
1071 external_network
.append(net
)
1072 net
['floating_ip'] = self
.config
.get('use_floating_ip')
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 and not self
.config
.get("no_port_security_extension"):
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'",
1088 name
, image_id
, flavor_id
, str(net_list_vim
), description
)
1090 security_groups
= self
.config
.get('security_groups')
1091 if type(security_groups
) is str:
1092 security_groups
= ( security_groups
, )
1094 config_drive
, userdata
= self
._create
_user
_data
(cloud_config
)
1096 # Create additional volumes in case these are present in disk_list
1097 base_disk_index
= ord('b')
1098 if disk_list
!= None:
1099 block_device_mapping
= {}
1100 for disk
in disk_list
:
1101 if 'image_id' in disk
:
1102 volume
= self
.cinder
.volumes
.create(size
= disk
['size'],name
= name
+ '_vd' +
1103 chr(base_disk_index
), imageRef
= disk
['image_id'])
1105 volume
= self
.cinder
.volumes
.create(size
=disk
['size'], name
=name
+ '_vd' +
1106 chr(base_disk_index
))
1107 created_items
["volume:" + str(volume
.id)] = True
1108 block_device_mapping
['_vd' + chr(base_disk_index
)] = volume
.id
1109 base_disk_index
+= 1
1111 # Wait until volumes are with status available
1114 while keep_waiting
and elapsed_time
< volume_timeout
:
1115 keep_waiting
= False
1116 for volume_id
in block_device_mapping
.itervalues():
1117 if self
.cinder
.volumes
.get(volume_id
).status
!= 'available':
1123 # If we exceeded the timeout rollback
1124 if elapsed_time
>= volume_timeout
:
1125 raise vimconn
.vimconnException('Timeout creating volumes for instance ' + name
,
1126 http_code
=vimconn
.HTTP_Request_Timeout
)
1127 # get availability Zone
1128 vm_av_zone
= self
._get
_vm
_availability
_zone
(availability_zone_index
, availability_zone_list
)
1130 self
.logger
.debug("nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
1131 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
1132 "block_device_mapping={})".format(name
, image_id
, flavor_id
, net_list_vim
,
1133 security_groups
, vm_av_zone
, self
.config
.get('keypair'),
1134 userdata
, config_drive
, block_device_mapping
))
1135 server
= self
.nova
.servers
.create(name
, image_id
, flavor_id
, nics
=net_list_vim
,
1136 security_groups
=security_groups
,
1137 availability_zone
=vm_av_zone
,
1138 key_name
=self
.config
.get('keypair'),
1140 config_drive
=config_drive
,
1141 block_device_mapping
=block_device_mapping
1142 ) # , description=description)
1144 vm_start_time
= time
.time()
1145 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1146 if no_secured_ports
:
1147 self
.__wait
_for
_vm
(server
.id, 'ACTIVE')
1149 for port_id
in no_secured_ports
:
1151 self
.neutron
.update_port(port_id
,
1152 {"port": {"port_security_enabled": False, "security_groups": None}})
1153 except Exception as e
:
1154 raise vimconn
.vimconnException("It was not possible to disable port security for port {}".format(
1156 # print "DONE :-)", server
1159 if external_network
:
1160 floating_ips
= self
.neutron
.list_floatingips().get("floatingips", ())
1161 for floating_network
in external_network
:
1166 ip
= floating_ips
.pop(0)
1167 if ip
.get("port_id", False) or ip
.get('tenant_id') != server
.tenant_id
:
1169 if isinstance(floating_network
['floating_ip'], str):
1170 if ip
.get("floating_network_id") != floating_network
['floating_ip']:
1172 free_floating_ip
= ip
.get("floating_ip_address")
1174 if isinstance(floating_network
['floating_ip'], str) and \
1175 floating_network
['floating_ip'].lower() != "true":
1176 pool_id
= floating_network
['floating_ip']
1178 # Find the external network
1179 external_nets
= list()
1180 for net
in self
.neutron
.list_networks()['networks']:
1181 if net
['router:external']:
1182 external_nets
.append(net
)
1184 if len(external_nets
) == 0:
1185 raise vimconn
.vimconnException("Cannot create floating_ip automatically since no external "
1186 "network is present",
1187 http_code
=vimconn
.HTTP_Conflict
)
1188 if len(external_nets
) > 1:
1189 raise vimconn
.vimconnException("Cannot create floating_ip automatically since multiple "
1190 "external networks are present",
1191 http_code
=vimconn
.HTTP_Conflict
)
1193 pool_id
= external_nets
[0].get('id')
1194 param
= {'floatingip': {'floating_network_id': pool_id
, 'tenant_id': server
.tenant_id
}}
1196 # self.logger.debug("Creating floating IP")
1197 new_floating_ip
= self
.neutron
.create_floatingip(param
)
1198 free_floating_ip
= new_floating_ip
['floatingip']['floating_ip_address']
1199 except Exception as e
:
1200 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot create new floating_ip " +
1201 str(e
), http_code
=vimconn
.HTTP_Conflict
)
1203 fix_ip
= floating_network
.get('ip')
1206 server
.add_floating_ip(free_floating_ip
, fix_ip
)
1208 except Exception as e
:
1209 # openstack need some time after VM creation to asign an IP. So retry if fails
1210 vm_status
= self
.nova
.servers
.get(server
.id).status
1211 if vm_status
!= 'ACTIVE' and vm_status
!= 'ERROR':
1212 if time
.time() - vm_start_time
< server_timeout
:
1215 raise vimconn
.vimconnException(
1216 "Cannot create floating_ip: {} {}".format(type(e
).__name
__, e
),
1217 http_code
=vimconn
.HTTP_Conflict
)
1219 except Exception as e
:
1220 if not floating_network
['exit_on_floating_ip_error']:
1221 self
.logger
.warn("Cannot create floating_ip. %s", str(e
))
1225 return server
.id, created_items
1226 # except nvExceptions.NotFound as e:
1227 # error_value=-vimconn.HTTP_Not_Found
1228 # error_text= "vm instance %s not found" % vm_id
1229 # except TypeError as e:
1230 # raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1232 except Exception as e
:
1235 server_id
= server
.id
1237 self
.delete_vminstance(server_id
, created_items
)
1238 except Exception as e2
:
1239 self
.logger
.error("new_vminstance rollback fail {}".format(e2
))
1241 self
._format
_exception
(e
)
1243 def get_vminstance(self
,vm_id
):
1244 '''Returns the VM instance information from VIM'''
1245 #self.logger.debug("Getting VM from VIM")
1247 self
._reload
_connection
()
1248 server
= self
.nova
.servers
.find(id=vm_id
)
1249 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
1250 return server
.to_dict()
1251 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1252 self
._format
_exception
(e
)
1254 def get_vminstance_console(self
,vm_id
, console_type
="vnc"):
1256 Get a console for the virtual machine
1258 vm_id: uuid of the VM
1259 console_type, can be:
1260 "novnc" (by default), "xvpvnc" for VNC types,
1261 "rdp-html5" for RDP types, "spice-html5" for SPICE types
1262 Returns dict with the console parameters:
1263 protocol: ssh, ftp, http, https, ...
1264 server: usually ip address
1265 port: the http, ssh, ... port
1266 suffix: extra text, e.g. the http path and query string
1268 self
.logger
.debug("Getting VM CONSOLE from VIM")
1270 self
._reload
_connection
()
1271 server
= self
.nova
.servers
.find(id=vm_id
)
1272 if console_type
== None or console_type
== "novnc":
1273 console_dict
= server
.get_vnc_console("novnc")
1274 elif console_type
== "xvpvnc":
1275 console_dict
= server
.get_vnc_console(console_type
)
1276 elif console_type
== "rdp-html5":
1277 console_dict
= server
.get_rdp_console(console_type
)
1278 elif console_type
== "spice-html5":
1279 console_dict
= server
.get_spice_console(console_type
)
1281 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
), http_code
=vimconn
.HTTP_Bad_Request
)
1283 console_dict1
= console_dict
.get("console")
1285 console_url
= console_dict1
.get("url")
1288 protocol_index
= console_url
.find("//")
1289 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1290 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1291 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1292 return -vimconn
.HTTP_Internal_Server_Error
, "Unexpected response from VIM"
1293 console_dict
={"protocol": console_url
[0:protocol_index
],
1294 "server": console_url
[protocol_index
+2:port_index
],
1295 "port": console_url
[port_index
:suffix_index
],
1296 "suffix": console_url
[suffix_index
+1:]
1300 raise vimconn
.vimconnUnexpectedResponse("Unexpected response from VIM")
1302 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.BadRequest
, ConnectionError
) as e
:
1303 self
._format
_exception
(e
)
1305 def delete_vminstance(self
, vm_id
, created_items
=None):
1306 '''Removes a VM instance from VIM. Returns the old identifier
1308 #print "osconnector: Getting VM from VIM"
1309 if created_items
== None:
1312 self
._reload
_connection
()
1313 # delete VM ports attached to this networks before the virtual machine
1314 for k
, v
in created_items
.items():
1315 if not v
: # skip already deleted
1318 k_item
, _
, k_id
= k
.partition(":")
1319 if k_item
== "port":
1320 self
.neutron
.delete_port(k_id
)
1321 except Exception as e
:
1322 self
.logger
.error("Error deleting port: {}: {}".format(type(e
).__name
__, e
))
1324 # #commented because detaching the volumes makes the servers.delete not work properly ?!?
1325 # #dettach volumes attached
1326 # server = self.nova.servers.get(vm_id)
1327 # volumes_attached_dict = server._info['os-extended-volumes:volumes_attached'] #volume['id']
1328 # #for volume in volumes_attached_dict:
1329 # # self.cinder.volumes.detach(volume['id'])
1332 self
.nova
.servers
.delete(vm_id
)
1334 # delete volumes. Although having detached, they should have in active status before deleting
1335 # we ensure in this loop
1338 while keep_waiting
and elapsed_time
< volume_timeout
:
1339 keep_waiting
= False
1340 for k
, v
in created_items
.items():
1341 if not v
: # skip already deleted
1344 k_item
, _
, k_id
= k
.partition(":")
1345 if k_item
== "volume":
1346 if self
.cinder
.volumes
.get(k_id
).status
!= 'available':
1349 self
.cinder
.volumes
.delete(k_id
)
1350 except Exception as e
:
1351 self
.logger
.error("Error deleting volume: {}: {}".format(type(e
).__name
__, e
))
1356 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
1357 self
._format
_exception
(e
)
1359 def refresh_vms_status(self
, vm_list
):
1360 '''Get the status of the virtual machines and their interfaces/ports
1361 Params: the list of VM identifiers
1362 Returns a dictionary with:
1363 vm_id: #VIM id of this Virtual Machine
1364 status: #Mandatory. Text with one of:
1365 # DELETED (not found at vim)
1366 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1367 # OTHER (Vim reported other status not understood)
1368 # ERROR (VIM indicates an ERROR status)
1369 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1370 # CREATING (on building process), ERROR
1371 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1373 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1374 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1376 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1377 mac_address: #Text format XX:XX:XX:XX:XX:XX
1378 vim_net_id: #network id where this interface is connected
1379 vim_interface_id: #interface/port VIM id
1380 ip_address: #null, or text with IPv4, IPv6 address
1381 compute_node: #identification of compute node where PF,VF interface is allocated
1382 pci: #PCI address of the NIC that hosts the PF,VF
1383 vlan: #physical VLAN used for VF
1386 self
.logger
.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1387 for vm_id
in vm_list
:
1390 vm_vim
= self
.get_vminstance(vm_id
)
1391 if vm_vim
['status'] in vmStatus2manoFormat
:
1392 vm
['status'] = vmStatus2manoFormat
[ vm_vim
['status'] ]
1394 vm
['status'] = "OTHER"
1395 vm
['error_msg'] = "VIM status reported " + vm_vim
['status']
1397 vm
['vim_info'] = yaml
.safe_dump(vm_vim
, default_flow_style
=True, width
=256)
1398 except yaml
.representer
.RepresenterError
:
1399 vm
['vim_info'] = str(vm_vim
)
1400 vm
["interfaces"] = []
1401 if vm_vim
.get('fault'):
1402 vm
['error_msg'] = str(vm_vim
['fault'])
1405 self
._reload
_connection
()
1406 port_dict
= self
.neutron
.list_ports(device_id
=vm_id
)
1407 for port
in port_dict
["ports"]:
1410 interface
['vim_info'] = yaml
.safe_dump(port
, default_flow_style
=True, width
=256)
1411 except yaml
.representer
.RepresenterError
:
1412 interface
['vim_info'] = str(port
)
1413 interface
["mac_address"] = port
.get("mac_address")
1414 interface
["vim_net_id"] = port
["network_id"]
1415 interface
["vim_interface_id"] = port
["id"]
1416 # check if OS-EXT-SRV-ATTR:host is there,
1417 # in case of non-admin credentials, it will be missing
1418 if vm_vim
.get('OS-EXT-SRV-ATTR:host'):
1419 interface
["compute_node"] = vm_vim
['OS-EXT-SRV-ATTR:host']
1420 interface
["pci"] = None
1422 # check if binding:profile is there,
1423 # in case of non-admin credentials, it will be missing
1424 if port
.get('binding:profile'):
1425 if port
['binding:profile'].get('pci_slot'):
1426 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1427 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1428 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1429 pci
= port
['binding:profile']['pci_slot']
1430 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1431 interface
["pci"] = pci
1432 interface
["vlan"] = None
1433 #if network is of type vlan and port is of type direct (sr-iov) then set vlan id
1434 network
= self
.neutron
.show_network(port
["network_id"])
1435 if network
['network'].get('provider:network_type') == 'vlan' and \
1436 port
.get("binding:vnic_type") == "direct":
1437 interface
["vlan"] = network
['network'].get('provider:segmentation_id')
1439 #look for floating ip address
1441 floating_ip_dict
= self
.neutron
.list_floatingips(port_id
=port
["id"])
1442 if floating_ip_dict
.get("floatingips"):
1443 ips
.append(floating_ip_dict
["floatingips"][0].get("floating_ip_address") )
1447 for subnet
in port
["fixed_ips"]:
1448 ips
.append(subnet
["ip_address"])
1449 interface
["ip_address"] = ";".join(ips
)
1450 vm
["interfaces"].append(interface
)
1451 except Exception as e
:
1452 self
.logger
.error("Error getting vm interface information {}: {}".format(type(e
).__name
__, e
),
1454 except vimconn
.vimconnNotFoundException
as e
:
1455 self
.logger
.error("Exception getting vm status: %s", str(e
))
1456 vm
['status'] = "DELETED"
1457 vm
['error_msg'] = str(e
)
1458 except vimconn
.vimconnException
as e
:
1459 self
.logger
.error("Exception getting vm status: %s", str(e
))
1460 vm
['status'] = "VIM_ERROR"
1461 vm
['error_msg'] = str(e
)
1465 def action_vminstance(self
, vm_id
, action_dict
, created_items
={}):
1466 '''Send and action over a VM instance from VIM
1467 Returns None or the console dict if the action was successfully sent to the VIM'''
1468 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1470 self
._reload
_connection
()
1471 server
= self
.nova
.servers
.find(id=vm_id
)
1472 if "start" in action_dict
:
1473 if action_dict
["start"]=="rebuild":
1476 if server
.status
=="PAUSED":
1478 elif server
.status
=="SUSPENDED":
1480 elif server
.status
=="SHUTOFF":
1482 elif "pause" in action_dict
:
1484 elif "resume" in action_dict
:
1486 elif "shutoff" in action_dict
or "shutdown" in action_dict
:
1488 elif "forceOff" in action_dict
:
1490 elif "terminate" in action_dict
:
1492 elif "createImage" in action_dict
:
1493 server
.create_image()
1494 #"path":path_schema,
1495 #"description":description_schema,
1496 #"name":name_schema,
1497 #"metadata":metadata_schema,
1498 #"imageRef": id_schema,
1499 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1500 elif "rebuild" in action_dict
:
1501 server
.rebuild(server
.image
['id'])
1502 elif "reboot" in action_dict
:
1503 server
.reboot() #reboot_type='SOFT'
1504 elif "console" in action_dict
:
1505 console_type
= action_dict
["console"]
1506 if console_type
== None or console_type
== "novnc":
1507 console_dict
= server
.get_vnc_console("novnc")
1508 elif console_type
== "xvpvnc":
1509 console_dict
= server
.get_vnc_console(console_type
)
1510 elif console_type
== "rdp-html5":
1511 console_dict
= server
.get_rdp_console(console_type
)
1512 elif console_type
== "spice-html5":
1513 console_dict
= server
.get_spice_console(console_type
)
1515 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
),
1516 http_code
=vimconn
.HTTP_Bad_Request
)
1518 console_url
= console_dict
["console"]["url"]
1520 protocol_index
= console_url
.find("//")
1521 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1522 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1523 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1524 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1525 console_dict2
={"protocol": console_url
[0:protocol_index
],
1526 "server": console_url
[protocol_index
+2 : port_index
],
1527 "port": int(console_url
[port_index
+1 : suffix_index
]),
1528 "suffix": console_url
[suffix_index
+1:]
1530 return console_dict2
1531 except Exception as e
:
1532 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1535 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1536 self
._format
_exception
(e
)
1537 #TODO insert exception vimconn.HTTP_Unauthorized
1539 ####### VIO Specific Changes #########
1540 def _genrate_vlanID(self
):
1542 Method to get unused vlanID
1550 networks
= self
.get_network_list()
1551 for net
in networks
:
1552 if net
.get('provider:segmentation_id'):
1553 usedVlanIDs
.append(net
.get('provider:segmentation_id'))
1554 used_vlanIDs
= set(usedVlanIDs
)
1556 #find unused VLAN ID
1557 for vlanID_range
in self
.config
.get('dataplane_net_vlan_range'):
1559 start_vlanid
, end_vlanid
= map(int, vlanID_range
.replace(" ", "").split("-"))
1560 for vlanID
in xrange(start_vlanid
, end_vlanid
+ 1):
1561 if vlanID
not in used_vlanIDs
:
1563 except Exception as exp
:
1564 raise vimconn
.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp
))
1566 raise vimconn
.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1567 " All given Vlan IDs {} are in use.".format(self
.config
.get('dataplane_net_vlan_range')))
1570 def _validate_vlan_ranges(self
, dataplane_net_vlan_range
):
1572 Method to validate user given vlanID ranges
1576 for vlanID_range
in dataplane_net_vlan_range
:
1577 vlan_range
= vlanID_range
.replace(" ", "")
1579 vlanID_pattern
= r
'(\d)*-(\d)*$'
1580 match_obj
= re
.match(vlanID_pattern
, vlan_range
)
1582 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1583 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range
))
1585 start_vlanid
, end_vlanid
= map(int,vlan_range
.split("-"))
1586 if start_vlanid
<= 0 :
1587 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1588 "Start ID can not be zero. For VLAN "\
1589 "networks valid IDs are 1 to 4094 ".format(vlanID_range
))
1590 if end_vlanid
> 4094 :
1591 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1592 "End VLAN ID can not be greater than 4094. For VLAN "\
1593 "networks valid IDs are 1 to 4094 ".format(vlanID_range
))
1595 if start_vlanid
> end_vlanid
:
1596 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1597 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1598 "start_ID < end_ID ".format(vlanID_range
))
1602 def new_external_port(self
, port_data
):
1603 #TODO openstack if needed
1604 '''Adds a external port to VIM'''
1605 '''Returns the port identifier'''
1606 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.new_external_port() not implemented"
1608 def connect_port_network(self
, port_id
, network_id
, admin
=False):
1609 #TODO openstack if needed
1610 '''Connects a external port to a network'''
1611 '''Returns status code of the VIM response'''
1612 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.connect_port_network() not implemented"
1614 def new_user(self
, user_name
, user_passwd
, tenant_id
=None):
1615 '''Adds a new user to openstack VIM'''
1616 '''Returns the user identifier'''
1617 self
.logger
.debug("osconnector: Adding a new user to VIM")
1619 self
._reload
_connection
()
1620 user
=self
.keystone
.users
.create(user_name
, user_passwd
, tenant_id
=tenant_id
)
1621 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1623 except ksExceptions
.ConnectionError
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 except ksExceptions
.ClientException
as e
: #TODO remove
1627 error_value
=-vimconn
.HTTP_Bad_Request
1628 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1629 #TODO insert exception vimconn.HTTP_Unauthorized
1630 #if reaching here is because an exception
1631 self
.logger
.debug("new_user " + error_text
)
1632 return error_value
, error_text
1634 def delete_user(self
, user_id
):
1635 '''Delete a user from openstack VIM'''
1636 '''Returns the user identifier'''
1638 print("osconnector: Deleting a user from VIM")
1640 self
._reload
_connection
()
1641 self
.keystone
.users
.delete(user_id
)
1643 except ksExceptions
.ConnectionError
as e
:
1644 error_value
=-vimconn
.HTTP_Bad_Request
1645 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1646 except ksExceptions
.NotFound
as e
:
1647 error_value
=-vimconn
.HTTP_Not_Found
1648 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1649 except ksExceptions
.ClientException
as e
: #TODO remove
1650 error_value
=-vimconn
.HTTP_Bad_Request
1651 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1652 #TODO insert exception vimconn.HTTP_Unauthorized
1653 #if reaching here is because an exception
1654 self
.logger
.debug("delete_tenant " + error_text
)
1655 return error_value
, error_text
1657 def get_hosts_info(self
):
1658 '''Get the information of deployed hosts
1659 Returns the hosts content'''
1661 print("osconnector: Getting Host info from VIM")
1664 self
._reload
_connection
()
1665 hypervisors
= self
.nova
.hypervisors
.list()
1666 for hype
in hypervisors
:
1667 h_list
.append( hype
.to_dict() )
1668 return 1, {"hosts":h_list
}
1669 except nvExceptions
.NotFound
as e
:
1670 error_value
=-vimconn
.HTTP_Not_Found
1671 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1672 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1673 error_value
=-vimconn
.HTTP_Bad_Request
1674 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1675 #TODO insert exception vimconn.HTTP_Unauthorized
1676 #if reaching here is because an exception
1677 self
.logger
.debug("get_hosts_info " + error_text
)
1678 return error_value
, error_text
1680 def get_hosts(self
, vim_tenant
):
1681 '''Get the hosts and deployed instances
1682 Returns the hosts content'''
1683 r
, hype_dict
= self
.get_hosts_info()
1686 hypervisors
= hype_dict
["hosts"]
1688 servers
= self
.nova
.servers
.list()
1689 for hype
in hypervisors
:
1690 for server
in servers
:
1691 if server
.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype
['hypervisor_hostname']:
1693 hype
['vm'].append(server
.id)
1695 hype
['vm'] = [server
.id]
1697 except nvExceptions
.NotFound
as e
:
1698 error_value
=-vimconn
.HTTP_Not_Found
1699 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1700 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1701 error_value
=-vimconn
.HTTP_Bad_Request
1702 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1703 #TODO insert exception vimconn.HTTP_Unauthorized
1704 #if reaching here is because an exception
1705 self
.logger
.debug("get_hosts " + error_text
)
1706 return error_value
, error_text
1708 def new_classification(self
, name
, ctype
, definition
):
1710 'Adding a new (Traffic) Classification to VIM, named %s', name
)
1713 self
._reload
_connection
()
1714 if ctype
not in supportedClassificationTypes
:
1715 raise vimconn
.vimconnNotSupportedException(
1716 'OpenStack VIM connector doesn\'t support provided '
1717 'Classification Type {}, supported ones are: '
1718 '{}'.format(ctype
, supportedClassificationTypes
))
1719 if not self
._validate
_classification
(ctype
, definition
):
1720 raise vimconn
.vimconnException(
1721 'Incorrect Classification definition '
1722 'for the type specified.')
1723 classification_dict
= definition
1724 classification_dict
['name'] = name
1726 new_class
= self
.neutron
.create_sfc_flow_classifier(
1727 {'flow_classifier': classification_dict
})
1728 return new_class
['flow_classifier']['id']
1729 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1730 neExceptions
.NeutronException
, ConnectionError
) as e
:
1732 'Creation of Classification failed.')
1733 self
._format
_exception
(e
)
1735 def get_classification(self
, class_id
):
1736 self
.logger
.debug(" Getting Classification %s from VIM", class_id
)
1737 filter_dict
= {"id": class_id
}
1738 class_list
= self
.get_classification_list(filter_dict
)
1739 if len(class_list
) == 0:
1740 raise vimconn
.vimconnNotFoundException(
1741 "Classification '{}' not found".format(class_id
))
1742 elif len(class_list
) > 1:
1743 raise vimconn
.vimconnConflictException(
1744 "Found more than one Classification with this criteria")
1745 classification
= class_list
[0]
1746 return classification
1748 def get_classification_list(self
, filter_dict
={}):
1749 self
.logger
.debug("Getting Classifications from VIM filter: '%s'",
1752 filter_dict_os
= filter_dict
.copy()
1753 self
._reload
_connection
()
1754 if self
.api_version3
and "tenant_id" in filter_dict_os
:
1755 filter_dict_os
['project_id'] = filter_dict_os
.pop('tenant_id')
1756 classification_dict
= self
.neutron
.list_sfc_flow_classifiers(
1758 classification_list
= classification_dict
["flow_classifiers"]
1759 self
.__classification
_os
2mano
(classification_list
)
1760 return classification_list
1761 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1762 neExceptions
.NeutronException
, ConnectionError
) as e
:
1763 self
._format
_exception
(e
)
1765 def delete_classification(self
, class_id
):
1766 self
.logger
.debug("Deleting Classification '%s' from VIM", class_id
)
1768 self
._reload
_connection
()
1769 self
.neutron
.delete_sfc_flow_classifier(class_id
)
1771 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1772 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1773 ConnectionError
) as e
:
1774 self
._format
_exception
(e
)
1776 def new_sfi(self
, name
, ingress_ports
, egress_ports
, sfc_encap
=True):
1778 "Adding a new Service Function Instance to VIM, named '%s'", name
)
1781 self
._reload
_connection
()
1785 if len(ingress_ports
) != 1:
1786 raise vimconn
.vimconnNotSupportedException(
1787 "OpenStack VIM connector can only have "
1788 "1 ingress port per SFI")
1789 if len(egress_ports
) != 1:
1790 raise vimconn
.vimconnNotSupportedException(
1791 "OpenStack VIM connector can only have "
1792 "1 egress port per SFI")
1793 sfi_dict
= {'name': name
,
1794 'ingress': ingress_ports
[0],
1795 'egress': egress_ports
[0],
1796 'service_function_parameters': {
1797 'correlation': correlation
}}
1798 new_sfi
= self
.neutron
.create_sfc_port_pair({'port_pair': sfi_dict
})
1799 return new_sfi
['port_pair']['id']
1800 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1801 neExceptions
.NeutronException
, ConnectionError
) as e
:
1804 self
.neutron
.delete_sfc_port_pair(
1805 new_sfi
['port_pair']['id'])
1808 'Creation of Service Function Instance failed, with '
1809 'subsequent deletion failure as well.')
1810 self
._format
_exception
(e
)
1812 def get_sfi(self
, sfi_id
):
1814 'Getting Service Function Instance %s from VIM', sfi_id
)
1815 filter_dict
= {"id": sfi_id
}
1816 sfi_list
= self
.get_sfi_list(filter_dict
)
1817 if len(sfi_list
) == 0:
1818 raise vimconn
.vimconnNotFoundException(
1819 "Service Function Instance '{}' not found".format(sfi_id
))
1820 elif len(sfi_list
) > 1:
1821 raise vimconn
.vimconnConflictException(
1822 'Found more than one Service Function Instance '
1823 'with this criteria')
1827 def get_sfi_list(self
, filter_dict
={}):
1828 self
.logger
.debug("Getting Service Function Instances from "
1829 "VIM filter: '%s'", str(filter_dict
))
1831 self
._reload
_connection
()
1832 filter_dict_os
= filter_dict
.copy()
1833 if self
.api_version3
and "tenant_id" in filter_dict_os
:
1834 filter_dict_os
['project_id'] = filter_dict_os
.pop('tenant_id')
1835 sfi_dict
= self
.neutron
.list_sfc_port_pairs(**filter_dict_os
)
1836 sfi_list
= sfi_dict
["port_pairs"]
1837 self
.__sfi
_os
2mano
(sfi_list
)
1839 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1840 neExceptions
.NeutronException
, ConnectionError
) as e
:
1841 self
._format
_exception
(e
)
1843 def delete_sfi(self
, sfi_id
):
1844 self
.logger
.debug("Deleting Service Function Instance '%s' "
1847 self
._reload
_connection
()
1848 self
.neutron
.delete_sfc_port_pair(sfi_id
)
1850 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1851 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1852 ConnectionError
) as e
:
1853 self
._format
_exception
(e
)
1855 def new_sf(self
, name
, sfis
, sfc_encap
=True):
1856 self
.logger
.debug("Adding a new Service Function to VIM, "
1860 self
._reload
_connection
()
1861 # correlation = None
1863 # correlation = 'nsh'
1864 for instance
in sfis
:
1865 sfi
= self
.get_sfi(instance
)
1866 if sfi
.get('sfc_encap') != sfc_encap
:
1867 raise vimconn
.vimconnNotSupportedException(
1868 "OpenStack VIM connector requires all SFIs of the "
1869 "same SF to share the same SFC Encapsulation")
1870 sf_dict
= {'name': name
,
1872 new_sf
= self
.neutron
.create_sfc_port_pair_group({
1873 'port_pair_group': sf_dict
})
1874 return new_sf
['port_pair_group']['id']
1875 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1876 neExceptions
.NeutronException
, ConnectionError
) as e
:
1879 self
.neutron
.delete_sfc_port_pair_group(
1880 new_sf
['port_pair_group']['id'])
1883 'Creation of Service Function failed, with '
1884 'subsequent deletion failure as well.')
1885 self
._format
_exception
(e
)
1887 def get_sf(self
, sf_id
):
1888 self
.logger
.debug("Getting Service Function %s from VIM", sf_id
)
1889 filter_dict
= {"id": sf_id
}
1890 sf_list
= self
.get_sf_list(filter_dict
)
1891 if len(sf_list
) == 0:
1892 raise vimconn
.vimconnNotFoundException(
1893 "Service Function '{}' not found".format(sf_id
))
1894 elif len(sf_list
) > 1:
1895 raise vimconn
.vimconnConflictException(
1896 "Found more than one Service Function with this criteria")
1900 def get_sf_list(self
, filter_dict
={}):
1901 self
.logger
.debug("Getting Service Function from VIM filter: '%s'",
1904 self
._reload
_connection
()
1905 filter_dict_os
= filter_dict
.copy()
1906 if self
.api_version3
and "tenant_id" in filter_dict_os
:
1907 filter_dict_os
['project_id'] = filter_dict_os
.pop('tenant_id')
1908 sf_dict
= self
.neutron
.list_sfc_port_pair_groups(**filter_dict_os
)
1909 sf_list
= sf_dict
["port_pair_groups"]
1910 self
.__sf
_os
2mano
(sf_list
)
1912 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1913 neExceptions
.NeutronException
, ConnectionError
) as e
:
1914 self
._format
_exception
(e
)
1916 def delete_sf(self
, sf_id
):
1917 self
.logger
.debug("Deleting Service Function '%s' from VIM", sf_id
)
1919 self
._reload
_connection
()
1920 self
.neutron
.delete_sfc_port_pair_group(sf_id
)
1922 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1923 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1924 ConnectionError
) as e
:
1925 self
._format
_exception
(e
)
1927 def new_sfp(self
, name
, classifications
, sfs
, sfc_encap
=True, spi
=None):
1928 self
.logger
.debug("Adding a new Service Function Path to VIM, "
1932 self
._reload
_connection
()
1933 # In networking-sfc the MPLS encapsulation is legacy
1934 # should be used when no full SFC Encapsulation is intended
1938 sfp_dict
= {'name': name
,
1939 'flow_classifiers': classifications
,
1940 'port_pair_groups': sfs
,
1941 'chain_parameters': {'correlation': correlation
}}
1943 sfp_dict
['chain_id'] = spi
1944 new_sfp
= self
.neutron
.create_sfc_port_chain({'port_chain': sfp_dict
})
1945 return new_sfp
["port_chain"]["id"]
1946 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1947 neExceptions
.NeutronException
, ConnectionError
) as e
:
1950 self
.neutron
.delete_sfc_port_chain(new_sfp
['port_chain']['id'])
1953 'Creation of Service Function Path failed, with '
1954 'subsequent deletion failure as well.')
1955 self
._format
_exception
(e
)
1957 def get_sfp(self
, sfp_id
):
1958 self
.logger
.debug(" Getting Service Function Path %s from VIM", sfp_id
)
1959 filter_dict
= {"id": sfp_id
}
1960 sfp_list
= self
.get_sfp_list(filter_dict
)
1961 if len(sfp_list
) == 0:
1962 raise vimconn
.vimconnNotFoundException(
1963 "Service Function Path '{}' not found".format(sfp_id
))
1964 elif len(sfp_list
) > 1:
1965 raise vimconn
.vimconnConflictException(
1966 "Found more than one Service Function Path with this criteria")
1970 def get_sfp_list(self
, filter_dict
={}):
1971 self
.logger
.debug("Getting Service Function Paths from VIM filter: "
1972 "'%s'", str(filter_dict
))
1974 self
._reload
_connection
()
1975 filter_dict_os
= filter_dict
.copy()
1976 if self
.api_version3
and "tenant_id" in filter_dict_os
:
1977 filter_dict_os
['project_id'] = filter_dict_os
.pop('tenant_id')
1978 sfp_dict
= self
.neutron
.list_sfc_port_chains(**filter_dict_os
)
1979 sfp_list
= sfp_dict
["port_chains"]
1980 self
.__sfp
_os
2mano
(sfp_list
)
1982 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1983 neExceptions
.NeutronException
, ConnectionError
) as e
:
1984 self
._format
_exception
(e
)
1986 def delete_sfp(self
, sfp_id
):
1988 "Deleting Service Function Path '%s' from VIM", sfp_id
)
1990 self
._reload
_connection
()
1991 self
.neutron
.delete_sfc_port_chain(sfp_id
)
1993 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1994 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1995 ConnectionError
) as e
:
1996 self
._format
_exception
(e
)