RIFT OSM R1 Initial Submission
[osm/SO.git] / rwcal / plugins / vala / rwcal_cloudsim / rwcal_cloudsim.py
diff --git a/rwcal/plugins/vala/rwcal_cloudsim/rwcal_cloudsim.py b/rwcal/plugins/vala/rwcal_cloudsim/rwcal_cloudsim.py
new file mode 100644 (file)
index 0000000..6da8a2e
--- /dev/null
@@ -0,0 +1,1430 @@
+
+# 
+#   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