import gevent
from gevent import monkey
monkey.patch_all()

from CloudProviderFramework import *
from libcloud.compute.types import Provider
from libcloud.compute.types import NodeState
from libcloud.compute.providers import get_driver
from libcloud.compute.base import NodeAuthPassword
from cloud_provider_plugin_base import CloudProviderPluginBase
from concurrent.futures import ThreadPoolExecutor
import threading
import sys
import re
import uuid
import requests
import json

from gevent import getcurrent
from gevent.pool import Pool
from gevent.pool import Group

POOL_SIZE = 16

class CloudProviderPlugin(CloudProviderPluginBase):
    def __init__(self, pluginHolder):
        CloudProviderPluginBase.__init__(self, pluginHolder)
        self.cls = get_driver(Provider.GCE)
        self.pool = Pool(POOL_SIZE)
        self.user_id = None
        self.project_id = None
        self.key_file = None
        self.loc = None
        self.provider_name = ""
        self.multithreaded = True

    ### tools
    def get_exc_message(self):
        infos = sys.exc_info()
        message = '' 
        for info in infos:
            message = message + str(info) + ' '
        return message
    
    def create_driver(self):
        driver = None
        try:
            driver = self.cls(self.user_id, self.key_file,
                          project=self.project_id,
                          datacenter=self.loc)
        except:
            self.set_error(self.get_exc_message())
        
        return driver

    def spawn(self, func, *args, **kwargs):
            if self.multithreaded:
                parent = gevent.getcurrent()
                
                def on_error(subtask):
                    parent.throw(subtask.exception)

                g = self.pool.spawn(func, *args, **kwargs)
                g.link_exception(on_error)
            else:
               func(*args, **kwargs) 

    def handle_exception(self, greenlet):
        try:
            self.set_error("Error")
        except Exception as exc:
            pass
    
    # Get the driver for current thread.
    def get_driver(self):
        return self.create_driver()

    ###
    def initialize(self):
        self.user_id = self.subscriber_id()
        self.project_id = self.client_id()
        self.key_file = self.username()
        self.loc = self.location()
        self.provider_name = self.name()
    
    def update(self):
        self.pool.join(timeout=0.001)  
    ###
    
    # list locations
    def list_locations(self):
        self.spawn(self.process_list_locations)

    def process_list_locations(self):
        driver = self.get_driver()
        response = ListLocationsResponse()
        try:
            zones = driver.ex_list_zones()
            zone_names = []
            for zone in zones:
                zone_names.append(zone.name)
            response.locations = zone_names
        except:
            response.set_error_code('libcloud error')
            response.set_error_message(self.get_exc_message())
            self.list_locations_finished(response)
            return
        self.list_locations_finished(response)

    # list resource groups (instance group in GCE)
    def list_resource_groups(self):
        self.spawn(self.process_list_resource_groups)
    
    def process_list_resource_groups(self):
        driver = self.get_driver()
        response = ListResourceGroupsResponse()
        try:
            groups = driver.ex_list_instancegroups(self.loc)
            group_names = []
            for group in groups:
                group_names.append(group.name)
            response.resource_group_names = group_names
        except:
            response.set_error_code('libcloud error')
            response.set_error_message(self.loc + " " + self.get_exc_message())
            self.list_resource_groups_finished(response)
            return
        self.list_resource_groups_finished(response)

    # def process_list_resource_groups(self):
    #     response = ListResourceGroupsResponse()
    #     response.set_error_code('debug error')
    #     key = ""
    #     key = serialization.load_pem_private_key(b"your key bytes", password=None)
    #     if key == None or key == "":
    #         error = "CREDS ERROR!!!!"

    #     response.set_error_message(self.loc + " " + error)
    #     self.list_resource_groups_finished(response)

    # create a resource group (instance group in GCE)
    def create_resource_group(self, resourceGroupName, location, vmConfig):
        self.spawn(self.process_create_resource_group, resourceGroupName.lower(), location, vmConfig)
    
    def process_create_resource_group(self, resourceGroupName, location, vmConfig):
        driver = self.get_driver()
        response = CreateResourceGroupResponse()
        group = None
        net = None
        subnet = None



        try:
            group = driver.ex_get_instancegroup(resourceGroupName)
        except:
            pass
            
        try:
            if (len(vmConfig.virtual_network_name) > 0):
                net = driver.ex_get_network(vmConfig.virtual_network_name)
                subnet = driver.ex_get_subnetwork('rendersubnet')        
            if (group == None):
                if (net != None and subnet != None):
                    group = driver.ex_create_instancegroup(resourceGroupName, location, network=net, subnetwork=subnet)
                else:
                    group = driver.ex_create_instancegroup(resourceGroupName, location)
        except:
            response.set_error_code('libcloud error')
            response.set_error_message(self.get_exc_message())
            self.create_resource_group_finished(response)
            return
        self.create_resource_group_finished(response)
    
    # delete a resource group (instance group in GCE)
    def delete_resource_group(self, resourceGroupName):
        self.spawn(self.process_create_resource_group, resourceGroupName.lower())
    
    def process_delete_resource_group(self, resourceGroupName):
        pass

    # list all available VM sizes (number of cores, RAM, HDD, etc.) in a location
    def list_skus(self, location):
        self.spawn(self.process_list_skus, location)
        #self.process_list_skus(location)

    def process_list_skus(self, location):
        driver = self.get_driver()
        skuResponse = ListSkusResponse()
        skuResponse.provider_name = self.provider_name
        priceResponse = ListVmPricesResponse()
        priceResponse.provider_name = self.provider_name

        priceDict = dict()
        #priceDict = self.get_pricing("API-KEY", "europe-west3")

        try:
            sizes = driver.list_sizes()
            sku_dict = dict()
            prices = []

            for size in sizes:
                sku_info = SkuInfo()
                price_info = VmPriceInfo()
                sku_info.name = size.name
                sku_info.memory_size = size.ram / 1024
                sku_info.os_disk_size = size.disk * 1024
                price_info.sku_name = size.name
                price_info.unit_of_measure = ""
                price_info.unit_price = -1
                sku_dict[size.name] = sku_info
                
                if len(priceDict) > 0:
                    prefixList = re.findall("([acemnfg][1,2]d?)-.+", size.name)
                    if len(prefixList) > 0:
                        prefix = prefixList[0]
                        ramPrices = priceDict["ram"]
                        corePrices = priceDict["core"]
                        # debugStr = "Debug: " + str(type(ramPrices)) + " " + str(type(corePrices)) + " " + prefix
                        if prefix in ramPrices and prefix in corePrices:
                            ram = size.ram / 1024
                            ramPricePerUnit = ramPrices[prefix]
                            cores = size.extra["guestCpus"]
                            corePricePerUnit = corePrices[prefix]
                            price_info.unit_price = ram * ramPricePerUnit + cores * corePricePerUnit

                
                prices.append(price_info)

            skuResponse.sku_info_map = sku_dict
            priceResponse.vm_prices = prices

        except:
            skuResponse.set_error_code('libcloud error')
            skuResponse.set_error_message(self.get_exc_message())
            priceResponse.set_error_code('libcloud error')
            priceResponse.set_error_message(self.get_exc_message())

        self.list_skus_finished(skuResponse)
        self.list_vm_prices_finished(priceResponse)

    def get_sku_price(self, sku, description, instanceType, preempt=False):
        prices = {}
        vmPrefixes = ["a2", "n1", "n2", "n2d", "e2", "c2", "m1", "m2"]
        for vmPrefix in vmPrefixes:
            vmPrefixCaps = vmPrefix.upper()
            pricingDescription = vmPrefixCaps + " Instance " + instanceType
            if (vmPrefix == "n1"):
                pricingDescription.replace("Instance", "Predefined Instance")
            if (preempt):
                pricingDescription = "Preemptible " + pricingDescription
            if description.startswith(pricingDescription):
                units = float(sku["pricingInfo"][0]["pricingExpression"]["tieredRates"][0]["unitPrice"]["units"])
                nanos = float(sku["pricingInfo"][0]["pricingExpression"]["tieredRates"][0]["unitPrice"]["nanos"])
                if units >= 0:
                    prices[vmPrefix] = units + nanos / 1000000000
                else:
                    prices[vmPrefix] = units - nanos / 1000000000

        return prices
        
    def get_pricing(self, apiKey, region):
        url = "https://cloudbilling.googleapis.com/v1/services/6F81-5844-456A/skus?key=" + apiKey
        prices = {}
        corePrices = {}
        preemptCorePrices = {}
        ramPrices = {}
        preemptRamPrices = {}

        vmPrefixes = ["a2", "n1", "n2", "n2d", "e2", "c2", "m1", "m2"]
        
        token = ""

        while token != None:
            if len(token) > 0:
                url += "&pageToken=" + token

            response = requests.get(url)
            if (response.ok):
                print("Response OK!")
                data = json.loads(response.content)
                if "skus" in data:         
                    skus = data["skus"]
                    for sku in skus:
                        description = sku["description"]
                        displayName = sku["category"]["serviceDisplayName"]
                        resourceGroup = sku["category"]["resourceGroup"]
                        resourceFamily = sku["category"]["resourceFamily"]
                        usageType = sku["category"]["usageType"]
                        regions = sku["serviceRegions"]
                        if resourceFamily == "Compute" and region in regions :
                            if usageType == "OnDemand":
                                if resourceGroup == "RAM":
                                    prices = self.get_sku_price(sku, description, "Ram")
                                    ramPrices.update(prices)
                                elif resourceGroup == "CPU":
                                    prices = self.get_sku_price(sku, description, "Core")
                                    corePrices.update(prices)
                            elif usageType == "Preemptible":
                                if resourceGroup == "RAM":
                                    prices = self.get_sku_price(sku, description, "Ram", True)
                                    preemptRamPrices.update(prices)
                                elif resourceGroup == "CPU":
                                    prices = self.get_sku_price(sku, description, "Core", True)
                                    preemptCorePrices.update(prices)
                            
                                    
                if "nextPageToken" in data and len(data["nextPageToken"]) > 0:
                    token = data["nextPageToken"]
                else:
                    token = None

        prices["ram"] = ramPrices
        prices["ramPreempt"] = preemptRamPrices
        prices["core"] = corePrices
        prices["corePreempt"] = preemptCorePrices
        
        return prices

    # list all available VM sizes (number of cores, RAM, HDD, etc.) in a location
    def list_vm_sizes(self, resourceGroupName, location):
        self.spawn(self.process_list_vm_sizes, resourceGroupName.lower(), location)
    
    def process_list_vm_sizes(self, resourceGroupName, location):
        driver = self.get_driver()
        response = VmSizesResponse()
        try:
            sizes = driver.list_sizes()
            size_names = []
            for size in sizes:
                size_names.append(size.name)

            response.vm_sizes = size_names
        except:
            response.set_error_code('libcloud error')
            response.set_error_message(self.get_exc_message())
        self.list_vm_sizes_finished(response)
        
    # list all VMs in a resource group (instance group for GCE)
    def list_vms(self, resourceGroupName):
        self.spawn(self.process_list_vms, resourceGroupName.lower())
        
    def process_list_vms(self, resourceGroupName):
        driver = self.get_driver()
        response = VirtualMachinesResponse()
        response.resource_group_name = resourceGroupName
        nic_response = NetworkInterfacesResponse()
        try:
            group = driver.ex_get_instancegroup(resourceGroupName)
            nodes = driver.ex_instancegroup_list_instances(group)
        except:
            response.set_error_code('libcloud error')
            response.set_error_message(self.get_exc_message())
            self.list_vms_finished(response)
            return   
        vm_dict = dict()
        nic_dict = dict()
        for node in nodes:
            vm_info = VmInfo()
            vm_info.instance_name = node.name
            vm_info.resource_group_name = resourceGroupName
            vm_dict[node.name] = vm_info
            
            if (len(node.private_ips) > 0):
                nic_info = NicInfo()
                nic_info.name = node.name
                nic_info.ip_address = node.private_ips[0]
                nic_dict[node.name] = nic_info
        
        response.vm_info_map = vm_dict
        nic_response.nic_info_map = nic_dict
        
        self.list_vms_finished(response)
        self.list_nics_finished(nic_response)
    
    # list nics (private/public IPs) of VMs in a given resource group (instance group in GCE)
    def list_nics(self, resourceGroupName):
        self.spawn(self.process_list_nics, resourceGroupName.lower())
    
    def process_list_nics(self, resourceGroupName):
        pass

        driver = self.get_driver()
        nic_response = NetworkInterfacesResponse()
        try:
            group = driver.ex_get_instancegroup(resourceGroupName)
            nodes = driver.ex_instancegroup_list_instances(group)
        except:
            nic_response.set_error_code('libcloud error')
            nic_response.set_error_message(self.get_exc_message())
            self.list_nics_finished(nic_response)
            return
            
        nic_dict = dict()
        for node in nodes:    
            if (len(node.private_ips) > 0):
                nic_info = NicInfo()
                nic_info.name = node.name
                nic_info.ip_address = node.private_ips[0]
                if ((len(node.public_ips) > 0) and (node.public_ips[0] != None)):
                    nic_info.ip_address_public = node.public_ips[0]
                nic_dict[node.name] = nic_info
        
        nic_response.nic_info_map = nic_dict
        self.list_nics_finished(nic_response)
        
    # get the state of a given VM (running, starting, stopping, etc.)
    def get_vm_info(self, vmName, resourceGroupName):
        self.spawn(self.process_get_vm_info, vmName, resourceGroupName.lower())
        
    def process_get_vm_info(self, vmName, resourceGroupName):
        driver = self.get_driver()
        response = InstanceResponse()
        response.vm_name = vmName
        try:
            node = driver.ex_get_node(vmName)
            if (node.state == NodeState.RUNNING):
                response.power_state = RestPowerState.PS_ReadyRole
                response.power_state_string = 'running'
            elif (node.state == NodeState.STARTING):
                response.power_state = RestPowerState.PS_StartingRole
                response.power_state_string = 'starting'
            elif (node.state == NodeState.REBOOTING):
                response.power_state = RestPowerState.PS_RestartingRole
                response.power_state_string = 'restarting'
            elif (node.state == NodeState.TERMINATED):
                response.power_state = RestPowerState.PS_UnresponsiveRole
                response.power_state_string = 'unresponsive'
            elif (node.state == NodeState.STOPPING):
                response.power_state = RestPowerState.PS_StoppingVM
                response.power_state_string = 'stopping'
            elif (node.state == NodeState.STOPPED):
                response.power_state = RestPowerState.PS_StoppedDeallocated
                response.power_state_string = 'deallocated'
            elif (node.state == NodeState.PENDING):
                response.power_state = RestPowerState.PS_Preparing
                response.power_state_string = 'preparing'
            elif (node.state == NodeState.SUSPENDED):
                response.power_state = RestPowerState.PS_UnresponsiveRole
                response.power_state_string = 'suspended'
            elif (node.state == NodeState.ERROR):
                response.power_state = RestPowerState.PS_FailedStartingRole
                response.power_state_string = 'failed'
            elif (node.state == NodeState.PAUSED ):
                response.power_state = RestPowerState.PS_StoppedVM
                response.power_state_string = 'stopped'
            elif (node.state == NodeState.RECONFIGURING  ):
                response.power_state = RestPowerState.PS_BusyRole
                response.power_state_string = 'busy'
            else:
                response.power_state = RestPowerState.PS_Unknown
                response.power_state_string = 'unknown'
            
        except:
            response.set_error_code('libcloud error')
            response.set_error_message(self.get_exc_message())
            
        self.get_vm_info_finished(response)
        
    # add new VMs to a given resource group (instance group in GCE)
    def add_vms(self, vmNames, vmConfig, resourceGroupName):
        self.spawn(self.process_add_vms, vmNames, vmConfig, resourceGroupName.lower())
        
    def process_add_vms(self, vmNames, vmConfig, resourceGroupName):
        driver = self.get_driver()
        response = CreateVMsResponse()
        location = vmConfig.cloud_connector_config.provider_config.cloud_location
        group = None
        try:
            net = driver.ex_get_network(vmConfig.virtual_network_name)
            subnet = driver.ex_get_subnetwork(vmConfig.subnet.lower())        
            group = driver.ex_get_instancegroup(resourceGroupName)

        except:
            response.set_error_code('libcloud error')
            response.set_error_message(self.get_exc_message())
            self.add_vms_finished(response)
            return
        
        nodes = []
        for vm_name in vmNames:
            try:
                #image = driver.ex_get_image('almalinux-9-v20251209', ex_project_list='almalinux-cloud', ex_standard_projects=False)
                #node = driver.create_node(name=vm_name.lower(), image=image, size=vmConfig.cloud_connector_config.vm_size, location=location, ex_network=net, ex_subnetwork=subnet, ex_can_ip_forward=True, ex_preemptible=vmConfig.cloud_connector_config.vm_low_prio)
                node = driver.create_node(name=vm_name.lower(), image=vmConfig.cloud_connector_config.vm_image_name, size=vmConfig.cloud_connector_config.vm_size, location=location, ex_network=net, ex_subnetwork=subnet, ex_can_ip_forward=True, ex_preemptible=vmConfig.cloud_connector_config.vm_low_prio)
                nodes.append(node)
            except:
                response.set_error_code('libcloud error')
                response.set_error_message(self.get_exc_message())
                
        if len(nodes) > 0 and group != None:
            group.add_instances(nodes)
            
        self.add_vms_finished(response)
        
    # delete a VM from a given resource group (instance group in GCE)
    def delete_vm(self, vmName, resourceGroupName):
        self.process_delete_vm(vmName, resourceGroupName.lower())
        #self.pool.spawn(self.process_delete_vm, vmName, resourceGroupName)
        
    def process_delete_vm(self, vmName, resourceGroupName):
        pass
        #response = DeleteVMResponse()
        #self.delete_vm_finished(response)
    
    # starts VMs in a given resource group (instance group in GCE)
    def start_vms(self, vmNames, resourceGroupName):
        self.spawn(self.process_start_vms, vmNames, resourceGroupName.lower())
        
    def process_start_vms(self, vmNames, resourceGroupName):
        driver = self.get_driver()
        response = VirtualMachineNameResponse()
        for vm_name in vmNames:
            try:
                node = driver.ex_get_node(vm_name)
                driver.start_node(node, ex_sync=False)
            except:
                response.set_error_code('libcloud error')
                response.set_error_message(self.get_exc_message())
        self.start_vms_finished(response)
    
    # shutdown VMs in a resource group (instance group in GCE)
    def shutdown_vms(self, vmNames, resourceGroupName):
        self.spawn(self.process_shutdown_vms, vmNames, resourceGroupName.lower())
        
    def process_shutdown_vms(self, vmNames, resourceGroupName):
        driver = self.get_driver()
        response = VirtualMachineNameResponse()
        for vm_name in vmNames:
            try:
                node = driver.ex_get_node(vm_name)
                driver.stop_node(node, ex_sync=False)
            except:
                response.set_error_code('libcloud error')
                response.set_error_message(self.get_exc_message())
        self.shutdown_vms_finished(response)

    # shutdown VMs in a resource group
    def simulate_vm_eviction(self, vmName, resourceGroupName):
        self.spawn(self.process_simulate_vm_eviction, vmName, resourceGroupName.lower())
        
    def process_simulate_vm_eviction(self, vmName, resourceGroupName):
        self.process_shutdown_vms(vmName, resourceGroupName.lower())
    
    def generalize_vm(self, vmName, resourceGroupName):
        self.process_generalize_vm(vmName, resourceGroupName.lower())
        #self.pool.spawn(self.process_generalize_vm, vmName, resourceGroupName)
            
    def process_generalize_vm(self, vmName, resourceGroupName):
        pass
        #response = RestResponse()
        #self.generalize_vm_finished(response)
     
    def capture_image_from_vm(self, imageName, vmName, resourceGroupName):
        self.process_capture_image_from_vm(imageName, vmName, resourceGroupName.lower())
        #self.pool.spawn(self.process_capture_image_from_vm, imageName, vmName, resourceGroupName)
        
    def process_capture_image_from_vm(self, imageName, vmName, resourceGroupName):
        pass
        #response = RestResponse()
        #self.capture_image_from_vm_finished(response)

    
    # create a new virtual network with a given config
    # this function creates the following resources:
    # 1. VPN with render subnet and VPN subnet
    # 2. Firewall entries allowing ssh and vpn traffic
    # 3. (optional) a new VM which hosts the OpenVPN server with static address
    # 4. (optional) additional routes
    def create_virtual_network(self, resourceGroupName, virtualNetworkConfig):
        self.spawn(self.process_create_virtual_network, resourceGroupName.lower(), virtualNetworkConfig)
        
    def process_create_virtual_network(self, resourceGroupName, virtualNetworkConfig):
        driver = self.get_driver()
        vn_name = virtualNetworkConfig.name
        render_subnet = virtualNetworkConfig.render_subnet
        vpn_subnet = virtualNetworkConfig.vpn_subnet
        vpn_client_subnet = virtualNetworkConfig.vpn_client_subnet
        vn_vpn_local_network = virtualNetworkConfig.vpn_local_network
        location = virtualNetworkConfig.location
        response = RestResponse()
        deployment_response = DeploymentInfoResponse()
        deployment_response.provisioning_state = 'succeeded'
        region = re.sub('-[a-d]$', '', location)
        network = None

        try:
            network = driver.ex_create_network(vn_name, cidr=None, description=None, mode='custom', routing_mode=None)
            allow_ssh = [{"IPProtocol": "tcp", "ports": ["22"]}]
            driver.ex_create_firewall('allow-ssh', allowed=allow_ssh, denied=None, network=vn_name, priority=65534, source_service_accounts=None, target_service_accounts=None, source_tags=None, target_tags=None, description=None)
            allow_rdp = [{"IPProtocol": "tcp", "ports": ["3389"]}]
            driver.ex_create_firewall('allow-rdp', allowed=allow_rdp, denied=None, network=vn_name, priority=65534, source_service_accounts=None, target_service_accounts=None, source_tags=None, target_tags=None, description=None)
        except:
            response.set_error_code('libcloud error')
            response.set_error_message(self.get_exc_message())
            deployment_response.set_error_code('libcloud error')
            deployment_response.set_error_message(self.get_exc_message())
            deployment_response.provisioning_state = 'failed'
            self.create_virtual_network_finished(response)
            self.get_deployment_info_finished(deployment_response)
            return
            
        try:    
            subnet = driver.ex_create_subnetwork('rendersubnet', render_subnet, network, region)
        except:
            response.set_error_code('libcloud error')
            response.set_error_message(self.get_exc_message())
            deployment_response.set_error_code('libcloud error')
            deployment_response.set_error_message(self.get_exc_message())
            deployment_response.provisioning_state = 'failed'
            self.create_virtual_network_finished(response)
            self.get_deployment_info_finished(deployment_response)
            return

        if virtualNetworkConfig.create_vpn:    
            try:    
                subnet = driver.ex_create_subnetwork('vpnsubnet', vpn_subnet, network, region)
                allow_openvpn = [{"IPProtocol": "udp", "ports": ["1194"]}]
                driver.ex_create_firewall('allow-openvpn', allowed=allow_openvpn, denied=None, network=vn_name, priority=65534, source_service_accounts=None, target_service_accounts=None, source_tags=None, target_tags=None, description=None)
                allow_rendersubnet = [{"IPProtocol": "all"}]
                source_ranges_rendersubnet = [render_subnet]
                driver.ex_create_firewall('allow-rendersubnet', allowed=allow_rendersubnet, denied=None, network=vn_name, source_ranges=source_ranges_rendersubnet, priority=65534, source_service_accounts=None, target_service_accounts=None, source_tags=None, target_tags=None, description=None)
                allow_vpnsubnet = [{"IPProtocol": "all"}]
                source_ranges_vpnsubnet = [vpn_subnet]
                driver.ex_create_firewall('allow-vpnsubnet', allowed=allow_vpnsubnet, denied=None, network=vn_name, source_ranges=source_ranges_vpnsubnet, priority=65534, source_service_accounts=None, target_service_accounts=None, source_tags=None, target_tags=None, description=None)
                allow_vpnclients = [{"IPProtocol": "all"}]
                source_ranges_vpnclients = [vpn_client_subnet]
                driver.ex_create_firewall('allow-vpnclients', allowed=allow_vpnclients, denied=None, network=vn_name, source_ranges=source_ranges_vpnclients, priority=65534, source_service_accounts=None, target_service_accounts=None, source_tags=None, target_tags=None, description=None)
            except:
                response.set_error_code('libcloud error')
                response.set_error_message(self.get_exc_message())
                deployment_response.set_error_code('libcloud error')
                deployment_response.set_error_message(self.get_exc_message())
                deployment_response.provisioning_state = 'failed'
                self.create_virtual_network_finished(response)
                self.get_deployment_info_finished(deployment_response)
                return

            try:            
                vm_name = 'vpnvm'
                group = driver.ex_get_instancegroup(resourceGroupName)
                if group == None:
                    group = driver.ex_create_instancegroup(resourceGroupName, location, network=network, subnetwork=subnet)
                elif (group.network != network) or (group.subnet != subnet):
                    nodes = driver.ex_instancegroup_list_instances(group)
                    driver.ex_destroy_instancegroup(group)
                    group = driver.ex_create_instancegroup(resourceGroupName, location, network=network, subnetwork=subnet)
                    if (len(nodes) > 0):
                        group.add_instances(nodes)
                
                node = driver.create_node(name=vm_name, image=virtualNetworkConfig.vpn_image_name, size=virtualNetworkConfig.vpn_vm_size, location=virtualNetworkConfig.location, ex_network=network, ex_subnetwork=subnet, ex_can_ip_forward=True)
                group.add_instances([node])
                if (len(node.public_ips) > 0):
                    static_ip = node.public_ips[0]
                    driver.ex_create_address('vpnvpm-static', region=region, address=static_ip)
                if (len(node.private_ips) > 0):
                    next_hop_ip = node.private_ips[0]
                    if (len(vpn_client_subnet) > 0):
                        driver.ex_create_route('render-clients-to-vpn-clients', vpn_client_subnet, priority=500, network=vn_name, tags=None, next_hop=next_hop_ip, description=None)
                    if (len(vn_vpn_local_network) > 0):
                        driver.ex_create_route('render-clients-to-local-network', vn_vpn_local_network, priority=500, network=vn_name, tags=None, next_hop=next_hop_ip, description=None)
                
            except:
                response.set_error_code('libcloud error')
                response.set_error_message(self.get_exc_message())
                deployment_response.set_error_code('libcloud error')
                deployment_response.set_error_message(self.get_exc_message())
                deployment_response.provisioning_state = 'failed'
                self.create_virtual_network_finished(response)
                self.get_deployment_info_finished(deployment_response)
                return
            
        self.create_virtual_network_finished(response)
        self.get_deployment_info_finished(deployment_response)
        
    def create_uuid(context):
        random_uuid = context + '_' + str(uuid.uuid4())
        return random_uuid
        