3 # Copyright 2016 RIFT.IO Inc
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
28 from gi
import require_version
29 require_version('RwCal', '1.0')
31 from gi
.repository
import (
39 import rift
.cal
.rwcal_status
as rwcal_status
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
47 logger
= logging
.getLogger('rwcal.cloudsim')
49 rwstatus_exception_map
= { IndexError: RwTypes
.RwStatus
.NOTFOUND
,
50 KeyError: RwTypes
.RwStatus
.NOTFOUND
,
51 NotImplementedError: RwTypes
.RwStatus
.NOT_IMPLEMENTED
,}
53 rwstatus
= rw_status
.rwstatus_from_exc_map(rwstatus_exception_map
)
54 rwcalstatus
= rwcal_status
.rwcalstatus_from_exc_map(rwstatus_exception_map
)
57 class UnknownAccountError(Exception):
61 class MissingFileError(Exception):
65 class ImageLocationError(Exception):
69 class CreateNetworkError(Exception):
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
,
81 class Resources(object):
86 def rwcal_copy_object(obj
):
92 MGMT_NETWORK_NAME
= "virbr0"
93 MGMT_NETWORK_INTERFACE_IP
= ipaddress
.IPv4Interface("192.168.122.1/24")
96 class IPPoolError(Exception):
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
= []
107 def allocate_ip(self
):
109 ip
= str(next(self
._ip
_gen
))
110 except StopIteration:
112 ip
= self
._unallocated
_ips
.pop()
114 raise IPPoolError("All ip addresses exhausted")
116 self
._allocated
_ips
.append(ip
)
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")
123 self
._allocated
_ips
.remove(ip
)
124 self
._unallocated
_ips
.append(ip
)
127 class CalManager(object):
135 self
._port
_to
_vm
= {}
136 self
._vm
_to
_image
= {}
137 self
._port
_to
_network
= {}
138 self
._network
_to
_ip
_pool
= {}
140 self
._vm
_to
_ports
= collections
.defaultdict(list)
141 self
._image
_to
_vms
= collections
.defaultdict(list)
142 self
._network
_to
_ports
= collections
.defaultdict(list)
144 self
._vm
_id
_gen
= itertools
.count(1)
145 self
._network
_id
_gen
= itertools
.count(1)
146 self
._image
_id
_gen
= itertools
.count(1)
148 def add_image(self
, image
):
149 image_id
= str(next(self
._image
_id
_gen
))
150 self
._images
[image_id
] = image
154 def remove_image(self
, image_id
):
155 for vm_id
in self
.get_image_vms(image_id
):
156 self
.remove_vm(vm_id
)
158 del self
._images
[image_id
]
159 del self
._image
_to
_vms
[image_id
]
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
))
166 return self
._images
[image_id
]
168 def get_image_list(self
):
169 return list(self
._images
.values())
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
))
176 return self
._image
_to
_vms
[image_id
]
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
))
183 if vm_id
not in self
._vms
:
184 msg
= "Unable to find vm {}"
185 raise exceptions
.RWErrorNotFound(msg
.format(vm_id
))
187 port_id
= str(uuid
.uuid4())
188 self
._ports
[port_id
] = port
190 self
._vm
_to
_ports
[vm_id
].append(port_id
)
191 self
._network
_to
_ports
[network_id
].append(port_id
)
193 self
._port
_to
_vm
[port_id
] = vm_id
194 self
._port
_to
_network
[port_id
] = network_id
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
))
203 network_id
= self
._port
_to
_network
[port_id
]
204 vm_id
= self
._port
_to
_vm
[port_id
]
206 self
._vm
_to
_ports
[vm_id
].remove(port_id
)
207 self
._network
_to
_ports
[network_id
].remove(port_id
)
209 del self
._ports
[port_id
]
210 del self
._port
_to
_vm
[port_id
]
211 del self
._port
_to
_network
[port_id
]
213 def get_port(self
, port_id
):
214 return self
._ports
[port_id
]
216 def get_port_list(self
):
217 return list(self
._ports
.values())
219 def add_network(self
, network
):
220 network_id
= str(next(self
._network
_id
_gen
))
221 self
._networks
[network_id
] = network
225 def remove_network(self
, network_id
):
226 for port_id
in self
.get_network_ports(network_id
):
227 self
.remove_port(port_id
)
229 del self
._networks
[network_id
]
231 def get_network(self
, network_id
):
232 return self
._networks
[network_id
]
234 def add_network_ip_pool(self
, network_id
, ip_pool
):
235 self
._network
_to
_ip
_pool
[network_id
] = ip_pool
237 def get_network_ip_pool(self
, network_id
):
238 return self
._network
_to
_ip
_pool
[network_id
]
240 def remove_network_ip_pool(self
, network_id
):
241 del self
._network
_to
_ip
_pool
[network_id
]
243 def get_network_list(self
):
244 return list(self
._networks
.values())
246 def get_network_ports(self
, network_id
):
247 return self
._network
_to
_ports
[network_id
]
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
))
254 vm_id
= str(next(self
._vm
_id
_gen
))
255 self
._vms
[vm_id
] = vm
257 self
._vm
_to
_image
[vm_id
] = image_id
258 self
._image
_to
_vms
[image_id
].append(vm_id
)
262 def remove_vm(self
, vm_id
):
263 for port_id
in self
.get_vm_ports(vm_id
):
264 self
.remove_port(port_id
)
266 image_id
= self
._vm
_to
_image
[vm_id
]
268 self
._image
_to
_vms
[image_id
].remove(vm_id
)
271 del self
._vm
_to
_image
[vm_id
]
273 def get_vm(self
, vm_id
):
274 return self
._vms
[vm_id
]
276 def get_vm_list(self
):
277 return list(self
._vms
.values())
279 def get_vm_ports(self
, vm_id
):
280 return self
._vm
_to
_ports
[vm_id
]
283 class LxcManager(object):
285 self
._containers
= {}
289 self
._port
_to
_container
= {}
290 self
._port
_to
_bridge
= {}
292 self
._container
_to
_ports
= collections
.defaultdict(list)
293 self
._bridge
_to
_ports
= collections
.defaultdict(list)
295 # Create the management network
296 self
.mgmt_network
= RwcalYang
.NetworkInfoItem()
297 self
.mgmt_network
.network_name
= MGMT_NETWORK_NAME
299 network
= MGMT_NETWORK_INTERFACE_IP
.network
300 self
.mgmt_network
.subnet
= str(network
)
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()
308 # The default virsh profile create a virbr0 interface
309 # with a 192.168.122.1 ip address. Also sets up iptables
311 net
.virsh_start("default")
314 mgmt_network_hosts
= network
.hosts()
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
)
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())
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
))
330 if container_id
not in self
._containers
:
331 msg
= "Unable to find container {}"
332 raise exceptions
.RWErrorNotFound(msg
.format(container_id
))
334 port_id
= str(uuid
.uuid4())
335 self
._ports
[port_id
] = port
337 self
._container
_to
_ports
[container_id
].append(port_id
)
338 self
._bridge
_to
_ports
[bridge_id
].append(port_id
)
340 self
._port
_to
_container
[port_id
] = container_id
341 self
._port
_to
_bridge
[port_id
] = bridge_id
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
))
350 bridge_id
= self
._port
_to
_bridge
[port_id
]
351 container_id
= self
._port
_to
_container
[port_id
]
353 self
._container
_to
_ports
[container_id
].remove(port_id
)
354 self
._bridge
_to
_ports
[bridge_id
].remove(port_id
)
356 del self
._ports
[port_id
]
357 del self
._port
_to
_bridge
[port_id
]
358 del self
._port
_to
_container
[port_id
]
360 def get_port(self
, port_id
):
361 return self
._ports
[port_id
]
363 def add_bridge(self
, bridge
):
364 bridge_id
= str(uuid
.uuid4())
365 self
._bridges
[bridge_id
] = bridge
369 def remove_bridge(self
, bridge_id
):
370 for port_id
in self
._bridge
_to
_ports
[bridge_id
]:
371 self
.remove_port(port_id
)
373 del self
._bridges
[bridge_id
]
375 def get_bridge(self
, bridge_id
):
376 return self
._bridges
[bridge_id
]
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
]
382 def add_container(self
, container
):
383 container_id
= str(uuid
.uuid4())
384 self
._containers
[container_id
] = container
388 def remove_container(self
, container_id
):
389 for port_id
in self
.get_container_ports(container_id
):
390 self
.remove_port(port_id
)
392 del self
._containers
[container_id
]
394 def get_container(self
, container_id
):
395 return self
._containers
[container_id
]
397 def get_container_ports(self
, container_id
):
398 return self
._container
_to
_ports
[container_id
]
402 class Datastore(object):
404 This class is used to store data that is shared among different instance of
408 self
.lxc_manager
= LxcManager()
409 self
.cal_manager
= CalManager()
410 self
.cal_to_lxc
= {'image': {}, 'port': {}, 'network': {}, 'vm': {}}
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.
423 GObject
.Object
.__init
__(self
)
424 if CloudSimPlugin
.datastore
is None:
425 CloudSimPlugin
.datastore
= Datastore()
429 return CloudSimPlugin
.datastore
.lxc_manager
433 return CloudSimPlugin
.datastore
.cal_manager
436 def volume_group(self
):
437 return lvm
.get("rift")
440 def cal_to_lxc(self
):
441 return CloudSimPlugin
.datastore
.cal_to_lxc
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
)
449 def do_init(self
, rwlog_ctx
):
450 if not any(isinstance(h
, rwlogger
.RwLogger
) for h
in logger
.handlers
):
453 category
="rw-cal-log",
454 subcategory
="cloudsim",
459 @rwstatus(ret_on_failure
=[None])
460 def do_validate_cloud_creds(self
, account
):
462 Validates the cloud account credentials for the specified account.
463 If creds are not valid, returns an error code & reason string
465 account - a cloud account to validate
468 Validation Code and Details String
470 status
= RwcalYang
.CloudConnectionStatus(
477 @rwstatus(ret_on_failure
=[None])
478 def do_get_management_network(self
, account
):
479 """Returns the management network
482 account - a cloud account
488 return self
.lxc
.mgmt_network
491 def do_create_tenant(self
, account
, name
):
495 @param name - name to assign to the tenant.
497 raise NotImplementedError()
500 def do_delete_tenant(self
, account
, tenant_id
):
504 @param tenant_id - id of tenant to be deleted.
506 raise NotImplementedError()
508 @rwstatus(ret_on_failure
=[[]])
509 def do_get_tenant_list(self
, account
):
514 raise NotImplementedError()
517 def do_create_role(self
, account
, name
):
521 @param name - name to assign to the role.
523 raise NotImplementedError()
526 def do_delete_role(self
, account
, role_id
):
530 @param role_id - id of role to be deleted.
532 raise NotImplementedError()
534 @rwstatus(ret_on_failure
=[[]])
535 def do_get_role_list(self
, account
):
540 raise NotImplementedError()
542 @rwstatus(ret_on_failure
=[None])
543 def do_create_image(self
, account
, image
):
544 """Create a new image
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.
550 account - a cloud account
551 image - an ImageInfo object
554 An RWErrorDuplicate is raised if create_image is called and there
558 The UUID of the new image
561 def file_md5(path
, block_size
=2 ** 20):
563 Block size directly depends on the block size of your filesystem
564 to avoid performances issues.
567 with
open(path
, 'rb') as f
:
568 for chunk
in iter(lambda: f
.read(block_size
), b
''):
571 return md5
.hexdigest()
573 current_images
= self
.cal
.get_image_list()
574 lxc_name
= "rwm{}".format(len(current_images
))
576 if not image
.has_field("disk_format"):
577 logger
.warning("Image disk format not provided assuming qcow2")
578 image
.disk_format
= "qcow2"
580 if image
.disk_format
not in ["qcow2"]:
581 msg
= "Only qcow2 currently supported for container CAL"
582 raise exceptions
.RWErrorNotSupported(msg
)
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'
589 container
= lxc
.create_container(
591 template_path
=os
.path
.join(
592 os
.environ
['RIFT_INSTALL'],
593 "etc/lxc-fedora-rift.lxctemplate",
596 rootfs_qcow2file
=image
.location
,
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
)
604 # Create the CAL to LXC mapping
605 self
.cal_to_lxc
["image"][cal_image_id
] = lxc_image_id
607 image
.id = cal_image_id
612 def do_delete_image(self
, account
, image_id
):
615 This function will remove the record of the image from the CAL and
616 destroy the associated container.
619 account - a cloud account
620 image_id - the UUID of the image to delete
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.
628 container_id
= self
.cal_to_lxc
["image"][image_id
]
629 container
= self
.lxc
.get_container(container_id
)
631 # Stop the image and destroy it (NB: it should not be necessary to stop
632 # the container, but just in case)
636 self
.cal
.remove_image(image_id
)
637 self
.lxc
.remove_container(container_id
)
639 @rwstatus(ret_on_failure
=[None])
640 def do_get_image(self
, account
, image_id
):
641 """Returns the specified image
644 account - a cloud account
645 image_id - the UUID of the image to retrieve
648 An RWErrorNotFound exception is raised if the image_id does not
649 match any of the known images.
655 return self
.cal
.get_image(image_id
)
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
))
667 def do_create_vm(self
, account
, vm
):
671 vm - the VM info used to define the desire VM
674 An RWErrorFailure is raised if there is not
677 a string containing the unique id of the created VM
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
)
684 # Create a container snapshot
685 snapshot
= container
.snapshot(self
.next_snapshot_name())
686 snapshot
.hostname
= vm
.vm_name
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
)
692 self
.cal_to_lxc
["vm"][vm
.vm_id
] = snapshot_id
697 def do_start_vm(self
, account
, vm_id
):
698 """Starts the specified VM
701 vm_id - the id of the vm to start
704 An RWErrorNotFound is raised if the specified vm id is not known to
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
))
712 container_id
= self
.cal_to_lxc
["vm"][vm_id
]
714 snapshot
= self
.lxc
.get_container(container_id
)
715 port_ids
= self
.lxc
.get_container_ports(container_id
)
717 config
= lxc
.ContainerConfig(snapshot
.name
)
719 for port_id
in port_ids
:
720 port
= self
.lxc
.get_port(port_id
)
721 config
.add_network_config(port
)
723 vm
= self
.cal
.get_vm(vm_id
)
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
730 # Add the management interface
731 config
.add_network_config(
734 link
=self
.lxc
.mgmt_network
.network_name
,
736 ipv4
=vm
.management_ip
,
741 # Add rift root as a mount point
742 config
.add_mount_point_config(
744 local
=os
.environ
["RIFT_ROOT"],
745 remote
=os
.environ
["RIFT_ROOT"][1:],
751 if vm
.cloud_init
.has_field("userdata"):
752 userdata
= vm
.cloud_init
.userdata
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!!
762 def do_stop_vm(self
, account
, vm_id
):
763 """Stops the specified VM
766 vm_id - the id of the vm to stop
769 An RWErrorNotFound is raised if the specified vm id is not known to
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
))
778 container_id
= self
.cal_to_lxc
["vm"][vm_id
]
779 snapshot
= self
.lxc
.get_container(container_id
)
783 def do_delete_vm(self
, account
, vm_id
):
784 """Deletes the specified VM
787 vm_id - the id of the vm to delete
790 An RWErrorNotFound is raised if the specified vm id is not known to
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
))
798 container_id
= self
.cal_to_lxc
["vm"][vm_id
]
800 snapshot
= self
.lxc
.get_container(container_id
)
804 self
.cal
.remove_vm(vm_id
)
805 self
.lxc
.remove_container(container_id
)
807 # TODO: Recycle management ip
810 def do_reboot_vm(self
, account
, vm_id
):
812 reboot a virtual machine.
814 @param vm_id - Instance id of VM to be deleted.
816 self
.do_stop_vm(account
, vm_id
, no_rwstatus
=True)
817 self
.do_start_vm(account
, vm_id
, no_rwstatus
=True)
820 def do_get_vm(self
, account
, vm_id
):
821 """Returns the specified VM
824 vm_id - the id of the vm to return
827 An RWErrorNotFound is raised if the specified vm id is not known to
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
))
838 return self
.cal
.get_vm(vm_id
)
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
845 a list of VMInfoItem objects
848 resources
= RwcalYang
.VimResources()
849 for vm
in self
.cal
.get_vm_list():
850 resources
.vminfo_list
.append(rwcal_copy_object(vm
))
855 def do_create_flavor(self
, account
, flavor
):
859 @param flavor - Flavor object
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
))
868 def do_delete_flavor(self
, account
, flavor_id
):
872 @param flavor_id - Flavor id to be deleted.
874 logger
.debug('Deleted flavor: {}'.format(flavor_id
))
875 self
.cal
.flavors
.pop(flavor_id
)
877 @rwstatus(ret_on_failure
=[None])
878 def do_get_flavor(self
, account
, flavor_id
):
880 Return the specified flavor
882 @param flavor_id - the id of the flavor to return
884 flavor
= self
.cal
.flavors
[flavor_id
]
885 logger
.debug('Returning flavor-info for : {}'.format(flavor_id
))
888 @rwstatus(ret_on_failure
=[[]])
889 def do_get_flavor_list(self
, account
):
891 Return a list of flavors
893 vim_resources
= RwcalYang
.VimResources()
894 for flavor
in self
.cal
.flavors
.values():
895 f
= RwcalYang
.FlavorInfoItem()
897 vim_resources
.flavorinfo_list
.append(f
)
898 logger
.debug("Returning list of flavor-info of size: %d", len(vim_resources
.flavorinfo_list
))
902 def do_add_host(self
, account
, host
):
903 raise NotImplementedError()
906 def do_remove_host(self
, account
, host_id
):
907 raise NotImplementedError()
909 @rwstatus(ret_on_failure
=[None])
910 def do_get_host(self
, account
, host_id
):
911 raise NotImplementedError()
913 @rwstatus(ret_on_failure
=[[]])
914 def do_get_host_list(self
, account
):
915 raise NotImplementedError()
918 def do_create_port(self
, account
, port
):
919 """Create a port between a network and a virtual machine
922 account - a cloud account
923 port - a description of port to create
926 Raises an RWErrorNotFound exception if either the network or the VM
927 associated with the port cannot be found.
930 the ID of the newly created port.
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
))
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
))
941 if port
.has_field("ip_address"):
942 raise exceptions
.RWErrorFailure("IP address of the port must not be specific")
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()
948 net_config
= lxc
.NetworkConfig(
950 link
=network
.network_name
[:15],
951 name
="veth" + str(uuid
.uuid4())[:10],
952 ipv4
=port
.ip_address
,
955 lxc_network_id
= self
.cal_to_lxc
["network"][port
.network_id
]
956 lxc_vm_id
= self
.cal_to_lxc
["vm"][port
.vm_id
]
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
)
961 self
.cal_to_lxc
["port"][cal_port_id
] = lxc_port_id
962 port
.port_id
= cal_port_id
967 def do_delete_port(self
, account
, port_id
):
968 """Delete the specified port
971 account - a cloud account
972 port_id - the ID of the port to delete
975 A RWErrorNotFound exception is raised if the specified port cannot
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
))
983 lxc_port_id
= self
.cal_to_lxc
["port"][port_id
]
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
)
990 self
.cal
.remove_port(port_id
)
991 self
.lxc
.remove_port(lxc_port_id
)
993 del self
.cal_to_lxc
["port"][port_id
]
995 @rwstatus(ret_on_failure
=[None])
996 def do_get_port(self
, account
, port_id
):
997 """Return the specified port
1000 account - a cloud account
1001 port_id - the ID of the port to return
1004 A RWErrorNotFound exception is raised if the specified port cannot
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
))
1015 return self
.cal
.get_port(port_id
)
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
))
1027 def do_create_network(self
, account
, network
):
1031 account - a cloud account
1032 network - a description of the network to create
1035 The ID of the newly created network
1039 # Create the network
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
)
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")
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
))
1057 ip_pool
= NetworkIPPool(network
.subnet
)
1059 # Create the management bridge with interface information
1060 net
.create(network
.network_name
)
1062 except Exception as e
:
1063 logger
.warning(str(e
))
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
)
1070 self
.cal_to_lxc
["network"][cal_network_id
] = lxc_network_id
1072 # Set the ID of the network object
1073 network
.network_id
= cal_network_id
1075 return network
.network_id
1078 def do_delete_network(self
, account
, network_id
):
1081 account - a cloud account
1082 network_id - the UUID of the network to delete
1085 An RWErrorNotFound is raised if the specified network cannot be
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
))
1093 # Get the associated bridge ID
1094 bridge_id
= self
.cal_to_lxc
["network"][network_id
]
1096 # Delete the network
1097 network
= self
.cal
.get_network(network_id
)
1098 net
.delete(network
.network_name
)
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
]
1105 @rwstatus(ret_on_failure
=[None])
1106 def do_get_network(self
, account
, network_id
):
1107 """Returns the specified network
1110 account - a cloud account
1111 network_id - the UUID of the network to delete
1114 An RWErrorNotFound is raised if the specified network cannot be
1118 The specified network
1121 return self
.cal
.get_network(network_id
)
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
))
1132 @rwcalstatus(ret_on_failure
=[""])
1133 def do_create_virtual_link(self
, account
, link_params
):
1134 """Create a new virtual link
1137 account - a cloud account
1138 link_params - information that defines the type of VDU to create
1143 network
= RwcalYang
.NetworkInfoItem()
1144 network
.network_name
= link_params
.name
1145 network
.subnet
= link_params
.subnet
1147 if link_params
.has_field("provider_network"):
1148 logger
.warning("Container CAL does not implement provider network")
1150 rs
, net_id
= self
.do_create_network(account
, network
)
1151 if rs
!= RwTypes
.RwStatus
.SUCCESS
:
1152 raise exceptions
.RWErrorFailure(rs
)
1157 def do_delete_virtual_link(self
, account
, link_id
):
1158 """Delete a virtual link
1161 account - a cloud account
1162 link_id - id for the virtual-link to be deleted
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)
1172 self
.do_delete_network(account
, link_id
, no_rwstatus
=True)
1175 def fill_connection_point_info(c_point
, port_info
):
1176 """Create a GI object for RwcalYang.VDUInfoParams_ConnectionPoints()
1178 Converts Port information dictionary object returned by container cal
1179 driver into Protobuf Gi Object
1182 port_info - Port information from container cal
1184 Protobuf Gi object for RwcalYang.VDUInfoParams_ConnectionPoints
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
1194 def create_virtual_link_info(network_info
, port_list
):
1195 """Create a GI object for VirtualLinkInfoParams
1197 Converts Network and Port information dictionary object
1198 returned by container manager into Protobuf Gi Object
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
1205 Protobuf Gi object for VirtualLinkInfoParams
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
)
1215 link
.subnet
= network_info
.subnet
1219 @rwstatus(ret_on_failure
=[None])
1220 def do_get_virtual_link(self
, account
, link_id
):
1221 """Get information about virtual link.
1224 account - a cloud account
1225 link_id - id for the virtual-link
1228 Object of type RwcalYang.VirtualLinkInfoParams
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
]
1235 virtual_link
= CloudSimPlugin
.create_virtual_link_info(
1241 @rwstatus(ret_on_failure
=[None])
1242 def do_get_virtual_link_list(self
, account
):
1243 """Get information about all the virtual links
1246 account - a cloud account
1249 A list of objects of type RwcalYang.VirtualLinkInfoParams
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
)
1257 return vnf_resources
1259 def _create_connection_point(self
, account
, c_point
, vdu_id
):
1261 Create a connection point
1263 account - a cloud account
1264 c_point - connection_points
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
1271 port_id
= self
.do_create_port(account
, port
, no_rwstatus
=True)
1274 @rwcalstatus(ret_on_failure
=[""])
1275 def do_create_vdu(self
, account
, vdu_init
):
1276 """Create a new virtual deployment unit
1279 account - a cloud account
1280 vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
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
1293 vm_id
= self
.do_create_vm(account
, vm
, no_rwstatus
=True)
1295 ### Now create required number of ports aka connection points
1297 for c_point
in vdu_init
.connection_points
:
1298 virtual_link_id
= c_point
.virtual_link_id
1300 # Attempt to fetch the network to verify that the network
1302 self
.do_get_network(account
, virtual_link_id
, no_rwstatus
=True)
1304 port_id
= self
._create
_connection
_point
(account
, c_point
, vm_id
)
1305 port_list
.append(port_id
)
1307 # Finally start the vm
1308 self
.do_start_vm(account
, vm_id
, no_rwstatus
=True)
1313 def do_modify_vdu(self
, account
, vdu_modify
):
1314 """Modify Properties of existing virtual deployment unit
1317 account - a cloud account
1318 vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
1320 ### First create required number of ports aka connection points
1323 if not vdu_modify
.has_field("vdu_id"):
1324 raise ValueError("vdu_id must not be empty")
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")
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
)
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)
1338 self
.do_reboot_vm(account
, vdu_modify
.vdu_id
)
1341 def do_delete_vdu(self
, account
, vdu_id
):
1342 """Delete a virtual deployment unit
1345 account - a cloud account
1346 vdu_id - id for the vdu to be deleted
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
]
1355 self
.do_delete_port(account
, port
.port_id
, no_rwstatus
=True)
1356 self
.do_delete_vm(account
, vdu_id
, no_rwstatus
=True)
1359 def fill_vdu_info(vm_info
, port_list
):
1360 """create a gi object for vduinfoparams
1362 converts vm information dictionary object returned by openstack
1363 driver into protobuf gi object
1366 vm_info - vm information from openstack
1367 mgmt_network - management network
1368 port_list - a list of port information from container cal
1370 protobuf gi object for vduinfoparams
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'
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
)
1386 vdu
.vm_flavor
.vcpu_count
= 1
1387 vdu
.vm_flavor
.memory_mb
= 8 * 1024 # 8GB
1388 vdu
.vm_flavor
.storage_gb
= 10
1392 @rwstatus(ret_on_failure
=[None])
1393 def do_get_vdu(self
, account
, vdu_id
):
1394 """Get information about a virtual deployment unit.
1397 account - a cloud account
1398 vdu_id - id for the vdu
1401 Object of type RwcalYang.VDUInfoParams
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
)
1410 @rwstatus(ret_on_failure
=[None])
1411 def do_get_vdu_list(self
, account
):
1412 """Get information about all the virtual deployment units
1415 account - a cloud account
1418 A list of objects of type RwcalYang.VDUInfoParams
1421 vnf_resources
= RwcalYang
.VNFResources()
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
)
1430 return vnf_resources