RIFT OSM R1 Initial Submission

Signed-off-by: Jeremy Mordkoff <jeremy.mordkoff@riftio.com>
diff --git a/rwcal/test/CMakeLists.txt b/rwcal/test/CMakeLists.txt
new file mode 100644
index 0000000..79e66c5
--- /dev/null
+++ b/rwcal/test/CMakeLists.txt
@@ -0,0 +1,67 @@
+# 
+#   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.
+#
+# Author(s): Anil Gunturu
+# Creation Date: 06/27/2014
+# 
+
+cmake_minimum_required(VERSION 2.8)
+
+set(subdirs cal_module_test)
+rift_add_subdirs(SUBDIR_LIST ${subdirs})
+
+# rift_gtest(unittest_rwcal_cloud
+#   TEST_SRCS rwcal_cloud_gtest.cpp
+#   TEST_LIBS
+#     rwcal_api
+#     rwcal_yang_gen
+# )
+
+rift_gtest(unittest_rwcal_callback
+  TEST_SRCS rwcal_callback_gtest.cpp
+  TEST_LIBS
+    rwcal-1.0
+    rwcal_api
+)
+
+##
+# Add the basic plugin python test
+##
+#rift_py3test(openstack_cal_tests
+#  LONG_UNITTEST_TARGET
+#  TEST_ARGS -m pytest --junit-xml=${RIFT_UNITTEST_DIR}/openstack_cal/unittest.xml #${CMAKE_CURRENT_SOURCE_DIR}/test_rwcal_openstack_pytest.py
+#)
+
+
+add_executable(rwcal_dump rwcal_dump.cpp)
+target_link_libraries(rwcal_dump
+  rwcal_api
+  rwlib
+  rwyang
+  rwcal_yang_gen
+  CoreFoundation
+  glib-2.0
+  protobuf-c
+)
+
+# added for 4.0
+install(
+  FILES 
+    RIFT.ware-ready.py 
+    openstack_resources.py
+  DESTINATION usr/bin
+  COMPONENT ${PKG_LONG_NAME}
+)
+
diff --git a/rwcal/test/RIFT.ware-ready.py b/rwcal/test/RIFT.ware-ready.py
new file mode 100755
index 0000000..1cd69f1
--- /dev/null
+++ b/rwcal/test/RIFT.ware-ready.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+
+# 
+#   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 re
+import sys
+from rift.rwcal.openstack.openstack_drv import OpenstackDriver
+
+
+
+def test_openstack(drv):
+    print("checking endpoints")
+    for ep in [ 'compute', 'image', 'network', 'metering' ]: 
+        url = drv.ks_drv.get_service_endpoint(ep, 'publicURL')
+        print("%s: %s" % ( ep, url))
+        if re.search(url, '127.0.0'): 
+            raise Exception("endpoint %s is using a loopback URL: %s" % ( ep, url))
+
+    def verify(name, min, count):
+        if count < min:
+            raise Exception("only %d instances of %s found. Minimum is %d" % ( count, name, min))
+        print("found %d %s" % ( count, name ))
+        
+    verify("images"     , 1, len(drv.glance_image_list()))
+    verify("flavors "    , 1, len(drv.nova_flavor_list()))
+    verify("floating ips "    , 1, len(drv.nova_floating_ip_list()))
+    verify("servers"     , 0, len(drv.nova_server_list()))
+    verify("networks"     , 1, len(drv.neutron_network_list()))
+    verify("subnets"     , 1, len(drv.neutron_subnet_list()))
+    verify("ports"         , 1, len(drv.neutron_port_list()))
+    #verify("ceilometers"     , 1, len(drv.ceilo_meter_list()))
+    
+
+
+if len(sys.argv) != 6:
+    print("ARGS are admin_user admin_password auth_url tenant_name mgmt_network_name")
+    print("e.g. %s pluto mypasswd http://10.95.4.2:5000/v3 demo private" % __file__ )
+    sys.exit(1)
+
+args=tuple(sys.argv[1:6])
+print("Using args \"%s\"" % ",".join(args))
+
+try:
+    v3 = OpenstackDriver(*args)
+except Exception as e:
+    print("\n\nunable to instantiate a endpoint: %s" % e)
+else:
+    print("\n\n endpoint instantiated")
+    try:
+        test_openstack(v3)
+    except Exception as e:
+        print("\n\nendpoint verification failed: %s" % e)
+    else:
+        print("\n\nSUCCESS! openstack is working")
+        sys.exit(0)
+
+
+
+sys.exit(1)
+
+
+# need to check if any public urls are loopbacks
+# need to check DNS is set up right 
+#    neutron subnet-show private_subnet
+#    host repo.riftio.com  10.64.1.3
+
diff --git a/rwcal/test/aws_resources.py b/rwcal/test/aws_resources.py
new file mode 100644
index 0000000..875de56
--- /dev/null
+++ b/rwcal/test/aws_resources.py
@@ -0,0 +1,370 @@
+#!/usr/bin/python3
+
+# 
+#   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 os
+import sys
+import uuid
+import rw_peas
+from gi import require_version
+require_version('RwCal', '1.0')
+
+from gi.repository import RwcalYang
+from gi.repository.RwTypes import RwStatus
+import argparse
+import logging
+import rwlogger
+import boto3
+import botocore
+
+persistent_resources = {
+    'vms'      : [],
+    'networks' : [],
+}
+
+MISSION_CONTROL_NAME = 'mission-control'
+LAUNCHPAD_NAME = 'launchpad'
+
+RIFT_IMAGE_AMI = 'ami-7070231a'
+
+logging.basicConfig(level=logging.ERROR)
+logger = logging.getLogger('rift.cal.awsresources')
+logger.setLevel(logging.INFO)
+
+def get_cal_plugin():
+    """
+        Load AWS cal plugin
+    """
+    plugin = rw_peas.PeasPlugin('rwcal_aws', 'RwCal-1.0')
+    engine, info, extension = plugin()
+    cal = plugin.get_interface("Cloud")
+    rwloggerctx = rwlogger.RwLog.Ctx.new("Cal-Log")
+    try:
+        rc = cal.init(rwloggerctx)
+        assert rc == RwStatus.SUCCESS
+    except Exception as e:
+        logger.error("ERROR:Cal plugin instantiation failed with exception %s",repr(e))
+    else:
+        logger.info("AWS Cal plugin successfully instantiated")
+        return cal
+
+def get_cal_account(**kwargs):
+    """
+    Returns AWS cal account
+    """
+    account                        = RwcalYang.CloudAccount()
+    account.account_type           = "aws"
+    account.aws.key = kwargs['key']
+    account.aws.secret = kwargs['secret']
+    account.aws.region = kwargs['region']
+    if 'ssh_key' in kwargs and kwargs['ssh_key'] is not None:
+        account.aws.ssh_key = kwargs['ssh_key']
+    account.aws.availability_zone = kwargs['availability_zone']
+    if 'vpcid' in kwargs and kwargs['vpcid'] is not None: 
+        account.aws.vpcid =  kwargs['vpcid']
+    if 'default_subnet_id' in kwargs and kwargs['default_subnet_id'] is not None:
+        account.aws.default_subnet_id = kwargs['default_subnet_id']
+    return account
+
+class AWSResources(object):
+    """
+    Class with methods to manage AWS resources
+    """
+    def __init__(self,**kwargs):
+        self._cal      = get_cal_plugin()
+        self._acct     = get_cal_account(**kwargs)
+
+    def _destroy_vms(self):
+        """
+        Destroy VMs
+        """
+        logger.info("Initiating VM cleanup")
+        rc, rsp = self._cal.get_vdu_list(self._acct)
+        vdu_list = [vm for vm in rsp.vdu_info_list if vm.name not in persistent_resources['vms']]
+        logger.info("Deleting VMs : %s" %([x.name for x in vdu_list]))
+
+        for vdu in vdu_list:
+            self._cal.delete_vdu(self._acct, vdu.vdu_id)
+
+        logger.info("VM cleanup complete")
+
+    def _destroy_networks(self):
+        """
+        Destroy Networks
+        """
+        logger.info("Initiating Network cleanup")
+        driver = self._cal._get_driver(self._acct)
+        subnets = driver.get_subnet_list()
+        subnet_list = [subnet for subnet in subnets if subnet.default_for_az is False]
+
+        logger.info("Deleting Networks : %s" %([x.id for x in subnet_list]))
+        for subnet in subnet_list:
+            self._cal.delete_virtual_link(self._acct, subnet.subnet_id)
+        logger.info("Network cleanup complete")
+
+    def destroy_resource(self):
+        """
+        Destroy resources
+        """
+        logger.info("Cleaning up AWS resources")
+        self._destroy_vms()
+        self._destroy_networks()
+        logger.info("Cleaning up AWS resources.......[Done]")
+
+    def _destroy_mission_control(self):
+        """
+        Destroy Mission Control VM
+        """
+        logger.info("Initiating MC VM cleanup")
+        rc, rsp = self._cal.get_vdu_list(self._acct)
+        vdu_list = [vm for vm in rsp.vdu_info_list if vm.name == MISSION_CONTROL_NAME]
+        logger.info("Deleting VMs : %s" %([x.name for x in vdu_list]))
+
+        for vdu in vdu_list:
+            self._cal.delete_vdu(self._acct, vdu.vdu_id)
+        logger.info("MC VM cleanup complete")
+
+    def _destroy_launchpad(self):
+        """
+        Destroy Launchpad VM
+        """
+        logger.info("Initiating LP VM cleanup")
+        rc, rsp = self._cal.get_vdu_list(self._acct)
+        vdu_list = [vm for vm in rsp.vdu_info_list if vm.name == LAUNCHPAD_NAME]
+        logger.info("Deleting VMs : %s" %([x.name for x in vdu_list]))
+
+        for vdu in vdu_list:
+            self._cal.delete_vdu(self._acct, vdu.vdu_id)
+        logger.info("LP VM cleanup complete")
+        
+
+    def create_mission_control(self):
+        """
+        Create Mission Control VM in AWS
+        """ 
+        logger.info("Creating mission control VM")
+        vdu = RwcalYang.VDUInitParams()
+        vdu.name = MISSION_CONTROL_NAME
+        vdu.image_id = RIFT_IMAGE_AMI
+        vdu.flavor_id = 'c3.large'
+        vdu.allocate_public_address = True
+        vdu.vdu_init.userdata = "#cloud-config\n\nruncmd:\n - echo Sleeping for 5 seconds and attempting to start salt-master\n - sleep 5\n - /bin/systemctl restart salt-master.service\n"
+
+        rc,rs=self._cal.create_vdu(self._acct,vdu)
+        assert rc == RwStatus.SUCCESS
+        self._mc_id = rs
+
+        driver = self._cal._get_driver(self._acct)
+        inst=driver.get_instance(self._mc_id)
+        inst.wait_until_running()
+
+        rc,rs =self._cal.get_vdu(self._acct,self._mc_id)
+        assert rc == RwStatus.SUCCESS
+        self._mc_public_ip = rs.public_ip
+        self._mc_private_ip = rs.management_ip
+        
+        logger.info("Started Mission Control VM with id %s and IP Address %s\n",self._mc_id, self._mc_public_ip)
+
+    def create_launchpad_vm(self, salt_master = None):        
+        """
+        Create Launchpad VM in AWS
+        Arguments
+            salt_master (String): String with Salt master IP typically MC VM private IP
+        """
+        logger.info("Creating launchpad VM")
+        USERDATA_FILENAME = os.path.join(os.environ['RIFT_INSTALL'],
+                                 'etc/userdata-template')
+
+        try:
+            fd = open(USERDATA_FILENAME, 'r')
+        except Exception as e:
+                sys.exit(-1)
+        else:
+            LP_USERDATA_FILE = fd.read()
+            # Run the enable lab script when the openstack vm comes up
+            LP_USERDATA_FILE += "runcmd:\n"
+            LP_USERDATA_FILE += " - echo Sleeping for 5 seconds and attempting to start elastic-network-interface\n"
+            LP_USERDATA_FILE += " - sleep 5\n"
+            LP_USERDATA_FILE += " - /bin/systemctl restart elastic-network-interfaces.service\n"
+
+        if salt_master is None:
+            salt_master=self._mc_private_ip
+        node_id = str(uuid.uuid4())
+
+        vdu = RwcalYang.VDUInitParams()
+        vdu.name = LAUNCHPAD_NAME
+        vdu.image_id = RIFT_IMAGE_AMI
+        vdu.flavor_id = 'c3.xlarge'
+        vdu.allocate_public_address = True
+        vdu.vdu_init.userdata = LP_USERDATA_FILE.format(master_ip = salt_master,
+                                          lxcname = node_id)
+        vdu.node_id = node_id
+
+        rc,rs=self._cal.create_vdu(self._acct,vdu)
+        assert rc == RwStatus.SUCCESS
+        self._lp_id = rs
+
+        driver = self._cal._get_driver(self._acct)
+        inst=driver.get_instance(self._lp_id)
+        inst.wait_until_running()
+
+        rc,rs =self._cal.get_vdu(self._acct,self._lp_id)
+        assert rc == RwStatus.SUCCESS
+
+        self._lp_public_ip = rs.public_ip
+        self._lp_private_ip = rs.management_ip
+        logger.info("Started Launchpad VM with id %s and IP Address %s\n",self._lp_id, self._lp_public_ip)
+         
+    def upload_ssh_key_to_ec2(self):
+        """
+         Upload SSH key to EC2 region
+        """
+        driver = self._cal._get_driver(self._acct)
+        key_name = os.getlogin() + '-' + 'sshkey' 
+        key_path = '%s/.ssh/id_rsa.pub' % (os.environ['HOME'])
+        if os.path.isfile(key_path):
+            logger.info("Uploading ssh public key file in path %s with keypair name %s", key_path,key_name)
+            with open(key_path) as fp:
+                driver.upload_ssh_key(key_name,fp.read())
+        else:
+            logger.error("Valid Public key file %s not found", key_path)
+
+
+def main():
+    """
+    Main routine
+    """
+    parser = argparse.ArgumentParser(description='Script to manage AWS resources')
+
+    parser.add_argument('--aws-key',
+                        action = 'store',
+                        dest = 'aws_key',
+                        type = str,
+                        help='AWS key')
+
+    parser.add_argument('--aws-secret',
+                        action = 'store',
+                        dest = 'aws_secret',
+                        type = str,
+                        help='AWS secret')
+
+    parser.add_argument('--aws-region',
+                        action = 'store',
+                        dest = 'aws_region',
+                        type = str,
+                        help='AWS region')
+
+    parser.add_argument('--aws-az',
+                        action = 'store',
+                        dest = 'aws_az',
+                        type = str,
+                        help='AWS Availability zone')
+
+    parser.add_argument('--aws-sshkey',
+                        action = 'store',
+                        dest = 'aws_sshkey',
+                        type = str,
+                        help='AWS SSH Key to login to instance')
+
+    parser.add_argument('--aws-vpcid',
+                        action = 'store',
+                        dest = 'aws_vpcid',
+                        type = str,
+                        help='AWS VPC ID to use to indicate non default VPC')
+
+    parser.add_argument('--aws-default-subnet',
+                        action = 'store',
+                        dest = 'aws_default_subnet',
+                        type = str,
+                        help='AWS Default subnet id in VPC to be used for mgmt network')
+
+    parser.add_argument('--mission-control',
+                        action = 'store_true',
+                        dest = 'mission_control',
+                        help='Create Mission Control VM')
+
+    parser.add_argument('--launchpad',
+                        action = 'store_true',
+                        dest = 'launchpad',
+                        help='Create LaunchPad VM')
+
+    parser.add_argument('--salt-master',
+                        action = 'store',
+                        dest = 'salt_master',
+                        type = str,
+                        help='IP Address of salt controller. Required, if only launchpad  VM is being created.')
+
+    parser.add_argument('--cleanup',
+                        action = 'store',
+                        dest = 'cleanup',
+                        nargs = '+',
+                        type = str,
+                        help = 'Perform resource cleanup for AWS installation. \n Possible options are {all, mc, lp,  vms, networks }')
+
+    parser.add_argument('--upload-ssh-key',
+                         action = 'store_true',
+                         dest = 'upload_ssh_key',
+                         help = 'Upload users SSH public key ~/.ssh/id_rsa.pub')  
+
+    argument = parser.parse_args()
+
+    if (argument.aws_key is None or argument.aws_secret is None or argument.aws_region is None or
+       argument.aws_az is None):
+        logger.error("Missing mandatory params. AWS Key, Secret, Region, AZ and SSH key are mandatory params")
+        sys.exit(-1)
+
+    if (argument.cleanup is None and argument.mission_control is None and argument.launchpad is None 
+        and argument.upload_ssh_key is None):
+        logger.error('Insufficient parameters')
+        sys.exit(-1)
+
+    ### Start processing
+    logger.info("Instantiating cloud-abstraction-layer")
+    drv = AWSResources(key=argument.aws_key, secret=argument.aws_secret, region=argument.aws_region, availability_zone = argument.aws_az, 
+                       ssh_key = argument.aws_sshkey, vpcid = argument.aws_vpcid, default_subnet_id = argument.aws_default_subnet)
+    logger.info("Instantiating cloud-abstraction-layer.......[Done]")
+
+    if argument.upload_ssh_key:
+         drv.upload_ssh_key_to_ec2()
+
+    if argument.cleanup is not None:
+        for r_type in argument.cleanup:
+            if r_type == 'all':
+                drv.destroy_resource()
+                break
+            if r_type == 'vms':
+                drv._destroy_vms()
+            if r_type == 'networks':
+                drv._destroy_networks()
+            if r_type == 'mc':
+                drv._destroy_mission_control()
+            if r_type == 'lp':
+                drv._destroy_launchpad()
+
+    if argument.mission_control == True:
+        drv.create_mission_control()
+
+    if argument.launchpad == True:
+        if argument.salt_master is None and argument.mission_control is False:
+            logger.error('Salt Master IP address not provided to start Launchpad.')
+            sys.exit(-2)
+
+        drv.create_launchpad_vm(argument.salt_master)
+
+if __name__ == '__main__':
+    main()
diff --git a/rwcal/test/cal_module_test/CMakeLists.txt b/rwcal/test/cal_module_test/CMakeLists.txt
new file mode 100644
index 0000000..f637c28
--- /dev/null
+++ b/rwcal/test/cal_module_test/CMakeLists.txt
@@ -0,0 +1,41 @@
+# 
+#   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.
+#
+# Author(s): Varun Prasad
+# Creation Date: 21/01/2016
+# 
+
+cmake_minimum_required(VERSION 2.8)
+
+install(
+  PROGRAMS
+    cal_module_test
+  DESTINATION usr/rift/systemtest/cal_module_test
+  COMPONENT ${PKG_LONG_NAME})
+
+install(
+  FILES
+    pytest/conftest.py
+    pytest/cal_module_test.py
+  DESTINATION usr/rift/systemtest/cal_module_test/pytest
+  COMPONENT ${PKG_LONG_NAME})
+
+install(
+  FILES
+    racfg/cal_module_test.racfg
+  DESTINATION
+    usr/rift/systemtest/cal_module_test
+    COMPONENT ${PKG_LONG_NAME})
+
diff --git a/rwcal/test/cal_module_test/cal_module_test b/rwcal/test/cal_module_test/cal_module_test
new file mode 100755
index 0000000..d7f21b6
--- /dev/null
+++ b/rwcal/test/cal_module_test/cal_module_test
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+source $RIFT_INSTALL/usr/rift/systemtest/util/mano/mano_common.sh
+
+SYS_TEST=$RIFT_INSTALL/usr/rift/systemtest/
+PYTEST_DIR=$SYS_TEST/cal_module_test/pytest
+SCRIPT_TEST="py.test -x -v -p no:cacheprovider ${PYTEST_DIR}/cal_module_test.py"
+test_prefix="cal_module_test"
+TEST_NAME="TC_CAL_MODULE_TEST"
+RESULT_XML="cal_module_test.xml"
+
+parse_args "${@}"
+test_cmd="${SCRIPT_TEST}"
+append_args test_cmd os-host "\"${cloud_host}\""
+append_args test_cmd os-user "\"${user}\""
+append_args test_cmd os-tenant ${tenant[0]}
+append_args test_cmd junitprefix "\"${TEST_NAME}\""
+append_args test_cmd junitxml "\"${RIFT_MODULE_TEST}/${RESULT_XML}\""
+
+cd "${PYTEST_DIR}"
+eval ${test_cmd}
+
diff --git a/rwcal/test/cal_module_test/pytest/cal_module_test.py b/rwcal/test/cal_module_test/pytest/cal_module_test.py
new file mode 100644
index 0000000..ca3568f
--- /dev/null
+++ b/rwcal/test/cal_module_test/pytest/cal_module_test.py
@@ -0,0 +1,669 @@
+"""
+# 
+#   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.
+#
+
+@file cal_test.py
+@author Varun Prasad (varun.prasad@riftio.com)
+@date 22-Jan-2016
+
+"""
+
+import abc
+import logging
+import os
+import multiprocessing
+import signal
+import time
+import uuid
+import hashlib
+
+import pytest
+
+from gi import require_version
+require_version('RwCal', '1.0')
+
+from gi.repository import RwcalYang
+from gi.repository.RwTypes import RwStatus
+# import rift.cal.server as cal_server
+import rw_peas
+import rwlogger
+        
+
+logger = logging.getLogger('rwcal')
+logging.basicConfig(level=logging.INFO)
+
+
+class CloudConfig(object):
+    def __init__(self, cal, account):
+        self.cal = cal
+        self.account = account
+
+    def check_state(self, object_id, object_api, expected_state, state_attr_name="state"):
+        """For a given object (Vm, port etc) checks if the object has
+        reached the expected state.
+        """
+        get_object = getattr(self.cal, object_api)
+        for i in range(100):  # 100 poll iterations...
+            rc, rs = get_object(self.account, object_id)
+
+            curr_state = getattr(rs, state_attr_name)
+            if curr_state == expected_state:
+                break
+            else:
+                time.sleep(2)
+
+        rc, rs = get_object(self.account, object_id)
+        assert rc == RwStatus.SUCCESS
+        assert getattr(rs, state_attr_name) == expected_state
+
+    def start_server(self):
+        pass
+
+    def stop_server(self):
+        pass
+
+    @abc.abstractmethod
+    def _cal(self):
+        pass
+
+    @abc.abstractmethod
+    def _account(self, option):
+        pass
+
+    @abc.abstractmethod
+    def flavor(self):
+        pass
+
+    @abc.abstractmethod
+    def vdu(self):
+        pass
+
+    @abc.abstractmethod
+    def image(self):
+        pass
+
+    @abc.abstractmethod
+    def virtual_link(self):
+        pass
+
+
+class Aws(CloudConfig):
+    def __init__(self, option):
+        """
+        Args:
+            option (OptionParser): OptionParser instance.
+        """
+        self.image_id = 'ami-7070231a'
+        self.virtual_link_id = None
+        self.flavor_id = None
+        self.vdu_id = None
+
+        super().__init__(self._cal(), self._account(option))
+
+    def _cal(self):
+        """
+        Loads rw.cal plugin via libpeas
+        """
+        plugin = rw_peas.PeasPlugin('rwcal_aws', 'RwCal-1.0')
+
+        engine, info, extension = plugin()
+
+        # Get the RwLogger context
+        rwloggerctx = rwlogger.RwLog.Ctx.new("Cal-Log")
+
+        cal = plugin.get_interface("Cloud")
+        try:
+            rc = cal.init(rwloggerctx)
+            assert rc == RwStatus.SUCCESS
+        except:
+            logger.error("ERROR:Cal plugin instantiation failed. Aborting tests")
+        else:
+            logger.info("AWS Cal plugin successfully instantiated")
+        return cal
+
+    def _account(self, option):
+        """
+        Args:
+            option (OptionParser): OptionParser instance.
+
+        Return:
+            CloudAccount details
+        """
+        account = RwcalYang.CloudAccount.from_dict({
+                "account_type": "aws",
+                "aws": {
+                    "key": option.aws_user,
+                    "secret": option.aws_password,
+                    "region": option.aws_region,
+                    "availability_zone": option.aws_zone,
+                    "ssh_key": option.aws_ssh_key
+                }
+            })
+
+        return account
+
+    def flavor(self):
+        """
+        Returns:
+            FlavorInfoItem
+        """
+        flavor = RwcalYang.FlavorInfoItem.from_dict({
+                    "name": str(uuid.uuid4()),
+                    "vm_flavor": {
+                        "memory_mb": 1024,
+                        "vcpu_count": 1,
+                        "storage_gb": 0
+                    }
+            })
+
+        return flavor
+
+    def vdu(self):
+        """Provide AWS specific VDU config.
+
+        Returns:
+            VDUInitParams
+        """
+        vdu = RwcalYang.VDUInitParams.from_dict({
+                "name": str(uuid.uuid4()),
+                "node_id": "123456789012345",
+                "image_id": self.image_id,
+                "flavor_id": "t2.micro"
+            })
+
+        c1 = vdu.connection_points.add()
+        c1.name = str(uuid.uuid4())
+        c1.virtual_link_id = self.virtual_link_id
+
+        return vdu
+
+    def image(self):
+        raise NotImplementedError("Image create APIs are not implemented for AWS")
+
+    def virtual_link(self):
+        """Provide Vlink config
+
+        Returns:
+            VirtualLinkReqParams
+        """
+        vlink = RwcalYang.VirtualLinkReqParams.from_dict({
+                    "name": str(uuid.uuid4()),
+                    "subnet": '172.31.64.0/20',
+            })
+
+        return vlink
+
+
+class Cloudsim(CloudConfig):
+    def __init__(self, option):
+        self.image_id = None
+        self.virtual_link_id = None
+        self.flavor_id = None
+        self.vdu_id = None
+
+        self.server_process = None
+
+
+        super().__init__(self._cal(), self._account(option))
+
+    def _md5(fname, blksize=1048576):
+        hash_md5 = hashlib.md5()
+        with open(fname, "rb") as f:
+            for chunk in iter(lambda: f.read(blksize), b""):
+                hash_md5.update(chunk)
+        return hash_md5.hexdigest()
+                                    
+    def start_server(self):
+        logger = logging.getLogger(__name__)
+        server = cal_server.CloudsimServerOperations(logger)
+        self.server_process = multiprocessing.Process(
+                target=server.start_server,
+                args=(True,))
+        self.server_process.start()
+
+        # Sleep till the backup store is set up
+        time.sleep(30)
+
+    def stop_server(self):
+        self.server_process.terminate()
+
+        # If the process is not killed within the timeout, send a SIGKILL.
+        time.sleep(15)
+        if self.server_process.is_alive():
+            os.kill(self.server_process.pid, signal.SIGKILL)
+
+    def _cal(self):
+        """
+        Loads rw.cal plugin via libpeas
+        """
+        plugin = rw_peas.PeasPlugin('rwcal_cloudsimproxy', 'RwCal-1.0')
+        engine, info, extension = plugin()
+
+        # Get the RwLogger context
+        rwloggerctx = rwlogger.RwLog.Ctx.new("Cal-Log")
+
+        cal = plugin.get_interface("Cloud")
+        try:
+            rc = cal.init(rwloggerctx)
+            assert rc == RwStatus.SUCCESS
+        except:
+            logger.error("ERROR:Cal plugin instantiation failed. Aborting tests")
+        else:
+            logger.info("Cloudsim Cal plugin successfully instantiated")
+        return cal
+
+    def _account(self, option):
+        """
+        Args:
+            option (OptionParser): OptionParser instance.
+
+        Return:
+            CloudAccount details
+        """
+        account = RwcalYang.CloudAccount.from_dict({
+                'name': "cloudsim",
+                'account_type':'cloudsim_proxy'})
+
+        return account
+
+    def image(self):
+        """Provides Image config for openstack.
+
+        Returns:
+            ImageInfoItem
+        """
+        image = RwcalYang.ImageInfoItem.from_dict({
+                "name": str(uuid.uuid4()),
+                "location": os.path.join(os.getenv("RIFT_ROOT"), "images/rift-root-latest.qcow2"),
+                "disk_format": "qcow2",
+                "container_format": "bare",
+                "checksum": self._md5(os.path.join(os.getenv("RIFT_ROOT"), "images/rift-root-latest.qcow2")),
+            })
+        return image
+
+    def flavor(self):
+        """Flavor config for openstack
+
+        Returns:
+            FlavorInfoItem
+        """
+        flavor = RwcalYang.FlavorInfoItem.from_dict({
+                "name": str(uuid.uuid4()),
+                "vm_flavor": {
+                        "memory_mb": 16392,
+                        "vcpu_count": 4,
+                        "storage_gb": 40
+                }})
+
+        return flavor
+
+    def vdu(self):
+        """Returns VDU config
+
+        Returns:
+            VDUInitParams
+        """
+        vdu = RwcalYang.VDUInitParams.from_dict({
+                "name": str(uuid.uuid4()),
+                "node_id": "123456789012345",
+                "image_id": self.image_id,
+                "flavor_id": self.flavor_id,
+            })
+
+        c1 = vdu.connection_points.add()
+        c1.name = str(uuid.uuid4())
+        c1.virtual_link_id = self.virtual_link_id
+
+        return vdu
+
+    def virtual_link(self):
+        """vlink config for Openstack
+
+        Returns:
+            VirtualLinkReqParams
+        """
+        vlink = RwcalYang.VirtualLinkReqParams.from_dict({
+                    "name": str(uuid.uuid4()),
+                    "subnet": '192.168.1.0/24',
+            })
+
+        return vlink
+
+
+class Openstack(CloudConfig):
+    def __init__(self, option):
+        """
+        Args:
+            option (OptionParser)
+        """
+        self.image_id = None
+        self.virtual_link_id = None
+        self.flavor_id = None
+        self.vdu_id = None
+
+        super().__init__(self._cal(), self._account(option))
+
+    def _cal(self):
+        """
+        Loads rw.cal plugin via libpeas
+        """
+        plugin = rw_peas.PeasPlugin('rwcal_openstack', 'RwCal-1.0')
+        engine, info, extension = plugin()
+
+        # Get the RwLogger context
+        rwloggerctx = rwlogger.RwLog.Ctx.new("Cal-Log")
+
+        cal = plugin.get_interface("Cloud")
+        try:
+            rc = cal.init(rwloggerctx)
+            assert rc == RwStatus.SUCCESS
+        except:
+            logger.error("ERROR:Cal plugin instantiation failed. Aborting tests")
+        else:
+            logger.info("Openstack Cal plugin successfully instantiated")
+        return cal
+
+    def _account(self, option):
+        """Cloud account information for Account
+
+        Returns:
+            CloudAccount
+        """
+        acct = RwcalYang.CloudAccount.from_dict({
+            "account_type": "openstack",
+            "openstack": {
+                    "key": option.os_user,
+                    "secret": option.os_password,
+                    "auth_url": 'http://{}:5000/v3/'.format(option.os_host),
+                    "tenant": option.os_tenant,
+                    "mgmt_network": option.os_network
+                }
+            })
+
+        return acct
+    
+    def _md5(self, fname, blksize=1048576):
+        hash_md5 = hashlib.md5()
+        with open(fname, "rb") as f:
+            for chunk in iter(lambda: f.read(blksize), b""):
+                hash_md5.update(chunk)
+        return hash_md5.hexdigest()
+
+    def image(self):
+        """Provides Image config for openstack.
+
+        Returns:
+            ImageInfoItem
+        """
+        image = RwcalYang.ImageInfoItem.from_dict({
+                "name": str(uuid.uuid4()),
+                "location": os.path.join(os.getenv("RIFT_ROOT"), "images/rift-root-latest.qcow2"),
+                "disk_format": "qcow2",
+                "container_format": "bare",
+                "checksum": self._md5(os.path.join(os.getenv("RIFT_ROOT"), "images/rift-root-latest.qcow2")),
+            })
+        return image
+
+    def flavor(self):
+        """Flavor config for openstack
+
+        Returns:
+            FlavorInfoItem
+        """
+        flavor = RwcalYang.FlavorInfoItem.from_dict({
+                "name": str(uuid.uuid4()),
+                "vm_flavor": {
+                        "memory_mb": 16392,
+                        "vcpu_count": 4,
+                        "storage_gb": 40
+                },
+                "guest_epa": {
+                        "cpu_pinning_policy": "DEDICATED",
+                        "cpu_thread_pinning_policy": "SEPARATE",
+                }})
+
+        numa_node_count = 2
+        flavor.guest_epa.numa_node_policy.node_cnt = numa_node_count
+        for i in range(numa_node_count):
+            node = flavor.guest_epa.numa_node_policy.node.add()
+            node.id = i
+            if i == 0:
+                node.vcpu = [0, 1]
+            elif i == 1:
+                node.vcpu = [2, 3]
+            node.memory_mb = 8196
+
+        dev = flavor.guest_epa.pcie_device.add()
+        dev.device_id = "PCI_10G_ALIAS"
+        dev.count = 1
+
+        return flavor
+
+    def vdu(self):
+        """Returns VDU config
+
+        Returns:
+            VDUInitParams
+        """
+        vdu = RwcalYang.VDUInitParams.from_dict({
+                "name": str(uuid.uuid4()),
+                "node_id": "123456789012345",
+                "image_id": self.image_id,
+                "flavor_id": self.flavor_id,
+            })
+
+        c1 = vdu.connection_points.add()
+        c1.name = str(uuid.uuid4())
+        c1.virtual_link_id = self.virtual_link_id
+
+        return vdu
+
+    def virtual_link(self):
+        """vlink config for Openstack
+
+        Returns:
+            VirtualLinkReqParams
+        """
+        vlink = RwcalYang.VirtualLinkReqParams.from_dict({
+                    "name": str(uuid.uuid4()),
+                    "subnet": '192.168.1.0/24',
+            })
+
+        return vlink
+
+
+@pytest.fixture(scope="module", params=[Openstack], ids=lambda val: val.__name__)
+def cloud_config(request):
+    return request.param(request.config.option)
+
+
+@pytest.mark.incremental
+class TestCalSetup:
+
+    def test_start_server(self, cloud_config):
+        cloud_config.start_server()
+
+    def test_flavor_apis(self, cloud_config):
+        """
+        Asserts:
+            1. If the new flavor is created and available via read APIs
+            2. Verifies the READ APIs
+        """
+        account = cloud_config.account
+        cal = cloud_config.cal
+
+        status, new_flavor_id = cal.create_flavor(account, cloud_config.flavor())
+        cloud_config.flavor_id = new_flavor_id
+        assert status == RwStatus.SUCCESS
+
+        status, flavors = cal.get_flavor_list(account)
+        assert status == RwStatus.SUCCESS
+
+        ids = []
+        for flavor in flavors.flavorinfo_list:
+            status, flavor_single = cal.get_flavor(account, flavor.id)
+            assert status == RwStatus.SUCCESS
+            assert flavor.id == flavor_single.id
+            ids.append(flavor.id)
+
+        assert new_flavor_id in ids
+
+    def test_image_apis(self, cloud_config):
+        """
+        Asserts:
+            1. If the new image is created and available via read APIs
+            2. Verifies the READ APIs
+        """
+        account = cloud_config.account
+        cal = cloud_config.cal
+
+        if type(cloud_config) is Aws:
+            # Hack!
+            new_image_id = "ami-7070231a"
+        else:
+            status, new_image_id = cal.create_image(account, cloud_config.image())
+            cloud_config.image_id = new_image_id
+            assert status == RwStatus.SUCCESS
+            cloud_config.check_state(new_image_id, "get_image", "active")
+
+
+        status, images = cal.get_image_list(account)
+
+        ids = []
+        for image in images.imageinfo_list:
+            status, image_single = cal.get_image(account, image.id)
+            assert status == RwStatus.SUCCESS
+            assert image_single.id == image.id
+            ids.append(image.id)
+
+        assert new_image_id in ids
+
+    def test_virtual_link_create(self, cloud_config):
+        """
+        Asserts:
+            1. If the new Vlink is created and available via read APIs
+            2. Verifies the READ APIs
+        """
+        account = cloud_config.account
+        cal = cloud_config.cal
+
+        status, new_vlink_id = cal.create_virtual_link(account, cloud_config.virtual_link())
+        cloud_config.virtual_link_id = new_vlink_id
+        assert status.status == RwStatus.SUCCESS
+        cloud_config.check_state(new_vlink_id, "get_virtual_link", "active")
+
+        status, vlinks = cal.get_virtual_link_list(account)
+        assert status == RwStatus.SUCCESS
+
+        ids = []
+        for vlink in vlinks.virtual_link_info_list:
+            status, vlink_single = cal.get_virtual_link(account, vlink.virtual_link_id)
+            assert status == RwStatus.SUCCESS
+            assert vlink_single.virtual_link_id == vlink.virtual_link_id
+            ids.append(vlink.virtual_link_id)
+
+        assert new_vlink_id in ids
+
+    def test_vdu_apis(self, cloud_config):
+        """
+        Asserts:
+            1. If the new VDU is created and available via read APIs
+            2. Verifies the READ APIs
+        """
+        account = cloud_config.account
+        cal = cloud_config.cal
+
+        status, new_vdu_id = cal.create_vdu(account, cloud_config.vdu())
+        cloud_config.vdu_id = new_vdu_id
+        assert status.status == RwStatus.SUCCESS
+        cloud_config.check_state(new_vdu_id, "get_vdu", "active")
+
+        status, vdus = cal.get_vdu_list(account)
+        assert status == RwStatus.SUCCESS
+
+        ids = []
+        for vdu in vdus.vdu_info_list:
+            status, vdu_single = cal.get_vdu(account, vdu.vdu_id)
+            assert status == RwStatus.SUCCESS
+            assert vdu_single.vdu_id == vdu.vdu_id
+            ids.append(vdu.vdu_id)
+
+        assert new_vdu_id in ids
+
+    def test_modify_vdu_api(self, cloud_config):
+        account = cloud_config.account
+        cal = cloud_config.cal
+
+        vdu_modify = RwcalYang.VDUModifyParams()
+        vdu_modify.vdu_id = cloud_config.vdu_id
+        c1 = vdu_modify.connection_points_add.add()
+        c1.name = "c_modify1"
+        # Set the new vlink
+        c1.virtual_link_id = cloud_config.virtual_link_id
+
+        status = cal.modify_vdu(account, vdu_modify)
+        assert status == RwStatus.SUCCESS
+
+@pytest.mark.incremental
+class TestCalTeardown:
+    def test_flavor_delete(self, cloud_config):
+        """
+        Asserts:
+            1. If flavor is deleted
+        """
+        account = cloud_config.account
+        cal = cloud_config.cal
+
+        if type(cloud_config) != Aws:
+            status = cal.delete_flavor(account, cloud_config.flavor_id)
+            assert status == RwStatus.SUCCESS
+
+    def test_image_delete(self, cloud_config):
+        """
+        Asserts:
+            1. If image is deleted
+        """
+        account = cloud_config.account
+        cal = cloud_config.cal
+
+        if type(cloud_config) != Aws:
+            status = cal.delete_image(account, cloud_config.image_id)
+            assert status == RwStatus.SUCCESS
+
+    def test_virtual_link_delete(self, cloud_config):
+        """
+        Asserts:
+            1. If VLink is deleted
+        """
+        account = cloud_config.account
+        cal = cloud_config.cal
+
+        status = cal.delete_virtual_link(account, cloud_config.virtual_link_id)
+        assert status == RwStatus.SUCCESS
+
+    def test_delete_vdu(self, cloud_config):
+        """
+        Asserts:
+            1. If VDU is deleted
+        """
+        account = cloud_config.account
+        cal = cloud_config.cal
+
+        status = cal.delete_vdu(account, cloud_config.vdu_id)
+        assert status == RwStatus.SUCCESS
+
+    def test_stop_server(self, cloud_config):
+        cloud_config.stop_server()
diff --git a/rwcal/test/cal_module_test/pytest/conftest.py b/rwcal/test/cal_module_test/pytest/conftest.py
new file mode 100644
index 0000000..c4b6705
--- /dev/null
+++ b/rwcal/test/cal_module_test/pytest/conftest.py
@@ -0,0 +1,37 @@
+"""
+# 
+#   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.
+#
+
+@file conftest.py
+@author Varun Prasad (varun.prasad@riftio.com)
+@date 21/01/2016
+
+"""
+
+def pytest_addoption(parser):
+    # Openstack related options
+    parser.addoption("--os-host", action="store", default="10.66.4.102")
+    parser.addoption("--os-user", action="store", default="pluto")
+    parser.addoption("--os-password", action="store", default="mypasswd")
+    parser.addoption("--os-tenant", action="store", default="demo")
+    parser.addoption("--os-network", action="store", default="private")
+
+    # aws related options
+    parser.addoption("--aws-user", action="store", default="AKIAIKRDX7BDLFU37PDA")
+    parser.addoption("--aws-password", action="store", default="cjCRtJxVylVkbYvOUQeyvCuOWAHieU6gqcQw29Hw")
+    parser.addoption("--aws-region", action="store", default="us-east-1")
+    parser.addoption("--aws-zone", action="store", default="us-east-1c")
+    parser.addoption("--aws-ssh-key", action="store", default="vprasad-sshkey")
diff --git a/rwcal/test/cal_module_test/racfg/cal_module_test.racfg b/rwcal/test/cal_module_test/racfg/cal_module_test.racfg
new file mode 100644
index 0000000..cd6d57a
--- /dev/null
+++ b/rwcal/test/cal_module_test/racfg/cal_module_test.racfg
@@ -0,0 +1,19 @@
+{
+  "test_name":"TC_CAL_MODULE_TESTS",
+  "commandline":"./cal_module_test --cloud-type 'openstack' --cloud-host={cloud_host} --user={user} {tenants}",
+  "target_vm":"VM",
+  "test_description":"System test targeting module tests for CAL accounts",
+  "run_as_root": true,
+  "status":"working",
+  "keywords":["nightly","smoke","MANO","openstack"],
+  "timelimit": 2400,
+  "networks":[],
+  "vms":[
+    {
+      "name": "VM",
+      "memory": 8192,
+      "cpus": 4
+    }
+  ]
+}
+
diff --git a/rwcal/test/cloudtool_cal.py b/rwcal/test/cloudtool_cal.py
new file mode 100755
index 0000000..92f4891
--- /dev/null
+++ b/rwcal/test/cloudtool_cal.py
@@ -0,0 +1,989 @@
+# 
+#   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 os,sys,platform
+import socket
+import time
+import re
+import logging
+
+from pprint import pprint
+import argparse
+
+from gi.repository import RwcalYang
+from gi.repository.RwTypes import RwStatus
+import rw_peas
+import rwlogger
+import time
+
+global nova
+nova = None
+
+def wait_till_active(driver, account, vm_id_list, timeout):                                                                                                              
+    """
+    Wait until VM reaches ACTIVE state. 
+    """
+    # Wait while VM goes to required state
+
+    start = time.time()
+    end = time.time() + timeout
+    done = False;
+
+    while ( time.time() < end ) and not done:
+       done = True      
+       for vm_id in vm_id_list:
+           rc, rs = driver.get_vm(account, vm_id)
+           assert rc == RwStatus.SUCCESS
+           if rs.state != 'ACTIVE':
+               done = False		   
+               time.sleep(2)
+
+
+def get_image_name(node):
+    images = driver.list_images()
+    for i in images:
+        if i.id == node.extra['imageId']:
+            return i.name
+    return None
+
+def get_flavor_name(flavorid):
+    global nova
+    if nova is None:
+        nova = ra_nova_connect(project='admin')
+    for f in nova.flavors.list(True):
+         if f.id == flavorid: 
+             return f.name
+    return None
+
+def hostname():
+    return socket.gethostname().split('.')[0]
+
+def vm_register(id, driver, account, cmdargs, header=True):
+    if testbed is None:
+        print("Cannot register VM without reservation system")
+        return False
+
+    if cmdargs.reserve_new_vms:
+        user=os.environ['USER']
+    else:
+        user=None
+    fmt="%-28s %-12s %-12s %-15s"
+    if header:
+        print('VM                           controller   compute      mgmt ip')
+        print('---------------------------- ------------ ------------ ---------------')
+    rc, nodes = driver.get_vm_list(account)
+    assert rc == RwStatus.SUCCESS
+    for node in nodes.vminfo_list:
+        if id == 'all' or node.vm_id == id:
+            flavor = driver.get_flavor(account, node.flavor_id)
+            assert rc == RwStatus.SUCCESS
+            ip = node.management_ip
+            
+            huge = 'DISABLED'	    
+            if flavor.guest_epa.mempage_size == 'LARGE':
+                huge = flavor.guest_epa.mempage_size							    	    
+            #compute = utils.find_resource(nova.servers, node.id)
+            #compute_name = compute._info['OS-EXT-SRV-ATTR:hypervisor_hostname'].split('.')[0]
+            compute_name = hostname()	    
+            try:
+                testbed.add_resource(node.vm_name, hostname(), ip, flavor.vm_flavor.memory_mb, flavor.vm_flavor.vcpu_count, user, flavor.name, compute=compute_name, huge_pages=huge )
+                print(fmt % ( node.vm_name, hostname(), compute_name, ip )) 
+            except Exception as e:
+                print("WARNING: Error \"%s\"adding resource to reservation system" % e)
+
+class OFromDict(object):
+  def __init__(self, d):
+    self.__dict__ = d
+
+
+def vm_create_subcommand(driver, account, cmdargs):
+    """Process the VM create subcommand."""
+    if cmdargs.name and cmdargs.count != 1:
+        sys.exit("Error: when VM name is specified, the count must be 1")
+
+    rc, sizes = driver.get_flavor_list(account)
+    assert rc == RwStatus.SUCCESS
+
+    try:
+        size = [s for s in sizes.flavorinfo_list if s.name == cmdargs.flavor][0]
+    except IndexError:
+        sys.exit("Error: Failed to create VM, couldn't find flavor %s" % \
+                 cmdargs.flavor)
+    print(size)
+    rc, images = driver.get_image_list(account)
+    assert rc == RwStatus.SUCCESS
+    if images is None:
+    	sys.exit("Error: No images found")
+    try:
+        image = [i for i in images.imageinfo_list if cmdargs.image in i.name][0]
+    except IndexError:
+        sys.exit("Error: Failed to create VM, couldn't find image %s" % \
+                 cmdargs.image)
+    print(image)
+
+    # VM name is not specified, so determine a unique VM name
+    # VM name should have the following format:
+    #     rwopenstack_<host>_vm<id>, e.g., rwopenstack_grunt16_vm1
+    # The following code gets the list of existing VMs and determines
+    # a unique id for the VM name construction.
+    rc, nodes = driver.get_vm_list(account)
+    assert rc == RwStatus.SUCCESS
+    prefix = 'rwopenstack_%s_vm' % hostname()
+    vmid = 0;
+    for n in nodes.vminfo_list:
+        if n.vm_name.startswith(prefix):
+            temp_str = n.vm_name[len(prefix):]
+            if temp_str == '':
+                temp = 1
+            else:
+                temp = int(n.vm_name[len(prefix):])
+
+            if (temp > vmid):
+                vmid = temp
+
+    nodelist = []
+    for i in range(0, cmdargs.count):
+            if cmdargs.name:
+                vm_name = cmdargs.name
+            else:
+                vm_name = '%s%d' % (prefix, vmid+i+1)
+ 
+            rc, netlist = driver.get_network_list(account)
+            assert rc == RwStatus.SUCCESS	
+            for network in netlist.networkinfo_list:
+                 print(network)    
+
+            vm = RwcalYang.VMInfoItem()
+            vm.vm_name = vm_name
+            vm.flavor_id = size.id
+            vm.image_id  = image.id
+            vm.cloud_init.userdata = ''
+
+            nets = dict()
+            for network in netlist.networkinfo_list:
+                if network.network_name != "public":
+                    nwitem = RwcalYang.VMInfoItem_NetworkList()			
+                    nwitem.network_id = network.network_id		    
+                    nets[network.network_name] = nwitem
+                     
+            logger.debug('creating VM using nets %s' % cmdargs.networks )
+            for net in cmdargs.networks.split(','):
+                if not net in nets:
+                    print(("Invalid network name '%s'" % net))
+                    print(('available nets are %s' % ','.join(list(nets.keys())) ))
+                    sys.exit(1)
+                if net != cmdargs.mgmt_network:
+                    vm.network_list.append(nets[net])
+
+            print(vm.network_list)
+            rc, node_id = driver.create_vm(account, vm) 
+
+            # wait for 1 to be up before starting the rest
+            # this is an attempt to make sure the image is cached
+            nodelist.append(node_id)
+            if i == 0 or cmdargs.wait_after_create is True:
+                #wait_until_running([node], timeout=300)
+                wait_till_active(driver, account, nodelist, timeout=300)		
+            print(node_id)
+    if cmdargs.reservation_server_url is not None:
+            if not cmdargs.wait_after_create:
+                print("Waiting for VMs to start")
+                wait_till_active(driver, account, nodelist, timeout=300)		
+                print("VMs are up")
+            header=True
+            for node in nodelist:
+                vm_register(node, driver, account, cmdargs, header)
+                header=False
+                
+
+def vm_destroy_subcommand(driver, account, cmdargs):
+    rc, nodes = driver.get_vm_list(account)
+    assert rc == RwStatus.SUCCESS	
+    ct = len(nodes.vminfo_list)
+    if cmdargs.destroy_all or cmdargs.wait:
+        rc=0
+        for n in nodes.vminfo_list:
+            if testbed is not None:
+                try:
+                    testbed.remove_resource(n.vm_name)
+                except:
+                    print("WARNING: error deleting resource from reservation system")
+            if RwStatus.SUCCESS != driver.delete_vm(account, n.vm_id):
+                print('Error: failed to destroy node %s' % n.vm_name)
+                rc=1
+        if rc:
+            sys.exit(1)
+        if cmdargs.wait:
+            while ct > 0:
+                sys.stderr.write("waiting for %d VMs to exit...\n" % ct)
+                time.sleep(1)
+                try:
+                    rc, nodesnw = driver.get_vm_list(account)
+                    assert rc == RwStatus.SUCCESS	
+                    ct = len(nodesnw.vminfo_list )
+                except:
+                    pass
+        
+    else:
+        vm_re = re.compile('^%s$' % cmdargs.vm_name)
+        ct = 0
+        for n in nodes.vminfo_list:
+            if vm_re.match(n.vm_name):
+                ct += 1
+                if testbed is not None:
+                    try:
+                        testbed.remove_resource(n.vm_name)
+                    except:
+                        print("WARNING: error deleting resource from reservation system")
+                if RwStatus.SUCCESS != driver.delete_vm(account, n.vm_id):
+                    print('Error: failed to destroy node %s' % n.vm_name)
+                    return
+                print('destroyed %s' % n.vm_name)
+        if ct == 0:
+            print("No VMs matching \"%s\" found" % ( cmdargs.vm_name ))
+        
+                    
+def vm_rebuild_subcommand(driver, account, cmdargs):
+    images = driver.list_images()
+    found=0
+    for i in images:
+        if i.name == cmdargs.image_name:
+            found=1
+            break
+    if found != 1:
+        print('Error: Rebuild failed - image %s not found' % cmdargs.image_name)
+        sys.exit(1)
+    image=i
+    nodes = driver.list_nodes()
+    if cmdargs.rebuild_all:
+        rc=0
+        for n in nodes:
+            if not driver.ex_rebuild(n,image):
+                print('Error: failed to rebuild node %s' % n.name)
+                rc=1
+            if rc:
+               sys.exit(1)
+            rebuilt=0
+            while rebuilt != 1:
+                time.sleep(10)
+                nw_nodes = driver.list_nodes()
+                for nw in nw_nodes:
+                    if nw.name == n.name:
+                        if nw.state == n.state:
+                            rebuilt=1
+                        break  
+    else:
+        vm_re = re.compile('^%s$' % cmdargs.vm_name)
+        ct = 0
+        for n in nodes:
+            if vm_re.match(n.name):
+                ct += 1
+                if not driver.ex_rebuild(n,image):
+                    print('Error: failed to rebuild node %s' % n.name)
+                    return
+                print('Rebuilt %s' % n.name)
+                rebuilt=0
+                while rebuilt != 1:
+                    time.sleep(10)
+                    nw_nodes = driver.list_nodes()
+                    for nw in nw_nodes:
+                        if nw.name == n.name:
+                            if nw.state == n.state:
+                                rebuilt=1
+                            break  
+        if ct == 0:
+            print("No VMs matching \"%s\" found" % ( cmdargs.vm_name ))
+        
+                    
+
+def vm_reboot_subcommand(driver, account, cmdargs):
+    rc, nodes = driver.get_vm_list(account)
+    assert rc == RwStatus.SUCCESS	
+    if cmdargs.reboot_all:
+        for n in nodes.vminfo_list:
+            '''
+            if not n.reboot():
+                print 'Error: failed to reboot node %s' % n.name
+            else:
+                print "rebooted %s" % n.name
+            '''
+            time.sleep(cmdargs.sleep_time)
+    else:
+        for n in nodes.vminfo_list:
+            if n.vm_name == cmdargs.vm_name:
+                if RwStatus.SUCCESS !=  driver.reboot_vm(account,n.vm_id):
+                    print('Error: failed to reboot node %s' % n.vm_name)
+                else:
+                    print("rebooted %s" % n.vm_name)
+                    
+
+def vm_start_subcommand(driver, account, cmdargs):
+    rc, nodes = driver.get_vm_list(account)
+    assert rc == RwStatus.SUCCESS	
+    if cmdargs.start_all:
+        for n in nodes.vminfo_list:
+            print(dir(n))
+            if RwStatus.SUCCESS != driver.start_vm(account, n.vm_id):
+                print('Error: failed to start node %s' % n.vm_name)
+            else:
+                print("started %s" % n.vm_name)
+    else:
+        for n in nodes.vminfo_list:
+            if n.vm_name == cmdargs.vm_name:
+                if RwStatus.SUCCESS != driver.start_vm(account, n.vm_id):
+                    print('Error: failed to start node %s' % n.vm_name)
+                else:
+                    print("started %s" % n.vm_name)
+                    
+def vm_subcommand(driver, account, cmdargs):
+    """Process the vm subcommand"""
+
+    if cmdargs.which == 'list':
+        rc, nodes = driver.get_vm_list(account)
+        assert rc == RwStatus.SUCCESS	
+        for n in nodes.vminfo_list:
+            print(n)		
+            if n.state == 4:
+                if not cmdargs.ipsonly:
+                    print("%s is shutoff" % n.vm_name)
+            elif cmdargs.ipsonly:
+                i = n.management_ip
+                if i is not None:
+                    print(i)
+            else: 
+                if n.management_ip is not None:
+                    if len(n.private_ip_list) > 0:
+                        print("%s %s,%s" % (n.vm_name, n.management_ip, ",".join([i.get_ip_address() for i in n.private_ip_list])))
+                    else:
+                        print("%s %s" % (n.vm_name, n.management_ip))
+                else:
+                    print("%s NO IP" % n.vm_name)
+
+    elif cmdargs.which == 'create':
+        vm_create_subcommand(driver, account, cmdargs)
+
+    elif cmdargs.which == 'reboot':
+        vm_reboot_subcommand(driver, account, cmdargs)
+    elif cmdargs.which == 'start':
+        vm_start_subcommand(driver, account, cmdargs)
+    elif cmdargs.which == 'destroy':
+        vm_destroy_subcommand(driver, account, cmdargs)
+    #elif cmdargs.which == 'rebuild':
+    #    vm_rebuild_subcommand(driver, account, cmdargs)
+
+def image_delete_subcommand(driver, account, cmdargs):
+    rc,images = driver.get_image_list(account)
+    assert rc == RwStatus.SUCCESS
+    account.openstack.key          = 'admin'
+    if cmdargs.delete_all:
+        for i in images.imageinfo_list:
+            if RwStatus.SUCCESS != driver.delete_image(account, i.id):
+                print('Error: failed to delete image %s' % i.name)
+    else:
+        for i in images.imageinfo_list:
+            if i.name == cmdargs.image_name:
+                if RwStatus.SUCCESS != driver.delete_image(account, i.id):
+                    print('Error: failed to delete image %s' % i.name)
+
+def image_subcommand(driver, account, cmdargs):
+    """Process the image subcommand"""
+    if cmdargs.which == 'list':
+        rc, images = driver.get_image_list(account)
+        assert rc == RwStatus.SUCCESS
+
+        for i in images.imageinfo_list:
+            print(i)
+
+    elif cmdargs.which == 'delete':
+        image_delete_subcommand(driver, account, cmdargs)
+
+    elif cmdargs.which == 'create':
+        account.openstack.key          = 'admin'
+        rc, images = driver.get_image_list(account)
+        assert rc == RwStatus.SUCCESS
+        for i in images.imageinfo_list:
+            if i.name == cmdargs.image_name:
+                print("FATAL: image \"%s\" already exists" % cmdargs.image_name)
+                return 1
+        
+        print("creating image \"%s\" using %s ..." % \
+              (cmdargs.image_name, cmdargs.file_name))
+        img = RwcalYang.ImageInfoItem()
+        img.name = cmdargs.image_name
+        img.location = cmdargs.file_name
+        img.disk_format = "qcow2"
+        img.container_format = "bare"
+        rc, img_id = driver.create_image(account, img)	
+        print("... done. image_id is %s" % img_id)
+        return img_id
+
+    elif cmdargs.which == 'getid':
+        rc, images = driver.get_image_list(account)
+        assert rc == RwStatus.SUCCESS
+        found=0
+        for i in images.imageinfo_list:
+            if i.name == cmdargs.image_name:
+                print(i.id)
+                found += 1
+        if found != 1:
+            sys.exit(1)
+        
+def flavor_subcommand(driver, account, cmdargs):
+    """Process the flavor subcommand"""
+    if cmdargs.which == 'list':
+        rc, sizes = driver.get_flavor_list(account)
+        assert rc == RwStatus.SUCCESS
+        for f in sizes.flavorinfo_list:
+            rc, flv = driver.get_flavor(account, f.id)	    
+            print(flv)	    
+    elif cmdargs.which == 'create':
+        account.openstack.key          = 'admin'    
+        flavor                                     = RwcalYang.FlavorInfoItem()
+        flavor.name                                = cmdargs.flavor_name
+        flavor.vm_flavor.memory_mb                 = cmdargs.memory_size
+        flavor.vm_flavor.vcpu_count                = cmdargs.vcpu_count
+        flavor.vm_flavor.storage_gb                = cmdargs.disc_size
+        if cmdargs.hugepages_kilo:
+            flavor.guest_epa.mempage_size              = cmdargs.hugepages_kilo
+        if cmdargs.numa_nodes:
+            flavor.guest_epa.numa_node_policy.node_cnt = cmdargs.numa_nodes
+        if cmdargs.dedicated_cpu:
+            flavor.guest_epa.cpu_pinning_policy        = 'DEDICATED'
+        if cmdargs.pci_count:
+            dev = flavor.guest_epa.pcie_device.add()
+            dev.device_id = 'PCI_%dG_ALIAS' % (cmdargs.pci_speed)
+            dev.count = cmdargs.pci_count 
+        if cmdargs.colleto:
+            dev = flavor.guest_epa.pcie_device.add()
+            dev.device_id = 'COLETO_VF_ALIAS'
+            dev.count = cmdargs.colleto 
+        if cmdargs.trusted_host:
+            flavor.guest_epa.trusted_execution = True 
+
+        rc, flavor_id = driver.create_flavor(account, flavor)
+        assert rc == RwStatus.SUCCESS
+
+        print("created flavor %s id %s" % (cmdargs.flavor_name, flavor_id)) 
+
+    elif cmdargs.which == 'delete':
+        account.openstack.key          = 'admin'    
+        rc, sizes = driver.get_flavor_list(account)
+        assert rc == RwStatus.SUCCESS
+        for f in sizes.flavorinfo_list:
+            if f.name == cmdargs.flavor_name:
+                rc = driver.delete_flavor(account, f.id)
+                assert rc == RwStatus.SUCCESS
+
+def hostagg_subcommand(driver, account, cmdargs):
+    """Process the hostagg subcommand"""
+    if cmdargs.which == 'list':
+        nova = ra_nova_connect(project='admin')
+        for f in nova.aggregates.list():
+            print("%-12s %-12s" % \
+                  (f.name, f.availability_zone))
+                
+    elif cmdargs.which == 'create':
+        nova = ra_nova_connect(project='admin')
+        hostagg = nova.aggregates.create(cmdargs.hostagg_name, 
+                                     cmdargs.avail_zone)
+        print("created hostagg %s in %s" % (hostagg.name, hostagg.availability_zone)) 
+
+    elif cmdargs.which == 'delete':
+        nova = ra_nova_connect(project='admin')
+        for f in nova.aggregates.list():
+            if f.name == cmdargs.hostagg_name:
+                if cmdargs.force_delete_hosts:
+                    for h in f.hosts:
+                        f.remove_host(h)
+
+                f.delete()
+
+    elif cmdargs.which == 'addhost':
+        nova = ra_nova_connect(project='admin')
+        for f in nova.aggregates.list():
+            if f.name == cmdargs.hostagg_name:
+                f.add_host(cmdargs.host_name)
+
+    elif cmdargs.which == 'delhost':
+        nova = ra_nova_connect(project='admin')
+        for f in nova.aggregates.list():
+            if f.name == cmdargs.hostagg_name:
+                f.remove_host(cmdargs.host_name)
+
+    elif cmdargs.which == 'setmetadata':
+        nova = ra_nova_connect(project='admin')
+        for f in nova.aggregates.list():
+            if f.name == cmdargs.hostagg_name:
+                d = dict([cmdargs.extra_specs.split("="),])		    
+                f.set_metadata(d)
+
+def quota_subcommand(driver, account, cmdargs):
+    """Process the quota subcommand"""
+    nova = ra_nova_connect(project='admin')
+    cfgfile = get_openstack_file(None,  cmdargs.project)
+    kwargs = load_params(cfgfile)
+
+    keystone = keystone_client.Client(username=kwargs.get('OS_USERNAME'),
+                               password=kwargs.get('OS_PASSWORD'),
+                               tenant_name=kwargs.get('OS_TENANT_NAME'),
+                               auth_url=kwargs.get('OS_AUTH_URL'))
+    if cmdargs.which == 'set':
+        nova.quotas.update(keystone.tenant_id, 
+                           ram=cmdargs.memory, 
+                           floating_ips=cmdargs.ips, 
+                           instances=cmdargs.vms, 
+                           cores=cmdargs.vcpus)
+    elif cmdargs.which == 'get':
+        print("get quotas for tenant %s %s" % \
+              (cmdargs.project, keystone.tenant_id))
+        q = nova.quotas.get(keystone.tenant_id)
+        for att in [ 'ram', 'floating_ips', 'instances', 'cores' ]: 
+            print("%12s: %6d" % ( att, getattr(q, att) ))
+        
+def rules_subcommand(driver, account, cmdargs):
+    nova = ra_nova_connect(project='demo')
+    group=nova.security_groups.find(name='default')
+    if cmdargs.which == 'set':
+        try:
+            nova.security_group_rules.create(group.id,ip_protocol='tcp', from_port=1, to_port=65535 )
+        except BadRequest:
+            pass
+        try: 
+            nova.security_group_rules.create(group.id, ip_protocol='icmp',from_port=-1, to_port=-1 )
+        except BadRequest:
+            pass
+            
+    elif cmdargs.which == 'list':
+        for r in group.rules:
+            if r['from_port'] == -1:
+                print("rule %d proto %s from IP %s" % ( r['id'], r['ip_protocol'], r['ip_range']['cidr'] ))
+            else:
+                print("rule %d proto %s from port %d to %d from IP %s" % ( r['id'], r['ip_protocol'], r['from_port'], r['to_port'], r['ip_range']['cidr'] ))
+
+
+def register_subcommand(driver, account, cmdargs):
+    cmdargs.reserve_new_vms = False
+    vm_register('all', driver, account, cmdargs)       
+           
+##
+# Command line argument specification
+##
+desc="""This tool is used to manage the VMs"""
+kilo=platform.dist()[1]=='21'
+parser = argparse.ArgumentParser(description=desc)
+subparsers = parser.add_subparsers()
+ipaddr = socket.gethostbyname(socket.getfqdn())
+reservation_server_url = os.environ.get('RESERVATION_SERVER', 'http://reservation.eng.riftio.com:80')
+# ipaddr = netifaces.ifaddresses('br0')[netifaces.AF_INET][0]['addr']
+#default_auth_url = 'http://%s:5000/v3/' % ipaddr
+default_auth_url = 'http://10.66.4.27:5000/v3/'
+
+parser.add_argument('-t', '--provider-type', dest='provider_type',
+                    type=str, default='OPENSTACK', 
+                    help='Cloud provider type (default: %(default)s)')
+parser.add_argument('-u', '--user-name', dest='user', 
+                    type=str, default='demo', 
+                    help='User name (default: %(default)s)')
+parser.add_argument('-p', '--password', dest='passwd', 
+                    type=str, default='mypasswd', 
+                    help='Password (default: %(default)s)')
+parser.add_argument('-m', '--mgmt-nw', dest='mgmt_network', 
+                    type=str, default='private', 
+                    help='mgmt-network (default: %(default)s)')
+parser.add_argument('-a', '--auth-url', dest='auth_url', 
+                    type=str, default=default_auth_url, 
+                    help='Password (default: %(default)s)')
+parser.add_argument('-r', '--reservation_server_url', dest='reservation_server_url', 
+                    type=str, default=reservation_server_url, 
+                    help='reservation server url, use None to disable (default %(default)s)' )
+parser.add_argument('-d', '--debug', dest='debug', action='store_true', help='raise the logging level')
+
+##
+# Subparser for VM
+##
+vm_parser = subparsers.add_parser('vm')
+vm_subparsers = vm_parser.add_subparsers()
+
+# Create VM subparser
+vm_create_parser = vm_subparsers.add_parser('create')
+vm_create_parser.add_argument('-c', '--count',
+                              type=int, default=1,
+                              help='The number of VMs to launch '
+                                   '(default: %(default)d)')
+vm_create_parser.add_argument('-i', '--image', 
+							  default='rwopenstack_vm',
+                              help='Specify the image for the VM  (default: %(default)s)')
+vm_create_parser.add_argument('-n', '--name',
+                              help='Specify the name of the VM')
+vm_create_parser.add_argument('-f', '--flavor',
+                              help='Specify the flavor for the VM')
+vm_create_parser.add_argument('-R', '--reserve', dest='reserve_new_vms', 
+                    action='store_true', help='reserve any newly created VMs')
+vm_create_parser.add_argument('-s', '--single', dest='wait_after_create', 
+                    action='store_true', help='wait for each VM to start before creating the next')
+vm_create_parser.add_argument('-N', '--networks', dest='networks', type=str, 
+                                default='private,private2,private3,private4',
+                                help='comma separated list of networks to connect these VMs to (default: %(default)s)' )
+
+vm_create_parser.set_defaults(which='create')
+# Reboot VM subparser
+vm_reboot_parser = vm_subparsers.add_parser('reboot')
+group = vm_reboot_parser.add_mutually_exclusive_group()
+group.add_argument('-n', '--vm-name', dest='vm_name',
+                   type=str,
+                   help='Specify the name of the VM')
+group.add_argument('-a', '--reboot-all', 
+                   dest='reboot_all', action='store_true',
+                   help='Reboot all VMs')
+vm_reboot_parser.add_argument('-s', '--sleep', dest='sleep_time', type=int, default=4, help='time in seconds to sleep between reboots')
+vm_reboot_parser.set_defaults(which='reboot')
+
+
+"""
+# start VM subparser
+vm_start_parser = vm_subparsers.add_parser('start')
+group = vm_start_parser.add_mutually_exclusive_group()
+group.add_argument('-n', '--vm-name', dest='vm_name',
+                   type=str,
+                   help='Specify the name of the VM')
+group.add_argument('-a', '--start-all', 
+                   dest='start_all', action='store_true',
+                   help='Start all VMs')
+vm_start_parser.set_defaults(which='start')
+"""
+
+# Destroy VM subparser
+vm_destroy_parser = vm_subparsers.add_parser('destroy')
+group = vm_destroy_parser.add_mutually_exclusive_group()
+group.add_argument('-n', '--vm-name', dest='vm_name',
+                   type=str,
+                   help='Specify the name of the VM (accepts regular expressions)')
+group.add_argument('-a', '--destroy-all', 
+                   dest='destroy_all', action='store_true',
+                   help='Delete all VMs')
+group.add_argument('-w', '--wait', 
+                   dest='wait', action='store_true',
+                   help='destroy all and wait until all VMs have exited')
+vm_destroy_parser.set_defaults(which='destroy')
+
+# Rebuild VM subparser
+vm_rebuild_parser = vm_subparsers.add_parser('rebuild')
+group = vm_rebuild_parser.add_mutually_exclusive_group()
+group.add_argument('-n', '--vm-name', dest='vm_name',
+                   type=str,
+                   help='Specify the name of the VM (accepts regular expressions)')
+group.add_argument('-a', '--rebuild-all', 
+                   dest='rebuild_all', action='store_true',
+                   help='Rebuild all VMs')
+vm_rebuild_parser.add_argument('-i', '--image-name', dest='image_name',
+                              type=str,
+                              help='Specify the name of the image')
+vm_rebuild_parser.set_defaults(which='rebuild')
+
+# List VM subparser
+vm_list_parser = vm_subparsers.add_parser('list')
+vm_list_parser.set_defaults(which='list')
+vm_list_parser.add_argument('-i', '--ips_only', dest='ipsonly', 
+                            action='store_true', 
+                            help='only list IP addresses')
+
+vm_parser.set_defaults(func=vm_subcommand)
+
+##
+# Subparser for image
+##
+image_parser = subparsers.add_parser('image')
+image_subparsers = image_parser.add_subparsers()
+
+# List image subparser
+image_list_parser = image_subparsers.add_parser('list')
+image_list_parser.set_defaults(which='list')
+
+# Delete image subparser
+image_destroy_parser = image_subparsers.add_parser('delete')
+group = image_destroy_parser.add_mutually_exclusive_group()
+group.add_argument('-n', '--image-name', dest='image_name',
+                   type=str,
+                   help='Specify the name of the image')
+group.add_argument('-a', '--delete-all', 
+                   dest='delete_all', action='store_true',
+                   help='Delete all images')
+image_destroy_parser.set_defaults(which='delete')
+
+# create image
+image_create_parser = image_subparsers.add_parser('create')
+image_create_parser.set_defaults(which='create')
+image_create_parser.add_argument('-n', '--image-name', dest='image_name',
+                                  type=str,
+                                  default="rwopenstack_vm",
+                                  help='Specify the name of the image')
+image_create_parser.add_argument('-f', '--filename', dest='file_name',
+                                  type=str, 
+                                  default='/net/sharedfiles/home1/common/vm/rift-root-current.qcow2',
+                                  help='name of the existing qcow2 image file')
+
+
+image_create_parser = image_subparsers.add_parser('getid')
+image_create_parser.set_defaults(which='getid')
+image_create_parser.add_argument('-n', '--image-name', dest='image_name',
+                                  type=str,
+                                  default="rwopenstack_vm",
+                                  help='Specify the name of the image')
+image_parser.set_defaults(func=image_subcommand)
+
+##
+# Subparser for flavor
+##
+flavor_parser = subparsers.add_parser('flavor')
+flavor_subparsers = flavor_parser.add_subparsers()
+
+# List flavor subparser
+flavor_list_parser = flavor_subparsers.add_parser('list')
+flavor_list_parser.set_defaults(which='list')
+
+# Create flavor subparser
+flavor_create_parser = flavor_subparsers.add_parser('create')
+flavor_create_parser.set_defaults(which='create')
+flavor_create_parser.add_argument('-n', '--flavor-name', dest='flavor_name',
+                                  type=str,
+                                  help='Specify the name of the flavor')
+flavor_create_parser.add_argument('-m', '--memory-size', dest='memory_size',
+                                  type=int, default=1024,
+                                  help='Specify the size of the memory in MB '
+                                       '(default: %(default)d)')
+flavor_create_parser.add_argument('-d', '--disc-size', dest='disc_size',
+                                  type=int, default=16,
+                                  help='Specify the size of the disc in GB '
+                                       '(default: %(default)d)')
+flavor_create_parser.add_argument('-v', '--vcpu-count', dest='vcpu_count',
+                                  type=int, default=1,
+                                  help='Specify the number of VCPUs '
+                                       '(default: %(default)d)')
+flavor_create_parser.add_argument('-p', '--pci-count', dest='pci_count',
+                                  type=int, default=0,
+                                  help='Specify the number of PCI devices '
+                                       '(default: %(default)d)')
+flavor_create_parser.add_argument('-s', '--pci-speed', dest='pci_speed',
+                                  type=int, default=10,
+                                  help='Specify the speed of the PCI devices in Gbps (default: %(default)d)')
+flavor_create_parser.add_argument('-e', '--hostagg-extra-specs', dest='extra_specs',
+                                  type=str, 
+                                  help='Specify the extra spec ')
+flavor_create_parser.add_argument('-b', '--back-with-hugepages', dest='enable_hugepages',
+                                  action='store_true',
+                                  help='Enable memory backing with hugepages')
+flavor_create_parser.add_argument('-B', '--back-with-hugepages-kilo', dest='hugepages_kilo',
+                                  type=str,
+                                  help='Enable memory backing with hugepages for kilo')
+flavor_create_parser.add_argument('-D', '--dedicated_cpu', dest='dedicated_cpu',
+                                  action='store_true',
+                                  help='Dedicated CPU usage')
+flavor_create_parser.add_argument('-T', '--cpu_threads', dest='cpu_threads',
+                                  type=str, 
+                                  help='CPU threads usage')
+flavor_create_parser.add_argument('-N', '--numa_nodes', dest='numa_nodes',
+                                  type=int, 
+                                  help='Configure numa nodes')
+flavor_create_parser.add_argument('-t', '--trusted-host', dest='trusted_host',  action='store_true', help='restrict instances to trusted hosts')
+flavor_create_parser.add_argument('-c', '--crypto-cards', dest='colleto',  type=int, default=0,  \
+                                    help='how many colleto creek VFs should be passed thru to the VM')
+
+# Delete flavor subparser
+flavor_delete_parser = flavor_subparsers.add_parser('delete')
+flavor_delete_parser.set_defaults(which='delete')
+flavor_delete_parser.add_argument('-n', '--flavor-name', dest='flavor_name',
+                                  type=str,
+                                  help='Specify the name of the flavor')
+
+flavor_parser.set_defaults(func=flavor_subcommand)
+
+##
+# Subparser for host-aggregate 
+##
+hostagg_parser = subparsers.add_parser('hostagg')
+hostagg_subparsers = hostagg_parser.add_subparsers()
+
+# List host-aggregate subparser
+hostagg_list_parser = hostagg_subparsers.add_parser('list')
+hostagg_list_parser.set_defaults(which='list')
+
+# Create hostagg subparser
+hostagg_create_parser = hostagg_subparsers.add_parser('create')
+hostagg_create_parser.set_defaults(which='create')
+hostagg_create_parser.add_argument('-n', '--hostagg-name', dest='hostagg_name',
+                                  type=str,
+                                  help='Specify the name of the hostagg')
+hostagg_create_parser.add_argument('-a', '--avail-zone', dest='avail_zone',
+                                  type=str,
+                                  help='Specify the name of the availability zone')
+# Delete hostagg subparser
+hostagg_delete_parser = hostagg_subparsers.add_parser('delete')
+hostagg_delete_parser.set_defaults(which='delete')
+hostagg_delete_parser.add_argument('-n', '--hostagg-name', dest='hostagg_name',
+                                  type=str,
+                                  help='Specify the name of the hostagg')
+hostagg_delete_parser.add_argument('-f', '--force-delete-hosts', dest='force_delete_hosts',
+                                  action='store_true',
+                                  help='Delete the existing hosts')
+
+# Add host subparser
+hostagg_addhost_parser = hostagg_subparsers.add_parser('addhost')
+hostagg_addhost_parser.set_defaults(which='addhost')
+hostagg_addhost_parser.add_argument('-n', '--hostagg-name', dest='hostagg_name',
+                                  type=str,
+                                  help='Specify the name of the hostagg')
+hostagg_addhost_parser.add_argument('-c', '--compute-host-name', dest='host_name',
+                                  type=str,
+                                  help='Specify the name of the host to be added')
+
+# Remove host subparser
+hostagg_delhost_parser = hostagg_subparsers.add_parser('delhost')
+hostagg_delhost_parser.set_defaults(which='delhost')
+hostagg_delhost_parser.add_argument('-n', '--hostagg-name', dest='hostagg_name',
+                                  type=str,
+                                  help='Specify the name of the hostagg')
+hostagg_delhost_parser.add_argument('-c', '--compute-host-name', dest='host_name',
+                                  type=str,
+                                  help='Specify the name of the host to be removed')
+
+# Set meta-data subparser
+hostagg_setdata_parser = hostagg_subparsers.add_parser('setmetadata')
+hostagg_setdata_parser.set_defaults(which='setmetadata')
+hostagg_setdata_parser.add_argument('-n', '--hostagg-name', dest='hostagg_name',
+                                  type=str,
+                                  help='Specify the name of the hostagg')
+hostagg_setdata_parser.add_argument('-d', '--meta-data', dest='extra_specs',
+                                  type=str,
+                                  help='Specify the meta-data to be associated to this host aggregate')
+
+hostagg_parser.set_defaults(func=hostagg_subcommand)
+
+##
+# Subparser for quota
+##
+quota_parser = subparsers.add_parser('quota')
+quota_subparser = quota_parser.add_subparsers()
+quota_set_parser = quota_subparser.add_parser('set')
+
+# quota set subparser
+quota_set_parser.set_defaults(which='set')
+quota_set_parser.add_argument('-p', '--project', dest='project', 
+                              type=str, default='demo', 
+                              help='project name that you wish to set '
+                                   'the quotas for')
+quota_set_parser.add_argument('-c', '--vcpus', dest='vcpus', 
+                              type=int, default=48, 
+                              help='Maximum number of virtual CPUs that can '
+                                   'be assigned to all VMs in aggregate')
+quota_set_parser.add_argument('-v', '--vms', dest='vms', 
+                              type=int, default=24, 
+                              help='Maximum number of VMs that can be created ' 
+                                   'on this openstack instance '
+                                   '(which may be more than 1 machine)')
+quota_set_parser.add_argument('-i', '--ips', dest='ips', 
+                              type=int, default=250, 
+                              help='Maximum number of Floating IP Addresses '
+                                   'that can be assigned to all VMs '
+                                   'in aggregate')
+quota_set_parser.add_argument('-m', '--memory', dest='memory', 
+                              type=int, default=122880, 
+                              help='Maximum amount of RAM in MB that can be '
+                                   'assigned to all VMs in aggregate')
+
+# quota get subparser
+quota_get_parser = quota_subparser.add_parser('get')
+quota_get_parser.add_argument('-p', '--project', dest='project', 
+                              type=str, default='demo', 
+                              help='project name that you wish to get '
+                                   'the quotas for')
+quota_get_parser.set_defaults(which='get')
+quota_parser.set_defaults(func=quota_subcommand)
+
+##
+# rules subparser
+##
+rules_parser = subparsers.add_parser('rules')
+rules_parser.set_defaults(func=rules_subcommand)
+rules_subparser = rules_parser.add_subparsers()
+rules_set_parser = rules_subparser.add_parser('set')
+rules_set_parser.set_defaults(which='set')
+rules_list_parser = rules_subparser.add_parser('list')
+rules_list_parser.set_defaults(which='list')
+
+register_parser = subparsers.add_parser('register')
+register_parser.set_defaults(func=register_subcommand)
+ 
+cmdargs = parser.parse_args()
+
+
+if __name__ == "__main__":
+    logger=logging.getLogger(__name__)
+    if cmdargs.debug:
+        logging.basicConfig(format='%(asctime)-15s %(levelname)s %(message)s', level=logging.DEBUG) 
+    else:
+        logging.basicConfig(format='%(asctime)-15s %(levelname)s %(message)s', level=logging.WARNING) 
+
+    if cmdargs.provider_type == 'OPENSTACK':
+        #cls = get_driver(Provider.OPENSTACK)
+        pass
+    elif cmdargs.provider_type == 'VSPHERE':
+        cls = get_driver(Provider.VSPHERE)
+    else:
+        sys.exit("Cloud provider %s is NOT supported yet" % cmdargs.provider_type)
+
+    if cmdargs.reservation_server_url == "None" or cmdargs.reservation_server_url == "":
+        cmdargs.reservation_server_url = None
+    if cmdargs.reservation_server_url is not None:
+        sys.path.append('/usr/rift/lib')
+        try:
+            import ndl
+        except Exception as e:
+            logger.warning("Error loading Reservation library")
+            testbed=None
+        else:
+            testbed=ndl.Testbed()
+            testbed.set_server(cmdargs.reservation_server_url)
+            
+
+
+    if cmdargs.provider_type == 'OPENSTACK':
+        account                        = RwcalYang.CloudAccount()
+        account.account_type           = "openstack"
+        account.openstack.key          = cmdargs.user
+        account.openstack.secret       = cmdargs.passwd
+        account.openstack.auth_url     = cmdargs.auth_url
+        account.openstack.tenant       = cmdargs.user
+        account.openstack.mgmt_network = cmdargs.mgmt_network
+
+        plugin = rw_peas.PeasPlugin('rwcal_openstack', 'RwCal-1.0')
+        engine, info, extension = plugin()
+        driver = plugin.get_interface("Cloud")
+        # Get the RwLogger context
+        rwloggerctx = rwlogger.RwLog.Ctx.new("Cal-Log")
+        try:
+            rc = driver.init(rwloggerctx)
+            assert rc == RwStatus.SUCCESS
+        except:
+            logger.error("ERROR:Cal plugin instantiation failed. Aborting tests")
+        else:
+            logger.info("Openstack Cal plugin successfully instantiated")
+
+        cmdargs.func(driver, account, cmdargs)
+
+    elif cmdargs.provider_type == 'VSPHERE':
+        driver = cls(cmdargs.user, cmdargs.passwd, host='vcenter' )
+        cmdargs.func(driver, cmdargs)
diff --git a/rwcal/test/ec2.py b/rwcal/test/ec2.py
new file mode 100644
index 0000000..59ad049
--- /dev/null
+++ b/rwcal/test/ec2.py
@@ -0,0 +1,275 @@
+
+# 
+#   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 glob
+import itertools
+import os
+
+import boto
+import boto.vpc
+
+# TODO:  Pull the lastest of owned instances.
+__default_instance_ami__ = 'ami-e421bc8c'
+
+# TODO:  Make VPC's per user?
+__default_subnet__ = 'subnet-4b484363'
+__default_security_group__ = 'sg-d9da90bc'
+
+__default_instance_type__ = 'm1.medium'
+__default_vpc__ = 'vpc-e7ed4482'
+
+class RWEC2(object):
+    def __init__(self,  subnet=None, ami=None):
+        self._subnet = subnet if subnet is not None else __default_subnet__
+        self._ami = ami if ami is not None else __default_instance_ami__
+
+        self._conn = boto.connect_ec2()
+
+    @staticmethod
+    def cloud_init_current_user():
+        """
+        Return user_data configuration suitable for cloud-init that will create a user
+        with sudo and ssh key access on the remote instance.
+
+        ssh keys are found with the glob ~/.ssh/*pub*
+        """
+        user_data = "users:\n"
+        user_data += " - name: %s\n" % (os.getlogin(),)
+        user_data += "   groups: [wheel, adm, systemd-journal]\n"
+        user_data += "   sudo: [\"ALL=(ALL) NOPASSWD:ALL\"]\n"
+        user_data += "   shell: /bin/bash\n"
+        user_data += "   ssh_authorized_keys:\n"
+        for pub_key in glob.glob('%s/.ssh/*pub*' % (os.environ['HOME'],)):
+            with open(pub_key) as fp:
+                user_data += "    -  %s" % (fp.read(),)
+
+        return user_data
+
+
+    @staticmethod
+    def cloud_init_yum_repos():
+        """
+        Return a string of user_data commands that can be used to update the yum
+        repos to point to the correct location.  They should be added by the caller
+        within a 'runcmd:' block.
+        """
+        ret = " - sed -i -e 's,www\.,,' -e 's,riftio\.com/mirrors,riftio.com:8881,' /etc/yum.repos.d/*.repo\n"
+        return ret
+
+    def instances(self, cluster_component, cluster_instance):
+        """
+        List of instances owned by the given cluster instance
+
+        @param cluster_component  - parent cluster of each instance
+        @param cluster_instance   - instance id of the owning cluster
+        @param n_instances        - number of requested instances
+
+        @return                   - list of boto.ec2.instance.Instances provisioned
+        """
+        ret = []
+        reservations = self._conn.get_all_instances()
+        for instance in [instance for reservation in reservations for instance in reservation.instances]:
+            tags = instance.tags
+            if (tags.get('parent_component') == cluster_component
+                    and tags.get('parent_instance') == cluster_instance):
+                ret.append(instance)
+
+        return ret
+
+    def provision_master(self, cluster_component, cluster_instance):
+        """
+        Provision a master instance in EC2.  The master instance is a special instance with the
+        following features:
+            - Public IP
+            - /home shared over NFS
+
+        @param cluster_component  - parent cluster of each instance
+        @param cluster_instance   - instance id of the owning cluster
+
+        @return                   - boto.ec2.instance.Instances provisioned
+        """
+        vpc = boto.vpc.VPCConnection()
+        subnet = vpc.get_all_subnets(subnet_ids=__default_subnet__)[0]
+        cidr_block = subnet.cidr_block
+        vpc.close()
+
+        user_data = "#cloud-config\n"
+        user_data += "runcmd:\n"
+        user_data += " - echo '/home %s(rw,root_squash,sync)' >  /etc/exports\n" % (cidr_block,)
+        user_data += " - systemctl start nfs-server\n"
+        user_data += " - systemctl enable nfs-server\n"
+        user_data += self.cloud_init_yum_repos()
+        user_data += self.cloud_init_current_user()
+
+
+        net_if = boto.ec2.networkinterface.NetworkInterfaceSpecification(
+                subnet_id=__default_subnet__,
+                groups=[__default_security_group__,],
+                associate_public_ip_address=True)
+
+        net_ifs = boto.ec2.networkinterface.NetworkInterfaceCollection(net_if)
+
+        new_reservation = self._conn.run_instances(
+                image_id=self._ami,
+                min_count=1,
+                max_count=1,
+                instance_type=__default_instance_type__,
+                network_interfaces=net_ifs,
+                tenancy='default',
+                user_data=user_data)
+        instance = new_reservation.instances[0]
+
+        instance.add_tag('parent_component', cluster_component)
+        instance.add_tag('parent_instance', cluster_instance)
+        instance.add_tag('master', 'self')
+
+        return instance
+
+
+    def provision(self, cluster_component, cluster_instance, n_instances=1, master_instance=None, net_ifs=None):
+        """
+        Provision a number of EC2 instanced to be used in a cluster.
+
+        @param cluster_component  - parent cluster of each instance
+        @param cluster_instance   - instance id of the owning cluster
+        @param n_instances        - number of requested instances
+        @param master_instance    - if specified, the boto.ec2.instance.Instance that is providing master
+                                    services for this cluster
+
+        @return                   - list of boto.ec2.instance.Instances provisioned
+        """
+        instances = []
+        cluster_instance = int(cluster_instance)
+
+        def posess_instance(instance):
+            instances.append(instance)
+            instance.add_tag('parent_component', cluster_component)
+            instance.add_tag('parent_instance', cluster_instance)
+            if master_instance is not None:
+                instance.add_tag('master', master_instance.id)
+            else:
+                instance.add_tag('master', 'None')
+
+        user_data = "#cloud-config\n"
+        user_data += self.cloud_init_current_user()
+        user_data += "runcmd:\n"
+        user_data += self.cloud_init_yum_repos()
+
+        if master_instance is not None:
+            user_data += " - echo '%s:/home /home nfs rw,soft,sync 0 0' >> /etc/fstab\n" % (
+                    master_instance.private_ip_address,)
+            user_data += " - mount /home\n"
+
+        if net_ifs is not None:
+            kwds = {'subnet_id': __default_subnet__}
+        else:
+            kwds = {'network_interfaces': net_ifs}
+            print net_ifs
+
+        new_reservation = self._conn.run_instances(
+            image_id=self._ami,
+            min_count=n_instances,
+            max_count=n_instances,
+            instance_type=__default_instance_type__,
+            tenancy='default',
+            user_data=user_data,
+            network_interfaces=net_ifs)
+
+        _ = [posess_instance(i) for i in new_reservation.instances]
+
+        return instances
+
+    def stop(self, instance_id, free_resources=True):
+        """
+        Stop the specified instance, freeing all allocated resources (elastic ips, etc) if requested.
+
+        @param instance_id      - name of the instance to stop
+        @param free_resource    - If True that all resources that were only owned by this instance
+                                  will be deallocated as well.
+        """
+        self._conn.terminate_instances(instance_ids=[instance_id,])
+
+    def fastpath111(self):
+        vpc_conn = boto.vpc.VPCConnection()
+        vpc = vpc_conn.get_all_vpcs(vpc_ids=[__default_vpc__,])[0]
+        subnet_addrs_split = vpc.cidr_block.split('.')
+
+        networks = {
+            'mgmt': [s for s in vpc_conn.get_all_subnets() if s.id == __default_subnet__][0],
+            'tg_fabric': None,
+            'ts_fabric': None,
+            'tg_lb_ext': None,
+            'lb_ts_ext': None,
+        }
+
+        for i, network in enumerate([n for n, s in networks.items() if s == None]):
+            addr = "%s.%s.10%d.0/25" % (subnet_addrs_split[0], subnet_addrs_split[1], i)
+            try:
+                subnet = vpc_conn.create_subnet(vpc.id, addr)
+            except boto.exception.EC2ResponseError, e:
+                if 'InvalidSubnet.Conflict' == e.error_code:
+                    subnet = vpc_conn.get_all_subnets(filters=[('vpcId', vpc.id), ('cidrBlock', addr)])[0]
+                else:
+                    raise
+
+            networks[network] = subnet
+
+        def create_interfaces(nets):
+            ret = boto.ec2.networkinterface.NetworkInterfaceCollection()
+
+            for i, network in enumerate(nets):
+                spec = boto.ec2.networkinterface.NetworkInterfaceSpecification(
+                        subnet_id=networks[network].id,
+                        description='%s iface' % (network,),
+                        groups=[__default_security_group__],
+                        device_index=i)
+                ret.append(spec)
+
+            return ret
+
+        ret = {}
+
+        ret['cli'] = self.provision_master('fp111', 1)
+        ret['cli'].add_tag('Name', 'cli')
+
+        net_ifs = create_interfaces(['mgmt'])
+        ret['mgmt'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0]
+        ret['mgmt'].add_tag('Name', 'mgmt')
+
+        net_ifs = create_interfaces(['mgmt', 'tg_fabric'])
+        ret['tg1'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0]
+        ret['tg1'].add_tag('Name', 'tg1')
+
+        net_ifs = create_interfaces(['mgmt', 'tg_fabric', 'tg_lb_ext'])
+        ret['tg2'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0]
+        ret['tg2'].add_tag('Name', 'tg2')
+
+        net_ifs = create_interfaces(['mgmt', 'ts_fabric'])
+        ret['ts1'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0]
+        ret['ts1'].add_tag('Name', 'ts1')
+
+        net_ifs = create_interfaces(['mgmt', 'ts_fabric', 'lb_ts_ext'])
+        ret['ts3'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0]
+        ret['ts3'].add_tag('Name', 'ts3')
+
+        net_ifs = create_interfaces(['mgmt', 'ts_fabric', 'lb_ts_ext', 'tg_lb_ext'])
+        ret['ts2'] = self.provision('fp111', 1, master_instance=ret['cli'], net_ifs=net_ifs)[0]
+        ret['ts2'].add_tag('Name', 'ts2')
+
+        return ret
+
+# vim: sw=4
diff --git a/rwcal/test/openstack_resources.py b/rwcal/test/openstack_resources.py
new file mode 100755
index 0000000..f7fb00d
--- /dev/null
+++ b/rwcal/test/openstack_resources.py
@@ -0,0 +1,483 @@
+#!/usr/bin/env python3
+
+# 
+#   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.
+#
+
+from gi import require_version
+require_version('RwCal', '1.0')
+
+from gi.repository import RwcalYang
+from gi.repository.RwTypes import RwStatus
+import logging
+import rw_peas
+import rwlogger
+import time
+import argparse
+import os
+import sys
+import uuid
+from os.path import basename
+
+FLAVOR_NAME = 'm1.medium'
+DEFAULT_IMAGE='/net/sharedfiles/home1/common/vm/rift-root-latest.qcow2'
+
+persistent_resources = {
+    'vms'      : ['mission_control','launchpad',],
+    'networks' : ['public', 'private', 'multisite'],
+    'flavors'  : ['m1.tiny', 'm1.small', 'm1.medium', 'm1.large', 'm1.xlarge'],
+    'images'   : ['rwimage','rift-root-latest.qcow2','rift-root-latest-trafgen.qcow2', 'rift-root-latest-trafgen-f.qcow2']
+}
+
+#
+# Important information about openstack installation. This needs to be manually verified 
+#
+openstack_info = {
+    'username'           : 'pluto',
+    'password'           : 'mypasswd',
+    'project_name'       : 'demo',
+    'mgmt_network'       : 'private',
+    'physical_network'   : 'physnet1',
+    'network_type'       : 'VLAN',
+    'segmentation_id'    : 42, ### What else?
+    'subnets'            : ["11.0.0.0/24", "12.0.0.0/24", "13.0.0.0/24", "14.0.0.0/24"],
+    'subnet_index'       : 0,
+    }
+
+
+logging.basicConfig(level=logging.INFO)
+
+USERDATA_FILENAME = os.path.join(os.environ['RIFT_INSTALL'],
+                                 'etc/userdata-template')
+
+
+RIFT_BASE_USERDATA = '''
+#cloud-config
+runcmd:
+ - sleep 5
+ - /usr/rift/scripts/cloud/enable_lab
+ - /usr/rift/etc/fix_this_vm
+'''
+
+try:
+    fd = open(USERDATA_FILENAME, 'r')
+except Exception as e:
+    #logger.error("Received exception during opening of userdata (%s) file. Exception: %s" %(USERDATA_FILENAME, str(e)))
+    sys.exit(-1)
+else:
+    LP_USERDATA_FILE = fd.read()
+    # Run the enable lab script when the openstack vm comes up
+    LP_USERDATA_FILE += "runcmd:\n"
+    LP_USERDATA_FILE += " - /usr/rift/scripts/cloud/enable_lab\n"
+    LP_USERDATA_FILE += " - /usr/rift/etc/fix_this_vm\n"
+
+
+
+def get_cal_plugin():
+    """
+    Loads rw.cal plugin via libpeas
+    """
+    plugin = rw_peas.PeasPlugin('rwcal_openstack', 'RwCal-1.0')
+    engine, info, extension = plugin()
+    cal = plugin.get_interface("Cloud")
+    # Get the RwLogger context
+    rwloggerctx = rwlogger.RwLog.Ctx.new("Cal-Log")
+    try:
+        rc = cal.init(rwloggerctx)
+        assert rc == RwStatus.SUCCESS
+    except:
+        logger.error("ERROR:Cal plugin instantiation failed. Aborting tests")
+    else:
+        logger.info("Openstack Cal plugin successfully instantiated")
+        return cal 
+    
+def get_cal_account(auth_url):
+    """
+    Returns cal account
+    """
+    account                        = RwcalYang.CloudAccount()
+    account.account_type           = "openstack"
+    account.openstack.key          = openstack_info['username']
+    account.openstack.secret       = openstack_info['password']
+    account.openstack.auth_url     = auth_url
+    account.openstack.tenant       = openstack_info['project_name']
+    account.openstack.mgmt_network = openstack_info['mgmt_network']
+    return account
+
+
+logger = logging.getLogger('rift.cal.openstackresources')
+
+class OpenstackResources(object):
+    """
+    A stupid class to manage bunch of openstack resources
+    """
+    def __init__(self, controller):    
+        self._cal      = get_cal_plugin()
+        self._acct     = get_cal_account('http://'+controller+':5000/v3/')
+        self._id       = 0
+        self._image_id = None
+        self._flavor_id = None
+        
+    def _destroy_vms(self):
+        """
+        Destroy VMs
+        """
+        logger.info("Initiating VM cleanup")
+        rc, rsp = self._cal.get_vdu_list(self._acct)
+        vdu_list = [vm for vm in rsp.vdu_info_list if vm.name not in persistent_resources['vms']]
+        logger.info("Deleting VMs : %s" %([x.name for x in vdu_list]))
+        
+        for vdu in vdu_list:
+            self._cal.delete_vdu(self._acct, vdu.vdu_id)
+
+        logger.info("VM cleanup complete")
+
+    def _destroy_networks(self):
+        """
+        Destroy Networks
+        """
+        logger.info("Initiating Network cleanup")
+        rc, rsp = self._cal.get_virtual_link_list(self._acct)
+        vlink_list = [vlink for vlink in rsp.virtual_link_info_list if vlink.name not in persistent_resources['networks']]
+
+        logger.info("Deleting Networks : %s" %([x.name for x in vlink_list]))
+        for vlink in vlink_list:
+            self._cal.delete_virtual_link(self._acct, vlink.virtual_link_id)
+        logger.info("Network cleanup complete")
+
+    def _destroy_flavors(self):
+        """
+        Destroy Flavors
+        """
+        logger.info("Initiating flavor cleanup")
+        rc, rsp = self._cal.get_flavor_list(self._acct)
+        flavor_list = [flavor for flavor in rsp.flavorinfo_list if flavor.name not in persistent_resources['flavors']]
+            
+        logger.info("Deleting flavors : %s" %([x.name for x in flavor_list]))
+
+        for flavor in flavor_list:
+            self._cal.delete_flavor(self._acct, flavor.id)
+            
+        logger.info("Flavor cleanup complete")
+
+    def _destroy_images(self):
+        logger.info("Initiating image cleanup")
+        rc, rsp = self._cal.get_image_list(self._acct)
+        image_list = [image for image in rsp.imageinfo_list if image.name not in persistent_resources['images']]
+
+        logger.info("Deleting images : %s" %([x.name for x in image_list]))
+            
+        for image in image_list:
+            self._cal.delete_image(self._acct, image.id)
+            
+        logger.info("Image cleanup complete")
+        
+    def destroy_resource(self):
+        """
+        Destroy resources
+        """
+        logger.info("Cleaning up openstack resources")
+        self._destroy_vms()
+        self._destroy_networks()
+        self._destroy_flavors()
+        self._destroy_images()
+        logger.info("Cleaning up openstack resources.......[Done]")
+
+    def create_mission_control(self):
+        vm_id = self.create_vm('mission_control',
+                               userdata = RIFT_BASE_USERDATA)
+        return vm_id
+    
+
+    def create_launchpad_vm(self, salt_master=None):
+        node_id = str(uuid.uuid4())
+        if salt_master is not None:
+           userdata = LP_USERDATA_FILE.format(master_ip = salt_master,
+                                           lxcname = node_id)
+        else:
+           userdata = RIFT_BASE_USERDATA
+
+        vm_id = self.create_vm('launchpad',
+                              userdata = userdata,
+                              node_id = node_id)
+#        vm_id = self.create_vm('launchpad2',
+#                               userdata = userdata,
+#                               node_id = node_id)
+        return vm_id
+    
+    def create_vm(self, name, userdata, node_id = None):
+        """
+        Creates a VM. The VM name is derived from username
+
+        """
+        vm = RwcalYang.VDUInitParams()
+        vm.name = name
+        vm.flavor_id = self._flavor_id
+        vm.image_id  = self._image_id
+        if node_id is not None:
+            vm.node_id = node_id
+        vm.vdu_init.userdata = userdata
+        vm.allocate_public_address = True
+        logger.info("Starting a VM with parameter: %s" %(vm))
+     
+        rc, vm_id = self._cal.create_vdu(self._acct, vm)
+        assert rc == RwStatus.SUCCESS
+        logger.info('Created vm: %s with id: %s', name, vm_id)
+        return vm_id
+        
+    def create_network(self, name):
+        logger.info("Creating network with name: %s" %name)
+        network                = RwcalYang.NetworkInfoItem()
+        network.network_name   = name
+        network.subnet         = openstack_info['subnets'][openstack_info['subnet_index']]
+
+        if openstack_info['subnet_index'] == len(openstack_info['subnets']):
+            openstack_info['subnet_index'] = 0
+        else:
+            openstack_info['subnet_index'] += 1
+        
+        if openstack_info['physical_network']:
+            network.provider_network.physical_network = openstack_info['physical_network']
+        if openstack_info['network_type']:
+            network.provider_network.overlay_type     = openstack_info['network_type']
+        if openstack_info['segmentation_id']:
+            network.provider_network.segmentation_id  = openstack_info['segmentation_id']
+            openstack_info['segmentation_id'] += 1
+
+        rc, net_id = self._cal.create_network(self._acct, network)
+        assert rc == RwStatus.SUCCESS
+
+        logger.info("Successfully created network with id: %s" %net_id)
+        return net_id
+    
+        
+
+    def create_image(self, location):
+        img = RwcalYang.ImageInfoItem()
+        img.name = basename(location)
+        img.location = location
+        img.disk_format = "qcow2"
+        img.container_format = "bare"
+
+        logger.info("Uploading image : %s" %img.name)
+        rc, img_id = self._cal.create_image(self._acct, img)
+        assert rc == RwStatus.SUCCESS
+
+        rs = None
+        rc = None
+        image = None
+        for i in range(100):
+            rc, rs = self._cal.get_image(self._acct, img_id)
+            assert rc == RwStatus.SUCCESS
+            logger.info("Image (image_id: %s) reached status : %s" %(img_id, rs.state))
+            if rs.state == 'active':
+                image = rs
+                break
+            else:
+                time.sleep(2) # Sleep for a second
+
+        if image is None:
+            logger.error("Failed to upload openstack image: %s", img)
+            sys.exit(1)
+
+        self._image_id = img_id
+        logger.info("Uploading image.......[Done]")
+        
+    def create_flavor(self):
+        """
+        Create Flavor suitable for rift_ping_pong VNF
+        """
+        flavor = RwcalYang.FlavorInfoItem()
+        flavor.name = FLAVOR_NAME
+        flavor.vm_flavor.memory_mb   = 16384 # 16GB
+        flavor.vm_flavor.vcpu_count  = 4 
+        flavor.vm_flavor.storage_gb  = 20 # 20 GB
+
+        logger.info("Creating new flavor. Flavor Info: %s" %str(flavor.vm_flavor))
+
+        rc, flavor_id = self._cal.create_flavor(self._acct, flavor)
+        assert rc == RwStatus.SUCCESS
+        logger.info("Creating new flavor.......[Done]")
+        return flavor_id
+
+    def find_image(self, name):
+        logger.info("Searching for uploaded image: %s" %name)
+        rc, rsp = self._cal.get_image_list(self._acct)
+        image_list = [image for image in rsp.imageinfo_list if image.name ==  name]
+
+        if not image_list:
+            logger.error("Image %s not found" %name)
+            return None
+
+        self._image_id = image_list[0].id
+        logger.info("Searching for uploaded image.......[Done]")
+        return self._image_id
+
+    def find_flavor(self, name=FLAVOR_NAME):
+        logger.info("Searching for required flavor: %s" %name)
+        rc, rsp = self._cal.get_flavor_list(self._acct)
+        flavor_list = [flavor for flavor in rsp.flavorinfo_list if flavor.name == name]
+
+        if not flavor_list:
+            logger.error("Flavor %s not found" %name)
+            self._flavor_id = self.create_flavor()
+        else:
+            self._flavor_id = flavor_list[0].id
+
+        logger.info("Searching for required flavor.......[Done]")
+        return self._flavor_id
+
+        
+    
+
+def main():
+    """
+    Main routine
+    """
+    parser = argparse.ArgumentParser(description='Script to manage openstack resources')
+    
+    parser.add_argument('--controller',
+                        action = 'store',
+                        dest = 'controller',
+                        type = str,
+                        help='IP Address of openstack controller. This is mandatory parameter')
+
+    parser.add_argument('--cleanup',
+                        action = 'store',
+                        dest = 'cleanup',
+                        nargs = '+',
+                        type = str,
+                        help = 'Perform resource cleanup for openstack installation. \n Possible options are {all, flavors, vms, networks, images}')
+
+    parser.add_argument('--persist-vms',
+                        action = 'store',
+                        dest = 'persist_vms',
+                        help = 'VM instance name to persist')
+
+    parser.add_argument('--salt-master',
+                        action = 'store',
+                        dest = 'salt_master',
+                        type = str,
+                        help='IP Address of salt controller. Required, if VMs are being created.')
+
+    parser.add_argument('--upload-image',
+                        action = 'store',
+                        dest = 'upload_image',
+                        help='Openstack image location to upload and use when creating vms.x')
+
+    parser.add_argument('--use-image',
+                        action = 'store',
+                        dest = 'use_image',
+                        help='Image name to be used for VM creation')
+
+    parser.add_argument('--use-flavor',
+                        action = 'store',
+                        dest = 'use_flavor',
+                        help='Flavor name to be used for VM creation')
+    
+    parser.add_argument('--mission-control',
+                        action = 'store_true',
+                        dest = 'mission_control',
+                        help='Create Mission Control VM')
+
+
+    parser.add_argument('--launchpad',
+                        action = 'store_true',
+                        dest = 'launchpad',
+                        help='Create LaunchPad VM')
+
+    parser.add_argument('--use-project',
+                        action = 'store',
+                        dest = 'use_project',
+                        help='Project name to be used for VM creation')
+
+    parser.add_argument('--clean-mclp',
+                        action='store_true',
+                        dest='clean_mclp',
+                        help='Remove Mission Control and Launchpad VMs')
+
+    argument = parser.parse_args()
+
+    if argument.persist_vms is not None:
+        global persistent_resources
+        vm_name_list = argument.persist_vms.split(',')
+        for single_vm in vm_name_list:
+                persistent_resources['vms'].append(single_vm)
+        logger.info("persist-vms: %s" % persistent_resources['vms'])
+
+    if argument.clean_mclp:
+        persistent_resources['vms'] = []
+
+    if argument.controller is None:
+        logger.error('Need openstack controller IP address')
+        sys.exit(-1)
+
+    
+    if argument.use_project is not None:
+        openstack_info['project_name'] = argument.use_project
+
+    ### Start processing
+    logger.info("Instantiating cloud-abstraction-layer")
+    drv = OpenstackResources(argument.controller)
+    logger.info("Instantiating cloud-abstraction-layer.......[Done]")
+
+        
+    if argument.cleanup is not None:
+        for r_type in argument.cleanup:
+            if r_type == 'all':
+                drv.destroy_resource()
+                break
+            if r_type == 'images':
+                drv._destroy_images()
+            if r_type == 'flavors':
+                drv._destroy_flavors()
+            if r_type == 'vms':
+                drv._destroy_vms()
+            if r_type == 'networks':
+                drv._destroy_networks()
+
+    if argument.upload_image is not None:
+        image_name_list = argument.upload_image.split(',')
+        logger.info("Will upload %d image(s): %s" % (len(image_name_list), image_name_list))
+        for image_name in image_name_list:
+            drv.create_image(image_name)
+            #print("Uploaded :", image_name)
+
+    elif argument.use_image is not None:
+        img = drv.find_image(argument.use_image)
+        if img == None:
+            logger.error("Image: %s not found" %(argument.use_image))
+            sys.exit(-4)
+    else:
+        if argument.mission_control or argument.launchpad:
+            img = drv.find_image(basename(DEFAULT_IMAGE))
+            if img == None:
+                drv.create_image(DEFAULT_IMAGE)
+
+    if argument.use_flavor is not None:
+        drv.find_flavor(argument.use_flavor)
+    else:
+        drv.find_flavor()
+        
+    if argument.mission_control == True:
+        drv.create_mission_control()
+
+    if argument.launchpad == True:
+        drv.create_launchpad_vm(salt_master = argument.salt_master)
+        
+    
+if __name__ == '__main__':
+    main()
+        
diff --git a/rwcal/test/rwcal_callback_gtest.cpp b/rwcal/test/rwcal_callback_gtest.cpp
new file mode 100644
index 0000000..52dc6f6
--- /dev/null
+++ b/rwcal/test/rwcal_callback_gtest.cpp
@@ -0,0 +1,79 @@
+
+/*
+ * 
+ *   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.
+ *
+ */
+
+
+
+#include <rwut.h>
+
+#include "rwcal-api.h"
+
+struct test_struct {
+  int accessed;
+};
+
+struct test_struct g_test_struct;
+
+class RWCalCallbackTest : public ::testing::Test {
+  /*
+   * This is a tough one to test as we're really relying on the
+   * gobject introspection to do all the data marshalling for us
+   * correctly.  At this point, all I can think of to do is to
+   * just create a closure and then call it the same way it would
+   * typically be called in C and make sure that everything
+   * executed as expected.
+   */
+ protected:
+  rwcal_module_ptr_t rwcal;
+
+  virtual void SetUp() {
+    rwcal = rwcal_module_alloc();
+    ASSERT_TRUE(rwcal);
+
+    g_test_struct.accessed = 0;
+  }
+
+  virtual void TearDown() {
+    rwcal_module_free(&rwcal);
+  }
+
+  virtual void TestSuccess() {
+    ASSERT_TRUE(rwcal);
+#if 0
+    rwcal_closure_ptr_t closure;
+
+    closure = rwcal_closure_alloc(
+        rwcal,
+        &update_accessed,
+        (void *)&g_test_struct);
+    ASSERT_TRUE(closure);
+
+    ASSERT_EQ(g_test_struct.accessed, 0);
+    rw_cal_closure_callback(closure);
+    ASSERT_EQ(g_test_struct.accessed, 1);
+
+    rwcal_closure_free(&closure);
+    ASSERT_FALSE(closure);
+#endif
+  }
+};
+
+
+TEST_F(RWCalCallbackTest, TestSuccess) {
+  TestSuccess();
+}
diff --git a/rwcal/test/rwcal_dump.cpp b/rwcal/test/rwcal_dump.cpp
new file mode 100644
index 0000000..ff6fd73
--- /dev/null
+++ b/rwcal/test/rwcal_dump.cpp
@@ -0,0 +1,77 @@
+
+/*
+ * 
+ *   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.
+ *
+ */
+
+
+
+/**
+ * @file cal_dump
+ * @author Jeremy Mordkoff
+ * @date 05/14/2015 
+ * @brief test program to dump what we can glean from an installation
+ */
+
+
+#include <limits.h>
+#include <cstdlib>
+#include <iostream>
+
+#include "rwcal-api.h"
+
+
+int main(int argc, char ** argv, char ** envp)
+{
+
+#if 0
+    rw_status_t status;
+    rwcal_module_ptr_t m_mod;
+    Rwcal__YangData__Rwcal__Flavorinfo__FlavorinfoList  *flavor;
+    rwpb_gi_Rwcal_FlavorInfo *flavors;
+    Rwcal__YangData__Rwcal__Flavorinfo *flavorinfo;
+    unsigned int i;
+    char url[128];
+
+    if (argc != 4 ) {
+    	fprintf(stderr, "args are IP user password\n");
+    	return(1);
+    }
+    snprintf(url, 128, "http://%s:35357/v2.0/tokens", argv[1] );
+
+    m_mod = rwcal_module_alloc();
+    status = rwcal_cloud_init(m_mod, RW_MANIFEST_RWCAL_CLOUD_TYPE_OPENSTACK_AUTH_URL, argv[2], argv[3], url );
+    if (status != RW_STATUS_SUCCESS)
+      return status;
+
+    status = rwcal_cloud_flavor_infos(m_mod, &flavors);
+    if (status != RW_STATUS_SUCCESS)
+      return status;
+    flavorinfo = flavors->s.message;
+    printf("ID                                       NAME             MEM    DISK VCPU PCI  HP TC\n");
+    printf("---------------------------------------- ---------------- ------ ---- ---- ---- -- --\n");
+    for (i = 0; i<flavorinfo->n_flavorinfo_list; i++) {
+      flavor = flavorinfo->flavorinfo_list[i];
+      printf("%-40s %-16s %6d %4d %4d %4d %2d %2d\n", flavor->id, flavor->name, flavor->memory, flavor->disk, flavor->vcpus, flavor->pci_passthru_bw, 
+              flavor->has_huge_pages, flavor->trusted_host_only );
+    }
+
+    rwcal__yang_data__rwcal__flavorinfo__gi_unref(flavors);
+#endif
+    return 0;
+
+}
+
diff --git a/rwcal/test/test_container_cal.py b/rwcal/test/test_container_cal.py
new file mode 100644
index 0000000..3ec5ca1
--- /dev/null
+++ b/rwcal/test/test_container_cal.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+
+# 
+#   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 argparse
+import logging
+import os
+import sys
+import time
+
+import rw_peas
+import rwlogger
+
+from gi.repository import RwcalYang
+
+import rift.rwcal.cloudsim
+import rift.rwcal.cloudsim.lxc as lxc
+
+logger = logging.getLogger('rift.cal')
+
+
+def main(argv=sys.argv[1:]):
+    """
+    Assuming that an LVM backing-store has been created with a volume group
+    called 'rift', the following creates an lxc 'image' and a pair of 'vms'.
+    In the LXC based container CAL, an 'image' is container and a 'vm' is a
+    snapshot of the original container.
+
+    In addition to the LVM backing store, it is assumed that there is a network
+    bridge called 'virbr0'.
+
+    """
+    logging.basicConfig(level=logging.DEBUG)
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--rootfs', '-r')
+    parser.add_argument('--num-vms', '-n', type=int, default=2)
+    parser.add_argument('--terminate', '-t', action='store_true')
+
+    args = parser.parse_args(argv)
+
+    # Acquire the plugin from peas
+    plugin = rw_peas.PeasPlugin('rwcal-plugin', 'RwCal-1.0')
+    engine, info, extension = plugin()
+
+    # Get the RwLogger context
+    rwloggerctx = rwlogger.RwLog.Ctx.new("Cal-Log")
+
+    cal = plugin.get_interface("Cloud")
+    cal.init(rwloggerctx)
+
+    # The account object is not currently used, but it is required by the CAL
+    # interface, so we create an empty object here to represent it.
+    account = RwcalYang.CloudAccount()
+    account.account_type = "lxc"
+
+    # Make sure that any containers that were previously created have been
+    # stopped and destroyed.
+    containers = lxc.containers()
+
+    for container in containers:
+        lxc.stop(container)
+
+    for container in containers:
+        lxc.destroy(container)
+
+    template = os.path.join(
+            os.environ['RIFT_INSTALL'],
+            'etc/lxc-fedora-rift.lxctemplate',
+            )
+
+    logger.info(template)
+    logger.info(args.rootfs)
+
+    # Create an image that can be used to create VMs
+    image = RwcalYang.ImageInfoItem()
+    image.name = 'rift-master'
+    image.lxc.size = '2.5G'
+    image.lxc.template_path = template
+    image.lxc.tarfile = args.rootfs
+
+    cal.create_image(account, image)
+
+    # Create a VM
+    vms = []
+    for index in range(args.num_vms):
+        vm = RwcalYang.VMInfoItem()
+        vm.vm_name = 'rift-s{}'.format(index + 1)
+        vm.image_id = image.id
+
+        cal.create_vm(account, vm)
+
+        vms.append(vm)
+
+    # Create the default and data networks
+    network = RwcalYang.NetworkInfoItem(network_name='virbr0')
+    cal.create_network(account, network)
+
+    os.system('/usr/sbin/brctl show')
+
+    # Create pairs of ports to connect the networks
+    for index, vm in enumerate(vms):
+        port = RwcalYang.PortInfoItem()
+        port.port_name = "eth0"
+        port.network_id = network.network_id
+        port.vm_id = vm.vm_id
+        port.ip_address = "192.168.122.{}".format(index + 101)
+        port.lxc.veth_name = "rws{}".format(index)
+
+        cal.create_port(account, port)
+
+    # Swap out the current instance of the plugin to test that the data is
+    # shared among different instances
+    cal = plugin.get_interface("Cloud")
+    cal.init()
+
+    # Start the VMs
+    for vm in vms:
+        cal.start_vm(account, vm.vm_id)
+
+    lxc.ls()
+
+    # Exit if the containers are not supposed to be terminated
+    if not args.terminate:
+        return
+
+    time.sleep(3)
+
+    # Stop the VMs
+    for vm in vms:
+        cal.stop_vm(account, vm.vm_id)
+
+    lxc.ls()
+
+    # Delete the VMs
+    for vm in vms:
+        cal.delete_vm(account, vm.vm_id)
+
+    # Delete the image
+    cal.delete_image(account, image.id)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/rwcal/test/test_openstack_install.py b/rwcal/test/test_openstack_install.py
new file mode 100644
index 0000000..0e4a61f
--- /dev/null
+++ b/rwcal/test/test_openstack_install.py
@@ -0,0 +1,567 @@
+"""
+#
+# 
+#   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.
+#
+#
+# @file test_openstack_install.py
+# @author Varun Prasad (varun.prasad@riftio.com)
+# @date 10/10/2015
+# @brief Test Openstack/os install
+#
+"""
+
+import logging
+import re
+import socket
+import sys
+import time
+import tempfile
+
+from keystoneclient.v3 import client
+import paramiko
+import pytest
+import requests
+import xmlrpc.client
+
+from gi.repository import RwcalYang
+from gi.repository.RwTypes import RwStatus
+import rw_peas
+import rwlogger
+
+
+logger = logging.getLogger()
+logging.basicConfig(level=logging.INFO)
+
+
+class Host(object):
+    """A wrapper on top of a host, which provides a ssh connection instance.
+
+    Assumption:
+    The username/password for the VM is default.
+    """
+    _USERNAME = "root"
+    _PASSWORD = "riftIO"
+
+    def __init__(self, hostname):
+        """
+        Args:
+            hostname (str): Hostname (grunt3.qanet.riftio.com)
+        """
+        self.hostname = hostname
+        try:
+            self.ip = socket.gethostbyname(hostname)
+        except socket.gaierror:
+            logger.error("Unable to resolve the hostname {}".format(hostname))
+            sys.exit(1)
+
+        self.ssh = paramiko.SSHClient()
+        # Note: Do not load the system keys as the test will fail if the keys
+        # change.
+        self.ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
+
+    def connect(self):
+        """Set up ssh connection.
+        """
+        logger.debug("Trying to connect to {}: {}".format(
+                self.hostname,
+                self.ip))
+
+        self.ssh.connect(
+                self.ip,
+                username=self._USERNAME,
+                password=self._PASSWORD)
+
+    def put(self, content, dest):
+        """Creates a tempfile and puts it in the destination path in the HOST.
+        Args:
+            content (str): Content to be written to a file.
+            dest (str): Path to store the content.
+        """
+        temp_file = tempfile.NamedTemporaryFile(delete=False)
+        temp_file.write(content.encode("UTF-8"))
+        temp_file.close()
+
+        logger.info("Writing {} file in {}".format(dest, self.hostname))
+        sftp = self.ssh.open_sftp()
+        sftp.put(temp_file.name, dest)
+        sftp.close()
+
+    def clear(self):
+        """Clean up
+        """
+        self.ssh.close()
+
+
+class Grunt(Host):
+    """A wrapper on top of grunt machine, provides functionalities to check
+    if the grunt is up, IP resolution.
+    """
+    @property
+    def grunt_name(self):
+        """Extract the grunt name from the FQDN
+
+        Returns:
+            str: e.g. grunt3 from grunt3.qanet.riftio.com
+        """
+        return self.hostname.split(".")[0]
+
+    @property
+    def dns_server(self):
+        """Hard-coded for now.
+        """
+        return "10.95.0.3"
+
+    @property
+    def floating_ip(self):
+        return "10.95.1.0"
+
+    @property
+    def private_ip(self):
+        """Construct the private IP from the grunt name. 10.0.xx.0 where xx is
+        value of the grunt (3 in case of grunt3)
+        """
+        host_part = re.sub(r"[a-zA-z]+", "", self.grunt_name)
+        return '10.0.{}.0'.format(host_part)
+
+    def is_system_up(self):
+        """Checks if system is up using ssh login.
+
+        Returns:
+            bool: Indicates if system is UP
+        """
+        try:
+            self.connect()
+        except OSError:
+            return False
+
+        return True
+
+    def wait_till_system_is_up(self, timeout=50, check_openstack=False):
+        """Blocking call to check if system is up.
+        Args:
+            timeout (int, optional): In mins(~).
+            check_openstack (bool, optional): If true will also check if
+                openstack is up and running on the system.
+
+        Raises:
+            OSError: If system start exceeds the timeout
+        """
+
+        TRY_DURATION = 20  # secs
+        total_tries = timeout * (60 / TRY_DURATION)  # 3 tries/mins i.e. 20 secs.
+        tries = 0
+
+        while tries < total_tries:
+            if self.is_system_up():
+                if check_openstack and self.is_openstack_up():
+                        return
+                elif not check_openstack:
+                    return
+
+            logger.info("{} down: Sleeping for {} secs. Try {} of {}".format(
+                    self.hostname,
+                    TRY_DURATION,
+                    tries,
+                    int(total_tries)))
+
+            time.sleep(TRY_DURATION)
+            tries += 1
+
+        raise OSError("Exception in system start {}({})".format(
+                self.hostname,
+                self.ip))
+
+    def is_openstack_up(self):
+        """Checks if openstack is UP, by verifying the URL.
+
+        Returns:
+            bool: Indicates if system is UP
+        """
+        url = "http://{}/dashboard/".format(self.ip)
+
+        logger.info("Checking if openstack({}) is UP".format(url))
+
+        try:
+            requests.get(url)
+        except requests.ConnectionError:
+            return False
+
+        return True
+
+
+class Cobbler(Host):
+    """A thin wrapper on cobbler and provides an interface using XML rpc client.
+
+    Assumption:
+    System instances are already added to cobbler(with ipmi). Adding instances
+    can also be automated, can be taken up sometime later.
+    """
+    def __init__(self, hostname, username="cobbler", password="cobbler"):
+        """
+        Args:
+            hostname (str): Cobbler host.
+            username (str, optional): username.
+            password (str, optional): password
+        """
+        super().__init__(hostname)
+
+        url = "https://{}/cobbler_api".format(hostname)
+
+        self.server = xmlrpc.client.ServerProxy(url)
+        logger.info("obtained a cobbler instance for the host {}".format(hostname))
+
+        self.token = self.server.login(username, password)
+        self.connect()
+
+    def create_profile(self, profile_name, ks_file):
+        """Create the profile for the system.
+
+        Args:
+            profile_name (str): Name of the profile.
+            ks_file (str): Path of the kick start file.
+        """
+        profile_attrs = {
+                "name": profile_name,
+                "kickstart": ks_file,
+                "repos": ['riftware', 'rift-misc', 'fc21-x86_64-updates',
+                          'fc21-x86_64', 'openstack-kilo'],
+                "owners": ["admin"],
+                "distro": "FC21.3-x86_64"
+                }
+
+        profile_id = self.server.new_profile(self.token)
+        for key, value in profile_attrs.items():
+            self.server.modify_profile(profile_id, key, value, self.token)
+        self.server.save_profile(profile_id, self.token)
+
+    def create_snippet(self, snippet_name, snippet_content):
+        """Unfortunately the XML rpc apis don't provide a direct interface to
+        create snippets, so falling back on the default sftp methods.
+
+        Args:
+            snippet_name (str): Name.
+            snippet_content (str): snippet's content.
+
+        Returns:
+            str: path where the snippet is stored
+        """
+        path = "/var/lib/cobbler/snippets/{}".format(snippet_name)
+        self.put(snippet_content, path)
+        return path
+
+    def create_kickstart(self, ks_name, ks_content):
+        """Creates and returns the path of the ks file.
+
+        Args:
+            ks_name (str): Name of the ks file to be saved.
+            ks_content (str): Content for ks file.
+
+        Returns:
+            str: path where the ks file is saved.
+        """
+        path = "/var/lib/cobbler/kickstarts/{}".format(ks_name)
+        self.put(ks_content, path)
+        return path
+
+    def boot_system(self, grunt, profile_name, false_boot=False):
+        """Boots the system with the profile specified. Also enable net-boot
+
+        Args:
+            grunt (Grunt): instance of grunt
+            profile_name (str): A valid profile name.
+            false_boot (bool, optional): debug only option.
+        """
+        if false_boot:
+            return
+
+        system_id = self.server.get_system_handle(
+                grunt.grunt_name,
+                self.token)
+        self.server.modify_system(
+                system_id,
+                "profile",
+                profile_name,
+                self.token)
+
+        self.server.modify_system(
+                system_id,
+                "netboot_enabled",
+                "True",
+                self.token)
+        self.server.save_system(system_id, self.token)
+        self.server.power_system(system_id, "reboot", self.token)
+
+
+class OpenstackTest(object):
+    """Driver class to automate the installation.
+    """
+    def __init__(
+            self,
+            cobbler,
+            controller,
+            compute_nodes=None,
+            test_prefix="openstack_test"):
+        """
+        Args:
+            cobbler (Cobbler): Instance of Cobbler
+            controller (Controller): Controller node instance
+            compute_nodes (TYPE, optional): A list of Grunt nodes to be set up
+                    as compute nodes.
+            test_prefix (str, optional): All entities created by the script are
+                    prefixed with this string.
+        """
+        self.cobbler = cobbler
+        self.controller = controller
+        self.compute_nodes = [] if compute_nodes is None else compute_nodes
+        self.test_prefix = test_prefix
+
+    def _prepare_snippet(self):
+        """Prepares the config based on the controller and compute nodes.
+
+        Returns:
+            str: Openstack config content.
+        """
+        content = ""
+
+        config = {}
+        config['host_name'] = self.controller.grunt_name
+        config['ip'] = self.controller.ip
+        config['dns_server'] = self.controller.dns_server
+        config['private_ip'] = self.controller.private_ip
+        config['floating_ip'] = self.controller.floating_ip
+
+        content += Template.GRUNT_CONFIG.format(**config)
+        for compute_node in self.compute_nodes:
+            config["host_name"] = compute_node.grunt_name
+            content += Template.GRUNT_CONFIG.format(**config)
+
+        content = Template.SNIPPET_TEMPLATE.format(config=content)
+
+        return content
+
+    def prepare_profile(self):
+        """Creates the cobbler profile.
+        """
+        snippet_content = self._prepare_snippet()
+        self.cobbler.create_snippet(
+                "{}.cfg".format(self.test_prefix),
+                snippet_content)
+
+        ks_content = Template.KS_TEMPATE
+        ks_file = self.cobbler.create_kickstart(
+                "{}.ks".format(self.test_prefix),
+                ks_content)
+
+        self.cobbler.create_profile(self.test_prefix, ks_file)
+        return self.test_prefix
+
+    def _get_cal_account(self):
+        """
+        Creates an object for class RwcalYang.CloudAccount()
+        """
+        account                        = RwcalYang.CloudAccount()
+        account.account_type           = "openstack"
+        account.openstack.key          = "{}_user".format(self.test_prefix)
+        account.openstack.secret       = "mypasswd"
+        account.openstack.auth_url     = 'http://{}:35357/v3/'.format(self.controller.ip)
+        account.openstack.tenant       = self.test_prefix
+
+        return account
+
+    def start(self):
+        """Starts the installation.
+        """
+        profile_name = self.prepare_profile()
+
+        self.cobbler.boot_system(self.controller, profile_name)
+        self.controller.wait_till_system_is_up(check_openstack=True)
+
+        try:
+            logger.info("Controller system is UP. Setting up compute nodes")
+            for compute_node in self.compute_nodes:
+                self.cobbler.boot_system(compute_node, profile_name)
+                compute_node.wait_till_system_is_up()
+        except OSError as e:
+            logger.error("System set-up failed {}".format(e))
+            sys.exit(1)
+
+        # Currently we don't have wrapper on top of users/projects so using
+        # keystone API directly
+        acct = self._get_cal_account()
+
+        keystone_conn = client.Client(
+                auth_url=acct.openstack.auth_url,
+                username='admin',
+                password='mypasswd')
+
+        # Create a test project
+        project = keystone_conn.projects.create(
+                acct.openstack.tenant,
+                "default",
+                description="Openstack test project")
+
+        # Create an user
+        user = keystone_conn.users.create(
+                acct.openstack.key,
+                password=acct.openstack.secret,
+                default_project=project)
+
+        # Make the newly created user as ADMIN
+        admin_role = keystone_conn.roles.list(name="admin")[0]
+        keystone_conn.roles.grant(
+                admin_role.id,
+                user=user.id,
+                project=project.id)
+
+        # nova API needs to be restarted, otherwise the new service doesn't play
+        # well
+        self.controller.ssh.exec_command("source keystonerc_admin && "
+                "service openstack-nova-api restart")
+        time.sleep(10)
+
+        return acct
+
+    def clear(self):
+        """Close out all SFTP connections.
+        """
+        nodes = [self.controller]
+        nodes.extend(self.compute_nodes)
+        for node in nodes:
+            node.clear()
+
+
+###############################################################################
+## Begin pytests
+###############################################################################
+
+
+@pytest.fixture(scope="session")
+def cal(request):
+    """
+    Loads rw.cal plugin via libpeas
+    """
+    plugin = rw_peas.PeasPlugin('rwcal_openstack', 'RwCal-1.0')
+    engine, info, extension = plugin()
+
+    # Get the RwLogger context
+    rwloggerctx = rwlogger.RwLog.Ctx.new("Cal-Log")
+
+    cal = plugin.get_interface("Cloud")
+    try:
+        rc = cal.init(rwloggerctx)
+        assert rc == RwStatus.SUCCESS
+    except:
+        logger.error("ERROR:Cal plugin instantiation failed. Aborting tests")
+    else:
+        logger.info("Openstack Cal plugin successfully instantiated")
+
+    return cal
+
+
+@pytest.fixture(scope="session")
+def account(request):
+    """Creates an openstack instance with 1 compute node and returns the newly
+    created account.
+    """
+    cobbler = Cobbler("qacobbler.eng.riftio.com")
+    controller = Grunt("grunt3.qanet.riftio.com")
+    compute_nodes = [Grunt("grunt5.qanet.riftio.com")]
+
+    test = OpenstackTest(cobbler, controller, compute_nodes)
+    account = test.start()
+
+    request.addfinalizer(test.clear)
+    return account
+
+
+def test_list_images(cal, account):
+    """Verify if 2 images are present
+    """
+    status, resources = cal.get_image_list(account)
+    assert len(resources.imageinfo_list) == 2
+
+def test_list_flavors(cal, account):
+    """Basic flavor checks
+    """
+    status, resources = cal.get_flavor_list(account)
+    assert len(resources.flavorinfo_list) == 5
+
+
+class Template(object):
+    """A container to hold all cobbler related templates.
+    """
+    GRUNT_CONFIG = """
+{host_name})
+    CONTROLLER={ip}
+    BRGIF=1
+    OVSDPDK=N
+    TRUSTED=N
+    QAT=N
+    HUGEPAGE=0
+    VLAN=10:14
+    PRIVATE_IP={private_ip}
+    FLOATING_IP={floating_ip}
+    DNS_SERVER={dns_server}
+    ;;
+
+    """
+
+    SNIPPET_TEMPLATE = """
+# =====================Begining of snippet=================
+# snippet openstack_test.cfg
+case $name in
+
+{config}
+
+*)
+    ;;
+esac
+
+# =====================End of snippet=================
+
+"""
+
+    KS_TEMPATE = """
+$SNIPPET('rift-repos')
+$SNIPPET('rift-base')
+%packages
+@core
+wget
+$SNIPPET('rift-grunt-fc21-packages')
+ganglia-gmetad
+ganglia-gmond
+%end
+
+%pre
+$SNIPPET('log_ks_pre')
+$SNIPPET('kickstart_start')
+# Enable installation monitoring
+$SNIPPET('pre_anamon')
+%end
+
+%post --log=/root/ks_post.log
+$SNIPPET('openstack_test.cfg')
+$SNIPPET('ganglia')
+$SNIPPET('rift-post-yum')
+$SNIPPET('rift-post')
+$SNIPPET('rift_fix_grub')
+
+$SNIPPET('rdo-post')
+echo "banner RDO test" >> /etc/profile
+
+$SNIPPET('kickstart_done')
+%end
+"""
diff --git a/rwcal/test/test_rwcal_openstack.py b/rwcal/test/test_rwcal_openstack.py
new file mode 100644
index 0000000..4ce494b
--- /dev/null
+++ b/rwcal/test/test_rwcal_openstack.py
@@ -0,0 +1,1057 @@
+
+# 
+#   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 datetime
+import logging
+import time
+import unittest
+import hashlib
+
+import novaclient.exceptions as nova_exception
+import paramiko
+import rw_peas
+import rwlogger
+from keystoneclient import v3 as ksclient
+
+from gi.repository import RwcalYang
+from gi.repository.RwTypes import RwStatus
+from rift.rwcal.openstack.openstack_drv import KeystoneDriver, NovaDriver
+
+logger = logging.getLogger('rwcal-openstack')
+
+#
+# Important information about openstack installation. This needs to be manually verified 
+#
+openstack_info = {
+    'username'           : 'pluto',
+    'password'           : 'mypasswd',
+    'auth_url'           : 'http://10.66.4.14:5000/v3/',
+    'project_name'       : 'demo',
+    'mgmt_network'       : 'private',
+    'reserved_flavor'    : 'm1.medium',
+    'reserved_image'     : 'rift-root-latest.qcow2',
+    'physical_network'   : None,
+    'network_type'       : None,
+    'segmentation_id'    : None
+    }
+
+
+def get_cal_account():
+    """
+    Creates an object for class RwcalYang.CloudAccount()
+    """
+    account                        = RwcalYang.CloudAccount()
+    account.account_type           = "openstack"
+    account.openstack.key          = openstack_info['username']
+    account.openstack.secret       = openstack_info['password']
+    account.openstack.auth_url     = openstack_info['auth_url']
+    account.openstack.tenant       = openstack_info['project_name']
+    account.openstack.mgmt_network = openstack_info['mgmt_network']
+    return account
+
+def get_cal_plugin():
+    """
+    Loads rw.cal plugin via libpeas
+    """
+    plugin = rw_peas.PeasPlugin('rwcal_openstack', 'RwCal-1.0')
+    engine, info, extension = plugin()
+
+    # Get the RwLogger context
+    rwloggerctx = rwlogger.RwLog.Ctx.new("Cal-Log")
+
+    cal = plugin.get_interface("Cloud")
+    try:
+        rc = cal.init(rwloggerctx)
+        assert rc == RwStatus.SUCCESS
+    except:
+        logger.error("ERROR:Cal plugin instantiation failed. Aborting tests")
+    else:
+        logger.info("Openstack Cal plugin successfully instantiated")
+    return cal 
+
+
+class OpenStackTest(unittest.TestCase):
+    NodeID = "123456789012345" # Some random number to test VM tagging
+    MemoryPageSize = "LARGE"
+    CpuPolicy = "DEDICATED"
+    CpuThreadPolicy = "SEPARATE"
+    CpuThreads = 1
+    NumaNodeCount = 2
+    HostTrust = "trusted"
+    PCIPassThroughAlias = "PCI_10G_ALIAS"
+    SEG_ID = openstack_info['segmentation_id']
+    
+    def setUp(self):
+        """
+        Assumption:
+         - It is assumed that openstack install has a flavor and image precreated.
+         - Flavor_name: x1.xlarge
+         - Image_name : rwimage
+
+        If these resources are not then this test will fail.
+        """
+        self._acct = get_cal_account()
+        logger.info("Openstack-CAL-Test: setUp")
+        self.cal   = get_cal_plugin()
+        logger.info("Openstack-CAL-Test: setUpEND")
+        
+        # First check for VM Flavor and Image and get the corresponding IDs
+        rc, rs = self.cal.get_flavor_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        flavor_list = [ flavor for flavor in rs.flavorinfo_list if flavor.name == openstack_info['reserved_flavor'] ]
+        self.assertNotEqual(len(flavor_list), 0)
+        self._flavor = flavor_list[0]
+
+        rc, rs = self.cal.get_image_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        image_list = [ image for image in rs.imageinfo_list if image.name == openstack_info['reserved_image'] ]
+        self.assertNotEqual(len(image_list), 0)
+        self._image = image_list[0]
+
+        rc, rs = self.cal.get_network_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        networks = [ network for network in rs.networkinfo_list if (network.network_name == 'rift.cal.unittest.network' or network.network_name == 'rift.cal.virtual_link') ]
+        for network in networks:
+            self.cal.delete_virtual_link(self._acct, network.network_id)
+            
+    def tearDown(self):
+        logger.info("Openstack-CAL-Test: tearDown")
+        
+
+    def _md5(fname, blksize=1048576):
+        hash_md5 = hashlib.md5()
+        with open(fname, "rb") as f:
+            for chunk in iter(lambda: f.read(blksize), b""):
+                hash_md5.update(chunk)
+        return hash_md5.hexdigest()
+
+    @unittest.skip("Skipping test_list_flavors")        
+    def test_list_flavor(self):
+        """
+        List existing flavors from openstack installation
+        """
+        logger.info("Openstack-CAL-Test: Starting List Flavors Test")
+        rc, rsp = self.cal.get_flavor_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Received %d flavors" %(len(rsp.flavorinfo_list)))
+        for flavor in rsp.flavorinfo_list:
+            rc, flv = self.cal.get_flavor(self._acct, flavor.id)
+            self.assertEqual(rc, RwStatus.SUCCESS)
+            self.assertEqual(flavor.id, flv.id)
+        
+    @unittest.skip("Skipping test_list_images")                    
+    def test_list_images(self):
+        """
+        List existing images from openstack installation
+        """
+        logger.info("Openstack-CAL-Test: Starting List Images Test")
+        rc, rsp = self.cal.get_image_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Received %d images" %(len(rsp.imageinfo_list)))
+        #for image in rsp.imageinfo_list:
+        #    rc, img = self.cal.get_image(self._acct, image.id)
+        #    self.assertEqual(rc, RwStatus.SUCCESS)
+        #    self.assertEqual(image.id, img.id)
+        
+    @unittest.skip("Skipping test_list_vms")                
+    def test_list_vms(self):
+        """
+        List existing VMs from openstack installation
+        """
+        logger.info("Openstack-CAL-Test: Starting List VMs Test")
+        rc, rsp = self.cal.get_vm_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Received %d VMs" %(len(rsp.vminfo_list)))
+        for vm in rsp.vminfo_list:
+            rc, server = self.cal.get_vm(self._acct, vm.vm_id)
+            self.assertEqual(vm.vm_id, server.vm_id)
+            
+    @unittest.skip("Skipping test_list_networks")                            
+    def test_list_networks(self):
+        """
+        List existing Network from openstack installation
+        """
+        logger.info("Openstack-CAL-Test: Starting List Networks Test")
+        rc, rsp = self.cal.get_network_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Received %d Networks" %(len(rsp.networkinfo_list)))
+        for network in rsp.networkinfo_list:
+            rc, net = self.cal.get_network(self._acct, network.network_id)
+            self.assertEqual(network.network_id, net.network_id)
+        
+    @unittest.skip("Skipping test_list_ports")                                    
+    def test_list_ports(self):
+        """
+        List existing Ports from openstack installation
+        """
+        logger.info("Openstack-CAL-Test: Starting List Ports Test")
+        rc, rsp = self.cal.get_port_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        assert(rc == RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Received %d Ports" %(len(rsp.portinfo_list)))
+        for port in rsp.portinfo_list:
+            rc, p = self.cal.get_port(self._acct, port.port_id)
+            self.assertEqual(port.port_id, p.port_id)
+
+    def _get_image_info_request(self):
+        """
+        Returns request object of type RwcalYang.ImageInfoItem()
+        """
+        img = RwcalYang.ImageInfoItem()
+        img.name = "rift.cal.unittest.image"
+        img.location = '/net/sharedfiles/home1/common/vm/rift-root-latest.qcow2'
+        img.disk_format = "qcow2"
+        img.container_format = "bare"
+        img.checksum = self._md5(img.location)
+        return img
+
+    def _get_image_info(self, img_id):
+        """
+        Checks the image status until it becomes active or timeout occurs (100sec)
+        Returns the image_info dictionary
+        """
+        rs = None
+        rc = None
+        for i in range(100):
+            rc, rs = self.cal.get_image(self._acct, img_id)
+            self.assertEqual(rc, RwStatus.SUCCESS)
+            logger.info("Openstack-CAL-Test: Image (image_id: %s) reached state : %s" %(img_id, rs.state))
+            if rs.state == 'active':
+                break
+            else:
+                time.sleep(2) # Sleep for a second
+        return rs
+    
+    @unittest.skip("Skipping test_create_delete_image")                            
+    def test_create_delete_image(self):
+        """
+        Create/Query/Delete a new image in openstack installation
+        """
+        logger.info("Openstack-CAL-Test: Starting Image create test")
+        img = self._get_image_info_request()
+        rc, img_id = self.cal.create_image(self._acct, img)
+        logger.info("Openstack-CAL-Test: Created Image with image_id: %s" %(img_id))
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        img_info = self._get_image_info(img_id)
+        self.assertNotEqual(img_info, None)
+        self.assertEqual(img_id, img_info.id)
+        logger.info("Openstack-CAL-Test: Image (image_id: %s) reached state : %s" %(img_id, img_info.state))
+        self.assertEqual(img_info.has_field('checksum'), True)
+        #self.assertEqual(img_info.checksum, OpenStackTest.IMG_Checksum)
+        logger.info("Openstack-CAL-Test: Initiating Delete Image operation for image_id: %s" %(img_id))
+        rc = self.cal.delete_image(self._acct, img_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Image (image_id: %s) successfully deleted" %(img_id))
+
+    def _get_flavor_info_request(self):
+        """
+        Returns request object of type RwcalYang.FlavorInfoItem()
+        """
+        flavor                                     = RwcalYang.FlavorInfoItem()
+        flavor.name                                = 'rift.cal.unittest.flavor'
+        flavor.vm_flavor.memory_mb                 = 16384 # 16GB
+        flavor.vm_flavor.vcpu_count                = 4 
+        flavor.vm_flavor.storage_gb                = 40 # 40GB
+        flavor.guest_epa.mempage_size              = OpenStackTest.MemoryPageSize
+        flavor.guest_epa.cpu_pinning_policy        = OpenStackTest.CpuPolicy
+        flavor.guest_epa.cpu_thread_pinning_policy = OpenStackTest.CpuThreadPolicy
+        flavor.guest_epa.numa_node_policy.node_cnt = OpenStackTest.NumaNodeCount
+        for i in range(OpenStackTest.NumaNodeCount):
+            node = flavor.guest_epa.numa_node_policy.node.add()
+            node.id = i
+            if i == 0:
+                node.vcpu = [0,1]
+            elif i == 1:
+                node.vcpu = [2,3]
+            node.memory_mb = 8196
+        dev = flavor.guest_epa.pcie_device.add()
+        dev.device_id = OpenStackTest.PCIPassThroughAlias
+        dev.count = 1
+        return flavor
+        
+    @unittest.skip("Skipping test_create_delete_flavor")                            
+    def test_create_delete_flavor(self):
+        """
+        Create/Query/Delete a new flavor in openstack installation
+        """
+        logger.info("Openstack-CAL-Test: Starting Image create/delete test")
+
+        ### Delete any previously created flavor with name rift.cal.unittest.flavor
+        rc, rs = self.cal.get_flavor_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        flavor_list = [ flavor for flavor in rs.flavorinfo_list if flavor.name == 'rift.cal.unittest.flavor' ]
+        if flavor_list:
+            rc = self.cal.delete_flavor(self._acct, flavor_list[0].id)
+            self.assertEqual(rc, RwStatus.SUCCESS)
+        
+        flavor = self._get_flavor_info_request()
+        rc, flavor_id = self.cal.create_flavor(self._acct, flavor)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        
+        logger.info("Openstack-CAL-Test: Created new flavor with flavor_id : %s" %(flavor_id))
+        rc, rs = self.cal.get_flavor(self._acct, flavor_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        self.assertEqual(rs.id, flavor_id)
+
+        # Verify EPA Attributes
+        self.assertEqual(rs.guest_epa.mempage_size, OpenStackTest.MemoryPageSize)
+        self.assertEqual(rs.guest_epa.cpu_pinning_policy, OpenStackTest.CpuPolicy)
+        self.assertEqual(rs.guest_epa.cpu_thread_pinning_policy, OpenStackTest.CpuThreadPolicy)
+        self.assertEqual(rs.guest_epa.numa_node_policy.node_cnt, OpenStackTest.NumaNodeCount)
+        self.assertEqual(len(rs.guest_epa.pcie_device), 1)
+        self.assertEqual(rs.guest_epa.pcie_device[0].device_id, OpenStackTest.PCIPassThroughAlias)
+        self.assertEqual(rs.guest_epa.pcie_device[0].count, 1)
+        logger.info("Openstack-CAL-Test: Initiating delete for flavor_id : %s" %(flavor_id))
+        rc = self.cal.delete_flavor(self._acct, flavor_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        # Check that flavor does not exist anymore in list_flavor
+        rc, rs = self.cal.get_flavor_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        flavor_list = [ flavor for flavor in rs.flavorinfo_list if flavor.id == flavor_id ]
+        # Flavor List should be empty
+        self.assertEqual(len(flavor_list), 0)
+        logger.info("Openstack-CAL-Test: Flavor (flavor_id: %s) successfully deleted" %(flavor_id))
+
+    def _get_vm_info_request(self, flavor_id, image_id):
+        """
+        Returns request object of type RwcalYang.VMInfoItem
+        """
+        vm = RwcalYang.VMInfoItem()
+        vm.vm_name = 'rift.cal.unittest.vm'
+        vm.flavor_id = flavor_id
+        vm.image_id  = image_id
+        vm.cloud_init.userdata = ''
+        vm.user_tags.node_id  = OpenStackTest.NodeID
+        return vm
+
+    def _check_vm_state(self, vm_id, expected_state):
+        """
+        Wait until VM reaches particular state (expected_state). 
+        """
+        # Wait while VM goes to required state
+
+        for i in range(50): # 50 poll iterations...
+            rc, rs = self.cal.get_vm(self._acct, vm_id)
+            self.assertEqual(rc, RwStatus.SUCCESS)
+            logger.info("Openstack-CAL-Test: VM vm_id : %s. Current VM state : %s " %(vm_id, rs.state))
+            if rs.state == expected_state:
+                break
+            else:
+                time.sleep(1)
+
+        rc, rs = self.cal.get_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        self.assertEqual(rs.state, expected_state)
+
+    def _create_vm(self, flavor, image, port_list = None):
+        """
+        Create VM and perform validity checks
+        """
+        logger.info("Openstack-CAL-Test: Using image : %s and flavor : %s " %(image.name, flavor.name))
+        vm = self._get_vm_info_request(flavor.id, image.id)
+
+        if port_list:
+            for port_id in port_list:
+                port = vm.port_list.add()
+                port.port_id = port_id 
+
+        rc, vm_id = self.cal.create_vm(self._acct, vm)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        ### Check if VM creation is successful
+        rc, rs = self.cal.get_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Successfully created VM with vm_id : %s. Current VM state : %s " %(vm_id, rs.state))
+
+        ### Ensure the VM state is active
+        self._check_vm_state(vm_id, 'ACTIVE')
+
+        ### Ensure that userdata tags are set as expected
+        rc, rs = self.cal.get_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        self.assertEqual(rs.user_tags.has_field('node_id'), True)
+        self.assertEqual(getattr(rs.user_tags, 'node_id'), OpenStackTest.NodeID)
+        logger.info("Openstack-CAL-Test: Successfully verified the user tags for VM-ID: %s" %(vm_id))
+        return rs, vm_id
+
+    def _delete_vm(self, vm_id):
+        """
+        Delete VM and perform validity checks
+        """
+        rc, rs = self.cal.get_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        logger.info("Openstack-CAL-Test: Initiating VM Delete operation on VM vm_id : %s. Current VM state : %s " %(vm_id, rs.state))
+
+        rc = self.cal.delete_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        for i in range(50):
+            # Check if VM still exists
+            rc, rs = self.cal.get_vm_list(self._acct)
+            self.assertEqual(rc, RwStatus.SUCCESS)
+            vm_list = [vm for vm in rs.vminfo_list if vm.vm_id == vm_id]
+            if not len(vm_list):
+                break
+        
+        rc, rs = self.cal.get_vm_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        vm_list = [vm for vm in rs.vminfo_list if vm.vm_id == vm_id]
+        self.assertEqual(len(vm_list), 0)
+        logger.info("Openstack-CAL-Test: VM with vm_id : %s successfully deleted" %(vm_id))
+
+    def _stop_vm(self, vm_id):
+        """
+        Stop VM and perform validity checks
+        """
+        rc, rs = self.cal.get_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Initiating Stop VM operation on VM vm_id : %s. Current VM state : %s " %(vm_id, rs.state))
+        rc = self.cal.stop_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        ### Ensure that VM state is SHUTOFF
+        self._check_vm_state(vm_id, 'SHUTOFF')
+        
+        
+    def _start_vm(self, vm_id):
+        """
+        Starts VM and performs validity checks
+        """
+        rc, rs = self.cal.get_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Initiating Start VM operation on VM vm_id : %s. Current VM state : %s " %(vm_id, rs.state))
+        rc = self.cal.start_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        ### Ensure that VM state is ACTIVE
+        self._check_vm_state(vm_id, 'ACTIVE')
+
+        
+    def _reboot_vm(self, vm_id):
+        """
+        Reboot VM and perform validity checks
+        """
+        rc, rs = self.cal.get_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Initiating Reboot VM operation on VM vm_id : %s. Current VM state : %s " %(vm_id, rs.state))
+        rc = self.cal.reboot_vm(self._acct, vm_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        ### Ensure that VM state is ACTIVE
+        self._check_vm_state(vm_id, 'ACTIVE')
+
+    def assert_vm(self, vm_data, flavor):
+        """Verify the newly created VM for attributes specified in the flavor.
+
+        Args:
+            vm_data (VmData): Instance of the newly created VM
+            flavor (FlavorInfoItem): Config flavor.
+        """
+        vm_config = flavor
+
+        # Page size seems to be 4096, regardless of the page size name.
+        page_lookup = {"large": '4096', "small": '4096'}
+        FIELDS = ["vcpus", "cpu_threads", "memory_page_size", "disk",
+                  "numa_node_count", "memory", "pci_passthrough_device_list"]
+
+        for field in FIELDS:
+            if field not in vm_config:
+                continue
+
+            vm_value = getattr(vm_data, field)
+            config_value = getattr(vm_config, field)
+
+            if field == "memory_page_size":
+                config_value = page_lookup[config_value]
+
+            if field == "memory":
+                config_value = int(config_value/1000)
+
+            if field == "pci_passthrough_device_list":
+                config_value = len(config_value)
+                vm_value = len(vm_value)
+
+            self.assertEqual(vm_value, config_value)
+
+    @unittest.skip("Skipping test_vm_epa_attributes")
+    def test_vm_epa_attributes(self):
+        """
+        Primary goal: To create a VM with the specified EPA Attributes
+        Secondary goal: To verify flavor creation/delete
+        """
+
+        logger.info("Openstack-CAL-Test: Starting VM(EPA) create/delete test")
+        flavor = self._get_flavor_info_request()
+   
+        rc, flavor_id = self.cal.do_create_flavor(self._acct, flavor)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        flavor.id = flavor_id
+
+        data, vm_id = self._create_vm(flavor, self._image)
+
+        vm_data = VmData(data.host_name, data.management_ip)
+        self.assert_vm(vm_data, flavor)
+
+        self._delete_vm(vm_id)
+
+        rc = self.cal.do_delete_flavor(self._acct, flavor_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+    @unittest.skip("Skipping test_expiry_token")
+    def test_expiry_token(self):
+        """
+        Primary goal: To verify if we are refreshing the expired tokens.
+        """
+        logger.info("Openstack-CAL-Test: Starting token refresh test")
+        drv = KeystoneDriver(
+                openstack_info['username'],
+                openstack_info['password'],
+                openstack_info['auth_url'],
+                openstack_info['project_name'])
+        # Get hold of the client instance need for Token Manager
+        client = drv._get_keystone_connection()
+
+        auth_ref = client.auth_ref
+        token = auth_ref['auth_token']
+
+        # Verify if the newly acquired token works.
+        nova = NovaDriver(drv)
+        flavors = nova.flavor_list()
+        self.assertTrue(len(flavors) > 1)
+
+        # Invalidate the token
+        token_manger = ksclient.tokens.TokenManager(client)
+        token_manger.revoke_token(token)
+
+        time.sleep(10)
+
+        unauth_exp = False
+        try:
+            flavors = nova.flavor_list()
+            print (flavors)
+        except nova_exception.AuthorizationFailure:
+            unauth_exp = True
+
+        self.assertTrue(unauth_exp)
+
+        # Explicitly reset the expire time, to test if we acquire a new token
+        now = datetime.datetime.utcnow()
+        time_str = format(now, "%Y-%m-%dT%H:%M:%S.%fZ")
+        drv._get_keystone_connection().auth_ref['expires_at'] = time_str
+
+        flavors = nova.flavor_list()
+        self.assertTrue(len(flavors) > 1)
+
+    @unittest.skip("Skipping test_vm_operations")                            
+    def test_vm_operations(self):
+        """
+        Primary goal: Create/Query/Delete VM in openstack installation.
+        Secondary goal: VM pause/resume operations on VM.
+
+        """
+        logger.info("Openstack-CAL-Test: Starting VM Operations test")
+
+        # Create VM
+        data, vm_id = self._create_vm(self._flavor, self._image)
+
+        # Stop VM
+        self._stop_vm(vm_id)
+        # Start VM
+        self._start_vm(vm_id)
+
+        vm_data = VmData(data.host_name, data.management_ip)
+        self.assert_vm(vm_data, self._flavor)
+
+        # Reboot VM
+        self._reboot_vm(vm_id)
+        ### Delete the VM
+        self._delete_vm(vm_id)
+
+        
+    def _get_network_info_request(self):
+        """
+        Returns request object of type RwcalYang.NetworkInfoItem
+        """
+        network                            = RwcalYang.NetworkInfoItem()
+        network.network_name               = 'rift.cal.unittest.network'
+        network.subnet                     = '192.168.16.0/24'
+        if openstack_info['physical_network']:
+            network.provider_network.physical_network = openstack_info['physical_network']
+        if openstack_info['network_type']:
+            network.provider_network.overlay_type     = openstack_info['network_type']
+        if OpenStackTest.SEG_ID:
+            network.provider_network.segmentation_id  = OpenStackTest.SEG_ID
+            OpenStackTest.SEG_ID += 1
+        return network
+
+
+    def _create_network(self):
+        """
+        Create a network and verify that network creation is successful
+        """
+        network = self._get_network_info_request()
+
+        ### Create network
+        logger.info("Openstack-CAL-Test: Creating a network with name : %s" %(network.network_name))
+        rc, net_id = self.cal.create_network(self._acct, network)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        ### Verify network is created successfully
+        rc, rs = self.cal.get_network(self._acct, net_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Successfully create Network : %s  with id : %s." %(network.network_name, net_id ))
+
+        return net_id
+
+    def _delete_network(self, net_id):
+        """
+        Delete network and verify that delete operation is successful
+        """
+        rc, rs = self.cal.get_network(self._acct, net_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        logger.info("Openstack-CAL-Test: Deleting a network with id : %s. " %(net_id))
+        rc = self.cal.delete_network(self._acct, net_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        
+        # Verify that network is no longer available via get_network_list API
+        rc, rs = self.cal.get_network_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        network_info = [ network for network in rs.networkinfo_list if network.network_id == net_id ]
+        self.assertEqual(len(network_info), 0)
+        logger.info("Openstack-CAL-Test: Successfully deleted Network with id : %s" %(net_id))
+        
+        
+    @unittest.skip("Skipping test_network_operations")                            
+    def test_network_operations(self):
+        """
+        Create/Delete Networks
+        """
+        logger.info("Openstack-CAL-Test: Starting Network Operation test")
+
+        ### Create Network
+        net_id = self._create_network()
+
+        ### Delete Network
+        self._delete_network(net_id)
+
+    def _get_port_info_request(self, network_id, vm_id):
+        """
+        Returns an object of type RwcalYang.PortInfoItem
+        """
+        port = RwcalYang.PortInfoItem()
+        port.port_name = 'rift.cal.unittest.port'
+        port.network_id = network_id
+        if vm_id != None:
+            port.vm_id = vm_id
+        return port
+
+    def _create_port(self, net_id, vm_id = None):
+        """
+        Create a port in network with network_id: net_id and verifies that operation is successful
+        """
+        if vm_id != None:
+            logger.info("Openstack-CAL-Test: Creating a port in network with network_id: %s and VM with vm_id: %s" %(net_id, vm_id))
+        else:
+            logger.info("Openstack-CAL-Test: Creating a port in network with network_id: %s" %(net_id))
+
+        ### Create Port
+        port = self._get_port_info_request(net_id, vm_id)
+        rc, port_id = self.cal.create_port(self._acct, port)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        ### Get Port
+        rc, rs = self.cal.get_port(self._acct, port_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Successfully create Port with id : %s. Port State :  %s" %(port_id, rs.port_state))
+
+        return port_id
+
+    def _delete_port(self, port_id):
+        """
+        Deletes a port and verifies that operation is successful
+        """
+        rc, rs = self.cal.get_port(self._acct, port_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Deleting Port with id : %s. Port State :  %s" %(port_id, rs.port_state))
+
+        ### Delete Port
+        self.cal.delete_port(self._acct, port_id)
+        
+        rc, rs = self.cal.get_port_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        port_list = [ port for port in rs.portinfo_list if port.port_id == port_id ]
+        self.assertEqual(len(port_list), 0)
+        logger.info("Openstack-CAL-Test: Successfully Deleted Port with id : %s" %(port_id))
+
+    def _monitor_port(self, port_id, expected_state):
+        """
+        Monitor the port state until it reaches expected_state
+        """
+        for i in range(50):
+            rc, rs = self.cal.get_port(self._acct, port_id)
+            self.assertEqual(rc, RwStatus.SUCCESS)
+            logger.info("Openstack-CAL-Test: Port with id : %s. Port State :  %s" %(port_id, rs.port_state))
+            if rs.port_state == expected_state:
+                break
+        rc, rs = self.cal.get_port(self._acct, port_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        self.assertEqual(rs.port_state, expected_state)
+        logger.info("Openstack-CAL-Test: Port with port_id : %s reached expected state  : %s" %(port_id, rs.port_state))
+            
+    @unittest.skip("Skipping test_port_operations_with_vm")
+    def test_port_operations_with_vm(self):
+        """
+        Create/Delete Ports in a network and associate it with a VM
+        """
+        logger.info("Openstack-CAL-Test: Starting Port Operation test with VM")
+
+        ### First create a network
+        net_id = self._create_network()
+
+        ### Create a VM
+        data, vm_id = self._create_vm(self._flavor, self._image)
+
+        ### Now create Port which connects VM to Network
+        port_id = self._create_port(net_id, vm_id)
+
+        ### Verify that port goes to active state
+        self._monitor_port(port_id, 'ACTIVE')
+
+        ### Delete VM
+        self._delete_vm(vm_id)
+        
+        ### Delete Port
+        self._delete_port(port_id)
+
+        ### Delete the network
+        self._delete_network(net_id)
+
+    @unittest.skip("Skipping test_create_vm_with_port")
+    def test_create_vm_with_port(self):
+        """
+        Create VM and add ports to it during boot time.
+        """
+        logger.info("Openstack-CAL-Test: Starting Create VM with port test")
+
+        ### First create a network
+        net_id = self._create_network()
+
+        ### Now create Port which connects VM to Network
+        port_id = self._create_port(net_id)
+
+        ### Create a VM
+        data, vm_id = self._create_vm(self._flavor, self._image, [port_id])
+
+        ### Verify that port goes to active state
+        self._monitor_port(port_id, 'ACTIVE')
+
+        ### Delete VM
+        self._delete_vm(vm_id)
+        
+        ### Delete Port
+        self._delete_port(port_id)
+
+        ### Delete the network
+        self._delete_network(net_id)
+
+    @unittest.skip("Skipping test_get_vdu_list")
+    def test_get_vdu_list(self):
+        """
+        Test the get_vdu_list API
+        """
+        logger.info("Openstack-CAL-Test: Test Get VDU List APIs")
+        rc, rsp = self.cal.get_vdu_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Received %d VDUs" %(len(rsp.vdu_info_list)))
+        for vdu in rsp.vdu_info_list:
+            rc, vdu2 = self.cal.get_vdu(self._acct, vdu.vdu_id)
+            self.assertEqual(vdu2.vdu_id, vdu.vdu_id)
+
+
+    @unittest.skip("Skipping test_get_virtual_link_list")
+    def test_get_virtual_link_list(self):
+        """
+        Test the get_virtual_link_list API
+        """
+        logger.info("Openstack-CAL-Test: Test Get virtual_link List APIs")
+        rc, rsp = self.cal.get_virtual_link_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Received %d virtual_links" %(len(rsp.virtual_link_info_list)))
+        for virtual_link in rsp.virtual_link_info_list:
+            rc, virtual_link2 = self.cal.get_virtual_link(self._acct, virtual_link.virtual_link_id)
+            self.assertEqual(virtual_link2.virtual_link_id, virtual_link.virtual_link_id)
+
+    def _get_virtual_link_request_info(self):
+        """
+        Returns object of type RwcalYang.VirtualLinkReqParams
+        """
+        vlink = RwcalYang.VirtualLinkReqParams()
+        vlink.name = 'rift.cal.virtual_link'
+        vlink.subnet = '192.168.1.0/24'
+        if openstack_info['physical_network']:
+            vlink.provider_network.physical_network = openstack_info['physical_network']
+        if openstack_info['network_type']:
+            vlink.provider_network.overlay_type     = openstack_info['network_type'].upper()
+        if OpenStackTest.SEG_ID:
+            vlink.provider_network.segmentation_id  = OpenStackTest.SEG_ID
+            OpenStackTest.SEG_ID += 1
+        return vlink
+        
+    def _get_vdu_request_info(self, virtual_link_id):
+        """
+        Returns object of type RwcalYang.VDUInitParams
+        """
+        vdu = RwcalYang.VDUInitParams()
+        vdu.name = "cal.vdu"
+        vdu.node_id = OpenStackTest.NodeID
+        vdu.image_id = self._image.id
+        vdu.flavor_id = self._flavor.id
+        vdu.vdu_init.userdata = ''
+        vdu.allocate_public_address = True
+        c1 = vdu.connection_points.add()
+        c1.name = "c_point1"
+        c1.virtual_link_id = virtual_link_id
+        c1.type_yang = 'VIRTIO'
+        return vdu
+
+    def _get_vdu_modify_request_info(self, vdu_id, virtual_link_id):
+        """
+        Returns object of type RwcalYang.VDUModifyParams
+        """
+        vdu = RwcalYang.VDUModifyParams()
+        vdu.vdu_id = vdu_id
+        c1 = vdu.connection_points_add.add()
+        c1.name = "c_modify1"
+        c1.virtual_link_id = virtual_link_id
+       
+        return vdu 
+        
+    #@unittest.skip("Skipping test_create_delete_virtual_link_and_vdu")
+    def test_create_delete_virtual_link_and_vdu(self):
+        """
+        Test to create VDU
+        """
+        logger.info("Openstack-CAL-Test: Test Create Virtual Link API")
+        vlink_req = self._get_virtual_link_request_info()
+
+        rc, rsp = self.cal.create_virtual_link(self._acct, vlink_req)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Created virtual_link with Id: %s" %rsp)
+        vlink_id = rsp
+        
+        #Check if virtual_link create is successful
+        rc, rsp = self.cal.get_virtual_link(self._acct, rsp)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        self.assertEqual(rsp.virtual_link_id, vlink_id)
+
+        # Now create VDU
+        vdu_req = self._get_vdu_request_info(vlink_id)
+        logger.info("Openstack-CAL-Test: Test Create VDU API")
+
+        rc, rsp = self.cal.create_vdu(self._acct, vdu_req)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Created vdu with Id: %s" %rsp)
+
+        vdu_id = rsp
+
+        ## Check if VDU create is successful
+        rc, rsp = self.cal.get_vdu(self._acct, rsp)
+        self.assertEqual(rsp.vdu_id, vdu_id)
+
+        ### Wait until vdu_state is active
+        for i in range(50):
+            rc, rs = self.cal.get_vdu(self._acct, vdu_id)
+            self.assertEqual(rc, RwStatus.SUCCESS)
+            logger.info("Openstack-CAL-Test: VDU with id : %s. Reached State :  %s" %(vdu_id, rs.state))
+            if rs.state == 'active':
+                break
+        rc, rs = self.cal.get_vdu(self._acct, vdu_id)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        self.assertEqual(rs.state, 'active')
+        logger.info("Openstack-CAL-Test: VDU with id : %s reached expected state  : %s" %(vdu_id, rs.state))
+        logger.info("Openstack-CAL-Test: VDUInfo: %s" %(rs))
+        
+        vlink_req = self._get_virtual_link_request_info()
+
+        ### Create another virtual_link
+        rc, rsp = self.cal.create_virtual_link(self._acct, vlink_req)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Created virtual_link with Id: %s" %rsp)
+        vlink_id2= rsp
+
+        ### Now exercise the modify_vdu_api
+        vdu_modify = self._get_vdu_modify_request_info(vdu_id, vlink_id2)
+        rc = self.cal.modify_vdu(self._acct, vdu_modify)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        logger.info("Openstack-CAL-Test: Modified vdu with Id: %s" %vdu_id)
+
+        ### Lets delete the VDU
+        self.cal.delete_vdu(self._acct, vdu_id)
+
+        ### Lets delete the Virtual Link
+        self.cal.delete_virtual_link(self._acct, vlink_id)
+
+        ### Lets delete the Virtual Link-2
+        self.cal.delete_virtual_link(self._acct, vlink_id2)
+
+        time.sleep(5)
+        ### Verify that VDU and virtual link are successfully deleted
+        rc, rsp = self.cal.get_vdu_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+        for vdu in rsp.vdu_info_list:
+            self.assertNotEqual(vdu.vdu_id, vdu_id)
+
+        rc, rsp = self.cal.get_virtual_link_list(self._acct)
+        self.assertEqual(rc, RwStatus.SUCCESS)
+
+        for virtual_link in rsp.virtual_link_info_list:
+            self.assertNotEqual(virtual_link.virtual_link_id, vlink_id)
+
+        logger.info("Openstack-CAL-Test: VDU/Virtual Link create-delete test successfully completed")
+
+
+class VmData(object):
+    """A convenience class that provides all the stats and EPA Attributes
+    from the VM provided
+    """
+    def __init__(self, host, mgmt_ip):
+        """
+        Args:
+            host (str): host name.
+            mgmt_ip (str): The IP of the newly created VM.
+        """
+        # Sleep for 20s to ensure the VM is UP and ready to run commands
+        time.sleep(20)
+        logger.info("Connecting to host: {} and IP: {}".format(host, mgmt_ip))
+        self.client = paramiko.SSHClient()
+        self.client.set_missing_host_key_policy(paramiko.WarningPolicy())
+        self.client.connect(host)
+        self.ip = mgmt_ip
+
+        # Get all data from the newly created VM.
+        self._data = self._get_data()
+        self._page_size = self._exec_and_clean("getconf PAGE_SIZE")
+        self._disk_space = self._exec_and_clean(
+                "df -kh --output=size /",
+                line_no=1)
+        self._pci_data = self._exec('lspci -m | grep "10-Gigabit"')
+
+    def _get_data(self,):
+        """Runs the command and store the output in a python dict.
+
+        Returns:
+            dict: Containing all key => value pairs.
+        """
+        content = {}
+        cmds = ["lscpu", 'less /proc/meminfo']
+        for cmd in cmds:
+            ssh_out = self._exec(cmd)
+            content.update(self._convert_to_dict(ssh_out))
+        return content
+
+    def _exec_and_clean(self, cmd, line_no=0):
+        """A convenience method to run a command and extract the specified line
+        number.
+
+        Args:
+            cmd (str): Command to execute
+            line_no (int, optional): Default to 0, extracts the first line.
+
+        Returns:
+            str: line_no of the output of the command.
+        """
+        output = self._exec(cmd)[line_no]
+        output = ' '.join(output.split())
+        return output.strip()
+
+    def _exec(self, cmd):
+        """Thin wrapper that runs the command and returns the stdout data
+
+        Args:
+            cmd (str): Command to execute.
+
+        Returns:
+            list: Contains the command output.
+        """
+        _, ssh_out, _ = self.client.exec_command(
+                "/usr/rift/bin/ssh_root {} {}".format(self.ip,
+                                                      cmd))
+        return ssh_out.readlines()
+
+    def _convert_to_dict(self, content):
+        """convenience method that cleans and stores the line into dict.
+        data is split based on ":" or " ".
+
+        Args:
+            content (list): A list containing the stdout.
+
+        Returns:
+            dict: containing stat attribute => value.
+        """
+        flattened = {}
+        for line in content:
+            line = ' '.join(line.split())
+            if ":" in line:
+                key, value = line.split(":")
+            else:
+                key, value = line.split(" ")
+            key, value = key.strip(), value.strip()
+            flattened[key] = value
+        return flattened
+
+    @property
+    def disk(self):
+        disk = self._disk_space.replace("G", "")
+        return int(disk)
+
+    @property
+    def numa_node_count(self):
+        numa_cores = self._data['NUMA node(s)']
+        numa_cores = int(numa_cores)
+        return numa_cores
+
+    @property
+    def vcpus(self):
+        cores = int(self._data['CPU(s)'])
+        return cores
+
+    @property
+    def cpu_threads(self):
+        threads = int(self._data['Thread(s) per core'])
+        return threads
+
+    @property
+    def memory(self):
+        memory = self._data['MemTotal']
+        memory = int(memory.replace("kB", ""))/1000/1000
+        return int(memory)
+
+    @property
+    def memory_page_size(self):
+        return self._page_size
+
+    @property
+    def pci_passthrough_device_list(self):
+        return self._pci_data
+
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.INFO)
+    unittest.main()
diff --git a/rwcal/test/test_rwlxc_rwlaunchpad.py b/rwcal/test/test_rwlxc_rwlaunchpad.py
new file mode 100644
index 0000000..0119232
--- /dev/null
+++ b/rwcal/test/test_rwlxc_rwlaunchpad.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+
+# 
+#   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 logging
+import os
+
+import rift.rwcal.cloudsim.lxc as lxc
+import rift.rwcal.cloudsim.lvm as lvm
+
+
+logger = logging.getLogger('rwcal-test')
+
+
+def main():
+    template = os.path.realpath("../rift/cal/lxc-fedora-rift.lxctemplate")
+    tarfile = "/net/strange/localdisk/jdowner/lxc.tar.gz"
+    volume = 'rift-test'
+
+    lvm.create(volume, '/lvm/rift-test.img')
+
+    master = lxc.create_container('test-master', template, volume, tarfile)
+
+    snapshots = []
+    for index in range(5):
+        snapshots.append(master.snapshot('test-snap-{}'.format(index + 1)))
+
+    for snapshot in snapshots:
+        snapshot.destroy()
+
+    master.destroy()
+
+    lvm.destroy(volume)
+
+
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.DEBUG)
+    main()