
# 
#   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()
