--- /dev/null
+
+#
+# Copyright 2016 RIFT.IO Inc
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import collections
+import hashlib
+import itertools
+import logging
+import os
+import time
+import uuid
+
+import ipaddress
+
+from gi import require_version
+require_version('RwCal', '1.0')
+
+from gi.repository import (
+ GObject,
+ RwCal,
+ RwTypes,
+ RwcalYang,
+ )
+
+import rw_status
+import rift.cal.rwcal_status as rwcal_status
+import rwlogger
+
+import rift.rwcal.cloudsim.lxc as lxc
+import rift.rwcal.cloudsim.lvm as lvm
+import rift.rwcal.cloudsim.net as net
+import rift.rwcal.cloudsim.exceptions as exceptions
+
+logger = logging.getLogger('rwcal.cloudsim')
+
+rwstatus_exception_map = { IndexError: RwTypes.RwStatus.NOTFOUND,
+ KeyError: RwTypes.RwStatus.NOTFOUND,
+ NotImplementedError: RwTypes.RwStatus.NOT_IMPLEMENTED,}
+
+rwstatus = rw_status.rwstatus_from_exc_map(rwstatus_exception_map)
+rwcalstatus = rwcal_status.rwcalstatus_from_exc_map(rwstatus_exception_map)
+
+
+class UnknownAccountError(Exception):
+ pass
+
+
+class MissingFileError(Exception):
+ pass
+
+
+class ImageLocationError(Exception):
+ pass
+
+
+class CreateNetworkError(Exception):
+ pass
+
+
+rwstatus = rw_status.rwstatus_from_exc_map({
+ IndexError: RwTypes.RwStatus.NOTFOUND,
+ KeyError: RwTypes.RwStatus.NOTFOUND,
+ UnknownAccountError: RwTypes.RwStatus.NOTFOUND,
+ MissingFileError: RwTypes.RwStatus.NOTFOUND,
+ })
+
+
+class Resources(object):
+ def __init__(self):
+ self.images = dict()
+
+
+def rwcal_copy_object(obj):
+ dup = obj.__class__()
+ dup.copy_from(obj)
+ return dup
+
+
+MGMT_NETWORK_NAME = "virbr0"
+MGMT_NETWORK_INTERFACE_IP = ipaddress.IPv4Interface("192.168.122.1/24")
+
+
+class IPPoolError(Exception):
+ pass
+
+
+class NetworkIPPool(object):
+ def __init__(self, subnet):
+ self._network = ipaddress.IPv4Network(subnet)
+ self._ip_gen = self._network.hosts()
+ self._allocated_ips = []
+ self._unallocated_ips = []
+
+ def allocate_ip(self):
+ try:
+ ip = str(next(self._ip_gen))
+ except StopIteration:
+ try:
+ ip = self._unallocated_ips.pop()
+ except IndexError:
+ raise IPPoolError("All ip addresses exhausted")
+
+ self._allocated_ips.append(ip)
+ return ip
+
+ def deallocate_ip(self, ip):
+ if ip not in self._allocated_ips:
+ raise ValueError("Did not find IP %s in allocate ip pool")
+
+ self._allocated_ips.remove(ip)
+ self._unallocated_ips.append(ip)
+
+
+class CalManager(object):
+ def __init__(self):
+ self._vms = {}
+ self._ports = {}
+ self._images = {}
+ self._networks = {}
+ self.flavors = {}
+
+ self._port_to_vm = {}
+ self._vm_to_image = {}
+ self._port_to_network = {}
+ self._network_to_ip_pool = {}
+
+ self._vm_to_ports = collections.defaultdict(list)
+ self._image_to_vms = collections.defaultdict(list)
+ self._network_to_ports = collections.defaultdict(list)
+
+ self._vm_id_gen = itertools.count(1)
+ self._network_id_gen = itertools.count(1)
+ self._image_id_gen = itertools.count(1)
+
+ def add_image(self, image):
+ image_id = str(next(self._image_id_gen))
+ self._images[image_id] = image
+
+ return image_id
+
+ def remove_image(self, image_id):
+ for vm_id in self.get_image_vms(image_id):
+ self.remove_vm(vm_id)
+
+ del self._images[image_id]
+ del self._image_to_vms[image_id]
+
+ def get_image(self, image_id):
+ if image_id not in self._images:
+ msg = "Unable to find image {}"
+ raise exceptions.RWErrorNotFound(msg.format(image_id))
+
+ return self._images[image_id]
+
+ def get_image_list(self):
+ return list(self._images.values())
+
+ def get_image_vms(self, image_id):
+ if image_id not in self._images:
+ msg = "Unable to find image {}"
+ raise exceptions.RWErrorNotFound(msg.format(image_id))
+
+ return self._image_to_vms[image_id]
+
+ def add_port(self, network_id, vm_id, port):
+ if network_id not in self._networks:
+ msg = "Unable to find network {}"
+ raise exceptions.RWErrorNotFound(msg.format(network_id))
+
+ if vm_id not in self._vms:
+ msg = "Unable to find vm {}"
+ raise exceptions.RWErrorNotFound(msg.format(vm_id))
+
+ port_id = str(uuid.uuid4())
+ self._ports[port_id] = port
+
+ self._vm_to_ports[vm_id].append(port_id)
+ self._network_to_ports[network_id].append(port_id)
+
+ self._port_to_vm[port_id] = vm_id
+ self._port_to_network[port_id] = network_id
+
+ return port_id
+
+ def remove_port(self, port_id):
+ if port_id not in self._ports:
+ msg = "Unable to find port {}"
+ raise exceptions.RWErrorNotFound(msg.format(port_id))
+
+ network_id = self._port_to_network[port_id]
+ vm_id = self._port_to_vm[port_id]
+
+ self._vm_to_ports[vm_id].remove(port_id)
+ self._network_to_ports[network_id].remove(port_id)
+
+ del self._ports[port_id]
+ del self._port_to_vm[port_id]
+ del self._port_to_network[port_id]
+
+ def get_port(self, port_id):
+ return self._ports[port_id]
+
+ def get_port_list(self):
+ return list(self._ports.values())
+
+ def add_network(self, network):
+ network_id = str(next(self._network_id_gen))
+ self._networks[network_id] = network
+
+ return network_id
+
+ def remove_network(self, network_id):
+ for port_id in self.get_network_ports(network_id):
+ self.remove_port(port_id)
+
+ del self._networks[network_id]
+
+ def get_network(self, network_id):
+ return self._networks[network_id]
+
+ def add_network_ip_pool(self, network_id, ip_pool):
+ self._network_to_ip_pool[network_id] = ip_pool
+
+ def get_network_ip_pool(self, network_id):
+ return self._network_to_ip_pool[network_id]
+
+ def remove_network_ip_pool(self, network_id):
+ del self._network_to_ip_pool[network_id]
+
+ def get_network_list(self):
+ return list(self._networks.values())
+
+ def get_network_ports(self, network_id):
+ return self._network_to_ports[network_id]
+
+ def add_vm(self, image_id, vm):
+ if image_id not in self._images:
+ msg = "Unable to find image {}"
+ raise exceptions.RWErrorNotFound(msg.format(image_id))
+
+ vm_id = str(next(self._vm_id_gen))
+ self._vms[vm_id] = vm
+
+ self._vm_to_image[vm_id] = image_id
+ self._image_to_vms[image_id].append(vm_id)
+
+ return vm_id
+
+ def remove_vm(self, vm_id):
+ for port_id in self.get_vm_ports(vm_id):
+ self.remove_port(port_id)
+
+ image_id = self._vm_to_image[vm_id]
+
+ self._image_to_vms[image_id].remove(vm_id)
+
+ del self._vms[vm_id]
+ del self._vm_to_image[vm_id]
+
+ def get_vm(self, vm_id):
+ return self._vms[vm_id]
+
+ def get_vm_list(self):
+ return list(self._vms.values())
+
+ def get_vm_ports(self, vm_id):
+ return self._vm_to_ports[vm_id]
+
+
+class LxcManager(object):
+ def __init__(self):
+ self._containers = {}
+ self._ports = {}
+ self._bridges = {}
+
+ self._port_to_container = {}
+ self._port_to_bridge = {}
+
+ self._container_to_ports = collections.defaultdict(list)
+ self._bridge_to_ports = collections.defaultdict(list)
+
+ # Create the management network
+ self.mgmt_network = RwcalYang.NetworkInfoItem()
+ self.mgmt_network.network_name = MGMT_NETWORK_NAME
+
+ network = MGMT_NETWORK_INTERFACE_IP.network
+ self.mgmt_network.subnet = str(network)
+
+ # Create/Start the default virtd network for NAT-based
+ # connectivity inside containers (http://wiki.libvirt.org/page/Networking)
+ if "default" not in net.virsh_list_network_names():
+ logger.debug("default virtd network not found. Creating.")
+ net.virsh_define_default()
+
+ # The default virsh profile create a virbr0 interface
+ # with a 192.168.122.1 ip address. Also sets up iptables
+ # for NAT access.
+ net.virsh_start("default")
+
+ # Create the IP pool
+ mgmt_network_hosts = network.hosts()
+
+ # Remove the management interface ip from the pool
+ self._mgmt_ip_pool = list(mgmt_network_hosts)
+ self._mgmt_ip_pool.remove(MGMT_NETWORK_INTERFACE_IP.ip)
+
+ def acquire_mgmt_ip(self):
+ """Returns an IP address from the available pool"""
+ # TODO these ips will need to be recycled at some point
+ return str(self._mgmt_ip_pool.pop())
+
+ def add_port(self, bridge_id, container_id, port):
+ if bridge_id not in self._bridges:
+ msg = "Unable to find bridge {}"
+ raise exceptions.RWErrorNotFound(msg.format(bridge_id))
+
+ if container_id not in self._containers:
+ msg = "Unable to find container {}"
+ raise exceptions.RWErrorNotFound(msg.format(container_id))
+
+ port_id = str(uuid.uuid4())
+ self._ports[port_id] = port
+
+ self._container_to_ports[container_id].append(port_id)
+ self._bridge_to_ports[bridge_id].append(port_id)
+
+ self._port_to_container[port_id] = container_id
+ self._port_to_bridge[port_id] = bridge_id
+
+ return port_id
+
+ def remove_port(self, port_id):
+ if port_id not in self._ports:
+ msg = "Unable to find port {}"
+ raise exceptions.RWErrorNotFound(msg.format(port_id))
+
+ bridge_id = self._port_to_bridge[port_id]
+ container_id = self._port_to_container[port_id]
+
+ self._container_to_ports[container_id].remove(port_id)
+ self._bridge_to_ports[bridge_id].remove(port_id)
+
+ del self._ports[port_id]
+ del self._port_to_bridge[port_id]
+ del self._port_to_container[port_id]
+
+ def get_port(self, port_id):
+ return self._ports[port_id]
+
+ def add_bridge(self, bridge):
+ bridge_id = str(uuid.uuid4())
+ self._bridges[bridge_id] = bridge
+
+ return bridge_id
+
+ def remove_bridge(self, bridge_id):
+ for port_id in self._bridge_to_ports[bridge_id]:
+ self.remove_port(port_id)
+
+ del self._bridges[bridge_id]
+
+ def get_bridge(self, bridge_id):
+ return self._bridges[bridge_id]
+
+ def get_bridge_ports(self, bridge_id):
+ port_ids = self._bridge_to_ports[bridge_id]
+ return [self.get_port(port_id) for port_id in port_ids]
+
+ def add_container(self, container):
+ container_id = str(uuid.uuid4())
+ self._containers[container_id] = container
+
+ return container_id
+
+ def remove_container(self, container_id):
+ for port_id in self.get_container_ports(container_id):
+ self.remove_port(port_id)
+
+ del self._containers[container_id]
+
+ def get_container(self, container_id):
+ return self._containers[container_id]
+
+ def get_container_ports(self, container_id):
+ return self._container_to_ports[container_id]
+
+
+
+class Datastore(object):
+ """
+ This class is used to store data that is shared among different instance of
+ the Container class.
+ """
+ def __init__(self):
+ self.lxc_manager = LxcManager()
+ self.cal_manager = CalManager()
+ self.cal_to_lxc = {'image': {}, 'port': {}, 'network': {}, 'vm': {}}
+ self.last_index = 0
+
+
+class CloudSimPlugin(GObject.Object, RwCal.Cloud):
+ # HACK this is a work-around for sharing/persisting container information.
+ # This will only work for instances of CloudSimPlugin that are within the
+ # same process. Thus, it works in collapsed mode, but will not work in
+ # expanded mode. At the point where it is necessary to persist this
+ # information in expanded mode, we will need to find a better solution.
+ datastore = None
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+ if CloudSimPlugin.datastore is None:
+ CloudSimPlugin.datastore = Datastore()
+
+ @property
+ def lxc(self):
+ return CloudSimPlugin.datastore.lxc_manager
+
+ @property
+ def cal(self):
+ return CloudSimPlugin.datastore.cal_manager
+
+ @property
+ def volume_group(self):
+ return lvm.get("rift")
+
+ @property
+ def cal_to_lxc(self):
+ return CloudSimPlugin.datastore.cal_to_lxc
+
+ def next_snapshot_name(self):
+ """Generates a new snapshot name for a container"""
+ CloudSimPlugin.datastore.last_index += 1
+ return 'rws{}'.format(CloudSimPlugin.datastore.last_index)
+
+ @rwstatus
+ def do_init(self, rwlog_ctx):
+ if not any(isinstance(h, rwlogger.RwLogger) for h in logger.handlers):
+ logger.addHandler(
+ rwlogger.RwLogger(
+ category="rw-cal-log",
+ subcategory="cloudsim",
+ log_hdl=rwlog_ctx,
+ )
+ )
+
+ @rwstatus(ret_on_failure=[None])
+ def do_validate_cloud_creds(self, account):
+ """
+ Validates the cloud account credentials for the specified account.
+ If creds are not valid, returns an error code & reason string
+ Arguments:
+ account - a cloud account to validate
+
+ Returns:
+ Validation Code and Details String
+ """
+ status = RwcalYang.CloudConnectionStatus(
+ status="success",
+ details=""
+ )
+
+ return status
+
+ @rwstatus(ret_on_failure=[None])
+ def do_get_management_network(self, account):
+ """Returns the management network
+
+ Arguments:
+ account - a cloud account
+
+ Returns:
+ a NetworkInfo object
+
+ """
+ return self.lxc.mgmt_network
+
+ @rwstatus
+ def do_create_tenant(self, account, name):
+ """
+ Create a new tenant.
+
+ @param name - name to assign to the tenant.
+ """
+ raise NotImplementedError()
+
+ @rwstatus
+ def do_delete_tenant(self, account, tenant_id):
+ """
+ delete a tenant.
+
+ @param tenant_id - id of tenant to be deleted.
+ """
+ raise NotImplementedError()
+
+ @rwstatus(ret_on_failure=[[]])
+ def do_get_tenant_list(self, account):
+ """
+ List tenants.
+
+ """
+ raise NotImplementedError()
+
+ @rwstatus
+ def do_create_role(self, account, name):
+ """
+ Create a new role.
+
+ @param name - name to assign to the role.
+ """
+ raise NotImplementedError()
+
+ @rwstatus
+ def do_delete_role(self, account, role_id):
+ """
+ delete a role.
+
+ @param role_id - id of role to be deleted.
+ """
+ raise NotImplementedError()
+
+ @rwstatus(ret_on_failure=[[]])
+ def do_get_role_list(self, account):
+ """
+ List roles.
+
+ """
+ raise NotImplementedError()
+
+ @rwstatus(ret_on_failure=[None])
+ def do_create_image(self, account, image):
+ """Create a new image
+
+ Creates a new container based upon the template and tarfile specified.
+ Only one image is currently supported for a given instance of the CAL.
+
+ Arguments:
+ account - a cloud account
+ image - an ImageInfo object
+
+ Raises:
+ An RWErrorDuplicate is raised if create_image is called and there
+ is already an image.
+
+ Returns:
+ The UUID of the new image
+
+ """
+ def file_md5(path, block_size=2 ** 20):
+ """
+ Block size directly depends on the block size of your filesystem
+ to avoid performances issues.
+ """
+ md5 = hashlib.md5()
+ with open(path, 'rb') as f:
+ for chunk in iter(lambda: f.read(block_size), b''):
+ md5.update(chunk)
+
+ return md5.hexdigest()
+
+ current_images = self.cal.get_image_list()
+ lxc_name = "rwm{}".format(len(current_images))
+
+ if not image.has_field("disk_format"):
+ logger.warning("Image disk format not provided assuming qcow2")
+ image.disk_format = "qcow2"
+
+ if image.disk_format not in ["qcow2"]:
+ msg = "Only qcow2 currently supported for container CAL"
+ raise exceptions.RWErrorNotSupported(msg)
+
+ logger.debug('Calculating IMAGE checksum...')
+ image.checksum = file_md5(image.location)
+ logger.debug("Calculated image checksum: %s", image.checksum)
+ image.state = 'active'
+
+ container = lxc.create_container(
+ name=lxc_name,
+ template_path=os.path.join(
+ os.environ['RIFT_INSTALL'],
+ "etc/lxc-fedora-rift.lxctemplate",
+ ),
+ volume="rift",
+ rootfs_qcow2file=image.location,
+ )
+
+
+ # Add the images to the managers
+ cal_image_id = self.cal.add_image(image)
+ lxc_image_id = self.lxc.add_container(container)
+
+ # Create the CAL to LXC mapping
+ self.cal_to_lxc["image"][cal_image_id] = lxc_image_id
+
+ image.id = cal_image_id
+
+ return image.id
+
+ @rwstatus
+ def do_delete_image(self, account, image_id):
+ """Deletes an image
+
+ This function will remove the record of the image from the CAL and
+ destroy the associated container.
+
+ Arguments:
+ account - a cloud account
+ image_id - the UUID of the image to delete
+
+ Raises:
+ An RWErrorNotEmpty exception is raised if there are VMs based on
+ this image (the VMs need to be deleted first). An RWErrorNotFound
+ is raised if the image_id does not match any of the known images.
+
+ """
+ container_id = self.cal_to_lxc["image"][image_id]
+ container = self.lxc.get_container(container_id)
+
+ # Stop the image and destroy it (NB: it should not be necessary to stop
+ # the container, but just in case)
+ container.stop()
+ container.destroy()
+
+ self.cal.remove_image(image_id)
+ self.lxc.remove_container(container_id)
+
+ @rwstatus(ret_on_failure=[None])
+ def do_get_image(self, account, image_id):
+ """Returns the specified image
+
+ Arguments:
+ account - a cloud account
+ image_id - the UUID of the image to retrieve
+
+ Raises:
+ An RWErrorNotFound exception is raised if the image_id does not
+ match any of the known images.
+
+ Returns:
+ An image object
+
+ """
+ return self.cal.get_image(image_id)
+
+ @rwstatus(ret_on_failure=[[]])
+ def do_get_image_list(self, account):
+ """Returns a list of images"""
+ resources = RwcalYang.VimResources()
+ for image in self.cal.get_image_list():
+ resources.imageinfo_list.append(rwcal_copy_object(image))
+
+ return resources
+
+ @rwstatus
+ def do_create_vm(self, account, vm):
+ """Create a VM
+
+ Arguments:
+ vm - the VM info used to define the desire VM
+
+ Raises:
+ An RWErrorFailure is raised if there is not
+
+ Returns:
+ a string containing the unique id of the created VM
+
+ """
+ # Retrieve the container that will be used as the base of the snapshot
+ container_id = self.cal_to_lxc["image"][vm.image_id]
+ container = self.lxc.get_container(container_id)
+
+ # Create a container snapshot
+ snapshot = container.snapshot(self.next_snapshot_name())
+ snapshot.hostname = vm.vm_name
+
+ # Register the vm and container
+ snapshot_id = self.lxc.add_container(snapshot)
+ vm.vm_id = self.cal.add_vm(vm.image_id, vm)
+
+ self.cal_to_lxc["vm"][vm.vm_id] = snapshot_id
+
+ return vm.vm_id
+
+ @rwstatus
+ def do_start_vm(self, account, vm_id):
+ """Starts the specified VM
+
+ Arguments:
+ vm_id - the id of the vm to start
+
+ Raises:
+ An RWErrorNotFound is raised if the specified vm id is not known to
+ this driver.
+
+ """
+ if vm_id not in self.cal_to_lxc["vm"]:
+ msg = "Unable to find the specified VM ({})"
+ raise exceptions.RWErrorNotFound(msg.format(vm_id))
+
+ container_id = self.cal_to_lxc["vm"][vm_id]
+
+ snapshot = self.lxc.get_container(container_id)
+ port_ids = self.lxc.get_container_ports(container_id)
+
+ config = lxc.ContainerConfig(snapshot.name)
+
+ for port_id in port_ids:
+ port = self.lxc.get_port(port_id)
+ config.add_network_config(port)
+
+ vm = self.cal.get_vm(vm_id)
+
+ # Set the management IP on the vm if not yet set
+ if not vm.has_field("management_ip"):
+ mgmt_ip = self.lxc.acquire_mgmt_ip()
+ vm.management_ip = mgmt_ip
+
+ # Add the management interface
+ config.add_network_config(
+ lxc.NetworkConfig(
+ type="veth",
+ link=self.lxc.mgmt_network.network_name,
+ name="eth0",
+ ipv4=vm.management_ip,
+ ipv4_gateway='auto',
+ )
+ )
+
+ # Add rift root as a mount point
+ config.add_mount_point_config(
+ lxc.MountConfig(
+ local=os.environ["RIFT_ROOT"],
+ remote=os.environ["RIFT_ROOT"][1:],
+ read_only=False,
+ )
+ )
+
+ userdata=None
+ if vm.cloud_init.has_field("userdata"):
+ userdata = vm.cloud_init.userdata
+
+ snapshot.configure(config, userdata=userdata)
+ # For some reason, the cloud-init fails or runs only partially when
+ # you start the container immediately after writing the config files.
+ # A sleep of 1 sec seems to magically fix the issue!!
+ time.sleep(1)
+ snapshot.start()
+
+ @rwstatus
+ def do_stop_vm(self, account, vm_id):
+ """Stops the specified VM
+
+ Arguments:
+ vm_id - the id of the vm to stop
+
+ Raises:
+ An RWErrorNotFound is raised if the specified vm id is not known to
+ this driver.
+
+ """
+ if vm_id not in self.cal_to_lxc["vm"]:
+ msg = "Unable to find the specified VM ({})"
+ raise exceptions.RWErrorNotFound(msg.format(vm_id))
+
+ # Stop the container
+ container_id = self.cal_to_lxc["vm"][vm_id]
+ snapshot = self.lxc.get_container(container_id)
+ snapshot.stop()
+
+ @rwstatus
+ def do_delete_vm(self, account, vm_id):
+ """Deletes the specified VM
+
+ Arguments:
+ vm_id - the id of the vm to delete
+
+ Raises:
+ An RWErrorNotFound is raised if the specified vm id is not known to
+ this driver.
+
+ """
+ if vm_id not in self.cal_to_lxc["vm"]:
+ msg = "Unable to find the specified VM ({})"
+ raise exceptions.RWErrorNotFound(msg.format(vm_id))
+
+ container_id = self.cal_to_lxc["vm"][vm_id]
+
+ snapshot = self.lxc.get_container(container_id)
+ snapshot.stop()
+ snapshot.destroy()
+
+ self.cal.remove_vm(vm_id)
+ self.lxc.remove_container(container_id)
+
+ # TODO: Recycle management ip
+
+ @rwstatus
+ def do_reboot_vm(self, account, vm_id):
+ """
+ reboot a virtual machine.
+
+ @param vm_id - Instance id of VM to be deleted.
+ """
+ self.do_stop_vm(account, vm_id, no_rwstatus=True)
+ self.do_start_vm(account, vm_id, no_rwstatus=True)
+
+ @rwstatus
+ def do_get_vm(self, account, vm_id):
+ """Returns the specified VM
+
+ Arguments:
+ vm_id - the id of the vm to return
+
+ Raises:
+ An RWErrorNotFound is raised if the specified vm id is not known to
+ this driver.
+
+ Returns:
+ a VMInfoItem object
+
+ """
+ if vm_id not in self.cal_to_lxc["vm"]:
+ msg = "Unable to find the specified VM ({})"
+ raise exceptions.RWErrorNotFound(msg.format(vm_id))
+
+ return self.cal.get_vm(vm_id)
+
+ @rwstatus(ret_on_failure=[[]])
+ def do_get_vm_list(self, account):
+ """Returns the a list of the VMs known to the driver
+
+ Returns:
+ a list of VMInfoItem objects
+
+ """
+ resources = RwcalYang.VimResources()
+ for vm in self.cal.get_vm_list():
+ resources.vminfo_list.append(rwcal_copy_object(vm))
+
+ return resources
+
+ @rwstatus
+ def do_create_flavor(self, account, flavor):
+ """
+ create new flavor.
+
+ @param flavor - Flavor object
+ """
+ flavor_id = str(uuid.uuid4())
+ flavor.id = flavor_id
+ self.cal.flavors[flavor_id] = flavor
+ logger.debug('Created flavor: {}'.format(flavor_id))
+ return flavor_id
+
+ @rwstatus
+ def do_delete_flavor(self, account, flavor_id):
+ """
+ Delete flavor.
+
+ @param flavor_id - Flavor id to be deleted.
+ """
+ logger.debug('Deleted flavor: {}'.format(flavor_id))
+ self.cal.flavors.pop(flavor_id)
+
+ @rwstatus(ret_on_failure=[None])
+ def do_get_flavor(self, account, flavor_id):
+ """
+ Return the specified flavor
+
+ @param flavor_id - the id of the flavor to return
+ """
+ flavor = self.cal.flavors[flavor_id]
+ logger.debug('Returning flavor-info for : {}'.format(flavor_id))
+ return flavor
+
+ @rwstatus(ret_on_failure=[[]])
+ def do_get_flavor_list(self, account):
+ """
+ Return a list of flavors
+ """
+ vim_resources = RwcalYang.VimResources()
+ for flavor in self.cal.flavors.values():
+ f = RwcalYang.FlavorInfoItem()
+ f.copy_from(flavor)
+ vim_resources.flavorinfo_list.append(f)
+ logger.debug("Returning list of flavor-info of size: %d", len(vim_resources.flavorinfo_list))
+ return vim_resources
+
+ @rwstatus
+ def do_add_host(self, account, host):
+ raise NotImplementedError()
+
+ @rwstatus
+ def do_remove_host(self, account, host_id):
+ raise NotImplementedError()
+
+ @rwstatus(ret_on_failure=[None])
+ def do_get_host(self, account, host_id):
+ raise NotImplementedError()
+
+ @rwstatus(ret_on_failure=[[]])
+ def do_get_host_list(self, account):
+ raise NotImplementedError()
+
+ @rwstatus
+ def do_create_port(self, account, port):
+ """Create a port between a network and a virtual machine
+
+ Arguments:
+ account - a cloud account
+ port - a description of port to create
+
+ Raises:
+ Raises an RWErrorNotFound exception if either the network or the VM
+ associated with the port cannot be found.
+
+ Returns:
+ the ID of the newly created port.
+
+ """
+ if port.network_id not in self.cal_to_lxc["network"]:
+ msg = 'Unable to find the specified network ({})'
+ raise exceptions.RWErrorNotFound(msg.format(port.network_id))
+
+ if port.vm_id not in self.cal_to_lxc["vm"]:
+ msg = "Unable to find the specified VM ({})"
+ raise exceptions.RWErrorNotFound(msg.format(port.vm_id))
+
+ if port.has_field("ip_address"):
+ raise exceptions.RWErrorFailure("IP address of the port must not be specific")
+
+ network = self.cal.get_network(port.network_id)
+ ip_pool = self.cal.get_network_ip_pool(port.network_id)
+ port.ip_address = ip_pool.allocate_ip()
+
+ net_config = lxc.NetworkConfig(
+ type='veth',
+ link=network.network_name[:15],
+ name="veth" + str(uuid.uuid4())[:10],
+ ipv4=port.ip_address,
+ )
+
+ lxc_network_id = self.cal_to_lxc["network"][port.network_id]
+ lxc_vm_id = self.cal_to_lxc["vm"][port.vm_id]
+
+ cal_port_id = self.cal.add_port(port.network_id, port.vm_id, port)
+ lxc_port_id = self.lxc.add_port(lxc_network_id, lxc_vm_id, net_config)
+
+ self.cal_to_lxc["port"][cal_port_id] = lxc_port_id
+ port.port_id = cal_port_id
+
+ return port.port_id
+
+ @rwstatus
+ def do_delete_port(self, account, port_id):
+ """Delete the specified port
+
+ Arguments:
+ account - a cloud account
+ port_id - the ID of the port to delete
+
+ Raises:
+ A RWErrorNotFound exception is raised if the specified port cannot
+ be found.
+
+ """
+ if port_id not in self.cal_to_lxc["port"]:
+ msg = "Unable to find the specified port ({})"
+ raise exceptions.RWErrorNotFound(msg.format(port_id))
+
+ lxc_port_id = self.cal_to_lxc["port"][port_id]
+
+ # Release the port's ip address back into the network pool
+ port = self.cal.get_port(port_id)
+ ip_pool = self.cal.get_network_ip_pool(port.network_id)
+ ip_pool.deallocate_ip(port.ip_address)
+
+ self.cal.remove_port(port_id)
+ self.lxc.remove_port(lxc_port_id)
+
+ del self.cal_to_lxc["port"][port_id]
+
+ @rwstatus(ret_on_failure=[None])
+ def do_get_port(self, account, port_id):
+ """Return the specified port
+
+ Arguments:
+ account - a cloud account
+ port_id - the ID of the port to return
+
+ Raises:
+ A RWErrorNotFound exception is raised if the specified port cannot
+ be found.
+
+ Returns:
+ The specified port.
+
+ """
+ if port_id not in self.cal_to_lxc["port"]:
+ msg = "Unable to find the specified port ({})"
+ raise exceptions.RWErrorNotFound(msg.format(port_id))
+
+ return self.cal.get_port(port_id)
+
+ @rwstatus(ret_on_failure=[[]])
+ def do_get_port_list(self, account):
+ """Returns a list of ports"""
+ resources = RwcalYang.VimResources()
+ for port in self.datastore.cal_manager.get_port_list():
+ resources.portinfo_list.append(rwcal_copy_object(port))
+
+ return resources
+
+ @rwstatus
+ def do_create_network(self, account, network):
+ """Create a network
+
+ Arguments:
+ account - a cloud account
+ network - a description of the network to create
+
+ Returns:
+ The ID of the newly created network
+
+ """
+
+ # Create the network
+ try:
+ # Setup a pool of mgmt IPv4 addresses
+ if net.bridge_exists(network.network_name):
+ logger.warning("Bridge %s already exists. Removing.", network.network_name)
+ net.bridge_down(network.network_name)
+ net.bridge_remove(network.network_name)
+
+ # Ensure that the subnet field was filled out and is valid
+ if not network.has_field("subnet"):
+ raise CreateNetworkError("subnet not provided in create network request")
+
+ try:
+ ipaddress.IPv4Network(network.subnet)
+ except ValueError as e:
+ raise CreateNetworkError("Could not convert subnet into a "
+ "IPv4Network: %s" % str(network.subnet))
+
+ ip_pool = NetworkIPPool(network.subnet)
+
+ # Create the management bridge with interface information
+ net.create(network.network_name)
+
+ except Exception as e:
+ logger.warning(str(e))
+
+ # Register the network
+ cal_network_id = self.cal.add_network(network)
+ lxc_network_id = self.lxc.add_bridge(network)
+ self.cal.add_network_ip_pool(cal_network_id, ip_pool)
+
+ self.cal_to_lxc["network"][cal_network_id] = lxc_network_id
+
+ # Set the ID of the network object
+ network.network_id = cal_network_id
+
+ return network.network_id
+
+ @rwstatus
+ def do_delete_network(self, account, network_id):
+ """
+ Arguments:
+ account - a cloud account
+ network_id - the UUID of the network to delete
+
+ Raises:
+ An RWErrorNotFound is raised if the specified network cannot be
+ found.
+
+ """
+ if network_id not in self.cal_to_lxc["network"]:
+ msg = "Unable to find the specified network ({})"
+ raise exceptions.RWErrorNotFound(msg.format(network_id))
+
+ # Get the associated bridge ID
+ bridge_id = self.cal_to_lxc["network"][network_id]
+
+ # Delete the network
+ network = self.cal.get_network(network_id)
+ net.delete(network.network_name)
+
+ # Remove the network records
+ self.lxc.remove_bridge(bridge_id)
+ self.cal.remove_network(network_id)
+ del self.cal_to_lxc["network"][network_id]
+
+ @rwstatus(ret_on_failure=[None])
+ def do_get_network(self, account, network_id):
+ """Returns the specified network
+
+ Arguments:
+ account - a cloud account
+ network_id - the UUID of the network to delete
+
+ Raises:
+ An RWErrorNotFound is raised if the specified network cannot be
+ found.
+
+ Returns:
+ The specified network
+
+ """
+ return self.cal.get_network(network_id)
+
+ @rwstatus(ret_on_failure=[[]])
+ def do_get_network_list(self, account):
+ """Returns a list of network objects"""
+ resources = RwcalYang.VimResources()
+ for network in self.cal.get_network_list():
+ resources.networkinfo_list.append(rwcal_copy_object(network))
+
+ return resources
+
+ @rwcalstatus(ret_on_failure=[""])
+ def do_create_virtual_link(self, account, link_params):
+ """Create a new virtual link
+
+ Arguments:
+ account - a cloud account
+ link_params - information that defines the type of VDU to create
+
+ Returns:
+ The vdu_id
+ """
+ network = RwcalYang.NetworkInfoItem()
+ network.network_name = link_params.name
+ network.subnet = link_params.subnet
+
+ if link_params.has_field("provider_network"):
+ logger.warning("Container CAL does not implement provider network")
+
+ rs, net_id = self.do_create_network(account, network)
+ if rs != RwTypes.RwStatus.SUCCESS:
+ raise exceptions.RWErrorFailure(rs)
+
+ return net_id
+
+ @rwstatus
+ def do_delete_virtual_link(self, account, link_id):
+ """Delete a virtual link
+
+ Arguments:
+ account - a cloud account
+ link_id - id for the virtual-link to be deleted
+
+ Returns:
+ None
+ """
+
+ network_ports = self.cal.get_network_ports(link_id)
+ for port_id in network_ports:
+ self.do_delete_port(account, port_id, no_rwstatus=True)
+
+ self.do_delete_network(account, link_id, no_rwstatus=True)
+
+ @staticmethod
+ def fill_connection_point_info(c_point, port_info):
+ """Create a GI object for RwcalYang.VDUInfoParams_ConnectionPoints()
+
+ Converts Port information dictionary object returned by container cal
+ driver into Protobuf Gi Object
+
+ Arguments:
+ port_info - Port information from container cal
+ Returns:
+ Protobuf Gi object for RwcalYang.VDUInfoParams_ConnectionPoints
+ """
+ c_point.name = port_info.port_name
+ c_point.connection_point_id = port_info.port_id
+ c_point.ip_address = port_info.ip_address
+ c_point.state = 'active'
+ c_point.virtual_link_id = port_info.network_id
+ c_point.vdu_id = port_info.vm_id
+
+ @staticmethod
+ def create_virtual_link_info(network_info, port_list):
+ """Create a GI object for VirtualLinkInfoParams
+
+ Converts Network and Port information dictionary object
+ returned by container manager into Protobuf Gi Object
+
+ Arguments:
+ network_info - Network information from container cal
+ port_list - A list of port information from container cal
+ subnet: Subnet information from openstack
+ Returns:
+ Protobuf Gi object for VirtualLinkInfoParams
+ """
+ link = RwcalYang.VirtualLinkInfoParams()
+ link.name = network_info.network_name
+ link.state = 'active'
+ link.virtual_link_id = network_info.network_id
+ for port in port_list:
+ c_point = link.connection_points.add()
+ CloudSimPlugin.fill_connection_point_info(c_point, port)
+
+ link.subnet = network_info.subnet
+
+ return link
+
+ @rwstatus(ret_on_failure=[None])
+ def do_get_virtual_link(self, account, link_id):
+ """Get information about virtual link.
+
+ Arguments:
+ account - a cloud account
+ link_id - id for the virtual-link
+
+ Returns:
+ Object of type RwcalYang.VirtualLinkInfoParams
+ """
+
+ network = self.do_get_network(account, link_id, no_rwstatus=True)
+ port_ids = self.cal.get_network_ports(network.network_id)
+ ports = [self.cal.get_port(p_id) for p_id in port_ids]
+
+ virtual_link = CloudSimPlugin.create_virtual_link_info(
+ network, ports
+ )
+
+ return virtual_link
+
+ @rwstatus(ret_on_failure=[None])
+ def do_get_virtual_link_list(self, account):
+ """Get information about all the virtual links
+
+ Arguments:
+ account - a cloud account
+
+ Returns:
+ A list of objects of type RwcalYang.VirtualLinkInfoParams
+ """
+ networks = self.do_get_network_list(account, no_rwstatus=True)
+ vnf_resources = RwcalYang.VNFResources()
+ for network in networks.networkinfo_list:
+ virtual_link = self.do_get_virtual_link(account, network.network_id, no_rwstatus=True)
+ vnf_resources.virtual_link_info_list.append(virtual_link)
+
+ return vnf_resources
+
+ def _create_connection_point(self, account, c_point, vdu_id):
+ """
+ Create a connection point
+ Arguments:
+ account - a cloud account
+ c_point - connection_points
+ """
+ port = RwcalYang.PortInfoItem()
+ port.port_name = c_point.name
+ port.network_id = c_point.virtual_link_id
+ port.port_type = 'normal' ### Find Port type from network_profile under cloud account
+ port.vm_id = vdu_id
+ port_id = self.do_create_port(account, port, no_rwstatus=True)
+ return port_id
+
+ @rwcalstatus(ret_on_failure=[""])
+ def do_create_vdu(self, account, vdu_init):
+ """Create a new virtual deployment unit
+
+ Arguments:
+ account - a cloud account
+ vdu_init - information about VDU to create (RwcalYang.VDUInitParams)
+
+ Returns:
+ The vdu_id
+ """
+ ### Create VM
+ vm = RwcalYang.VMInfoItem()
+ vm.vm_name = vdu_init.name
+ vm.image_id = vdu_init.image_id
+ if vdu_init.vdu_init.has_field('userdata'):
+ vm.cloud_init.userdata = vdu_init.vdu_init.userdata
+ vm.user_tags.node_id = vdu_init.node_id
+
+ vm_id = self.do_create_vm(account, vm, no_rwstatus=True)
+
+ ### Now create required number of ports aka connection points
+ port_list = []
+ for c_point in vdu_init.connection_points:
+ virtual_link_id = c_point.virtual_link_id
+
+ # Attempt to fetch the network to verify that the network
+ # already exists.
+ self.do_get_network(account, virtual_link_id, no_rwstatus=True)
+
+ port_id = self._create_connection_point(account, c_point, vm_id)
+ port_list.append(port_id)
+
+ # Finally start the vm
+ self.do_start_vm(account, vm_id, no_rwstatus=True)
+
+ return vm_id
+
+ @rwstatus
+ def do_modify_vdu(self, account, vdu_modify):
+ """Modify Properties of existing virtual deployment unit
+
+ Arguments:
+ account - a cloud account
+ vdu_modify - Information about VDU Modification (RwcalYang.VDUModifyParams)
+ """
+ ### First create required number of ports aka connection points
+ port_list = []
+ network_list = []
+ if not vdu_modify.has_field("vdu_id"):
+ raise ValueError("vdu_id must not be empty")
+
+ for c_point in vdu_modify.connection_points_add:
+ if not c_point.has_field("virtual_link_id"):
+ raise ValueError("virtual link id not provided")
+
+ network_list.append(c_point.virtual_link_id)
+ port_id = self._create_connection_point(account, c_point, vdu_modify.vdu_id)
+ port_list.append(port_id)
+
+ ### Delete the requested connection_points
+ for c_point in vdu_modify.connection_points_remove:
+ self.do_delete_port(account, c_point.connection_point_id, no_rwstatus=True)
+
+ self.do_reboot_vm(account, vdu_modify.vdu_id)
+
+ @rwstatus
+ def do_delete_vdu(self, account, vdu_id):
+ """Delete a virtual deployment unit
+
+ Arguments:
+ account - a cloud account
+ vdu_id - id for the vdu to be deleted
+
+ Returns:
+ None
+ """
+ ### Get list of port on VM and delete them.
+ port_id_list = self.cal.get_vm_ports(vdu_id)
+ ports = [self.cal.get_port(p_id) for p_id in port_id_list]
+ for port in ports:
+ self.do_delete_port(account, port.port_id, no_rwstatus=True)
+ self.do_delete_vm(account, vdu_id, no_rwstatus=True)
+
+ @staticmethod
+ def fill_vdu_info(vm_info, port_list):
+ """create a gi object for vduinfoparams
+
+ converts vm information dictionary object returned by openstack
+ driver into protobuf gi object
+
+ arguments:
+ vm_info - vm information from openstack
+ mgmt_network - management network
+ port_list - a list of port information from container cal
+ returns:
+ protobuf gi object for vduinfoparams
+ """
+ vdu = RwcalYang.VDUInfoParams()
+ vdu.name = vm_info.vm_name
+ vdu.vdu_id = vm_info.vm_id
+ vdu.management_ip = vm_info.management_ip
+ vdu.public_ip = vm_info.management_ip
+ vdu.node_id = vm_info.user_tags.node_id
+ vdu.image_id = vm_info.image_id
+ vdu.state = 'active'
+
+ # fill the port information
+ for port in port_list:
+ c_point = vdu.connection_points.add()
+ CloudSimPlugin.fill_connection_point_info(c_point, port)
+
+ vdu.vm_flavor.vcpu_count = 1
+ vdu.vm_flavor.memory_mb = 8 * 1024 # 8GB
+ vdu.vm_flavor.storage_gb = 10
+
+ return vdu
+
+ @rwstatus(ret_on_failure=[None])
+ def do_get_vdu(self, account, vdu_id):
+ """Get information about a virtual deployment unit.
+
+ Arguments:
+ account - a cloud account
+ vdu_id - id for the vdu
+
+ Returns:
+ Object of type RwcalYang.VDUInfoParams
+ """
+ port_id_list = self.cal.get_vm_ports(vdu_id)
+ ports = [self.cal.get_port(p_id) for p_id in port_id_list]
+ vm_info = self.do_get_vm(account, vdu_id, no_rwstatus=True)
+ vdu_info = CloudSimPlugin.fill_vdu_info(vm_info, ports)
+
+ return vdu_info
+
+ @rwstatus(ret_on_failure=[None])
+ def do_get_vdu_list(self, account):
+ """Get information about all the virtual deployment units
+
+ Arguments:
+ account - a cloud account
+
+ Returns:
+ A list of objects of type RwcalYang.VDUInfoParams
+ """
+
+ vnf_resources = RwcalYang.VNFResources()
+
+ vm_resources = self.do_get_vm_list(account, no_rwstatus=True)
+ for vm in vm_resources.vminfo_list:
+ port_list = self.cal.get_vm_ports(vm.vm_id)
+ port_list = [self.cal.get_port(port_id) for port_id in port_list]
+ vdu = CloudSimPlugin.fill_vdu_info(vm, port_list)
+ vnf_resources.vdu_info_list.append(vdu)
+
+ return vnf_resources