1 # -*- coding: utf-8 -*-
4 # Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
5 # This file is part of openmano
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
12 # http://www.apache.org/licenses/LICENSE-2.0
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
25 osconnector implements all the methods to interact with openstack using the python-neutronclient.
27 For the VNF forwarding graph, The OpenStack VIM connector calls the
28 networking-sfc Neutron extension methods, whose resources are mapped
29 to the VIM connector's SFC resources as follows:
30 - Classification (OSM) -> Flow Classifier (Neutron)
31 - Service Function Instance (OSM) -> Port Pair (Neutron)
32 - Service Function (OSM) -> Port Pair Group (Neutron)
33 - Service Function Path (OSM) -> Port Chain (Neutron)
35 __author__
= "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C., Eduardo Sousa"
36 __date__
= "$22-sep-2017 23:59:59$"
47 from pprint
import pformat
48 from types
import StringTypes
50 from novaclient
import client
as nClient
, exceptions
as nvExceptions
51 from keystoneauth1
.identity
import v2
, v3
52 from keystoneauth1
import session
53 import keystoneclient
.exceptions
as ksExceptions
54 import keystoneclient
.v3
.client
as ksClient_v3
55 import keystoneclient
.v2_0
.client
as ksClient_v2
56 from glanceclient
import client
as glClient
57 import glanceclient
.exc
as gl1Exceptions
58 from cinderclient
import client
as cClient
59 from httplib
import HTTPException
60 from neutronclient
.neutron
import client
as neClient
61 from neutronclient
.common
import exceptions
as neExceptions
62 from requests
.exceptions
import ConnectionError
65 """contain the openstack virtual machine status to openmano status"""
66 vmStatus2manoFormat
={'ACTIVE':'ACTIVE',
68 'SUSPENDED': 'SUSPENDED',
71 'ERROR':'ERROR','DELETED':'DELETED'
73 netStatus2manoFormat
={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
76 supportedClassificationTypes
= ['legacy_flow_classifier']
78 #global var to have a timeout creating and deleting volumes
83 class SafeDumper(yaml
.SafeDumper
):
84 def represent_data(self
, data
):
85 # Openstack APIs use custom subclasses of dict and YAML safe dumper
86 # is designed to not handle that (reference issue 142 of pyyaml)
87 if isinstance(data
, dict) and data
.__class
__ != dict:
88 # A simple solution is to convert those items back to dicts
89 data
= dict(data
.items())
91 return super(SafeDumper
, self
).represent_data(data
)
94 class vimconnector(vimconn
.vimconnector
):
95 def __init__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
=None, user
=None, passwd
=None,
96 log_level
=None, config
={}, persistent_info
={}):
97 '''using common constructor parameters. In this case
98 'url' is the keystone authorization url,
99 'url_admin' is not use
101 api_version
= config
.get('APIversion')
102 if api_version
and api_version
not in ('v3.3', 'v2.0', '2', '3'):
103 raise vimconn
.vimconnException("Invalid value '{}' for config:APIversion. "
104 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version
))
105 vim_type
= config
.get('vim_type')
106 if vim_type
and vim_type
not in ('vio', 'VIO'):
107 raise vimconn
.vimconnException("Invalid value '{}' for config:vim_type."
108 "Allowed values are 'vio' or 'VIO'".format(vim_type
))
110 if config
.get('dataplane_net_vlan_range') is not None:
111 #validate vlan ranges provided by user
112 self
._validate
_vlan
_ranges
(config
.get('dataplane_net_vlan_range'))
114 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
,
117 if self
.config
.get("insecure") and self
.config
.get("ca_cert"):
118 raise vimconn
.vimconnException("options insecure and ca_cert are mutually exclusive")
120 if self
.config
.get("insecure"):
122 if self
.config
.get("ca_cert"):
123 self
.verify
= self
.config
.get("ca_cert")
126 raise TypeError('url param can not be NoneType')
127 self
.persistent_info
= persistent_info
128 self
.availability_zone
= persistent_info
.get('availability_zone', None)
129 self
.session
= persistent_info
.get('session', {'reload_client': True})
130 self
.nova
= self
.session
.get('nova')
131 self
.neutron
= self
.session
.get('neutron')
132 self
.cinder
= self
.session
.get('cinder')
133 self
.glance
= self
.session
.get('glance')
134 # self.glancev1 = self.session.get('glancev1')
135 self
.keystone
= self
.session
.get('keystone')
136 self
.api_version3
= self
.session
.get('api_version3')
137 self
.vim_type
= self
.config
.get("vim_type")
139 self
.vim_type
= self
.vim_type
.upper()
140 if self
.config
.get("use_internal_endpoint"):
141 self
.endpoint_type
= "internalURL"
143 self
.endpoint_type
= None
145 self
.logger
= logging
.getLogger('openmano.vim.openstack')
147 ####### VIO Specific Changes #########
148 if self
.vim_type
== "VIO":
149 self
.logger
= logging
.getLogger('openmano.vim.vio')
152 self
.logger
.setLevel( getattr(logging
, log_level
))
154 def __getitem__(self
, index
):
155 """Get individuals parameters.
157 if index
== 'project_domain_id':
158 return self
.config
.get("project_domain_id")
159 elif index
== 'user_domain_id':
160 return self
.config
.get("user_domain_id")
162 return vimconn
.vimconnector
.__getitem
__(self
, index
)
164 def __setitem__(self
, index
, value
):
165 """Set individuals parameters and it is marked as dirty so to force connection reload.
167 if index
== 'project_domain_id':
168 self
.config
["project_domain_id"] = value
169 elif index
== 'user_domain_id':
170 self
.config
["user_domain_id"] = value
172 vimconn
.vimconnector
.__setitem
__(self
, index
, value
)
173 self
.session
['reload_client'] = True
175 def serialize(self
, value
):
176 """Serialization of python basic types.
178 In the case value is not serializable a message will be logged and a
179 simple representation of the data that cannot be converted back to
182 if isinstance(value
, StringTypes
):
186 return yaml
.dump(value
, Dumper
=SafeDumper
,
187 default_flow_style
=True, width
=256)
188 except yaml
.representer
.RepresenterError
:
190 'The following entity cannot be serialized in YAML:'
191 '\n\n%s\n\n', pformat(value
), exc_info
=True)
194 def _reload_connection(self
):
195 '''Called before any operation, it check if credentials has changed
196 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
198 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
199 if self
.session
['reload_client']:
200 if self
.config
.get('APIversion'):
201 self
.api_version3
= self
.config
['APIversion'] == 'v3.3' or self
.config
['APIversion'] == '3'
202 else: # get from ending auth_url that end with v3 or with v2.0
203 self
.api_version3
= self
.url
.endswith("/v3") or self
.url
.endswith("/v3/")
204 self
.session
['api_version3'] = self
.api_version3
205 if self
.api_version3
:
206 if self
.config
.get('project_domain_id') or self
.config
.get('project_domain_name'):
207 project_domain_id_default
= None
209 project_domain_id_default
= 'default'
210 if self
.config
.get('user_domain_id') or self
.config
.get('user_domain_name'):
211 user_domain_id_default
= None
213 user_domain_id_default
= 'default'
214 auth
= v3
.Password(auth_url
=self
.url
,
216 password
=self
.passwd
,
217 project_name
=self
.tenant_name
,
218 project_id
=self
.tenant_id
,
219 project_domain_id
=self
.config
.get('project_domain_id', project_domain_id_default
),
220 user_domain_id
=self
.config
.get('user_domain_id', user_domain_id_default
),
221 project_domain_name
=self
.config
.get('project_domain_name'),
222 user_domain_name
=self
.config
.get('user_domain_name'))
224 auth
= v2
.Password(auth_url
=self
.url
,
226 password
=self
.passwd
,
227 tenant_name
=self
.tenant_name
,
228 tenant_id
=self
.tenant_id
)
229 sess
= session
.Session(auth
=auth
, verify
=self
.verify
)
230 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River Titanium cloud and StarlingX
231 region_name
= self
.config
.get('region_name')
232 if self
.api_version3
:
233 self
.keystone
= ksClient_v3
.Client(session
=sess
, endpoint_type
=self
.endpoint_type
, region_name
=region_name
)
235 self
.keystone
= ksClient_v2
.Client(session
=sess
, endpoint_type
=self
.endpoint_type
)
236 self
.session
['keystone'] = self
.keystone
237 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
238 # This implementation approach is due to the warning message in
239 # https://developer.openstack.org/api-guide/compute/microversions.html
240 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
241 # always require an specific microversion.
242 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
243 version
= self
.config
.get("microversion")
246 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River Titanium cloud and StarlingX
247 self
.nova
= self
.session
['nova'] = nClient
.Client(str(version
), session
=sess
, endpoint_type
=self
.endpoint_type
, region_name
=region_name
)
248 self
.neutron
= self
.session
['neutron'] = neClient
.Client('2.0', session
=sess
, endpoint_type
=self
.endpoint_type
, region_name
=region_name
)
249 self
.cinder
= self
.session
['cinder'] = cClient
.Client(2, session
=sess
, endpoint_type
=self
.endpoint_type
, region_name
=region_name
)
250 if self
.endpoint_type
== "internalURL":
251 glance_service_id
= self
.keystone
.services
.list(name
="glance")[0].id
252 glance_endpoint
= self
.keystone
.endpoints
.list(glance_service_id
, interface
="internal")[0].url
254 glance_endpoint
= None
255 self
.glance
= self
.session
['glance'] = glClient
.Client(2, session
=sess
, endpoint
=glance_endpoint
)
256 #using version 1 of glance client in new_image()
257 # self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
258 # endpoint=glance_endpoint)
259 self
.session
['reload_client'] = False
260 self
.persistent_info
['session'] = self
.session
261 # add availablity zone info inside self.persistent_info
262 self
._set
_availablity
_zones
()
263 self
.persistent_info
['availability_zone'] = self
.availability_zone
265 def __net_os2mano(self
, net_list_dict
):
266 '''Transform the net openstack format to mano format
267 net_list_dict can be a list of dict or a single dict'''
268 if type(net_list_dict
) is dict:
269 net_list_
=(net_list_dict
,)
270 elif type(net_list_dict
) is list:
271 net_list_
=net_list_dict
273 raise TypeError("param net_list_dict must be a list or a dictionary")
274 for net
in net_list_
:
275 if net
.get('provider:network_type') == "vlan":
280 def __classification_os2mano(self
, class_list_dict
):
281 """Transform the openstack format (Flow Classifier) to mano format
282 (Classification) class_list_dict can be a list of dict or a single dict
284 if isinstance(class_list_dict
, dict):
285 class_list_
= [class_list_dict
]
286 elif isinstance(class_list_dict
, list):
287 class_list_
= class_list_dict
290 "param class_list_dict must be a list or a dictionary")
291 for classification
in class_list_
:
292 id = classification
.pop('id')
293 name
= classification
.pop('name')
294 description
= classification
.pop('description')
295 project_id
= classification
.pop('project_id')
296 tenant_id
= classification
.pop('tenant_id')
297 original_classification
= copy
.deepcopy(classification
)
298 classification
.clear()
299 classification
['ctype'] = 'legacy_flow_classifier'
300 classification
['definition'] = original_classification
301 classification
['id'] = id
302 classification
['name'] = name
303 classification
['description'] = description
304 classification
['project_id'] = project_id
305 classification
['tenant_id'] = tenant_id
307 def __sfi_os2mano(self
, sfi_list_dict
):
308 """Transform the openstack format (Port Pair) to mano format (SFI)
309 sfi_list_dict can be a list of dict or a single dict
311 if isinstance(sfi_list_dict
, dict):
312 sfi_list_
= [sfi_list_dict
]
313 elif isinstance(sfi_list_dict
, list):
314 sfi_list_
= sfi_list_dict
317 "param sfi_list_dict must be a list or a dictionary")
318 for sfi
in sfi_list_
:
319 sfi
['ingress_ports'] = []
320 sfi
['egress_ports'] = []
321 if sfi
.get('ingress'):
322 sfi
['ingress_ports'].append(sfi
['ingress'])
323 if sfi
.get('egress'):
324 sfi
['egress_ports'].append(sfi
['egress'])
327 params
= sfi
.get('service_function_parameters')
330 correlation
= params
.get('correlation')
333 sfi
['sfc_encap'] = sfc_encap
334 del sfi
['service_function_parameters']
336 def __sf_os2mano(self
, sf_list_dict
):
337 """Transform the openstack format (Port Pair Group) to mano format (SF)
338 sf_list_dict can be a list of dict or a single dict
340 if isinstance(sf_list_dict
, dict):
341 sf_list_
= [sf_list_dict
]
342 elif isinstance(sf_list_dict
, list):
343 sf_list_
= sf_list_dict
346 "param sf_list_dict must be a list or a dictionary")
348 del sf
['port_pair_group_parameters']
349 sf
['sfis'] = sf
['port_pairs']
352 def __sfp_os2mano(self
, sfp_list_dict
):
353 """Transform the openstack format (Port Chain) to mano format (SFP)
354 sfp_list_dict can be a list of dict or a single dict
356 if isinstance(sfp_list_dict
, dict):
357 sfp_list_
= [sfp_list_dict
]
358 elif isinstance(sfp_list_dict
, list):
359 sfp_list_
= sfp_list_dict
362 "param sfp_list_dict must be a list or a dictionary")
363 for sfp
in sfp_list_
:
364 params
= sfp
.pop('chain_parameters')
367 correlation
= params
.get('correlation')
370 sfp
['sfc_encap'] = sfc_encap
371 sfp
['spi'] = sfp
.pop('chain_id')
372 sfp
['classifications'] = sfp
.pop('flow_classifiers')
373 sfp
['service_functions'] = sfp
.pop('port_pair_groups')
375 # placeholder for now; read TODO note below
376 def _validate_classification(self
, type, definition
):
377 # only legacy_flow_classifier Type is supported at this point
379 # TODO(igordcard): this method should be an abstract method of an
380 # abstract Classification class to be implemented by the specific
381 # Types. Also, abstract vimconnector should call the validation
382 # method before the implemented VIM connectors are called.
384 def _format_exception(self
, exception
):
385 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
386 if isinstance(exception
, (neExceptions
.NetworkNotFoundClient
, nvExceptions
.NotFound
, ksExceptions
.NotFound
, gl1Exceptions
.HTTPNotFound
)):
387 raise vimconn
.vimconnNotFoundException(type(exception
).__name
__ + ": " + str(exception
))
388 elif isinstance(exception
, (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
,
389 ConnectionError
, ksExceptions
.ConnectionError
, neExceptions
.ConnectionFailed
)):
390 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
391 elif isinstance(exception
, (KeyError, nvExceptions
.BadRequest
, ksExceptions
.BadRequest
)):
392 raise vimconn
.vimconnException(type(exception
).__name
__ + ": " + str(exception
))
393 elif isinstance(exception
, (nvExceptions
.ClientException
, ksExceptions
.ClientException
,
394 neExceptions
.NeutronException
)):
395 raise vimconn
.vimconnUnexpectedResponse(type(exception
).__name
__ + ": " + str(exception
))
396 elif isinstance(exception
, nvExceptions
.Conflict
):
397 raise vimconn
.vimconnConflictException(type(exception
).__name
__ + ": " + str(exception
))
398 elif isinstance(exception
, vimconn
.vimconnException
):
401 self
.logger
.error("General Exception " + str(exception
), exc_info
=True)
402 raise vimconn
.vimconnConnectionException(type(exception
).__name
__ + ": " + str(exception
))
404 def get_tenant_list(self
, filter_dict
={}):
405 '''Obtain tenants of VIM
406 filter_dict can contain the following keys:
407 name: filter by tenant name
408 id: filter by tenant uuid/id
410 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
412 self
.logger
.debug("Getting tenants from VIM filter: '%s'", str(filter_dict
))
414 self
._reload
_connection
()
415 if self
.api_version3
:
416 project_class_list
= self
.keystone
.projects
.list(name
=filter_dict
.get("name"))
418 project_class_list
= self
.keystone
.tenants
.findall(**filter_dict
)
420 for project
in project_class_list
:
421 if filter_dict
.get('id') and filter_dict
["id"] != project
.id:
423 project_list
.append(project
.to_dict())
425 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ConnectionError
) as e
:
426 self
._format
_exception
(e
)
428 def new_tenant(self
, tenant_name
, tenant_description
):
429 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
430 self
.logger
.debug("Adding a new tenant name: %s", tenant_name
)
432 self
._reload
_connection
()
433 if self
.api_version3
:
434 project
= self
.keystone
.projects
.create(tenant_name
, self
.config
.get("project_domain_id", "default"),
435 description
=tenant_description
, is_domain
=False)
437 project
= self
.keystone
.tenants
.create(tenant_name
, tenant_description
)
439 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ksExceptions
.BadRequest
, ConnectionError
) as e
:
440 self
._format
_exception
(e
)
442 def delete_tenant(self
, tenant_id
):
443 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
444 self
.logger
.debug("Deleting tenant %s from VIM", tenant_id
)
446 self
._reload
_connection
()
447 if self
.api_version3
:
448 self
.keystone
.projects
.delete(tenant_id
)
450 self
.keystone
.tenants
.delete(tenant_id
)
452 except (ksExceptions
.ConnectionError
, ksExceptions
.ClientException
, ksExceptions
.NotFound
, ConnectionError
) as e
:
453 self
._format
_exception
(e
)
455 def new_network(self
,net_name
, net_type
, ip_profile
=None, shared
=False, vlan
=None):
456 '''Adds a tenant network to VIM. Returns the network identifier'''
457 self
.logger
.debug("Adding a new network to VIM name '%s', type '%s'", net_name
, net_type
)
458 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
461 self
._reload
_connection
()
462 network_dict
= {'name': net_name
, 'admin_state_up': True}
463 if net_type
=="data" or net_type
=="ptp":
464 if self
.config
.get('dataplane_physical_net') == None:
465 raise vimconn
.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
466 network_dict
["provider:physical_network"] = self
.config
['dataplane_physical_net'] #"physnet_sriov" #TODO physical
467 network_dict
["provider:network_type"] = "vlan"
469 network_dict
["provider:network_type"] = vlan
471 ####### VIO Specific Changes #########
472 if self
.vim_type
== "VIO":
474 network_dict
["provider:segmentation_id"] = vlan
476 if self
.config
.get('dataplane_net_vlan_range') is None:
477 raise vimconn
.vimconnConflictException("You must provide "\
478 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
479 "at config value before creating sriov network with vlan tag")
481 network_dict
["provider:segmentation_id"] = self
._genrate
_vlanID
()
483 network_dict
["shared"]=shared
484 new_net
=self
.neutron
.create_network({'network':network_dict
})
486 #create subnetwork, even if there is no profile
489 if not ip_profile
.get('subnet_address'):
490 #Fake subnet is required
491 subnet_rand
= random
.randint(0, 255)
492 ip_profile
['subnet_address'] = "192.168.{}.0/24".format(subnet_rand
)
493 if 'ip_version' not in ip_profile
:
494 ip_profile
['ip_version'] = "IPv4"
495 subnet
= {"name":net_name
+"-subnet",
496 "network_id": new_net
["network"]["id"],
497 "ip_version": 4 if ip_profile
['ip_version']=="IPv4" else 6,
498 "cidr": ip_profile
['subnet_address']
500 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
501 if ip_profile
.get('gateway_address'):
502 subnet
['gateway_ip'] = ip_profile
['gateway_address']
504 subnet
['gateway_ip'] = None
505 if ip_profile
.get('dns_address'):
506 subnet
['dns_nameservers'] = ip_profile
['dns_address'].split(";")
507 if 'dhcp_enabled' in ip_profile
:
508 subnet
['enable_dhcp'] = False if \
509 ip_profile
['dhcp_enabled']=="false" or ip_profile
['dhcp_enabled']==False else True
510 if ip_profile
.get('dhcp_start_address'):
511 subnet
['allocation_pools'] = []
512 subnet
['allocation_pools'].append(dict())
513 subnet
['allocation_pools'][0]['start'] = ip_profile
['dhcp_start_address']
514 if ip_profile
.get('dhcp_count'):
515 #parts = ip_profile['dhcp_start_address'].split('.')
516 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
517 ip_int
= int(netaddr
.IPAddress(ip_profile
['dhcp_start_address']))
518 ip_int
+= ip_profile
['dhcp_count'] - 1
519 ip_str
= str(netaddr
.IPAddress(ip_int
))
520 subnet
['allocation_pools'][0]['end'] = ip_str
521 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
522 self
.neutron
.create_subnet({"subnet": subnet
} )
523 return new_net
["network"]["id"]
524 except Exception as e
:
526 self
.neutron
.delete_network(new_net
['network']['id'])
527 self
._format
_exception
(e
)
529 def get_network_list(self
, filter_dict
={}):
530 '''Obtain tenant networks of VIM
536 admin_state_up: boolean
538 Returns the network list of dictionaries
540 self
.logger
.debug("Getting network from VIM filter: '%s'", str(filter_dict
))
542 self
._reload
_connection
()
543 filter_dict_os
= filter_dict
.copy()
544 if self
.api_version3
and "tenant_id" in filter_dict_os
:
545 filter_dict_os
['project_id'] = filter_dict_os
.pop('tenant_id') #T ODO check
546 net_dict
= self
.neutron
.list_networks(**filter_dict_os
)
547 net_list
= net_dict
["networks"]
548 self
.__net
_os
2mano
(net_list
)
550 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
551 self
._format
_exception
(e
)
553 def get_network(self
, net_id
):
554 '''Obtain details of network from VIM
555 Returns the network information from a network id'''
556 self
.logger
.debug(" Getting tenant network %s from VIM", net_id
)
557 filter_dict
={"id": net_id
}
558 net_list
= self
.get_network_list(filter_dict
)
560 raise vimconn
.vimconnNotFoundException("Network '{}' not found".format(net_id
))
561 elif len(net_list
)>1:
562 raise vimconn
.vimconnConflictException("Found more than one network with this criteria")
565 for subnet_id
in net
.get("subnets", () ):
567 subnet
= self
.neutron
.show_subnet(subnet_id
)
568 except Exception as e
:
569 self
.logger
.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id
, str(e
)))
570 subnet
= {"id": subnet_id
, "fault": str(e
)}
571 subnets
.append(subnet
)
572 net
["subnets"] = subnets
573 net
["encapsulation"] = net
.get('provider:network_type')
574 net
["encapsulation_type"] = net
.get('provider:network_type')
575 net
["segmentation_id"] = net
.get('provider:segmentation_id')
576 net
["encapsulation_id"] = net
.get('provider:segmentation_id')
579 def delete_network(self
, net_id
):
580 '''Deletes a tenant network from VIM. Returns the old network identifier'''
581 self
.logger
.debug("Deleting network '%s' from VIM", net_id
)
583 self
._reload
_connection
()
584 #delete VM ports attached to this networks before the network
585 ports
= self
.neutron
.list_ports(network_id
=net_id
)
586 for p
in ports
['ports']:
588 self
.neutron
.delete_port(p
["id"])
589 except Exception as e
:
590 self
.logger
.error("Error deleting port %s: %s", p
["id"], str(e
))
591 self
.neutron
.delete_network(net_id
)
593 except (neExceptions
.ConnectionFailed
, neExceptions
.NetworkNotFoundClient
, neExceptions
.NeutronException
,
594 ksExceptions
.ClientException
, neExceptions
.NeutronException
, ConnectionError
) as e
:
595 self
._format
_exception
(e
)
597 def refresh_nets_status(self
, net_list
):
598 '''Get the status of the networks
599 Params: the list of network identifiers
600 Returns a dictionary with:
601 net_id: #VIM id of this network
602 status: #Mandatory. Text with one of:
603 # DELETED (not found at vim)
604 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
605 # OTHER (Vim reported other status not understood)
606 # ERROR (VIM indicates an ERROR status)
607 # ACTIVE, INACTIVE, DOWN (admin down),
608 # BUILD (on building process)
610 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
611 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
615 for net_id
in net_list
:
618 net_vim
= self
.get_network(net_id
)
619 if net_vim
['status'] in netStatus2manoFormat
:
620 net
["status"] = netStatus2manoFormat
[ net_vim
['status'] ]
622 net
["status"] = "OTHER"
623 net
["error_msg"] = "VIM status reported " + net_vim
['status']
625 if net
['status'] == "ACTIVE" and not net_vim
['admin_state_up']:
626 net
['status'] = 'DOWN'
628 net
['vim_info'] = self
.serialize(net_vim
)
630 if net_vim
.get('fault'): #TODO
631 net
['error_msg'] = str(net_vim
['fault'])
632 except vimconn
.vimconnNotFoundException
as e
:
633 self
.logger
.error("Exception getting net status: %s", str(e
))
634 net
['status'] = "DELETED"
635 net
['error_msg'] = str(e
)
636 except vimconn
.vimconnException
as e
:
637 self
.logger
.error("Exception getting net status: %s", str(e
))
638 net
['status'] = "VIM_ERROR"
639 net
['error_msg'] = str(e
)
640 net_dict
[net_id
] = net
643 def get_flavor(self
, flavor_id
):
644 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
645 self
.logger
.debug("Getting flavor '%s'", flavor_id
)
647 self
._reload
_connection
()
648 flavor
= self
.nova
.flavors
.find(id=flavor_id
)
649 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
650 return flavor
.to_dict()
651 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
652 self
._format
_exception
(e
)
654 def get_flavor_id_from_data(self
, flavor_dict
):
655 """Obtain flavor id that match the flavor description
656 Returns the flavor_id or raises a vimconnNotFoundException
657 flavor_dict: contains the required ram, vcpus, disk
658 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
659 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
660 vimconnNotFoundException is raised
662 exact_match
= False if self
.config
.get('use_existing_flavors') else True
664 self
._reload
_connection
()
665 flavor_candidate_id
= None
666 flavor_candidate_data
= (10000, 10000, 10000)
667 flavor_target
= (flavor_dict
["ram"], flavor_dict
["vcpus"], flavor_dict
["disk"])
669 numas
= flavor_dict
.get("extended", {}).get("numas")
672 raise vimconn
.vimconnNotFoundException("Flavor with EPA still not implemted")
674 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
676 # numas = extended.get("numas")
677 for flavor
in self
.nova
.flavors
.list():
678 epa
= flavor
.get_keys()
682 flavor_data
= (flavor
.ram
, flavor
.vcpus
, flavor
.disk
)
683 if flavor_data
== flavor_target
:
685 elif not exact_match
and flavor_target
< flavor_data
< flavor_candidate_data
:
686 flavor_candidate_id
= flavor
.id
687 flavor_candidate_data
= flavor_data
688 if not exact_match
and flavor_candidate_id
:
689 return flavor_candidate_id
690 raise vimconn
.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict
)))
691 except (nvExceptions
.NotFound
, nvExceptions
.ClientException
, ksExceptions
.ClientException
, ConnectionError
) as e
:
692 self
._format
_exception
(e
)
694 def new_flavor(self
, flavor_data
, change_name_if_used
=True):
695 '''Adds a tenant flavor to openstack VIM
696 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
697 Returns the flavor identifier
699 self
.logger
.debug("Adding flavor '%s'", str(flavor_data
))
704 name
=flavor_data
['name']
705 while retry
<max_retries
:
708 self
._reload
_connection
()
709 if change_name_if_used
:
712 fl
=self
.nova
.flavors
.list()
714 fl_names
.append(f
.name
)
715 while name
in fl_names
:
717 name
= flavor_data
['name']+"-" + str(name_suffix
)
719 ram
= flavor_data
.get('ram',64)
720 vcpus
= flavor_data
.get('vcpus',1)
723 extended
= flavor_data
.get("extended")
725 numas
=extended
.get("numas")
727 numa_nodes
= len(numas
)
729 return -1, "Can not add flavor with more than one numa"
730 numa_properties
= {"hw:numa_nodes":str(numa_nodes
)}
731 numa_properties
["hw:mem_page_size"] = "large"
732 numa_properties
["hw:cpu_policy"] = "dedicated"
733 numa_properties
["hw:numa_mempolicy"] = "strict"
734 if self
.vim_type
== "VIO":
735 numa_properties
["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
736 numa_properties
["vmware:latency_sensitivity_level"] = "high"
738 #overwrite ram and vcpus
739 #check if key 'memory' is present in numa else use ram value at flavor
741 ram
= numa
['memory']*1024
742 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
743 if 'paired-threads' in numa
:
744 vcpus
= numa
['paired-threads']*2
745 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
746 numa_properties
["hw:cpu_thread_policy"] = "require"
747 numa_properties
["hw:cpu_policy"] = "dedicated"
748 elif 'cores' in numa
:
749 vcpus
= numa
['cores']
750 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
751 numa_properties
["hw:cpu_thread_policy"] = "isolate"
752 numa_properties
["hw:cpu_policy"] = "dedicated"
753 elif 'threads' in numa
:
754 vcpus
= numa
['threads']
755 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
756 numa_properties
["hw:cpu_thread_policy"] = "prefer"
757 numa_properties
["hw:cpu_policy"] = "dedicated"
758 # for interface in numa.get("interfaces",() ):
759 # if interface["dedicated"]=="yes":
760 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
761 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
764 new_flavor
=self
.nova
.flavors
.create(name
,
767 flavor_data
.get('disk',0),
768 is_public
=flavor_data
.get('is_public', True)
772 new_flavor
.set_keys(numa_properties
)
774 except nvExceptions
.Conflict
as e
:
775 if change_name_if_used
and retry
< max_retries
:
777 self
._format
_exception
(e
)
778 #except nvExceptions.BadRequest as e:
779 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
, KeyError) as e
:
780 self
._format
_exception
(e
)
782 def delete_flavor(self
,flavor_id
):
783 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
786 self
._reload
_connection
()
787 self
.nova
.flavors
.delete(flavor_id
)
789 #except nvExceptions.BadRequest as e:
790 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
791 self
._format
_exception
(e
)
793 def new_image(self
,image_dict
):
795 Adds a tenant image to VIM. imge_dict is a dictionary with:
797 disk_format: qcow2, vhd, vmdk, raw (by default), ...
798 location: path or URI
799 public: "yes" or "no"
800 metadata: metadata of the image
805 while retry
<max_retries
:
808 self
._reload
_connection
()
809 #determine format http://docs.openstack.org/developer/glance/formats.html
810 if "disk_format" in image_dict
:
811 disk_format
=image_dict
["disk_format"]
812 else: #autodiscover based on extension
813 if image_dict
['location'].endswith(".qcow2"):
815 elif image_dict
['location'].endswith(".vhd"):
817 elif image_dict
['location'].endswith(".vmdk"):
819 elif image_dict
['location'].endswith(".vdi"):
821 elif image_dict
['location'].endswith(".iso"):
823 elif image_dict
['location'].endswith(".aki"):
825 elif image_dict
['location'].endswith(".ari"):
827 elif image_dict
['location'].endswith(".ami"):
831 self
.logger
.debug("new_image: '%s' loading from '%s'", image_dict
['name'], image_dict
['location'])
832 if self
.vim_type
== "VIO":
833 container_format
= "bare"
834 if 'container_format' in image_dict
:
835 container_format
= image_dict
['container_format']
836 new_image
= self
.glance
.images
.create(name
=image_dict
['name'], container_format
=container_format
,
837 disk_format
=disk_format
)
839 new_image
= self
.glance
.images
.create(name
=image_dict
['name'])
840 if image_dict
['location'].startswith("http"):
841 # TODO there is not a method to direct download. It must be downloaded locally with requests
842 raise vimconn
.vimconnNotImplemented("Cannot create image from URL")
844 with
open(image_dict
['location']) as fimage
:
845 self
.glance
.images
.upload(new_image
.id, fimage
)
846 #new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
847 # container_format="bare", data=fimage, disk_format=disk_format)
848 metadata_to_load
= image_dict
.get('metadata')
849 # TODO location is a reserved word for current openstack versions. fixed for VIO please check for openstack
850 if self
.vim_type
== "VIO":
851 metadata_to_load
['upload_location'] = image_dict
['location']
853 metadata_to_load
['location'] = image_dict
['location']
854 self
.glance
.images
.update(new_image
.id, **metadata_to_load
)
856 except (nvExceptions
.Conflict
, ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
857 self
._format
_exception
(e
)
858 except (HTTPException
, gl1Exceptions
.HTTPException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
859 if retry
==max_retries
:
861 self
._format
_exception
(e
)
862 except IOError as e
: #can not open the file
863 raise vimconn
.vimconnConnectionException(type(e
).__name
__ + ": " + str(e
)+ " for " + image_dict
['location'],
864 http_code
=vimconn
.HTTP_Bad_Request
)
866 def delete_image(self
, image_id
):
867 '''Deletes a tenant image from openstack VIM. Returns the old id
870 self
._reload
_connection
()
871 self
.glance
.images
.delete(image_id
)
873 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, gl1Exceptions
.HTTPNotFound
, ConnectionError
) as e
: #TODO remove
874 self
._format
_exception
(e
)
876 def get_image_id_from_path(self
, path
):
877 '''Get the image id from image path in the VIM database. Returns the image_id'''
879 self
._reload
_connection
()
880 images
= self
.glance
.images
.list()
882 if image
.metadata
.get("location")==path
:
884 raise vimconn
.vimconnNotFoundException("image with location '{}' not found".format( path
))
885 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
886 self
._format
_exception
(e
)
888 def get_image_list(self
, filter_dict
={}):
889 '''Obtain tenant images from VIM
893 checksum: image checksum
894 Returns the image list of dictionaries:
895 [{<the fields at Filter_dict plus some VIM specific>}, ...]
898 self
.logger
.debug("Getting image list from VIM filter: '%s'", str(filter_dict
))
900 self
._reload
_connection
()
901 filter_dict_os
= filter_dict
.copy()
902 #First we filter by the available filter fields: name, id. The others are removed.
903 image_list
= self
.glance
.images
.list()
905 for image
in image_list
:
907 if filter_dict
.get("name") and image
["name"] != filter_dict
["name"]:
909 if filter_dict
.get("id") and image
["id"] != filter_dict
["id"]:
911 if filter_dict
.get("checksum") and image
["checksum"] != filter_dict
["checksum"]:
914 filtered_list
.append(image
.copy())
915 except gl1Exceptions
.HTTPNotFound
:
918 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, gl1Exceptions
.CommunicationError
, ConnectionError
) as e
:
919 self
._format
_exception
(e
)
921 def __wait_for_vm(self
, vm_id
, status
):
922 """wait until vm is in the desired status and return True.
923 If the VM gets in ERROR status, return false.
924 If the timeout is reached generate an exception"""
926 while elapsed_time
< server_timeout
:
927 vm_status
= self
.nova
.servers
.get(vm_id
).status
928 if vm_status
== status
:
930 if vm_status
== 'ERROR':
935 # if we exceeded the timeout rollback
936 if elapsed_time
>= server_timeout
:
937 raise vimconn
.vimconnException('Timeout waiting for instance ' + vm_id
+ ' to get ' + status
,
938 http_code
=vimconn
.HTTP_Request_Timeout
)
940 def _get_openstack_availablity_zones(self
):
942 Get from openstack availability zones available
946 openstack_availability_zone
= self
.nova
.availability_zones
.list()
947 openstack_availability_zone
= [str(zone
.zoneName
) for zone
in openstack_availability_zone
948 if zone
.zoneName
!= 'internal']
949 return openstack_availability_zone
950 except Exception as e
:
953 def _set_availablity_zones(self
):
955 Set vim availablity zone
959 if 'availability_zone' in self
.config
:
960 vim_availability_zones
= self
.config
.get('availability_zone')
961 if isinstance(vim_availability_zones
, str):
962 self
.availability_zone
= [vim_availability_zones
]
963 elif isinstance(vim_availability_zones
, list):
964 self
.availability_zone
= vim_availability_zones
966 self
.availability_zone
= self
._get
_openstack
_availablity
_zones
()
968 def _get_vm_availability_zone(self
, availability_zone_index
, availability_zone_list
):
970 Return thge availability zone to be used by the created VM.
971 :return: The VIM availability zone to be used or None
973 if availability_zone_index
is None:
974 if not self
.config
.get('availability_zone'):
976 elif isinstance(self
.config
.get('availability_zone'), str):
977 return self
.config
['availability_zone']
979 # TODO consider using a different parameter at config for default AV and AV list match
980 return self
.config
['availability_zone'][0]
982 vim_availability_zones
= self
.availability_zone
983 # check if VIM offer enough availability zones describe in the VNFD
984 if vim_availability_zones
and len(availability_zone_list
) <= len(vim_availability_zones
):
985 # check if all the names of NFV AV match VIM AV names
986 match_by_index
= False
987 for av
in availability_zone_list
:
988 if av
not in vim_availability_zones
:
989 match_by_index
= True
992 return vim_availability_zones
[availability_zone_index
]
994 return availability_zone_list
[availability_zone_index
]
996 raise vimconn
.vimconnConflictException("No enough availability zones at VIM for this deployment")
998 def new_vminstance(self
, name
, description
, start
, image_id
, flavor_id
, net_list
, cloud_config
=None, disk_list
=None,
999 availability_zone_index
=None, availability_zone_list
=None):
1000 """Adds a VM instance to VIM
1002 start: indicates if VM must start or boot in pause mode. Ignored
1003 image_id,flavor_id: iamge and flavor uuid
1004 net_list: list of interfaces, each one is a dictionary with:
1006 net_id: network uuid to connect
1007 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
1008 model: interface model, ignored #TODO
1009 mac_address: used for SR-IOV ifaces #TODO for other types
1010 use: 'data', 'bridge', 'mgmt'
1011 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
1012 vim_id: filled/added by this function
1013 floating_ip: True/False (or it can be None)
1014 'cloud_config': (optional) dictionary with:
1015 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
1016 'users': (optional) list of users to be inserted, each item is a dict with:
1017 'name': (mandatory) user name,
1018 'key-pairs': (optional) list of strings with the public key to be inserted to the user
1019 'user-data': (optional) string is a text script to be passed directly to cloud-init
1020 'config-files': (optional). List of files to be transferred. Each item is a dict with:
1021 'dest': (mandatory) string with the destination absolute path
1022 'encoding': (optional, by default text). Can be one of:
1023 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
1024 'content' (mandatory): string with the content of the file
1025 'permissions': (optional) string with file permissions, typically octal notation '0644'
1026 'owner': (optional) file owner, string with the format 'owner:group'
1027 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
1028 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
1029 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
1030 'size': (mandatory) string with the size of the disk in GB
1031 'vim_id' (optional) should use this existing volume id
1032 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
1033 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
1034 availability_zone_index is None
1035 #TODO ip, security groups
1036 Returns a tuple with the instance identifier and created_items or raises an exception on error
1037 created_items can be None or a dictionary where this method can include key-values that will be passed to
1038 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
1039 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
1042 self
.logger
.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id
, flavor_id
,str(net_list
))
1048 external_network
= [] # list of external networks to be connected to instance, later on used to create floating_ip
1049 no_secured_ports
= [] # List of port-is with port-security disabled
1050 self
._reload
_connection
()
1051 # metadata_vpci = {} # For a specific neutron plugin
1052 block_device_mapping
= None
1053 for net
in net_list
:
1054 if not net
.get("net_id"): # skip non connected iface
1058 "network_id": net
["net_id"],
1059 "name": net
.get("name"),
1060 "admin_state_up": True
1062 if net
["type"]=="virtual":
1065 # metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
1066 elif net
["type"] == "VF" or net
["type"] == "SR-IOV": # for VF
1068 # if "VF" not in metadata_vpci:
1069 # metadata_vpci["VF"]=[]
1070 # metadata_vpci["VF"].append([ net["vpci"], "" ])
1071 port_dict
["binding:vnic_type"]="direct"
1072 # VIO specific Changes
1073 if self
.vim_type
== "VIO":
1074 # Need to create port with port_security_enabled = False and no-security-groups
1075 port_dict
["port_security_enabled"]=False
1076 port_dict
["provider_security_groups"]=[]
1077 port_dict
["security_groups"]=[]
1078 else: # For PT PCI-PASSTHROUGH
1079 # VIO specific Changes
1080 # Current VIO release does not support port with type 'direct-physical'
1081 # So no need to create virtual port in case of PCI-device.
1082 # Will update port_dict code when support gets added in next VIO release
1083 if self
.vim_type
== "VIO":
1084 raise vimconn
.vimconnNotSupportedException(
1085 "Current VIO release does not support full passthrough (PT)")
1087 # if "PF" not in metadata_vpci:
1088 # metadata_vpci["PF"]=[]
1089 # metadata_vpci["PF"].append([ net["vpci"], "" ])
1090 port_dict
["binding:vnic_type"]="direct-physical"
1091 if not port_dict
["name"]:
1092 port_dict
["name"]=name
1093 if net
.get("mac_address"):
1094 port_dict
["mac_address"]=net
["mac_address"]
1095 if net
.get("ip_address"):
1096 port_dict
["fixed_ips"] = [{'ip_address': net
["ip_address"]}]
1097 # TODO add 'subnet_id': <subnet_id>
1098 new_port
= self
.neutron
.create_port({"port": port_dict
})
1099 created_items
["port:" + str(new_port
["port"]["id"])] = True
1100 net
["mac_adress"] = new_port
["port"]["mac_address"]
1101 net
["vim_id"] = new_port
["port"]["id"]
1102 # if try to use a network without subnetwork, it will return a emtpy list
1103 fixed_ips
= new_port
["port"].get("fixed_ips")
1105 net
["ip"] = fixed_ips
[0].get("ip_address")
1109 port
= {"port-id": new_port
["port"]["id"]}
1110 if float(self
.nova
.api_version
.get_string()) >= 2.32:
1111 port
["tag"] = new_port
["port"]["name"]
1112 net_list_vim
.append(port
)
1114 if net
.get('floating_ip', False):
1115 net
['exit_on_floating_ip_error'] = True
1116 external_network
.append(net
)
1117 elif net
['use'] == 'mgmt' and self
.config
.get('use_floating_ip'):
1118 net
['exit_on_floating_ip_error'] = False
1119 external_network
.append(net
)
1120 net
['floating_ip'] = self
.config
.get('use_floating_ip')
1122 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1123 # As a workaround we wait until the VM is active and then disable the port-security
1124 if net
.get("port_security") == False and not self
.config
.get("no_port_security_extension"):
1125 no_secured_ports
.append(new_port
["port"]["id"])
1128 # metadata = {"pci_assignement": json.dumps(metadata_vpci)}
1129 # if len(metadata["pci_assignement"]) >255:
1130 # #limit the metadata size
1131 # #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1132 # self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
1135 self
.logger
.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s'",
1136 name
, image_id
, flavor_id
, str(net_list_vim
), description
)
1138 security_groups
= self
.config
.get('security_groups')
1139 if type(security_groups
) is str:
1140 security_groups
= ( security_groups
, )
1142 config_drive
, userdata
= self
._create
_user
_data
(cloud_config
)
1144 # Create additional volumes in case these are present in disk_list
1145 base_disk_index
= ord('b')
1147 block_device_mapping
= {}
1148 for disk
in disk_list
:
1149 if disk
.get('vim_id'):
1150 block_device_mapping
['_vd' + chr(base_disk_index
)] = disk
['vim_id']
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 created_items
["volume:" + str(volume
.id)] = True
1159 block_device_mapping
['_vd' + chr(base_disk_index
)] = volume
.id
1160 base_disk_index
+= 1
1162 # Wait until created volumes are with status available
1164 while elapsed_time
< volume_timeout
:
1165 for created_item
in created_items
:
1166 v
, _
, volume_id
= created_item
.partition(":")
1168 if self
.cinder
.volumes
.get(volume_id
).status
!= 'available':
1170 else: # all ready: break from while
1174 # If we exceeded the timeout rollback
1175 if elapsed_time
>= volume_timeout
:
1176 raise vimconn
.vimconnException('Timeout creating volumes for instance ' + name
,
1177 http_code
=vimconn
.HTTP_Request_Timeout
)
1178 # get availability Zone
1179 vm_av_zone
= self
._get
_vm
_availability
_zone
(availability_zone_index
, availability_zone_list
)
1181 self
.logger
.debug("nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
1182 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
1183 "block_device_mapping={})".format(name
, image_id
, flavor_id
, net_list_vim
,
1184 security_groups
, vm_av_zone
, self
.config
.get('keypair'),
1185 userdata
, config_drive
, block_device_mapping
))
1186 server
= self
.nova
.servers
.create(name
, image_id
, flavor_id
, nics
=net_list_vim
,
1187 security_groups
=security_groups
,
1188 availability_zone
=vm_av_zone
,
1189 key_name
=self
.config
.get('keypair'),
1191 config_drive
=config_drive
,
1192 block_device_mapping
=block_device_mapping
1193 ) # , description=description)
1195 vm_start_time
= time
.time()
1196 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1197 if no_secured_ports
:
1198 self
.__wait
_for
_vm
(server
.id, 'ACTIVE')
1200 for port_id
in no_secured_ports
:
1202 self
.neutron
.update_port(port_id
,
1203 {"port": {"port_security_enabled": False, "security_groups": None}})
1204 except Exception as e
:
1205 raise vimconn
.vimconnException("It was not possible to disable port security for port {}".format(
1207 # print "DONE :-)", server
1210 if external_network
:
1211 floating_ips
= self
.neutron
.list_floatingips().get("floatingips", ())
1212 for floating_network
in external_network
:
1217 ip
= floating_ips
.pop(0)
1218 if ip
.get("port_id", False) or ip
.get('tenant_id') != server
.tenant_id
:
1220 if isinstance(floating_network
['floating_ip'], str):
1221 if ip
.get("floating_network_id") != floating_network
['floating_ip']:
1223 free_floating_ip
= ip
.get("floating_ip_address")
1225 if isinstance(floating_network
['floating_ip'], str) and \
1226 floating_network
['floating_ip'].lower() != "true":
1227 pool_id
= floating_network
['floating_ip']
1229 # Find the external network
1230 external_nets
= list()
1231 for net
in self
.neutron
.list_networks()['networks']:
1232 if net
['router:external']:
1233 external_nets
.append(net
)
1235 if len(external_nets
) == 0:
1236 raise vimconn
.vimconnException("Cannot create floating_ip automatically since no external "
1237 "network is present",
1238 http_code
=vimconn
.HTTP_Conflict
)
1239 if len(external_nets
) > 1:
1240 raise vimconn
.vimconnException("Cannot create floating_ip automatically since multiple "
1241 "external networks are present",
1242 http_code
=vimconn
.HTTP_Conflict
)
1244 pool_id
= external_nets
[0].get('id')
1245 param
= {'floatingip': {'floating_network_id': pool_id
, 'tenant_id': server
.tenant_id
}}
1247 # self.logger.debug("Creating floating IP")
1248 new_floating_ip
= self
.neutron
.create_floatingip(param
)
1249 free_floating_ip
= new_floating_ip
['floatingip']['floating_ip_address']
1250 except Exception as e
:
1251 raise vimconn
.vimconnException(type(e
).__name
__ + ": Cannot create new floating_ip " +
1252 str(e
), http_code
=vimconn
.HTTP_Conflict
)
1254 fix_ip
= floating_network
.get('ip')
1257 server
.add_floating_ip(free_floating_ip
, fix_ip
)
1259 except Exception as e
:
1260 # openstack need some time after VM creation to asign an IP. So retry if fails
1261 vm_status
= self
.nova
.servers
.get(server
.id).status
1262 if vm_status
!= 'ACTIVE' and vm_status
!= 'ERROR':
1263 if time
.time() - vm_start_time
< server_timeout
:
1266 raise vimconn
.vimconnException(
1267 "Cannot create floating_ip: {} {}".format(type(e
).__name
__, e
),
1268 http_code
=vimconn
.HTTP_Conflict
)
1270 except Exception as e
:
1271 if not floating_network
['exit_on_floating_ip_error']:
1272 self
.logger
.warn("Cannot create floating_ip. %s", str(e
))
1276 return server
.id, created_items
1277 # except nvExceptions.NotFound as e:
1278 # error_value=-vimconn.HTTP_Not_Found
1279 # error_text= "vm instance %s not found" % vm_id
1280 # except TypeError as e:
1281 # raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1283 except Exception as e
:
1286 server_id
= server
.id
1288 self
.delete_vminstance(server_id
, created_items
)
1289 except Exception as e2
:
1290 self
.logger
.error("new_vminstance rollback fail {}".format(e2
))
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
, created_items
=None):
1357 '''Removes a VM instance from VIM. Returns the old identifier
1359 #print "osconnector: Getting VM from VIM"
1360 if created_items
== None:
1363 self
._reload
_connection
()
1364 # delete VM ports attached to this networks before the virtual machine
1365 for k
, v
in created_items
.items():
1366 if not v
: # skip already deleted
1369 k_item
, _
, k_id
= k
.partition(":")
1370 if k_item
== "port":
1371 self
.neutron
.delete_port(k_id
)
1372 except Exception as e
:
1373 self
.logger
.error("Error deleting port: {}: {}".format(type(e
).__name
__, e
))
1375 # #commented because detaching the volumes makes the servers.delete not work properly ?!?
1376 # #dettach volumes attached
1377 # server = self.nova.servers.get(vm_id)
1378 # volumes_attached_dict = server._info['os-extended-volumes:volumes_attached'] #volume['id']
1379 # #for volume in volumes_attached_dict:
1380 # # self.cinder.volumes.detach(volume['id'])
1383 self
.nova
.servers
.delete(vm_id
)
1385 # delete volumes. Although having detached, they should have in active status before deleting
1386 # we ensure in this loop
1389 while keep_waiting
and elapsed_time
< volume_timeout
:
1390 keep_waiting
= False
1391 for k
, v
in created_items
.items():
1392 if not v
: # skip already deleted
1395 k_item
, _
, k_id
= k
.partition(":")
1396 if k_item
== "volume":
1397 if self
.cinder
.volumes
.get(k_id
).status
!= 'available':
1400 self
.cinder
.volumes
.delete(k_id
)
1401 except Exception as e
:
1402 self
.logger
.error("Error deleting volume: {}: {}".format(type(e
).__name
__, e
))
1407 except (nvExceptions
.NotFound
, ksExceptions
.ClientException
, nvExceptions
.ClientException
, ConnectionError
) as e
:
1408 self
._format
_exception
(e
)
1410 def refresh_vms_status(self
, vm_list
):
1411 '''Get the status of the virtual machines and their interfaces/ports
1412 Params: the list of VM identifiers
1413 Returns a dictionary with:
1414 vm_id: #VIM id of this Virtual Machine
1415 status: #Mandatory. Text with one of:
1416 # DELETED (not found at vim)
1417 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1418 # OTHER (Vim reported other status not understood)
1419 # ERROR (VIM indicates an ERROR status)
1420 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1421 # CREATING (on building process), ERROR
1422 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1424 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1425 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1427 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1428 mac_address: #Text format XX:XX:XX:XX:XX:XX
1429 vim_net_id: #network id where this interface is connected
1430 vim_interface_id: #interface/port VIM id
1431 ip_address: #null, or text with IPv4, IPv6 address
1432 compute_node: #identification of compute node where PF,VF interface is allocated
1433 pci: #PCI address of the NIC that hosts the PF,VF
1434 vlan: #physical VLAN used for VF
1437 self
.logger
.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1438 for vm_id
in vm_list
:
1441 vm_vim
= self
.get_vminstance(vm_id
)
1442 if vm_vim
['status'] in vmStatus2manoFormat
:
1443 vm
['status'] = vmStatus2manoFormat
[ vm_vim
['status'] ]
1445 vm
['status'] = "OTHER"
1446 vm
['error_msg'] = "VIM status reported " + vm_vim
['status']
1448 vm
['vim_info'] = self
.serialize(vm_vim
)
1450 vm
["interfaces"] = []
1451 if vm_vim
.get('fault'):
1452 vm
['error_msg'] = str(vm_vim
['fault'])
1455 self
._reload
_connection
()
1456 port_dict
= self
.neutron
.list_ports(device_id
=vm_id
)
1457 for port
in port_dict
["ports"]:
1459 interface
['vim_info'] = self
.serialize(port
)
1460 interface
["mac_address"] = port
.get("mac_address")
1461 interface
["vim_net_id"] = port
["network_id"]
1462 interface
["vim_interface_id"] = port
["id"]
1463 # check if OS-EXT-SRV-ATTR:host is there,
1464 # in case of non-admin credentials, it will be missing
1465 if vm_vim
.get('OS-EXT-SRV-ATTR:host'):
1466 interface
["compute_node"] = vm_vim
['OS-EXT-SRV-ATTR:host']
1467 interface
["pci"] = None
1469 # check if binding:profile is there,
1470 # in case of non-admin credentials, it will be missing
1471 if port
.get('binding:profile'):
1472 if port
['binding:profile'].get('pci_slot'):
1473 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1474 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1475 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1476 pci
= port
['binding:profile']['pci_slot']
1477 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1478 interface
["pci"] = pci
1479 interface
["vlan"] = None
1480 #if network is of type vlan and port is of type direct (sr-iov) then set vlan id
1481 network
= self
.neutron
.show_network(port
["network_id"])
1482 if network
['network'].get('provider:network_type') == 'vlan' and \
1483 port
.get("binding:vnic_type") == "direct":
1484 interface
["vlan"] = network
['network'].get('provider:segmentation_id')
1486 #look for floating ip address
1488 floating_ip_dict
= self
.neutron
.list_floatingips(port_id
=port
["id"])
1489 if floating_ip_dict
.get("floatingips"):
1490 ips
.append(floating_ip_dict
["floatingips"][0].get("floating_ip_address") )
1494 for subnet
in port
["fixed_ips"]:
1495 ips
.append(subnet
["ip_address"])
1496 interface
["ip_address"] = ";".join(ips
)
1497 vm
["interfaces"].append(interface
)
1498 except Exception as e
:
1499 self
.logger
.error("Error getting vm interface information {}: {}".format(type(e
).__name
__, e
),
1501 except vimconn
.vimconnNotFoundException
as e
:
1502 self
.logger
.error("Exception getting vm status: %s", str(e
))
1503 vm
['status'] = "DELETED"
1504 vm
['error_msg'] = str(e
)
1505 except vimconn
.vimconnException
as e
:
1506 self
.logger
.error("Exception getting vm status: %s", str(e
))
1507 vm
['status'] = "VIM_ERROR"
1508 vm
['error_msg'] = str(e
)
1512 def action_vminstance(self
, vm_id
, action_dict
, created_items
={}):
1513 '''Send and action over a VM instance from VIM
1514 Returns None or the console dict if the action was successfully sent to the VIM'''
1515 self
.logger
.debug("Action over VM '%s': %s", vm_id
, str(action_dict
))
1517 self
._reload
_connection
()
1518 server
= self
.nova
.servers
.find(id=vm_id
)
1519 if "start" in action_dict
:
1520 if action_dict
["start"]=="rebuild":
1523 if server
.status
=="PAUSED":
1525 elif server
.status
=="SUSPENDED":
1527 elif server
.status
=="SHUTOFF":
1529 elif "pause" in action_dict
:
1531 elif "resume" in action_dict
:
1533 elif "shutoff" in action_dict
or "shutdown" in action_dict
:
1535 elif "forceOff" in action_dict
:
1537 elif "terminate" in action_dict
:
1539 elif "createImage" in action_dict
:
1540 server
.create_image()
1541 #"path":path_schema,
1542 #"description":description_schema,
1543 #"name":name_schema,
1544 #"metadata":metadata_schema,
1545 #"imageRef": id_schema,
1546 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1547 elif "rebuild" in action_dict
:
1548 server
.rebuild(server
.image
['id'])
1549 elif "reboot" in action_dict
:
1550 server
.reboot() #reboot_type='SOFT'
1551 elif "console" in action_dict
:
1552 console_type
= action_dict
["console"]
1553 if console_type
== None or console_type
== "novnc":
1554 console_dict
= server
.get_vnc_console("novnc")
1555 elif console_type
== "xvpvnc":
1556 console_dict
= server
.get_vnc_console(console_type
)
1557 elif console_type
== "rdp-html5":
1558 console_dict
= server
.get_rdp_console(console_type
)
1559 elif console_type
== "spice-html5":
1560 console_dict
= server
.get_spice_console(console_type
)
1562 raise vimconn
.vimconnException("console type '{}' not allowed".format(console_type
),
1563 http_code
=vimconn
.HTTP_Bad_Request
)
1565 console_url
= console_dict
["console"]["url"]
1567 protocol_index
= console_url
.find("//")
1568 suffix_index
= console_url
[protocol_index
+2:].find("/") + protocol_index
+2
1569 port_index
= console_url
[protocol_index
+2:suffix_index
].find(":") + protocol_index
+2
1570 if protocol_index
< 0 or port_index
<0 or suffix_index
<0:
1571 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1572 console_dict2
={"protocol": console_url
[0:protocol_index
],
1573 "server": console_url
[protocol_index
+2 : port_index
],
1574 "port": int(console_url
[port_index
+1 : suffix_index
]),
1575 "suffix": console_url
[suffix_index
+1:]
1577 return console_dict2
1578 except Exception as e
:
1579 raise vimconn
.vimconnException("Unexpected response from VIM " + str(console_dict
))
1582 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
, nvExceptions
.NotFound
, ConnectionError
) as e
:
1583 self
._format
_exception
(e
)
1584 #TODO insert exception vimconn.HTTP_Unauthorized
1586 ####### VIO Specific Changes #########
1587 def _genrate_vlanID(self
):
1589 Method to get unused vlanID
1597 networks
= self
.get_network_list()
1598 for net
in networks
:
1599 if net
.get('provider:segmentation_id'):
1600 usedVlanIDs
.append(net
.get('provider:segmentation_id'))
1601 used_vlanIDs
= set(usedVlanIDs
)
1603 #find unused VLAN ID
1604 for vlanID_range
in self
.config
.get('dataplane_net_vlan_range'):
1606 start_vlanid
, end_vlanid
= map(int, vlanID_range
.replace(" ", "").split("-"))
1607 for vlanID
in xrange(start_vlanid
, end_vlanid
+ 1):
1608 if vlanID
not in used_vlanIDs
:
1610 except Exception as exp
:
1611 raise vimconn
.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp
))
1613 raise vimconn
.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1614 " All given Vlan IDs {} are in use.".format(self
.config
.get('dataplane_net_vlan_range')))
1617 def _validate_vlan_ranges(self
, dataplane_net_vlan_range
):
1619 Method to validate user given vlanID ranges
1623 for vlanID_range
in dataplane_net_vlan_range
:
1624 vlan_range
= vlanID_range
.replace(" ", "")
1626 vlanID_pattern
= r
'(\d)*-(\d)*$'
1627 match_obj
= re
.match(vlanID_pattern
, vlan_range
)
1629 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1630 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range
))
1632 start_vlanid
, end_vlanid
= map(int,vlan_range
.split("-"))
1633 if start_vlanid
<= 0 :
1634 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1635 "Start ID can not be zero. For VLAN "\
1636 "networks valid IDs are 1 to 4094 ".format(vlanID_range
))
1637 if end_vlanid
> 4094 :
1638 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1639 "End VLAN ID can not be greater than 4094. For VLAN "\
1640 "networks valid IDs are 1 to 4094 ".format(vlanID_range
))
1642 if start_vlanid
> end_vlanid
:
1643 raise vimconn
.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1644 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1645 "start_ID < end_ID ".format(vlanID_range
))
1649 def new_external_port(self
, port_data
):
1650 #TODO openstack if needed
1651 '''Adds a external port to VIM'''
1652 '''Returns the port identifier'''
1653 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.new_external_port() not implemented"
1655 def connect_port_network(self
, port_id
, network_id
, admin
=False):
1656 #TODO openstack if needed
1657 '''Connects a external port to a network'''
1658 '''Returns status code of the VIM response'''
1659 return -vimconn
.HTTP_Internal_Server_Error
, "osconnector.connect_port_network() not implemented"
1661 def new_user(self
, user_name
, user_passwd
, tenant_id
=None):
1662 '''Adds a new user to openstack VIM'''
1663 '''Returns the user identifier'''
1664 self
.logger
.debug("osconnector: Adding a new user to VIM")
1666 self
._reload
_connection
()
1667 user
=self
.keystone
.users
.create(user_name
, password
=user_passwd
, default_project
=tenant_id
)
1668 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1670 except ksExceptions
.ConnectionError
as e
:
1671 error_value
=-vimconn
.HTTP_Bad_Request
1672 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1673 except ksExceptions
.ClientException
as e
: #TODO remove
1674 error_value
=-vimconn
.HTTP_Bad_Request
1675 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1676 #TODO insert exception vimconn.HTTP_Unauthorized
1677 #if reaching here is because an exception
1678 self
.logger
.debug("new_user " + error_text
)
1679 return error_value
, error_text
1681 def delete_user(self
, user_id
):
1682 '''Delete a user from openstack VIM'''
1683 '''Returns the user identifier'''
1685 print("osconnector: Deleting a user from VIM")
1687 self
._reload
_connection
()
1688 self
.keystone
.users
.delete(user_id
)
1690 except ksExceptions
.ConnectionError
as e
:
1691 error_value
=-vimconn
.HTTP_Bad_Request
1692 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1693 except ksExceptions
.NotFound
as e
:
1694 error_value
=-vimconn
.HTTP_Not_Found
1695 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1696 except ksExceptions
.ClientException
as e
: #TODO remove
1697 error_value
=-vimconn
.HTTP_Bad_Request
1698 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1699 #TODO insert exception vimconn.HTTP_Unauthorized
1700 #if reaching here is because an exception
1701 self
.logger
.debug("delete_tenant " + error_text
)
1702 return error_value
, error_text
1704 def get_hosts_info(self
):
1705 '''Get the information of deployed hosts
1706 Returns the hosts content'''
1708 print("osconnector: Getting Host info from VIM")
1711 self
._reload
_connection
()
1712 hypervisors
= self
.nova
.hypervisors
.list()
1713 for hype
in hypervisors
:
1714 h_list
.append( hype
.to_dict() )
1715 return 1, {"hosts":h_list
}
1716 except nvExceptions
.NotFound
as e
:
1717 error_value
=-vimconn
.HTTP_Not_Found
1718 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1719 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1720 error_value
=-vimconn
.HTTP_Bad_Request
1721 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1722 #TODO insert exception vimconn.HTTP_Unauthorized
1723 #if reaching here is because an exception
1724 self
.logger
.debug("get_hosts_info " + error_text
)
1725 return error_value
, error_text
1727 def get_hosts(self
, vim_tenant
):
1728 '''Get the hosts and deployed instances
1729 Returns the hosts content'''
1730 r
, hype_dict
= self
.get_hosts_info()
1733 hypervisors
= hype_dict
["hosts"]
1735 servers
= self
.nova
.servers
.list()
1736 for hype
in hypervisors
:
1737 for server
in servers
:
1738 if server
.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype
['hypervisor_hostname']:
1740 hype
['vm'].append(server
.id)
1742 hype
['vm'] = [server
.id]
1744 except nvExceptions
.NotFound
as e
:
1745 error_value
=-vimconn
.HTTP_Not_Found
1746 error_text
= (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1747 except (ksExceptions
.ClientException
, nvExceptions
.ClientException
) as e
:
1748 error_value
=-vimconn
.HTTP_Bad_Request
1749 error_text
= type(e
).__name
__ + ": "+ (str(e
) if len(e
.args
)==0 else str(e
.args
[0]))
1750 #TODO insert exception vimconn.HTTP_Unauthorized
1751 #if reaching here is because an exception
1752 self
.logger
.debug("get_hosts " + error_text
)
1753 return error_value
, error_text
1755 def new_classification(self
, name
, ctype
, definition
):
1757 'Adding a new (Traffic) Classification to VIM, named %s', name
)
1760 self
._reload
_connection
()
1761 if ctype
not in supportedClassificationTypes
:
1762 raise vimconn
.vimconnNotSupportedException(
1763 'OpenStack VIM connector doesn\'t support provided '
1764 'Classification Type {}, supported ones are: '
1765 '{}'.format(ctype
, supportedClassificationTypes
))
1766 if not self
._validate
_classification
(ctype
, definition
):
1767 raise vimconn
.vimconnException(
1768 'Incorrect Classification definition '
1769 'for the type specified.')
1770 classification_dict
= definition
1771 classification_dict
['name'] = name
1773 new_class
= self
.neutron
.create_sfc_flow_classifier(
1774 {'flow_classifier': classification_dict
})
1775 return new_class
['flow_classifier']['id']
1776 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1777 neExceptions
.NeutronException
, ConnectionError
) as e
:
1779 'Creation of Classification failed.')
1780 self
._format
_exception
(e
)
1782 def get_classification(self
, class_id
):
1783 self
.logger
.debug(" Getting Classification %s from VIM", class_id
)
1784 filter_dict
= {"id": class_id
}
1785 class_list
= self
.get_classification_list(filter_dict
)
1786 if len(class_list
) == 0:
1787 raise vimconn
.vimconnNotFoundException(
1788 "Classification '{}' not found".format(class_id
))
1789 elif len(class_list
) > 1:
1790 raise vimconn
.vimconnConflictException(
1791 "Found more than one Classification with this criteria")
1792 classification
= class_list
[0]
1793 return classification
1795 def get_classification_list(self
, filter_dict
={}):
1796 self
.logger
.debug("Getting Classifications from VIM filter: '%s'",
1799 filter_dict_os
= filter_dict
.copy()
1800 self
._reload
_connection
()
1801 if self
.api_version3
and "tenant_id" in filter_dict_os
:
1802 filter_dict_os
['project_id'] = filter_dict_os
.pop('tenant_id')
1803 classification_dict
= self
.neutron
.list_sfc_flow_classifiers(
1805 classification_list
= classification_dict
["flow_classifiers"]
1806 self
.__classification
_os
2mano
(classification_list
)
1807 return classification_list
1808 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1809 neExceptions
.NeutronException
, ConnectionError
) as e
:
1810 self
._format
_exception
(e
)
1812 def delete_classification(self
, class_id
):
1813 self
.logger
.debug("Deleting Classification '%s' from VIM", class_id
)
1815 self
._reload
_connection
()
1816 self
.neutron
.delete_sfc_flow_classifier(class_id
)
1818 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1819 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1820 ConnectionError
) as e
:
1821 self
._format
_exception
(e
)
1823 def new_sfi(self
, name
, ingress_ports
, egress_ports
, sfc_encap
=True):
1825 "Adding a new Service Function Instance to VIM, named '%s'", name
)
1828 self
._reload
_connection
()
1832 if len(ingress_ports
) != 1:
1833 raise vimconn
.vimconnNotSupportedException(
1834 "OpenStack VIM connector can only have "
1835 "1 ingress port per SFI")
1836 if len(egress_ports
) != 1:
1837 raise vimconn
.vimconnNotSupportedException(
1838 "OpenStack VIM connector can only have "
1839 "1 egress port per SFI")
1840 sfi_dict
= {'name': name
,
1841 'ingress': ingress_ports
[0],
1842 'egress': egress_ports
[0],
1843 'service_function_parameters': {
1844 'correlation': correlation
}}
1845 new_sfi
= self
.neutron
.create_sfc_port_pair({'port_pair': sfi_dict
})
1846 return new_sfi
['port_pair']['id']
1847 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1848 neExceptions
.NeutronException
, ConnectionError
) as e
:
1851 self
.neutron
.delete_sfc_port_pair(
1852 new_sfi
['port_pair']['id'])
1855 'Creation of Service Function Instance failed, with '
1856 'subsequent deletion failure as well.')
1857 self
._format
_exception
(e
)
1859 def get_sfi(self
, sfi_id
):
1861 'Getting Service Function Instance %s from VIM', sfi_id
)
1862 filter_dict
= {"id": sfi_id
}
1863 sfi_list
= self
.get_sfi_list(filter_dict
)
1864 if len(sfi_list
) == 0:
1865 raise vimconn
.vimconnNotFoundException(
1866 "Service Function Instance '{}' not found".format(sfi_id
))
1867 elif len(sfi_list
) > 1:
1868 raise vimconn
.vimconnConflictException(
1869 'Found more than one Service Function Instance '
1870 'with this criteria')
1874 def get_sfi_list(self
, filter_dict
={}):
1875 self
.logger
.debug("Getting Service Function Instances from "
1876 "VIM filter: '%s'", str(filter_dict
))
1878 self
._reload
_connection
()
1879 filter_dict_os
= filter_dict
.copy()
1880 if self
.api_version3
and "tenant_id" in filter_dict_os
:
1881 filter_dict_os
['project_id'] = filter_dict_os
.pop('tenant_id')
1882 sfi_dict
= self
.neutron
.list_sfc_port_pairs(**filter_dict_os
)
1883 sfi_list
= sfi_dict
["port_pairs"]
1884 self
.__sfi
_os
2mano
(sfi_list
)
1886 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1887 neExceptions
.NeutronException
, ConnectionError
) as e
:
1888 self
._format
_exception
(e
)
1890 def delete_sfi(self
, sfi_id
):
1891 self
.logger
.debug("Deleting Service Function Instance '%s' "
1894 self
._reload
_connection
()
1895 self
.neutron
.delete_sfc_port_pair(sfi_id
)
1897 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1898 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1899 ConnectionError
) as e
:
1900 self
._format
_exception
(e
)
1902 def new_sf(self
, name
, sfis
, sfc_encap
=True):
1903 self
.logger
.debug("Adding a new Service Function to VIM, "
1907 self
._reload
_connection
()
1908 # correlation = None
1910 # correlation = 'nsh'
1911 for instance
in sfis
:
1912 sfi
= self
.get_sfi(instance
)
1913 if sfi
.get('sfc_encap') != sfc_encap
:
1914 raise vimconn
.vimconnNotSupportedException(
1915 "OpenStack VIM connector requires all SFIs of the "
1916 "same SF to share the same SFC Encapsulation")
1917 sf_dict
= {'name': name
,
1919 new_sf
= self
.neutron
.create_sfc_port_pair_group({
1920 'port_pair_group': sf_dict
})
1921 return new_sf
['port_pair_group']['id']
1922 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1923 neExceptions
.NeutronException
, ConnectionError
) as e
:
1926 self
.neutron
.delete_sfc_port_pair_group(
1927 new_sf
['port_pair_group']['id'])
1930 'Creation of Service Function failed, with '
1931 'subsequent deletion failure as well.')
1932 self
._format
_exception
(e
)
1934 def get_sf(self
, sf_id
):
1935 self
.logger
.debug("Getting Service Function %s from VIM", sf_id
)
1936 filter_dict
= {"id": sf_id
}
1937 sf_list
= self
.get_sf_list(filter_dict
)
1938 if len(sf_list
) == 0:
1939 raise vimconn
.vimconnNotFoundException(
1940 "Service Function '{}' not found".format(sf_id
))
1941 elif len(sf_list
) > 1:
1942 raise vimconn
.vimconnConflictException(
1943 "Found more than one Service Function with this criteria")
1947 def get_sf_list(self
, filter_dict
={}):
1948 self
.logger
.debug("Getting Service Function from VIM filter: '%s'",
1951 self
._reload
_connection
()
1952 filter_dict_os
= filter_dict
.copy()
1953 if self
.api_version3
and "tenant_id" in filter_dict_os
:
1954 filter_dict_os
['project_id'] = filter_dict_os
.pop('tenant_id')
1955 sf_dict
= self
.neutron
.list_sfc_port_pair_groups(**filter_dict_os
)
1956 sf_list
= sf_dict
["port_pair_groups"]
1957 self
.__sf
_os
2mano
(sf_list
)
1959 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1960 neExceptions
.NeutronException
, ConnectionError
) as e
:
1961 self
._format
_exception
(e
)
1963 def delete_sf(self
, sf_id
):
1964 self
.logger
.debug("Deleting Service Function '%s' from VIM", sf_id
)
1966 self
._reload
_connection
()
1967 self
.neutron
.delete_sfc_port_pair_group(sf_id
)
1969 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
1970 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
1971 ConnectionError
) as e
:
1972 self
._format
_exception
(e
)
1974 def new_sfp(self
, name
, classifications
, sfs
, sfc_encap
=True, spi
=None):
1975 self
.logger
.debug("Adding a new Service Function Path to VIM, "
1979 self
._reload
_connection
()
1980 # In networking-sfc the MPLS encapsulation is legacy
1981 # should be used when no full SFC Encapsulation is intended
1982 correlation
= 'mpls'
1985 sfp_dict
= {'name': name
,
1986 'flow_classifiers': classifications
,
1987 'port_pair_groups': sfs
,
1988 'chain_parameters': {'correlation': correlation
}}
1990 sfp_dict
['chain_id'] = spi
1991 new_sfp
= self
.neutron
.create_sfc_port_chain({'port_chain': sfp_dict
})
1992 return new_sfp
["port_chain"]["id"]
1993 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
1994 neExceptions
.NeutronException
, ConnectionError
) as e
:
1997 self
.neutron
.delete_sfc_port_chain(new_sfp
['port_chain']['id'])
2000 'Creation of Service Function Path failed, with '
2001 'subsequent deletion failure as well.')
2002 self
._format
_exception
(e
)
2004 def get_sfp(self
, sfp_id
):
2005 self
.logger
.debug(" Getting Service Function Path %s from VIM", sfp_id
)
2006 filter_dict
= {"id": sfp_id
}
2007 sfp_list
= self
.get_sfp_list(filter_dict
)
2008 if len(sfp_list
) == 0:
2009 raise vimconn
.vimconnNotFoundException(
2010 "Service Function Path '{}' not found".format(sfp_id
))
2011 elif len(sfp_list
) > 1:
2012 raise vimconn
.vimconnConflictException(
2013 "Found more than one Service Function Path with this criteria")
2017 def get_sfp_list(self
, filter_dict
={}):
2018 self
.logger
.debug("Getting Service Function Paths from VIM filter: "
2019 "'%s'", str(filter_dict
))
2021 self
._reload
_connection
()
2022 filter_dict_os
= filter_dict
.copy()
2023 if self
.api_version3
and "tenant_id" in filter_dict_os
:
2024 filter_dict_os
['project_id'] = filter_dict_os
.pop('tenant_id')
2025 sfp_dict
= self
.neutron
.list_sfc_port_chains(**filter_dict_os
)
2026 sfp_list
= sfp_dict
["port_chains"]
2027 self
.__sfp
_os
2mano
(sfp_list
)
2029 except (neExceptions
.ConnectionFailed
, ksExceptions
.ClientException
,
2030 neExceptions
.NeutronException
, ConnectionError
) as e
:
2031 self
._format
_exception
(e
)
2033 def delete_sfp(self
, sfp_id
):
2035 "Deleting Service Function Path '%s' from VIM", sfp_id
)
2037 self
._reload
_connection
()
2038 self
.neutron
.delete_sfc_port_chain(sfp_id
)
2040 except (neExceptions
.ConnectionFailed
, neExceptions
.NeutronException
,
2041 ksExceptions
.ClientException
, neExceptions
.NeutronException
,
2042 ConnectionError
) as e
:
2043 self
._format
_exception
(e
)