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