CAL refactoring
[osm/SO.git] / rwcal / plugins / vala / rwcal_openstack / rwcal_openstack.py
1
2 #
3 # Copyright 2016 RIFT.IO Inc
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 import contextlib
19 import logging
20 import os
21 import subprocess
22 import tempfile
23 import yaml
24
25 import gi
26 gi.require_version('RwSdn', '1.0')
27 gi.require_version('RwCal', '1.0')
28 gi.require_version('RwcalYang', '1.0')
29
30 import rift.rwcal.openstack as openstack_drv
31
32
33 import rw_status
34 import rift.cal.rwcal_status as rwcal_status
35 import rwlogger
36 import neutronclient.common.exceptions as NeutronException
37 import keystoneclient.exceptions as KeystoneExceptions
38
39
40 from gi.repository import (
41 GObject,
42 RwCal,
43 RwSdn, # Vala package
44 RwsdnYang,
45 RwTypes,
46 RwcalYang)
47
48 PREPARE_VM_CMD = "prepare_vm.py --auth_url {auth_url} --username {username} --password {password} --tenant_name {tenant_name} --region {region} --user_domain {user_domain} --project_domain {project_domain} --mgmt_network {mgmt_network} --server_id {server_id} --port_metadata "
49
50 rwstatus_exception_map = { IndexError: RwTypes.RwStatus.NOTFOUND,
51 KeyError: RwTypes.RwStatus.NOTFOUND,
52 NotImplementedError: RwTypes.RwStatus.NOT_IMPLEMENTED,}
53
54 rwstatus = rw_status.rwstatus_from_exc_map(rwstatus_exception_map)
55 rwcalstatus = rwcal_status.rwcalstatus_from_exc_map(rwstatus_exception_map)
56
57
58 class OpenstackCALOperationFailure(Exception):
59 pass
60
61 class UninitializedPluginError(Exception):
62 pass
63
64
65 class OpenstackServerGroupError(Exception):
66 pass
67
68
69 class ImageUploadError(Exception):
70 pass
71
72
73 class RwcalAccountDriver(object):
74 """
75 Container class per cloud account
76 """
77 def __init__(self, logger, **kwargs):
78 self.log = logger
79 try:
80 self._driver = openstack_drv.OpenstackDriver(logger = self.log, **kwargs)
81 except (KeystoneExceptions.Unauthorized, KeystoneExceptions.AuthorizationFailure,
82 NeutronException.NotFound) as e:
83 raise
84 except Exception as e:
85 self.log.error("RwcalOpenstackPlugin: OpenstackDriver init failed. Exception: %s" %(str(e)))
86 raise
87
88 @property
89 def driver(self):
90 return self._driver
91
92 class RwcalOpenstackPlugin(GObject.Object, RwCal.Cloud):
93 """This class implements the CAL VALA methods for openstack."""
94
95 instance_num = 1
96
97 def __init__(self):
98 GObject.Object.__init__(self)
99 self._driver_class = openstack_drv.OpenstackDriver
100 self.log = logging.getLogger('rwcal.openstack.%s' % RwcalOpenstackPlugin.instance_num)
101 self.log.setLevel(logging.DEBUG)
102 self._rwlog_handler = None
103 self._account_drivers = dict()
104 RwcalOpenstackPlugin.instance_num += 1
105
106 def _use_driver(self, account):
107 if self._rwlog_handler is None:
108 raise UninitializedPluginError("Must call init() in CAL plugin before use.")
109
110 if account.name not in self._account_drivers:
111 self.log.debug("Creating OpenstackDriver")
112 kwargs = dict(username = account.openstack.key,
113 password = account.openstack.secret,
114 auth_url = account.openstack.auth_url,
115 project = account.openstack.tenant,
116 mgmt_network = account.openstack.mgmt_network,
117 cert_validate = account.openstack.cert_validate,
118 user_domain = account.openstack.user_domain,
119 project_domain = account.openstack.project_domain,
120 region = account.openstack.region)
121 drv = RwcalAccountDriver(self.log, **kwargs)
122 self._account_drivers[account.name] = drv
123 return drv.driver
124 else:
125 return self._account_drivers[account.name].driver
126
127
128 @rwstatus
129 def do_init(self, rwlog_ctx):
130 self._rwlog_handler = rwlogger.RwLogger(category="rw-cal-log",
131 subcategory="openstack",
132 log_hdl=rwlog_ctx,)
133 self.log.addHandler(self._rwlog_handler)
134 self.log.propagate = False
135
136 @rwstatus(ret_on_failure=[None])
137 def do_validate_cloud_creds(self, account):
138 """
139 Validates the cloud account credentials for the specified account.
140 Performs an access to the resources using Keystone API. If creds
141 are not valid, returns an error code & reason string
142 Arguments:
143 account - a cloud account to validate
144
145 Returns:
146 Validation Code and Details String
147 """
148 status = RwcalYang.CloudConnectionStatus()
149 drv = self._use_driver(account)
150 try:
151 drv.validate_account_creds()
152 except KeystoneExceptions.Unauthorized as e:
153 self.log.error("Invalid credentials given for VIM account %s", account.name)
154 status.status = "failure"
155 status.details = "Invalid Credentials: %s" % str(e)
156
157 except KeystoneExceptions.AuthorizationFailure as e:
158 self.log.error("Bad authentication URL given for VIM account %s. Given auth url: %s",
159 account.name, account.openstack.auth_url)
160 status.status = "failure"
161 status.details = "Invalid auth url: %s" % str(e)
162
163 except NeutronException.NotFound as e:
164 self.log.error("Given management network %s could not be found for VIM account %s",
165 account.openstack.mgmt_network,
166 account.name)
167 status.status = "failure"
168 status.details = "mgmt network does not exist: %s" % str(e)
169
170 except openstack_drv.ValidationError as e:
171 self.log.error("RwcalOpenstackPlugin: OpenstackDriver credential validation failed. Exception: %s", str(e))
172 status.status = "failure"
173 status.details = "Invalid Credentials: %s" % str(e)
174
175 except Exception as e:
176 msg = "RwcalOpenstackPlugin: OpenstackDriver connection failed. Exception: %s" %(str(e))
177 self.log.error(msg)
178 status.status = "failure"
179 status.details = msg
180
181 else:
182 status.status = "success"
183 status.details = "Connection was successful"
184
185 return status
186
187 @rwstatus(ret_on_failure=[""])
188 def do_get_management_network(self, account):
189 """
190 Returns the management network associated with the specified account.
191 Arguments:
192 account - a cloud account
193
194 Returns:
195 The management network
196 """
197 return account.openstack.mgmt_network
198
199 @rwstatus(ret_on_failure=[""])
200 def do_create_tenant(self, account, name):
201 """Create a new tenant.
202
203 Arguments:
204 account - a cloud account
205 name - name of the tenant
206
207 Returns:
208 The tenant id
209 """
210 raise NotImplementedError
211
212 @rwstatus
213 def do_delete_tenant(self, account, tenant_id):
214 """delete a tenant.
215
216 Arguments:
217 account - a cloud account
218 tenant_id - id of the tenant
219 """
220 raise NotImplementedError
221
222 @rwstatus(ret_on_failure=[[]])
223 def do_get_tenant_list(self, account):
224 """List tenants.
225
226 Arguments:
227 account - a cloud account
228
229 Returns:
230 List of tenants
231 """
232 raise NotImplementedError
233
234 @rwstatus(ret_on_failure=[""])
235 def do_create_role(self, account, name):
236 """Create a new user.
237
238 Arguments:
239 account - a cloud account
240 name - name of the user
241
242 Returns:
243 The user id
244 """
245 raise NotImplementedError
246
247 @rwstatus
248 def do_delete_role(self, account, role_id):
249 """Delete a user.
250
251 Arguments:
252 account - a cloud account
253 role_id - id of the user
254 """
255 raise NotImplementedError
256
257 @rwstatus(ret_on_failure=[[]])
258 def do_get_role_list(self, account):
259 """List roles.
260
261 Arguments:
262 account - a cloud account
263
264 Returns:
265 List of roles
266 """
267 raise NotImplementedError
268
269 @rwstatus(ret_on_failure=[""])
270 def do_create_image(self, account, image):
271 """Create an image
272
273 Arguments:
274 account - a cloud account
275 image - a description of the image to create
276
277 Returns:
278 The image id
279 """
280 drv = self._use_driver(account)
281 fd = drv.utils.image.create_image_handle(image)
282 kwargs = drv.utils.image.make_image_args(image)
283
284 try:
285 # Create Image
286 image_id = drv.glance_image_create(**kwargs)
287 drv.glance_image_upload(image_id, fd)
288 except Exception as e:
289 self.log.exception("Exception %s occured during image create", str(e))
290 raise
291 finally:
292 fd.close()
293
294 # Update image properties, if they are provided
295 try:
296 if image.has_field("properties") and image.properties is not None:
297 for key in image.properties:
298 drv.glance_image_update(image_id, **{key.name: key.property_value})
299 except Exception as e:
300 self.log.exception("Exception %s occured during image update", str(e))
301 raise
302
303 if image.checksum:
304 try:
305 stored_image = drv.glance_image_get(image_id)
306 if stored_image.checksum != image.checksum:
307 drv.glance_image_delete(image_id=image_id)
308 raise ImageUploadError("image checksum did not match (actual: %s, expected: %s). Deleting." %
309 (stored_image.checksum, image.checksum))
310 except Exception as e:
311 self.log.exception("Exception %s occured during image checksum verification", str(e))
312 raise
313
314 return image_id
315
316 @rwstatus
317 def do_delete_image(self, account, image_id):
318 """Delete a vm image.
319
320 Arguments:
321 account - a cloud account
322 image_id - id of the image to delete
323 """
324 drv = self._use_driver(account)
325 try:
326 drv.glance_image_delete(image_id=image_id)
327 except Exception as e:
328 self.log.exception("Exception %s occured during image deletion", str(e))
329 raise
330
331
332 @rwstatus(ret_on_failure=[[]])
333 def do_get_image_list(self, account):
334 """Return a list of the names of all available images.
335
336 Arguments:
337 account - a cloud account
338
339 Returns:
340 The the list of images in VimResources object
341 """
342 response = RwcalYang.VimResources()
343 drv = self._use_driver(account)
344 try:
345 images = drv.glance_image_list()
346 for img in images:
347 response.imageinfo_list.append(drv.utils.image.parse_cloud_image_info(img))
348 except Exception as e:
349 self.log.exception("Exception %s occured during get-image-list", str(e))
350 raise
351 return response
352
353 @rwstatus(ret_on_failure=[None])
354 def do_get_image(self, account, image_id):
355 """Return a image information.
356
357 Arguments:
358 account - a cloud account
359 image_id - an id of the image
360
361 Returns:
362 ImageInfoItem object containing image information.
363 """
364 drv = self._use_driver(account)
365 try:
366 image_info = drv.glance_image_get(image_id)
367 image = drv.utils.image.parse_cloud_image_info(image_info)
368 except Exception as e:
369 self.log.exception("Exception %s occured during get-image", str(e))
370 raise
371 return image
372
373
374 # This is being deprecated. Please do not use for new SW development
375 @rwstatus(ret_on_failure=[""])
376 def do_create_vm(self, account, vminfo):
377 """Create a new virtual machine.
378
379 Arguments:
380 account - a cloud account
381 vminfo - information that defines the type of VM to create
382
383 Returns:
384 The image id
385 """
386 from warnings import warn
387 warn("This function is deprecated")
388 kwargs = {}
389 kwargs['name'] = vminfo.vm_name
390 kwargs['flavor_id'] = vminfo.flavor_id
391 if vminfo.has_field('image_id'):
392 kwargs['image_id'] = vminfo.image_id
393
394 ### If floating_ip is required and we don't have one, better fail before any further allocation
395 pool_name = None
396 floating_ip = False
397 if vminfo.has_field('allocate_public_address') and vminfo.allocate_public_address:
398 if account.openstack.has_field('floating_ip_pool'):
399 pool_name = account.openstack.floating_ip_pool
400 floating_ip = True
401
402 if vminfo.has_field('cloud_init') and vminfo.cloud_init.has_field('userdata'):
403 kwargs['userdata'] = vminfo.cloud_init.userdata
404 else:
405 kwargs['userdata'] = ''
406
407 if account.openstack.security_groups:
408 kwargs['security_groups'] = account.openstack.security_groups
409
410 port_list = []
411 for port in vminfo.port_list:
412 port_list.append(port.port_id)
413
414 if port_list:
415 kwargs['port_list'] = port_list
416
417 network_list = []
418 for network in vminfo.network_list:
419 network_list.append(network.network_id)
420
421 if network_list:
422 kwargs['network_list'] = network_list
423
424 metadata = {}
425 for field in vminfo.user_tags.fields:
426 if vminfo.user_tags.has_field(field):
427 metadata[field] = getattr(vminfo.user_tags, field)
428 kwargs['metadata'] = metadata
429
430 if vminfo.has_field('availability_zone'):
431 kwargs['availability_zone'] = vminfo.availability_zone
432 else:
433 kwargs['availability_zone'] = None
434
435 if vminfo.has_field('server_group'):
436 kwargs['scheduler_hints'] = {'group': vminfo.server_group }
437 else:
438 kwargs['scheduler_hints'] = None
439
440 drv = self._use_driver(account)
441 vm_id = drv.nova_server_create(**kwargs)
442 if floating_ip:
443 self.prepare_vdu_on_boot(account, vm_id, floating_ip)
444
445 return vm_id
446
447 @rwstatus
448 def do_start_vm(self, account, vm_id):
449 """Start an existing virtual machine.
450
451 Arguments:
452 account - a cloud account
453 vm_id - an id of the VM
454 """
455 drv = self._use_driver(account)
456 drv.nova_server_start(vm_id)
457
458 @rwstatus
459 def do_stop_vm(self, account, vm_id):
460 """Stop a running virtual machine.
461
462 Arguments:
463 account - a cloud account
464 vm_id - an id of the VM
465 """
466 drv = self._use_driver(account)
467 drv.nova_server_stop(vm_id)
468
469 @rwstatus
470 def do_delete_vm(self, account, vm_id):
471 """Delete a virtual machine.
472
473 Arguments:
474 account - a cloud account
475 vm_id - an id of the VM
476 """
477 drv = self._use_driver(account)
478 drv.nova_server_delete(vm_id)
479
480 @rwstatus
481 def do_reboot_vm(self, account, vm_id):
482 """Reboot a virtual machine.
483
484 Arguments:
485 account - a cloud account
486 vm_id - an id of the VM
487 """
488 drv = self._use_driver(account)
489 drv.nova_server_reboot(vm_id)
490
491 @staticmethod
492 def _fill_vm_info(vm_info, mgmt_network):
493 """Create a GI object from vm info dictionary
494
495 Converts VM information dictionary object returned by openstack
496 driver into Protobuf Gi Object
497
498 Arguments:
499 vm_info - VM information from openstack
500 mgmt_network - Management network
501
502 Returns:
503 Protobuf Gi object for VM
504 """
505 vm = RwcalYang.VMInfoItem()
506 vm.vm_id = vm_info['id']
507 vm.vm_name = vm_info['name']
508 vm.image_id = vm_info['image']['id']
509 vm.flavor_id = vm_info['flavor']['id']
510 vm.state = vm_info['status']
511 for network_name, network_info in vm_info['addresses'].items():
512 if network_info:
513 if network_name == mgmt_network:
514 vm.public_ip = next((item['addr']
515 for item in network_info
516 if item['OS-EXT-IPS:type'] == 'floating'),
517 network_info[0]['addr'])
518 vm.management_ip = network_info[0]['addr']
519 else:
520 for interface in network_info:
521 addr = vm.private_ip_list.add()
522 addr.ip_address = interface['addr']
523
524 for network_name, network_info in vm_info['addresses'].items():
525 if network_info and network_name == mgmt_network and not vm.public_ip:
526 for interface in network_info:
527 if 'OS-EXT-IPS:type' in interface and interface['OS-EXT-IPS:type'] == 'floating':
528 vm.public_ip = interface['addr']
529
530 # Look for any metadata
531 for key, value in vm_info['metadata'].items():
532 if key in vm.user_tags.fields:
533 setattr(vm.user_tags, key, value)
534 if 'OS-EXT-SRV-ATTR:host' in vm_info:
535 if vm_info['OS-EXT-SRV-ATTR:host'] != None:
536 vm.host_name = vm_info['OS-EXT-SRV-ATTR:host']
537 if 'OS-EXT-AZ:availability_zone' in vm_info:
538 if vm_info['OS-EXT-AZ:availability_zone'] != None:
539 vm.availability_zone = vm_info['OS-EXT-AZ:availability_zone']
540 return vm
541
542 @rwstatus(ret_on_failure=[[]])
543 def do_get_vm_list(self, account):
544 """Return a list of the VMs as vala boxed objects
545
546 Arguments:
547 account - a cloud account
548
549 Returns:
550 List containing VM information
551 """
552 response = RwcalYang.VimResources()
553 drv = self._use_driver(account)
554 vms = drv.nova_server_list()
555 for vm in vms:
556 response.vminfo_list.append(RwcalOpenstackPlugin._fill_vm_info(vm, account.openstack.mgmt_network))
557 return response
558
559 @rwstatus(ret_on_failure=[None])
560 def do_get_vm(self, account, id):
561 """Return vm information.
562
563 Arguments:
564 account - a cloud account
565 id - an id for the VM
566
567 Returns:
568 VM information
569 """
570 drv = self._use_driver(account)
571 vm = drv.nova_server_get(id)
572 return RwcalOpenstackPlugin._fill_vm_info(vm, account.openstack.mgmt_network)
573
574
575 @rwstatus(ret_on_failure=[""])
576 def do_create_flavor(self, account, flavor):
577 """Create new flavor.
578
579 Arguments:
580 account - a cloud account
581 flavor - flavor of the VM
582
583 Returns:
584 flavor id
585 """
586 drv = self._use_driver(account)
587 return drv.nova_flavor_create(name = flavor.name,
588 ram = flavor.vm_flavor.memory_mb,
589 vcpus = flavor.vm_flavor.vcpu_count,
590 disk = flavor.vm_flavor.storage_gb,
591 epa_specs = drv.utils.flavor.get_extra_specs(flavor))
592
593
594 @rwstatus
595 def do_delete_flavor(self, account, flavor_id):
596 """Delete flavor.
597
598 Arguments:
599 account - a cloud account
600 flavor_id - id flavor of the VM
601 """
602 drv = self._use_driver(account)
603 drv.nova_flavor_delete(flavor_id)
604
605
606 @rwstatus(ret_on_failure=[[]])
607 def do_get_flavor_list(self, account):
608 """Return flavor information.
609
610 Arguments:
611 account - a cloud account
612
613 Returns:
614 List of flavors
615 """
616 response = RwcalYang.VimResources()
617 drv = self._use_driver(account)
618 flavors = drv.nova_flavor_list()
619 for flv in flavors:
620 response.flavorinfo_list.append(drv.utils.flavor.parse_flavor_info(flv))
621 return response
622
623 @rwstatus(ret_on_failure=[None])
624 def do_get_flavor(self, account, id):
625 """Return flavor information.
626
627 Arguments:
628 account - a cloud account
629 id - an id for the flavor
630
631 Returns:
632 Flavor info item
633 """
634 drv = self._use_driver(account)
635 flavor = drv.nova_flavor_get(id)
636 return drv.utils.flavor.parse_flavor_info(flavor)
637
638
639 def _fill_network_info(self, network_info, account):
640 """Create a GI object from network info dictionary
641
642 Converts Network information dictionary object returned by openstack
643 driver into Protobuf Gi Object
644
645 Arguments:
646 network_info - Network information from openstack
647 account - a cloud account
648
649 Returns:
650 Network info item
651 """
652 network = RwcalYang.NetworkInfoItem()
653 network.network_name = network_info['name']
654 network.network_id = network_info['id']
655 if ('provider:network_type' in network_info) and (network_info['provider:network_type'] != None):
656 network.provider_network.overlay_type = network_info['provider:network_type'].upper()
657 if ('provider:segmentation_id' in network_info) and (network_info['provider:segmentation_id']):
658 network.provider_network.segmentation_id = network_info['provider:segmentation_id']
659 if ('provider:physical_network' in network_info) and (network_info['provider:physical_network']):
660 network.provider_network.physical_network = network_info['provider:physical_network'].upper()
661
662 if 'subnets' in network_info and network_info['subnets']:
663 subnet_id = network_info['subnets'][0]
664 drv = self._use_driver(account)
665 subnet = drv.neutron_subnet_get(subnet_id)
666 network.subnet = subnet['cidr']
667 return network
668
669 @rwstatus(ret_on_failure=[[]])
670 def do_get_network_list(self, account):
671 """Return a list of networks
672
673 Arguments:
674 account - a cloud account
675
676 Returns:
677 List of networks
678 """
679 response = RwcalYang.VimResources()
680 drv = self._use_driver(account)
681 networks = drv.neutron_network_list()
682 for network in networks:
683 response.networkinfo_list.append(self._fill_network_info(network, account))
684 return response
685
686 @rwstatus(ret_on_failure=[None])
687 def do_get_network(self, account, id):
688 """Return a network
689
690 Arguments:
691 account - a cloud account
692 id - an id for the network
693
694 Returns:
695 Network info item
696 """
697 drv = self._use_driver(account)
698 network = drv.neutron_network_get(id)
699 return self._fill_network_info(network, account)
700
701 @rwstatus(ret_on_failure=[""])
702 def do_create_network(self, account, network):
703 """Create a new network
704
705 Arguments:
706 account - a cloud account
707 network - Network object
708
709 Returns:
710 Network id
711 """
712 from warnings import warn
713 warn("This function is deprecated")
714
715 kwargs = {}
716 kwargs['name'] = network.network_name
717 kwargs['admin_state_up'] = True
718 kwargs['external_router'] = False
719 kwargs['shared'] = False
720
721 if network.has_field('provider_network'):
722 if network.provider_network.has_field('physical_network'):
723 kwargs['physical_network'] = network.provider_network.physical_network
724 if network.provider_network.has_field('overlay_type'):
725 kwargs['network_type'] = network.provider_network.overlay_type.lower()
726 if network.provider_network.has_field('segmentation_id'):
727 kwargs['segmentation_id'] = network.provider_network.segmentation_id
728
729 drv = self._use_driver(account)
730 network_id = drv.neutron_network_create(**kwargs)
731 drv.neutron_subnet_create(network_id = network_id,
732 cidr = network.subnet)
733 return network_id
734
735 @rwstatus
736 def do_delete_network(self, account, network_id):
737 """Delete a network
738
739 Arguments:
740 account - a cloud account
741 network_id - an id for the network
742 """
743 drv = self._use_driver(account)
744 drv.neutron_network_delete(network_id)
745
746 @staticmethod
747 def _fill_port_info(port_info):
748 """Create a GI object from port info dictionary
749
750 Converts Port information dictionary object returned by openstack
751 driver into Protobuf Gi Object
752
753 Arguments:
754 port_info - Port information from openstack
755
756 Returns:
757 Port info item
758 """
759 port = RwcalYang.PortInfoItem()
760
761 port.port_name = port_info['name']
762 port.port_id = port_info['id']
763 port.network_id = port_info['network_id']
764 port.port_state = port_info['status']
765 if 'device_id' in port_info:
766 port.vm_id = port_info['device_id']
767 if 'fixed_ips' in port_info:
768 port.ip_address = port_info['fixed_ips'][0]['ip_address']
769 return port
770
771 @rwstatus(ret_on_failure=[None])
772 def do_get_port(self, account, port_id):
773 """Return a port
774
775 Arguments:
776 account - a cloud account
777 port_id - an id for the port
778
779 Returns:
780 Port info item
781 """
782 drv = self._use_driver(account)
783 port = drv.neutron_port_get(port_id)
784 return RwcalOpenstackPlugin._fill_port_info(port)
785
786 @rwstatus(ret_on_failure=[[]])
787 def do_get_port_list(self, account):
788 """Return a list of ports
789
790 Arguments:
791 account - a cloud account
792
793 Returns:
794 Port info list
795 """
796 response = RwcalYang.VimResources()
797 drv = self._use_driver(account)
798 ports = drv.neutron_port_list(*{})
799 for port in ports:
800 response.portinfo_list.append(RwcalOpenstackPlugin._fill_port_info(port))
801 return response
802
803 @rwstatus(ret_on_failure=[""])
804 def do_create_port(self, account, port):
805 """Create a new port
806
807 Arguments:
808 account - a cloud account
809 port - port object
810
811 Returns:
812 Port id
813 """
814 from warnings import warn
815 warn("This function is deprecated")
816
817 kwargs = {}
818 kwargs['name'] = port.port_name
819 kwargs['network_id'] = port.network_id
820 kwargs['admin_state_up'] = True
821 if port.has_field('vm_id'):
822 kwargs['vm_id'] = port.vm_id
823 if port.has_field('port_type'):
824 kwargs['port_type'] = port.port_type
825 else:
826 kwargs['port_type'] = "normal"
827
828 drv = self._use_driver(account)
829 return drv.neutron_port_create(**kwargs)
830
831 @rwstatus
832 def do_delete_port(self, account, port_id):
833 """Delete a port
834
835 Arguments:
836 account - a cloud account
837 port_id - an id for port
838 """
839 drv = self._use_driver(account)
840 drv.neutron_port_delete(port_id)
841
842 @rwstatus(ret_on_failure=[""])
843 def do_add_host(self, account, host):
844 """Add a new host
845
846 Arguments:
847 account - a cloud account
848 host - a host object
849
850 Returns:
851 An id for the host
852 """
853 raise NotImplementedError
854
855 @rwstatus
856 def do_remove_host(self, account, host_id):
857 """Remove a host
858
859 Arguments:
860 account - a cloud account
861 host_id - an id for the host
862 """
863 raise NotImplementedError
864
865 @rwstatus(ret_on_failure=[None])
866 def do_get_host(self, account, host_id):
867 """Return a host
868
869 Arguments:
870 account - a cloud account
871 host_id - an id for host
872
873 Returns:
874 Host info item
875 """
876 raise NotImplementedError
877
878 @rwstatus(ret_on_failure=[[]])
879 def do_get_host_list(self, account):
880 """Return a list of hosts
881
882 Arguments:
883 account - a cloud account
884
885 Returns:
886 List of hosts
887 """
888 raise NotImplementedError
889
890
891 @rwcalstatus(ret_on_failure=[""])
892 def do_create_virtual_link(self, account, link_params):
893 """Create a new virtual link
894
895 Arguments:
896 account - a cloud account
897 link_params - information that defines the type of VDU to create
898
899 Returns:
900 A kwargs dictionary for glance operation
901 """
902
903 drv = self._use_driver(account)
904 try:
905 kwargs = drv.utils.network.make_virtual_link_args(link_params)
906 network_id = drv.neutron_network_create(**kwargs)
907 except Exception as e:
908 self.log.error("Encountered exceptions during network creation. Exception: %s", str(e))
909 raise
910
911 kwargs = drv.utils.network.make_subnet_args(link_params, network_id)
912 drv.neutron_subnet_create(**kwargs)
913 return network_id
914
915
916 @rwstatus
917 def do_delete_virtual_link(self, account, link_id):
918 """Delete a virtual link
919
920 Arguments:
921 account - a cloud account
922 link_id - id for the virtual-link to be deleted
923
924 Returns:
925 None
926 """
927 drv = self._use_driver(account)
928 try:
929 port_list = drv.neutron_port_list(**{'network_id': link_id})
930 for port in port_list:
931 if ((port['device_owner'] == 'compute:None') or (port['device_owner'] == '')):
932 self.do_delete_port(account, port['id'], no_rwstatus=True)
933 self.do_delete_network(account, link_id, no_rwstatus=True)
934 except Exception as e:
935 self.log.exception("Exception %s occured during virtual-link deletion", str(e))
936 raise
937
938 @rwstatus(ret_on_failure=[None])
939 def do_get_virtual_link(self, account, link_id):
940 """Get information about virtual link.
941
942 Arguments:
943 account - a cloud account
944 link_id - id for the virtual-link
945
946 Returns:
947 Object of type RwcalYang.VirtualLinkInfoParams
948 """
949 drv = self._use_driver(account)
950 try:
951 network = drv.neutron_network_get(link_id)
952 if network:
953 port_list = drv.neutron_port_list(**{'network_id': network['id']})
954 if 'subnets' in network and network['subnets']:
955 subnet = drv.neutron_subnet_get(network['subnets'][0])
956 else:
957 subnet = None
958 virtual_link = drv.utils.network.parse_cloud_virtual_link_info(network, port_list, subnet)
959 except Exception as e:
960 self.log.exception("Exception %s occured during virtual-link-get", str(e))
961 raise
962 return virtual_link
963
964 @rwstatus(ret_on_failure=[None])
965 def do_get_virtual_link_list(self, account):
966 """Get information about all the virtual links
967
968 Arguments:
969 account - a cloud account
970
971 Returns:
972 A list of objects of type RwcalYang.VirtualLinkInfoParams
973 """
974 vnf_resources = RwcalYang.VNFResources()
975 drv = self._use_driver(account)
976 try:
977 networks = drv.neutron_network_list()
978 for network in networks:
979 port_list = drv.neutron_port_list(**{'network_id': network['id']})
980 if 'subnets' in network and network['subnets']:
981 subnet = drv.neutron_subnet_get(network['subnets'][0])
982 else:
983 subnet = None
984 virtual_link = drv.utils.network.parse_cloud_virtual_link_info(network, port_list, subnet)
985 vnf_resources.virtual_link_info_list.append(virtual_link)
986 except Exception as e:
987 self.log.exception("Exception %s occured during virtual-link-list-get", str(e))
988 raise
989 return vnf_resources
990
991
992
993 @rwcalstatus(ret_on_failure=[""])
994 def do_create_vdu(self, account, vdu_init):
995 """Create a new virtual deployment unit
996
997 Arguments:
998 account - a cloud account
999 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
1000
1001 Returns:
1002 The vdu_id
1003 """
1004 drv = self._use_driver(account)
1005 try:
1006 kwargs = drv.utils.compute.make_vdu_create_args(vdu_init, account)
1007 vm_id = drv.nova_server_create(**kwargs)
1008 self.prepare_vdu_on_boot(account, vm_id, vdu_init)
1009 except Exception as e:
1010 self.log.exception("Exception %s occured during create-vdu", str(e))
1011 raise
1012 return vm_id
1013
1014
1015 def prepare_vdu_on_boot(self, account, server_id, vdu_params):
1016 cmd = PREPARE_VM_CMD.format(auth_url = account.openstack.auth_url,
1017 username = account.openstack.key,
1018 password = account.openstack.secret,
1019 tenant_name = account.openstack.tenant,
1020 region = account.openstack.region,
1021 user_domain = account.openstack.user_domain,
1022 project_domain = account.openstack.project_domain,
1023 mgmt_network = account.openstack.mgmt_network,
1024 server_id = server_id)
1025 vol_list = list()
1026
1027 if vdu_params.has_field('allocate_public_address') and vdu_params.allocate_public_address:
1028 cmd += " --floating_ip"
1029 if account.openstack.has_field('floating_ip_pool'):
1030 cmd += (" --pool_name " + account.openstack.floating_ip_pool)
1031
1032 if vdu_params.has_field('volumes'):
1033 for volume in vdu_params.volumes:
1034 if volume.has_field('custom_meta_data'):
1035 vol_list.append(volume.as_dict())
1036
1037 if vol_list:
1038 with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_file:
1039 yaml.dump(vol_list, tmp_file)
1040 cmd += (" --vol_metadata {}").format(tmp_file.name)
1041
1042 exec_path = 'python3 ' + os.path.dirname(openstack_drv.__file__)
1043 exec_cmd = exec_path+'/'+cmd
1044 self.log.info("Running command: %s" %(exec_cmd))
1045 subprocess.call(exec_cmd, shell=True)
1046
1047 @rwstatus
1048 def do_modify_vdu(self, account, vdu_modify):
1049 """Modify Properties of existing virtual deployment unit
1050
1051 Arguments:
1052 account - a cloud account
1053 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
1054 """
1055 drv = self._use_driver(account)
1056 ### First create required number of ports aka connection points
1057 port_list = []
1058 network_list = []
1059 for c_point in vdu_modify.connection_points_add:
1060 if c_point.virtual_link_id in network_list:
1061 assert False, "Only one port per network supported. Refer: http://specs.openstack.org/openstack/nova-specs/specs/juno/implemented/nfv-multiple-if-1-net.html"
1062 else:
1063 network_list.append(c_point.virtual_link_id)
1064 port_id = self._create_connection_point(account, c_point)
1065 port_list.append(port_id)
1066
1067 drv = self._use_driver(account)
1068 ### Now add the ports to VM
1069 for port_id in port_list:
1070 drv.nova_server_add_port(vdu_modify.vdu_id, port_id)
1071
1072 ### Delete the requested connection_points
1073 for c_point in vdu_modify.connection_points_remove:
1074 self.do_delete_port(account, c_point.connection_point_id, no_rwstatus=True)
1075
1076 if vdu_modify.has_field('image_id'):
1077 drv.nova_server_rebuild(vdu_modify.vdu_id, vdu_modify.image_id)
1078
1079
1080 @rwstatus
1081 def do_delete_vdu(self, account, vdu_id):
1082 """Delete a virtual deployment unit
1083
1084 Arguments:
1085 account - a cloud account
1086 vdu_id - id for the vdu to be deleted
1087
1088 Returns:
1089 None
1090 """
1091 drv = self._use_driver(account)
1092 try:
1093 drv.utils.compute.perform_vdu_network_cleanup(vdu_id)
1094 drv.nova_server_delete(vdu_id)
1095 except Exception as e:
1096 self.log.exception("Exception %s occured during delete-vdu", str(e))
1097 raise
1098
1099
1100 @rwstatus(ret_on_failure=[None])
1101 def do_get_vdu(self, account, vdu_id):
1102 """Get information about a virtual deployment unit.
1103
1104 Arguments:
1105 account - a cloud account
1106 vdu_id - id for the vdu
1107
1108 Returns:
1109 Object of type RwcalYang.VDUInfoParams
1110 """
1111 drv = self._use_driver(account)
1112 try:
1113 vm_info = drv.nova_server_get(vdu_id)
1114 vdu_info = drv.utils.compute.parse_cloud_vdu_info(vm_info)
1115 except Exception as e:
1116 self.log.exception("Exception %s occured during get-vdu", str(e))
1117 raise
1118
1119 return vdu_info
1120
1121
1122 @rwstatus(ret_on_failure=[None])
1123 def do_get_vdu_list(self, account):
1124 """Get information about all the virtual deployment units
1125
1126 Arguments:
1127 account - a cloud account
1128
1129 Returns:
1130 A list of objects of type RwcalYang.VDUInfoParams
1131 """
1132 vnf_resources = RwcalYang.VNFResources()
1133 drv = self._use_driver(account)
1134 try:
1135 vms = drv.nova_server_list()
1136 for vm in vms:
1137 vdu = drv.utils.compute.parse_cloud_vdu_info(vm)
1138 vnf_resources.vdu_info_list.append(vdu)
1139 except Exception as e:
1140 self.log.exception("Exception %s occured during get-vdu-list", str(e))
1141 raise
1142 return vnf_resources
1143
1144
1145 class SdnOpenstackPlugin(GObject.Object, RwSdn.Topology):
1146 instance_num = 1
1147 def __init__(self):
1148 GObject.Object.__init__(self)
1149 self._driver_class = openstack_drv.OpenstackDriver
1150 self.log = logging.getLogger('rwsdn.openstack.%s' % SdnOpenstackPlugin.instance_num)
1151 self.log.setLevel(logging.DEBUG)
1152
1153 self._rwlog_handler = None
1154 SdnOpenstackPlugin.instance_num += 1
1155
1156 @contextlib.contextmanager
1157 def _use_driver(self, account):
1158 if self._rwlog_handler is None:
1159 raise UninitializedPluginError("Must call init() in CAL plugin before use.")
1160
1161 with rwlogger.rwlog_root_handler(self._rwlog_handler):
1162 try:
1163 drv = self._driver_class(username = account.openstack.key,
1164 password = account.openstack.secret,
1165 auth_url = account.openstack.auth_url,
1166 tenant_name = account.openstack.tenant,
1167 mgmt_network = account.openstack.mgmt_network,
1168 cert_validate = account.openstack.cert_validate )
1169 except Exception as e:
1170 self.log.error("SdnOpenstackPlugin: OpenstackDriver init failed. Exception: %s" %(str(e)))
1171 raise
1172
1173 yield drv
1174
1175 @rwstatus
1176 def do_init(self, rwlog_ctx):
1177 self._rwlog_handler = rwlogger.RwLogger(
1178 category="rw-cal-log",
1179 subcategory="openstack",
1180 log_hdl=rwlog_ctx,
1181 )
1182 self.log.addHandler(self._rwlog_handler)
1183 self.log.propagate = False
1184
1185 @rwstatus(ret_on_failure=[None])
1186 def do_validate_sdn_creds(self, account):
1187 """
1188 Validates the sdn account credentials for the specified account.
1189 Performs an access to the resources using Keystone API. If creds
1190 are not valid, returns an error code & reason string
1191
1192 @param account - a SDN account
1193
1194 Returns:
1195 Validation Code and Details String
1196 """
1197 status = RwsdnYang.SdnConnectionStatus()
1198 try:
1199 with self._use_driver(account) as drv:
1200 drv.validate_account_creds()
1201
1202 except openstack_drv.ValidationError as e:
1203 self.log.error("SdnOpenstackPlugin: OpenstackDriver credential validation failed. Exception: %s", str(e))
1204 status.status = "failure"
1205 status.details = "Invalid Credentials: %s" % str(e)
1206
1207 except Exception as e:
1208 msg = "SdnOpenstackPlugin: OpenstackDriver connection failed. Exception: %s" %(str(e))
1209 self.log.error(msg)
1210 status.status = "failure"
1211 status.details = msg
1212
1213 else:
1214 status.status = "success"
1215 status.details = "Connection was successful"
1216
1217 return status
1218
1219 @rwstatus(ret_on_failure=[""])
1220 def do_create_vnffg_chain(self, account,vnffg):
1221 """
1222 Creates Service Function chain in ODL
1223
1224 @param account - a SDN account
1225
1226 """
1227 self.log.debug('Received Create VNFFG chain for account {}, chain {}'.format(account,vnffg))
1228 with self._use_driver(account) as drv:
1229 port_list = list()
1230 vnf_chain_list = sorted(vnffg.vnf_chain_path, key = lambda x: x.order)
1231 prev_vm_id = None
1232 for path in vnf_chain_list:
1233 if prev_vm_id and path.vnfr_ids[0].vdu_list[0].vm_id == prev_vm_id:
1234 prev_entry = port_list.pop()
1235 port_list.append((prev_entry[0],path.vnfr_ids[0].vdu_list[0].port_id))
1236 prev_vm_id = None
1237 else:
1238 prev_vm_id = path.vnfr_ids[0].vdu_list[0].vm_id
1239 port_list.append((path.vnfr_ids[0].vdu_list[0].port_id,path.vnfr_ids[0].vdu_list[0].port_id))
1240 vnffg_id = drv.create_port_chain(vnffg.name,port_list)
1241 return vnffg_id
1242
1243 @rwstatus
1244 def do_terminate_vnffg_chain(self, account,vnffg_id):
1245 """
1246 Terminate Service Function chain in ODL
1247
1248 @param account - a SDN account
1249 """
1250 self.log.debug('Received terminate VNFFG chain for id %s ', vnffg_id)
1251 with self._use_driver(account) as drv:
1252 drv.delete_port_chain(vnffg_id)
1253
1254 @rwstatus(ret_on_failure=[None])
1255 def do_create_vnffg_classifier(self, account, vnffg_classifier):
1256 """
1257 Add VNFFG Classifier
1258
1259 @param account - a SDN account
1260 """
1261 self.log.debug('Received Create VNFFG classifier for account {}, classifier {}'.format(account,vnffg_classifier))
1262 protocol_map = {1:'ICMP',6:'TCP',17:'UDP'}
1263 flow_classifier_list = list()
1264 with self._use_driver(account) as drv:
1265 for rule in vnffg_classifier.match_attributes:
1266 classifier_name = vnffg_classifier.name + '_' + rule.name
1267 flow_dict = {}
1268 for field, value in rule.as_dict().items():
1269 if field == 'ip_proto':
1270 flow_dict['protocol'] = protocol_map.get(value,None)
1271 elif field == 'source_ip_address':
1272 flow_dict['source_ip_prefix'] = value
1273 elif field == 'destination_ip_address':
1274 flow_dict['destination_ip_prefix'] = value
1275 elif field == 'source_port':
1276 flow_dict['source_port_range_min'] = value
1277 flow_dict['source_port_range_max'] = value
1278 elif field == 'destination_port':
1279 flow_dict['destination_port_range_min'] = value
1280 flow_dict['destination_port_range_max'] = value
1281 if vnffg_classifier.has_field('port_id'):
1282 flow_dict['logical_source_port'] = vnffg_classifier.port_id
1283 flow_classifier_id = drv.create_flow_classifer(classifier_name, flow_dict)
1284 flow_classifier_list.append(flow_classifier_id)
1285 drv.update_port_chain(vnffg_classifier.rsp_id,flow_classifier_list)
1286 return flow_classifier_list
1287
1288 @rwstatus(ret_on_failure=[None])
1289 def do_terminate_vnffg_classifier(self, account, vnffg_classifier_list):
1290 """
1291 Add VNFFG Classifier
1292
1293 @param account - a SDN account
1294 """
1295 self.log.debug('Received terminate VNFFG classifier for id %s ', vnffg_classifier_list)
1296 with self._use_driver(account) as drv:
1297 for classifier_id in vnffg_classifier_list:
1298 drv.delete_flow_classifier(classifier_id)
1299
1300 @rwstatus(ret_on_failure=[None])
1301 def do_get_vnffg_rendered_paths(self, account):
1302 """
1303 Get ODL Rendered Service Path List (SFC)
1304
1305 @param account - a SDN account
1306 """
1307 self.log.debug('Received get VNFFG rendered path for account %s ', account)
1308 vnffg_rsps = RwsdnYang.VNFFGRenderedPaths()
1309 with self._use_driver(account) as drv:
1310 port_chain_list = drv.get_port_chain_list()
1311 for port_chain in port_chain_list:
1312 #rsp = vnffg_rsps.vnffg_rendered_path.add()
1313 #rsp.name = port_chain['name']
1314 pass
1315 return vnffg_rsps
1316
1317