Merge "CLI for OSM"
[osm/SO.git] / rwcal / plugins / vala / rwcal_cloudsim / rwcal_cloudsim.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 collections
19 import hashlib
20 import itertools
21 import logging
22 import os
23 import time
24 import uuid
25
26 import ipaddress
27
28 from gi import require_version
29 require_version('RwCal', '1.0')
30
31 from gi.repository import (
32 GObject,
33 RwCal,
34 RwTypes,
35 RwcalYang,
36 )
37
38 import rw_status
39 import rift.cal.rwcal_status as rwcal_status
40 import rwlogger
41
42 import rift.rwcal.cloudsim.lxc as lxc
43 import rift.rwcal.cloudsim.lvm as lvm
44 import rift.rwcal.cloudsim.net as net
45 import rift.rwcal.cloudsim.exceptions as exceptions
46
47 logger = logging.getLogger('rwcal.cloudsim')
48
49 rwstatus_exception_map = { IndexError: RwTypes.RwStatus.NOTFOUND,
50 KeyError: RwTypes.RwStatus.NOTFOUND,
51 NotImplementedError: RwTypes.RwStatus.NOT_IMPLEMENTED,}
52
53 rwstatus = rw_status.rwstatus_from_exc_map(rwstatus_exception_map)
54 rwcalstatus = rwcal_status.rwcalstatus_from_exc_map(rwstatus_exception_map)
55
56
57 class UnknownAccountError(Exception):
58 pass
59
60
61 class MissingFileError(Exception):
62 pass
63
64
65 class ImageLocationError(Exception):
66 pass
67
68
69 class CreateNetworkError(Exception):
70 pass
71
72
73 rwstatus = rw_status.rwstatus_from_exc_map({
74 IndexError: RwTypes.RwStatus.NOTFOUND,
75 KeyError: RwTypes.RwStatus.NOTFOUND,
76 UnknownAccountError: RwTypes.RwStatus.NOTFOUND,
77 MissingFileError: RwTypes.RwStatus.NOTFOUND,
78 })
79
80
81 class Resources(object):
82 def __init__(self):
83 self.images = dict()
84
85
86 def rwcal_copy_object(obj):
87 dup = obj.__class__()
88 dup.copy_from(obj)
89 return dup
90
91
92 MGMT_NETWORK_NAME = "virbr0"
93 MGMT_NETWORK_INTERFACE_IP = ipaddress.IPv4Interface("192.168.122.1/24")
94
95
96 class IPPoolError(Exception):
97 pass
98
99
100 class NetworkIPPool(object):
101 def __init__(self, subnet):
102 self._network = ipaddress.IPv4Network(subnet)
103 self._ip_gen = self._network.hosts()
104 self._allocated_ips = []
105 self._unallocated_ips = []
106
107 def allocate_ip(self):
108 try:
109 ip = str(next(self._ip_gen))
110 except StopIteration:
111 try:
112 ip = self._unallocated_ips.pop()
113 except IndexError:
114 raise IPPoolError("All ip addresses exhausted")
115
116 self._allocated_ips.append(ip)
117 return ip
118
119 def deallocate_ip(self, ip):
120 if ip not in self._allocated_ips:
121 raise ValueError("Did not find IP %s in allocate ip pool")
122
123 self._allocated_ips.remove(ip)
124 self._unallocated_ips.append(ip)
125
126
127 class CalManager(object):
128 def __init__(self):
129 self._vms = {}
130 self._ports = {}
131 self._images = {}
132 self._networks = {}
133 self.flavors = {}
134
135 self._port_to_vm = {}
136 self._vm_to_image = {}
137 self._port_to_network = {}
138 self._network_to_ip_pool = {}
139
140 self._vm_to_ports = collections.defaultdict(list)
141 self._image_to_vms = collections.defaultdict(list)
142 self._network_to_ports = collections.defaultdict(list)
143
144 self._vm_id_gen = itertools.count(1)
145 self._network_id_gen = itertools.count(1)
146 self._image_id_gen = itertools.count(1)
147
148 def add_image(self, image):
149 image_id = str(next(self._image_id_gen))
150 self._images[image_id] = image
151
152 return image_id
153
154 def remove_image(self, image_id):
155 for vm_id in self.get_image_vms(image_id):
156 self.remove_vm(vm_id)
157
158 del self._images[image_id]
159 del self._image_to_vms[image_id]
160
161 def get_image(self, image_id):
162 if image_id not in self._images:
163 msg = "Unable to find image {}"
164 raise exceptions.RWErrorNotFound(msg.format(image_id))
165
166 return self._images[image_id]
167
168 def get_image_list(self):
169 return list(self._images.values())
170
171 def get_image_vms(self, image_id):
172 if image_id not in self._images:
173 msg = "Unable to find image {}"
174 raise exceptions.RWErrorNotFound(msg.format(image_id))
175
176 return self._image_to_vms[image_id]
177
178 def add_port(self, network_id, vm_id, port):
179 if network_id not in self._networks:
180 msg = "Unable to find network {}"
181 raise exceptions.RWErrorNotFound(msg.format(network_id))
182
183 if vm_id not in self._vms:
184 msg = "Unable to find vm {}"
185 raise exceptions.RWErrorNotFound(msg.format(vm_id))
186
187 port_id = str(uuid.uuid4())
188 self._ports[port_id] = port
189
190 self._vm_to_ports[vm_id].append(port_id)
191 self._network_to_ports[network_id].append(port_id)
192
193 self._port_to_vm[port_id] = vm_id
194 self._port_to_network[port_id] = network_id
195
196 return port_id
197
198 def remove_port(self, port_id):
199 if port_id not in self._ports:
200 msg = "Unable to find port {}"
201 raise exceptions.RWErrorNotFound(msg.format(port_id))
202
203 network_id = self._port_to_network[port_id]
204 vm_id = self._port_to_vm[port_id]
205
206 self._vm_to_ports[vm_id].remove(port_id)
207 self._network_to_ports[network_id].remove(port_id)
208
209 del self._ports[port_id]
210 del self._port_to_vm[port_id]
211 del self._port_to_network[port_id]
212
213 def get_port(self, port_id):
214 return self._ports[port_id]
215
216 def get_port_list(self):
217 return list(self._ports.values())
218
219 def add_network(self, network):
220 network_id = str(next(self._network_id_gen))
221 self._networks[network_id] = network
222
223 return network_id
224
225 def remove_network(self, network_id):
226 for port_id in self.get_network_ports(network_id):
227 self.remove_port(port_id)
228
229 del self._networks[network_id]
230
231 def get_network(self, network_id):
232 return self._networks[network_id]
233
234 def add_network_ip_pool(self, network_id, ip_pool):
235 self._network_to_ip_pool[network_id] = ip_pool
236
237 def get_network_ip_pool(self, network_id):
238 return self._network_to_ip_pool[network_id]
239
240 def remove_network_ip_pool(self, network_id):
241 del self._network_to_ip_pool[network_id]
242
243 def get_network_list(self):
244 return list(self._networks.values())
245
246 def get_network_ports(self, network_id):
247 return self._network_to_ports[network_id]
248
249 def add_vm(self, image_id, vm):
250 if image_id not in self._images:
251 msg = "Unable to find image {}"
252 raise exceptions.RWErrorNotFound(msg.format(image_id))
253
254 vm_id = str(next(self._vm_id_gen))
255 self._vms[vm_id] = vm
256
257 self._vm_to_image[vm_id] = image_id
258 self._image_to_vms[image_id].append(vm_id)
259
260 return vm_id
261
262 def remove_vm(self, vm_id):
263 for port_id in self.get_vm_ports(vm_id):
264 self.remove_port(port_id)
265
266 image_id = self._vm_to_image[vm_id]
267
268 self._image_to_vms[image_id].remove(vm_id)
269
270 del self._vms[vm_id]
271 del self._vm_to_image[vm_id]
272
273 def get_vm(self, vm_id):
274 return self._vms[vm_id]
275
276 def get_vm_list(self):
277 return list(self._vms.values())
278
279 def get_vm_ports(self, vm_id):
280 return self._vm_to_ports[vm_id]
281
282
283 class LxcManager(object):
284 def __init__(self):
285 self._containers = {}
286 self._ports = {}
287 self._bridges = {}
288
289 self._port_to_container = {}
290 self._port_to_bridge = {}
291
292 self._container_to_ports = collections.defaultdict(list)
293 self._bridge_to_ports = collections.defaultdict(list)
294
295 # Create the management network
296 self.mgmt_network = RwcalYang.NetworkInfoItem()
297 self.mgmt_network.network_name = MGMT_NETWORK_NAME
298
299 network = MGMT_NETWORK_INTERFACE_IP.network
300 self.mgmt_network.subnet = str(network)
301
302 # Create/Start the default virtd network for NAT-based
303 # connectivity inside containers (http://wiki.libvirt.org/page/Networking)
304 if "default" not in net.virsh_list_network_names():
305 logger.debug("default virtd network not found. Creating.")
306 net.virsh_define_default()
307
308 # The default virsh profile create a virbr0 interface
309 # with a 192.168.122.1 ip address. Also sets up iptables
310 # for NAT access.
311 net.virsh_start("default")
312
313 # Create the IP pool
314 mgmt_network_hosts = network.hosts()
315
316 # Remove the management interface ip from the pool
317 self._mgmt_ip_pool = list(mgmt_network_hosts)
318 self._mgmt_ip_pool.remove(MGMT_NETWORK_INTERFACE_IP.ip)
319
320 def acquire_mgmt_ip(self):
321 """Returns an IP address from the available pool"""
322 # TODO these ips will need to be recycled at some point
323 return str(self._mgmt_ip_pool.pop())
324
325 def add_port(self, bridge_id, container_id, port):
326 if bridge_id not in self._bridges:
327 msg = "Unable to find bridge {}"
328 raise exceptions.RWErrorNotFound(msg.format(bridge_id))
329
330 if container_id not in self._containers:
331 msg = "Unable to find container {}"
332 raise exceptions.RWErrorNotFound(msg.format(container_id))
333
334 port_id = str(uuid.uuid4())
335 self._ports[port_id] = port
336
337 self._container_to_ports[container_id].append(port_id)
338 self._bridge_to_ports[bridge_id].append(port_id)
339
340 self._port_to_container[port_id] = container_id
341 self._port_to_bridge[port_id] = bridge_id
342
343 return port_id
344
345 def remove_port(self, port_id):
346 if port_id not in self._ports:
347 msg = "Unable to find port {}"
348 raise exceptions.RWErrorNotFound(msg.format(port_id))
349
350 bridge_id = self._port_to_bridge[port_id]
351 container_id = self._port_to_container[port_id]
352
353 self._container_to_ports[container_id].remove(port_id)
354 self._bridge_to_ports[bridge_id].remove(port_id)
355
356 del self._ports[port_id]
357 del self._port_to_bridge[port_id]
358 del self._port_to_container[port_id]
359
360 def get_port(self, port_id):
361 return self._ports[port_id]
362
363 def add_bridge(self, bridge):
364 bridge_id = str(uuid.uuid4())
365 self._bridges[bridge_id] = bridge
366
367 return bridge_id
368
369 def remove_bridge(self, bridge_id):
370 for port_id in self._bridge_to_ports[bridge_id]:
371 self.remove_port(port_id)
372
373 del self._bridges[bridge_id]
374
375 def get_bridge(self, bridge_id):
376 return self._bridges[bridge_id]
377
378 def get_bridge_ports(self, bridge_id):
379 port_ids = self._bridge_to_ports[bridge_id]
380 return [self.get_port(port_id) for port_id in port_ids]
381
382 def add_container(self, container):
383 container_id = str(uuid.uuid4())
384 self._containers[container_id] = container
385
386 return container_id
387
388 def remove_container(self, container_id):
389 for port_id in self.get_container_ports(container_id):
390 self.remove_port(port_id)
391
392 del self._containers[container_id]
393
394 def get_container(self, container_id):
395 return self._containers[container_id]
396
397 def get_container_ports(self, container_id):
398 return self._container_to_ports[container_id]
399
400
401
402 class Datastore(object):
403 """
404 This class is used to store data that is shared among different instance of
405 the Container class.
406 """
407 def __init__(self):
408 self.lxc_manager = LxcManager()
409 self.cal_manager = CalManager()
410 self.cal_to_lxc = {'image': {}, 'port': {}, 'network': {}, 'vm': {}}
411 self.last_index = 0
412
413
414 class CloudSimPlugin(GObject.Object, RwCal.Cloud):
415 # HACK this is a work-around for sharing/persisting container information.
416 # This will only work for instances of CloudSimPlugin that are within the
417 # same process. Thus, it works in collapsed mode, but will not work in
418 # expanded mode. At the point where it is necessary to persist this
419 # information in expanded mode, we will need to find a better solution.
420 datastore = None
421
422 def __init__(self):
423 GObject.Object.__init__(self)
424 if CloudSimPlugin.datastore is None:
425 CloudSimPlugin.datastore = Datastore()
426
427 @property
428 def lxc(self):
429 return CloudSimPlugin.datastore.lxc_manager
430
431 @property
432 def cal(self):
433 return CloudSimPlugin.datastore.cal_manager
434
435 @property
436 def volume_group(self):
437 return lvm.get("rift")
438
439 @property
440 def cal_to_lxc(self):
441 return CloudSimPlugin.datastore.cal_to_lxc
442
443 def next_snapshot_name(self):
444 """Generates a new snapshot name for a container"""
445 CloudSimPlugin.datastore.last_index += 1
446 return 'rws{}'.format(CloudSimPlugin.datastore.last_index)
447
448 @rwstatus
449 def do_init(self, rwlog_ctx):
450 if not any(isinstance(h, rwlogger.RwLogger) for h in logger.handlers):
451 logger.addHandler(
452 rwlogger.RwLogger(
453 category="rw-cal-log",
454 subcategory="cloudsim",
455 log_hdl=rwlog_ctx,
456 )
457 )
458
459 @rwstatus(ret_on_failure=[None])
460 def do_validate_cloud_creds(self, account):
461 """
462 Validates the cloud account credentials for the specified account.
463 If creds are not valid, returns an error code & reason string
464 Arguments:
465 account - a cloud account to validate
466
467 Returns:
468 Validation Code and Details String
469 """
470 status = RwcalYang.CloudConnectionStatus(
471 status="success",
472 details=""
473 )
474
475 return status
476
477 @rwstatus(ret_on_failure=[None])
478 def do_get_management_network(self, account):
479 """Returns the management network
480
481 Arguments:
482 account - a cloud account
483
484 Returns:
485 a NetworkInfo object
486
487 """
488 return self.lxc.mgmt_network
489
490 @rwstatus
491 def do_create_tenant(self, account, name):
492 """
493 Create a new tenant.
494
495 @param name - name to assign to the tenant.
496 """
497 raise NotImplementedError()
498
499 @rwstatus
500 def do_delete_tenant(self, account, tenant_id):
501 """
502 delete a tenant.
503
504 @param tenant_id - id of tenant to be deleted.
505 """
506 raise NotImplementedError()
507
508 @rwstatus(ret_on_failure=[[]])
509 def do_get_tenant_list(self, account):
510 """
511 List tenants.
512
513 """
514 raise NotImplementedError()
515
516 @rwstatus
517 def do_create_role(self, account, name):
518 """
519 Create a new role.
520
521 @param name - name to assign to the role.
522 """
523 raise NotImplementedError()
524
525 @rwstatus
526 def do_delete_role(self, account, role_id):
527 """
528 delete a role.
529
530 @param role_id - id of role to be deleted.
531 """
532 raise NotImplementedError()
533
534 @rwstatus(ret_on_failure=[[]])
535 def do_get_role_list(self, account):
536 """
537 List roles.
538
539 """
540 raise NotImplementedError()
541
542 @rwstatus(ret_on_failure=[None])
543 def do_create_image(self, account, image):
544 """Create a new image
545
546 Creates a new container based upon the template and tarfile specified.
547 Only one image is currently supported for a given instance of the CAL.
548
549 Arguments:
550 account - a cloud account
551 image - an ImageInfo object
552
553 Raises:
554 An RWErrorDuplicate is raised if create_image is called and there
555 is already an image.
556
557 Returns:
558 The UUID of the new image
559
560 """
561 def file_md5(path, block_size=2 ** 20):
562 """
563 Block size directly depends on the block size of your filesystem
564 to avoid performances issues.
565 """
566 md5 = hashlib.md5()
567 with open(path, 'rb') as f:
568 for chunk in iter(lambda: f.read(block_size), b''):
569 md5.update(chunk)
570
571 return md5.hexdigest()
572
573 current_images = self.cal.get_image_list()
574 lxc_name = "rwm{}".format(len(current_images))
575
576 if not image.has_field("disk_format"):
577 logger.warning("Image disk format not provided assuming qcow2")
578 image.disk_format = "qcow2"
579
580 if image.disk_format not in ["qcow2"]:
581 msg = "Only qcow2 currently supported for container CAL"
582 raise exceptions.RWErrorNotSupported(msg)
583
584 logger.debug('Calculating IMAGE checksum...')
585 image.checksum = file_md5(image.location)
586 logger.debug("Calculated image checksum: %s", image.checksum)
587 image.state = 'active'
588
589 container = lxc.create_container(
590 name=lxc_name,
591 template_path=os.path.join(
592 os.environ['RIFT_INSTALL'],
593 "etc/lxc-fedora-rift.lxctemplate",
594 ),
595 volume="rift",
596 rootfs_qcow2file=image.location,
597 )
598
599
600 # Add the images to the managers
601 cal_image_id = self.cal.add_image(image)
602 lxc_image_id = self.lxc.add_container(container)
603
604 # Create the CAL to LXC mapping
605 self.cal_to_lxc["image"][cal_image_id] = lxc_image_id
606
607 image.id = cal_image_id
608
609 return image.id
610
611 @rwstatus
612 def do_delete_image(self, account, image_id):
613 """Deletes an image
614
615 This function will remove the record of the image from the CAL and
616 destroy the associated container.
617
618 Arguments:
619 account - a cloud account
620 image_id - the UUID of the image to delete
621
622 Raises:
623 An RWErrorNotEmpty exception is raised if there are VMs based on
624 this image (the VMs need to be deleted first). An RWErrorNotFound
625 is raised if the image_id does not match any of the known images.
626
627 """
628 container_id = self.cal_to_lxc["image"][image_id]
629 container = self.lxc.get_container(container_id)
630
631 # Stop the image and destroy it (NB: it should not be necessary to stop
632 # the container, but just in case)
633 container.stop()
634 container.destroy()
635
636 self.cal.remove_image(image_id)
637 self.lxc.remove_container(container_id)
638
639 @rwstatus(ret_on_failure=[None])
640 def do_get_image(self, account, image_id):
641 """Returns the specified image
642
643 Arguments:
644 account - a cloud account
645 image_id - the UUID of the image to retrieve
646
647 Raises:
648 An RWErrorNotFound exception is raised if the image_id does not
649 match any of the known images.
650
651 Returns:
652 An image object
653
654 """
655 return self.cal.get_image(image_id)
656
657 @rwstatus(ret_on_failure=[[]])
658 def do_get_image_list(self, account):
659 """Returns a list of images"""
660 resources = RwcalYang.VimResources()
661 for image in self.cal.get_image_list():
662 resources.imageinfo_list.append(rwcal_copy_object(image))
663
664 return resources
665
666 @rwstatus
667 def do_create_vm(self, account, vm):
668 """Create a VM
669
670 Arguments:
671 vm - the VM info used to define the desire VM
672
673 Raises:
674 An RWErrorFailure is raised if there is not
675
676 Returns:
677 a string containing the unique id of the created VM
678
679 """
680 # Retrieve the container that will be used as the base of the snapshot
681 container_id = self.cal_to_lxc["image"][vm.image_id]
682 container = self.lxc.get_container(container_id)
683
684 # Create a container snapshot
685 snapshot = container.snapshot(self.next_snapshot_name())
686 snapshot.hostname = vm.vm_name
687
688 # Register the vm and container
689 snapshot_id = self.lxc.add_container(snapshot)
690 vm.vm_id = self.cal.add_vm(vm.image_id, vm)
691
692 self.cal_to_lxc["vm"][vm.vm_id] = snapshot_id
693
694 return vm.vm_id
695
696 @rwstatus
697 def do_start_vm(self, account, vm_id):
698 """Starts the specified VM
699
700 Arguments:
701 vm_id - the id of the vm to start
702
703 Raises:
704 An RWErrorNotFound is raised if the specified vm id is not known to
705 this driver.
706
707 """
708 if vm_id not in self.cal_to_lxc["vm"]:
709 msg = "Unable to find the specified VM ({})"
710 raise exceptions.RWErrorNotFound(msg.format(vm_id))
711
712 container_id = self.cal_to_lxc["vm"][vm_id]
713
714 snapshot = self.lxc.get_container(container_id)
715 port_ids = self.lxc.get_container_ports(container_id)
716
717 config = lxc.ContainerConfig(snapshot.name)
718
719 for port_id in port_ids:
720 port = self.lxc.get_port(port_id)
721 config.add_network_config(port)
722
723 vm = self.cal.get_vm(vm_id)
724
725 # Set the management IP on the vm if not yet set
726 if not vm.has_field("management_ip"):
727 mgmt_ip = self.lxc.acquire_mgmt_ip()
728 vm.management_ip = mgmt_ip
729
730 # Add the management interface
731 config.add_network_config(
732 lxc.NetworkConfig(
733 type="veth",
734 link=self.lxc.mgmt_network.network_name,
735 name="eth0",
736 ipv4=vm.management_ip,
737 ipv4_gateway='auto',
738 )
739 )
740
741 # Add rift root as a mount point
742 config.add_mount_point_config(
743 lxc.MountConfig(
744 local=os.environ["RIFT_ROOT"],
745 remote=os.environ["RIFT_ROOT"][1:],
746 read_only=False,
747 )
748 )
749
750 userdata=None
751 if vm.cloud_init.has_field("userdata"):
752 userdata = vm.cloud_init.userdata
753
754 snapshot.configure(config, userdata=userdata)
755 # For some reason, the cloud-init fails or runs only partially when
756 # you start the container immediately after writing the config files.
757 # A sleep of 1 sec seems to magically fix the issue!!
758 time.sleep(1)
759 snapshot.start()
760
761 @rwstatus
762 def do_stop_vm(self, account, vm_id):
763 """Stops the specified VM
764
765 Arguments:
766 vm_id - the id of the vm to stop
767
768 Raises:
769 An RWErrorNotFound is raised if the specified vm id is not known to
770 this driver.
771
772 """
773 if vm_id not in self.cal_to_lxc["vm"]:
774 msg = "Unable to find the specified VM ({})"
775 raise exceptions.RWErrorNotFound(msg.format(vm_id))
776
777 # Stop the container
778 container_id = self.cal_to_lxc["vm"][vm_id]
779 snapshot = self.lxc.get_container(container_id)
780 snapshot.stop()
781
782 @rwstatus
783 def do_delete_vm(self, account, vm_id):
784 """Deletes the specified VM
785
786 Arguments:
787 vm_id - the id of the vm to delete
788
789 Raises:
790 An RWErrorNotFound is raised if the specified vm id is not known to
791 this driver.
792
793 """
794 if vm_id not in self.cal_to_lxc["vm"]:
795 msg = "Unable to find the specified VM ({})"
796 raise exceptions.RWErrorNotFound(msg.format(vm_id))
797
798 container_id = self.cal_to_lxc["vm"][vm_id]
799
800 snapshot = self.lxc.get_container(container_id)
801 snapshot.stop()
802 snapshot.destroy()
803
804 self.cal.remove_vm(vm_id)
805 self.lxc.remove_container(container_id)
806
807 # TODO: Recycle management ip
808
809 @rwstatus
810 def do_reboot_vm(self, account, vm_id):
811 """
812 reboot a virtual machine.
813
814 @param vm_id - Instance id of VM to be deleted.
815 """
816 self.do_stop_vm(account, vm_id, no_rwstatus=True)
817 self.do_start_vm(account, vm_id, no_rwstatus=True)
818
819 @rwstatus
820 def do_get_vm(self, account, vm_id):
821 """Returns the specified VM
822
823 Arguments:
824 vm_id - the id of the vm to return
825
826 Raises:
827 An RWErrorNotFound is raised if the specified vm id is not known to
828 this driver.
829
830 Returns:
831 a VMInfoItem object
832
833 """
834 if vm_id not in self.cal_to_lxc["vm"]:
835 msg = "Unable to find the specified VM ({})"
836 raise exceptions.RWErrorNotFound(msg.format(vm_id))
837
838 return self.cal.get_vm(vm_id)
839
840 @rwstatus(ret_on_failure=[[]])
841 def do_get_vm_list(self, account):
842 """Returns the a list of the VMs known to the driver
843
844 Returns:
845 a list of VMInfoItem objects
846
847 """
848 resources = RwcalYang.VimResources()
849 for vm in self.cal.get_vm_list():
850 resources.vminfo_list.append(rwcal_copy_object(vm))
851
852 return resources
853
854 @rwstatus
855 def do_create_flavor(self, account, flavor):
856 """
857 create new flavor.
858
859 @param flavor - Flavor object
860 """
861 flavor_id = str(uuid.uuid4())
862 flavor.id = flavor_id
863 self.cal.flavors[flavor_id] = flavor
864 logger.debug('Created flavor: {}'.format(flavor_id))
865 return flavor_id
866
867 @rwstatus
868 def do_delete_flavor(self, account, flavor_id):
869 """
870 Delete flavor.
871
872 @param flavor_id - Flavor id to be deleted.
873 """
874 logger.debug('Deleted flavor: {}'.format(flavor_id))
875 self.cal.flavors.pop(flavor_id)
876
877 @rwstatus(ret_on_failure=[None])
878 def do_get_flavor(self, account, flavor_id):
879 """
880 Return the specified flavor
881
882 @param flavor_id - the id of the flavor to return
883 """
884 flavor = self.cal.flavors[flavor_id]
885 logger.debug('Returning flavor-info for : {}'.format(flavor_id))
886 return flavor
887
888 @rwstatus(ret_on_failure=[[]])
889 def do_get_flavor_list(self, account):
890 """
891 Return a list of flavors
892 """
893 vim_resources = RwcalYang.VimResources()
894 for flavor in self.cal.flavors.values():
895 f = RwcalYang.FlavorInfoItem()
896 f.copy_from(flavor)
897 vim_resources.flavorinfo_list.append(f)
898 logger.debug("Returning list of flavor-info of size: %d", len(vim_resources.flavorinfo_list))
899 return vim_resources
900
901 @rwstatus
902 def do_add_host(self, account, host):
903 raise NotImplementedError()
904
905 @rwstatus
906 def do_remove_host(self, account, host_id):
907 raise NotImplementedError()
908
909 @rwstatus(ret_on_failure=[None])
910 def do_get_host(self, account, host_id):
911 raise NotImplementedError()
912
913 @rwstatus(ret_on_failure=[[]])
914 def do_get_host_list(self, account):
915 raise NotImplementedError()
916
917 @rwstatus
918 def do_create_port(self, account, port):
919 """Create a port between a network and a virtual machine
920
921 Arguments:
922 account - a cloud account
923 port - a description of port to create
924
925 Raises:
926 Raises an RWErrorNotFound exception if either the network or the VM
927 associated with the port cannot be found.
928
929 Returns:
930 the ID of the newly created port.
931
932 """
933 if port.network_id not in self.cal_to_lxc["network"]:
934 msg = 'Unable to find the specified network ({})'
935 raise exceptions.RWErrorNotFound(msg.format(port.network_id))
936
937 if port.vm_id not in self.cal_to_lxc["vm"]:
938 msg = "Unable to find the specified VM ({})"
939 raise exceptions.RWErrorNotFound(msg.format(port.vm_id))
940
941 if port.has_field("ip_address"):
942 raise exceptions.RWErrorFailure("IP address of the port must not be specific")
943
944 network = self.cal.get_network(port.network_id)
945 ip_pool = self.cal.get_network_ip_pool(port.network_id)
946 port.ip_address = ip_pool.allocate_ip()
947
948 net_config = lxc.NetworkConfig(
949 type='veth',
950 link=network.network_name[:15],
951 name="veth" + str(uuid.uuid4())[:10],
952 ipv4=port.ip_address,
953 )
954
955 lxc_network_id = self.cal_to_lxc["network"][port.network_id]
956 lxc_vm_id = self.cal_to_lxc["vm"][port.vm_id]
957
958 cal_port_id = self.cal.add_port(port.network_id, port.vm_id, port)
959 lxc_port_id = self.lxc.add_port(lxc_network_id, lxc_vm_id, net_config)
960
961 self.cal_to_lxc["port"][cal_port_id] = lxc_port_id
962 port.port_id = cal_port_id
963
964 return port.port_id
965
966 @rwstatus
967 def do_delete_port(self, account, port_id):
968 """Delete the specified port
969
970 Arguments:
971 account - a cloud account
972 port_id - the ID of the port to delete
973
974 Raises:
975 A RWErrorNotFound exception is raised if the specified port cannot
976 be found.
977
978 """
979 if port_id not in self.cal_to_lxc["port"]:
980 msg = "Unable to find the specified port ({})"
981 raise exceptions.RWErrorNotFound(msg.format(port_id))
982
983 lxc_port_id = self.cal_to_lxc["port"][port_id]
984
985 # Release the port's ip address back into the network pool
986 port = self.cal.get_port(port_id)
987 ip_pool = self.cal.get_network_ip_pool(port.network_id)
988 ip_pool.deallocate_ip(port.ip_address)
989
990 self.cal.remove_port(port_id)
991 self.lxc.remove_port(lxc_port_id)
992
993 del self.cal_to_lxc["port"][port_id]
994
995 @rwstatus(ret_on_failure=[None])
996 def do_get_port(self, account, port_id):
997 """Return the specified port
998
999 Arguments:
1000 account - a cloud account
1001 port_id - the ID of the port to return
1002
1003 Raises:
1004 A RWErrorNotFound exception is raised if the specified port cannot
1005 be found.
1006
1007 Returns:
1008 The specified port.
1009
1010 """
1011 if port_id not in self.cal_to_lxc["port"]:
1012 msg = "Unable to find the specified port ({})"
1013 raise exceptions.RWErrorNotFound(msg.format(port_id))
1014
1015 return self.cal.get_port(port_id)
1016
1017 @rwstatus(ret_on_failure=[[]])
1018 def do_get_port_list(self, account):
1019 """Returns a list of ports"""
1020 resources = RwcalYang.VimResources()
1021 for port in self.datastore.cal_manager.get_port_list():
1022 resources.portinfo_list.append(rwcal_copy_object(port))
1023
1024 return resources
1025
1026 @rwstatus
1027 def do_create_network(self, account, network):
1028 """Create a network
1029
1030 Arguments:
1031 account - a cloud account
1032 network - a description of the network to create
1033
1034 Returns:
1035 The ID of the newly created network
1036
1037 """
1038
1039 # Create the network
1040 try:
1041 # Setup a pool of mgmt IPv4 addresses
1042 if net.bridge_exists(network.network_name):
1043 logger.warning("Bridge %s already exists. Removing.", network.network_name)
1044 net.bridge_down(network.network_name)
1045 net.bridge_remove(network.network_name)
1046
1047 # Ensure that the subnet field was filled out and is valid
1048 if not network.has_field("subnet"):
1049 raise CreateNetworkError("subnet not provided in create network request")
1050
1051 try:
1052 ipaddress.IPv4Network(network.subnet)
1053 except ValueError as e:
1054 raise CreateNetworkError("Could not convert subnet into a "
1055 "IPv4Network: %s" % str(network.subnet))
1056
1057 ip_pool = NetworkIPPool(network.subnet)
1058
1059 # Create the management bridge with interface information
1060 net.create(network.network_name)
1061
1062 except Exception as e:
1063 logger.warning(str(e))
1064
1065 # Register the network
1066 cal_network_id = self.cal.add_network(network)
1067 lxc_network_id = self.lxc.add_bridge(network)
1068 self.cal.add_network_ip_pool(cal_network_id, ip_pool)
1069
1070 self.cal_to_lxc["network"][cal_network_id] = lxc_network_id
1071
1072 # Set the ID of the network object
1073 network.network_id = cal_network_id
1074
1075 return network.network_id
1076
1077 @rwstatus
1078 def do_delete_network(self, account, network_id):
1079 """
1080 Arguments:
1081 account - a cloud account
1082 network_id - the UUID of the network to delete
1083
1084 Raises:
1085 An RWErrorNotFound is raised if the specified network cannot be
1086 found.
1087
1088 """
1089 if network_id not in self.cal_to_lxc["network"]:
1090 msg = "Unable to find the specified network ({})"
1091 raise exceptions.RWErrorNotFound(msg.format(network_id))
1092
1093 # Get the associated bridge ID
1094 bridge_id = self.cal_to_lxc["network"][network_id]
1095
1096 # Delete the network
1097 network = self.cal.get_network(network_id)
1098 net.delete(network.network_name)
1099
1100 # Remove the network records
1101 self.lxc.remove_bridge(bridge_id)
1102 self.cal.remove_network(network_id)
1103 del self.cal_to_lxc["network"][network_id]
1104
1105 @rwstatus(ret_on_failure=[None])
1106 def do_get_network(self, account, network_id):
1107 """Returns the specified network
1108
1109 Arguments:
1110 account - a cloud account
1111 network_id - the UUID of the network to delete
1112
1113 Raises:
1114 An RWErrorNotFound is raised if the specified network cannot be
1115 found.
1116
1117 Returns:
1118 The specified network
1119
1120 """
1121 return self.cal.get_network(network_id)
1122
1123 @rwstatus(ret_on_failure=[[]])
1124 def do_get_network_list(self, account):
1125 """Returns a list of network objects"""
1126 resources = RwcalYang.VimResources()
1127 for network in self.cal.get_network_list():
1128 resources.networkinfo_list.append(rwcal_copy_object(network))
1129
1130 return resources
1131
1132 @rwcalstatus(ret_on_failure=[""])
1133 def do_create_virtual_link(self, account, link_params):
1134 """Create a new virtual link
1135
1136 Arguments:
1137 account - a cloud account
1138 link_params - information that defines the type of VDU to create
1139
1140 Returns:
1141 The vdu_id
1142 """
1143 network = RwcalYang.NetworkInfoItem()
1144 network.network_name = link_params.name
1145 network.subnet = link_params.subnet
1146
1147 if link_params.has_field("provider_network"):
1148 logger.warning("Container CAL does not implement provider network")
1149
1150 rs, net_id = self.do_create_network(account, network)
1151 if rs != RwTypes.RwStatus.SUCCESS:
1152 raise exceptions.RWErrorFailure(rs)
1153
1154 return net_id
1155
1156 @rwstatus
1157 def do_delete_virtual_link(self, account, link_id):
1158 """Delete a virtual link
1159
1160 Arguments:
1161 account - a cloud account
1162 link_id - id for the virtual-link to be deleted
1163
1164 Returns:
1165 None
1166 """
1167
1168 network_ports = self.cal.get_network_ports(link_id)
1169 for port_id in network_ports:
1170 self.do_delete_port(account, port_id, no_rwstatus=True)
1171
1172 self.do_delete_network(account, link_id, no_rwstatus=True)
1173
1174 @staticmethod
1175 def fill_connection_point_info(c_point, port_info):
1176 """Create a GI object for RwcalYang.VDUInfoParams_ConnectionPoints()
1177
1178 Converts Port information dictionary object returned by container cal
1179 driver into Protobuf Gi Object
1180
1181 Arguments:
1182 port_info - Port information from container cal
1183 Returns:
1184 Protobuf Gi object for RwcalYang.VDUInfoParams_ConnectionPoints
1185 """
1186 c_point.name = port_info.port_name
1187 c_point.connection_point_id = port_info.port_id
1188 c_point.ip_address = port_info.ip_address
1189 c_point.state = 'active'
1190 c_point.virtual_link_id = port_info.network_id
1191 c_point.vdu_id = port_info.vm_id
1192
1193 @staticmethod
1194 def create_virtual_link_info(network_info, port_list):
1195 """Create a GI object for VirtualLinkInfoParams
1196
1197 Converts Network and Port information dictionary object
1198 returned by container manager into Protobuf Gi Object
1199
1200 Arguments:
1201 network_info - Network information from container cal
1202 port_list - A list of port information from container cal
1203 subnet: Subnet information from openstack
1204 Returns:
1205 Protobuf Gi object for VirtualLinkInfoParams
1206 """
1207 link = RwcalYang.VirtualLinkInfoParams()
1208 link.name = network_info.network_name
1209 link.state = 'active'
1210 link.virtual_link_id = network_info.network_id
1211 for port in port_list:
1212 c_point = link.connection_points.add()
1213 CloudSimPlugin.fill_connection_point_info(c_point, port)
1214
1215 link.subnet = network_info.subnet
1216
1217 return link
1218
1219 @rwstatus(ret_on_failure=[None])
1220 def do_get_virtual_link(self, account, link_id):
1221 """Get information about virtual link.
1222
1223 Arguments:
1224 account - a cloud account
1225 link_id - id for the virtual-link
1226
1227 Returns:
1228 Object of type RwcalYang.VirtualLinkInfoParams
1229 """
1230
1231 network = self.do_get_network(account, link_id, no_rwstatus=True)
1232 port_ids = self.cal.get_network_ports(network.network_id)
1233 ports = [self.cal.get_port(p_id) for p_id in port_ids]
1234
1235 virtual_link = CloudSimPlugin.create_virtual_link_info(
1236 network, ports
1237 )
1238
1239 return virtual_link
1240
1241 @rwstatus(ret_on_failure=[None])
1242 def do_get_virtual_link_list(self, account):
1243 """Get information about all the virtual links
1244
1245 Arguments:
1246 account - a cloud account
1247
1248 Returns:
1249 A list of objects of type RwcalYang.VirtualLinkInfoParams
1250 """
1251 networks = self.do_get_network_list(account, no_rwstatus=True)
1252 vnf_resources = RwcalYang.VNFResources()
1253 for network in networks.networkinfo_list:
1254 virtual_link = self.do_get_virtual_link(account, network.network_id, no_rwstatus=True)
1255 vnf_resources.virtual_link_info_list.append(virtual_link)
1256
1257 return vnf_resources
1258
1259 def _create_connection_point(self, account, c_point, vdu_id):
1260 """
1261 Create a connection point
1262 Arguments:
1263 account - a cloud account
1264 c_point - connection_points
1265 """
1266 port = RwcalYang.PortInfoItem()
1267 port.port_name = c_point.name
1268 port.network_id = c_point.virtual_link_id
1269 port.port_type = 'normal' ### Find Port type from network_profile under cloud account
1270 port.vm_id = vdu_id
1271 port_id = self.do_create_port(account, port, no_rwstatus=True)
1272 return port_id
1273
1274 @rwcalstatus(ret_on_failure=[""])
1275 def do_create_vdu(self, account, vdu_init):
1276 """Create a new virtual deployment unit
1277
1278 Arguments:
1279 account - a cloud account
1280 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
1281
1282 Returns:
1283 The vdu_id
1284 """
1285 ### Create VM
1286 vm = RwcalYang.VMInfoItem()
1287 vm.vm_name = vdu_init.name
1288 vm.image_id = vdu_init.image_id
1289 if vdu_init.vdu_init.has_field('userdata'):
1290 vm.cloud_init.userdata = vdu_init.vdu_init.userdata
1291 vm.user_tags.node_id = vdu_init.node_id
1292
1293 vm_id = self.do_create_vm(account, vm, no_rwstatus=True)
1294
1295 ### Now create required number of ports aka connection points
1296 port_list = []
1297 for c_point in vdu_init.connection_points:
1298 virtual_link_id = c_point.virtual_link_id
1299
1300 # Attempt to fetch the network to verify that the network
1301 # already exists.
1302 self.do_get_network(account, virtual_link_id, no_rwstatus=True)
1303
1304 port_id = self._create_connection_point(account, c_point, vm_id)
1305 port_list.append(port_id)
1306
1307 # Finally start the vm
1308 self.do_start_vm(account, vm_id, no_rwstatus=True)
1309
1310 return vm_id
1311
1312 @rwstatus
1313 def do_modify_vdu(self, account, vdu_modify):
1314 """Modify Properties of existing virtual deployment unit
1315
1316 Arguments:
1317 account - a cloud account
1318 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
1319 """
1320 ### First create required number of ports aka connection points
1321 port_list = []
1322 network_list = []
1323 if not vdu_modify.has_field("vdu_id"):
1324 raise ValueError("vdu_id must not be empty")
1325
1326 for c_point in vdu_modify.connection_points_add:
1327 if not c_point.has_field("virtual_link_id"):
1328 raise ValueError("virtual link id not provided")
1329
1330 network_list.append(c_point.virtual_link_id)
1331 port_id = self._create_connection_point(account, c_point, vdu_modify.vdu_id)
1332 port_list.append(port_id)
1333
1334 ### Delete the requested connection_points
1335 for c_point in vdu_modify.connection_points_remove:
1336 self.do_delete_port(account, c_point.connection_point_id, no_rwstatus=True)
1337
1338 self.do_reboot_vm(account, vdu_modify.vdu_id)
1339
1340 @rwstatus
1341 def do_delete_vdu(self, account, vdu_id):
1342 """Delete a virtual deployment unit
1343
1344 Arguments:
1345 account - a cloud account
1346 vdu_id - id for the vdu to be deleted
1347
1348 Returns:
1349 None
1350 """
1351 ### Get list of port on VM and delete them.
1352 port_id_list = self.cal.get_vm_ports(vdu_id)
1353 ports = [self.cal.get_port(p_id) for p_id in port_id_list]
1354 for port in ports:
1355 self.do_delete_port(account, port.port_id, no_rwstatus=True)
1356 self.do_delete_vm(account, vdu_id, no_rwstatus=True)
1357
1358 @staticmethod
1359 def fill_vdu_info(vm_info, port_list):
1360 """create a gi object for vduinfoparams
1361
1362 converts vm information dictionary object returned by openstack
1363 driver into protobuf gi object
1364
1365 arguments:
1366 vm_info - vm information from openstack
1367 mgmt_network - management network
1368 port_list - a list of port information from container cal
1369 returns:
1370 protobuf gi object for vduinfoparams
1371 """
1372 vdu = RwcalYang.VDUInfoParams()
1373 vdu.name = vm_info.vm_name
1374 vdu.vdu_id = vm_info.vm_id
1375 vdu.management_ip = vm_info.management_ip
1376 vdu.public_ip = vm_info.management_ip
1377 vdu.node_id = vm_info.user_tags.node_id
1378 vdu.image_id = vm_info.image_id
1379 vdu.state = 'active'
1380
1381 # fill the port information
1382 for port in port_list:
1383 c_point = vdu.connection_points.add()
1384 CloudSimPlugin.fill_connection_point_info(c_point, port)
1385
1386 vdu.vm_flavor.vcpu_count = 1
1387 vdu.vm_flavor.memory_mb = 8 * 1024 # 8GB
1388 vdu.vm_flavor.storage_gb = 10
1389
1390 return vdu
1391
1392 @rwstatus(ret_on_failure=[None])
1393 def do_get_vdu(self, account, vdu_id):
1394 """Get information about a virtual deployment unit.
1395
1396 Arguments:
1397 account - a cloud account
1398 vdu_id - id for the vdu
1399
1400 Returns:
1401 Object of type RwcalYang.VDUInfoParams
1402 """
1403 port_id_list = self.cal.get_vm_ports(vdu_id)
1404 ports = [self.cal.get_port(p_id) for p_id in port_id_list]
1405 vm_info = self.do_get_vm(account, vdu_id, no_rwstatus=True)
1406 vdu_info = CloudSimPlugin.fill_vdu_info(vm_info, ports)
1407
1408 return vdu_info
1409
1410 @rwstatus(ret_on_failure=[None])
1411 def do_get_vdu_list(self, account):
1412 """Get information about all the virtual deployment units
1413
1414 Arguments:
1415 account - a cloud account
1416
1417 Returns:
1418 A list of objects of type RwcalYang.VDUInfoParams
1419 """
1420
1421 vnf_resources = RwcalYang.VNFResources()
1422
1423 vm_resources = self.do_get_vm_list(account, no_rwstatus=True)
1424 for vm in vm_resources.vminfo_list:
1425 port_list = self.cal.get_vm_ports(vm.vm_id)
1426 port_list = [self.cal.get_port(port_id) for port_id in port_list]
1427 vdu = CloudSimPlugin.fill_vdu_info(vm, port_list)
1428 vnf_resources.vdu_info_list.append(vdu)
1429
1430 return vnf_resources