import subprocess
import time
import uuid

import lib.globals as globals


class kvmvLan:
    """ a vLan object used by the OnPrem kvm CMS 4.0
    """
    # properties
    name=""         # name of vLan - i.e. "Database"
    netmask=""      # netmask for vLand i.e. "255.255.255.0"
    network=""      # v4 network TCP/IP for vLan i.e "192.168.50.0"

    debug=False     # debug output?

    vLanId=""       # network ID value. i.e. "050168192"

    # which type of vLan - bridge (i.e. len(kvms)> 1) or  network (fully single internal vLAN) ?
    bridgedvLan=False

    # bridge based values - only valid if bridge=True (i.e. > 1 kvm host) Physical wiring needed...
    bridgeName=""   # name of the bridge device on the KVM host that the bridge will pass through

    # Network based values - only valid if bridge=False (i.e. =1 kvm host) pure virtual networks...
    networkName=""  # name of the KVM virtual network object

    lastErrror="(noError)"  # last error encountered

    def __init__(self, bridged, name, network, netmask, debug):

        self.name=name
        self.netmask=netmask
        self.network=network
        self.debug=debug
        self.bridgedvLan=bridged

        
        # define vLanId
        ip1, ip2, ip3, ip4 = str(self.network).split('.')
        self.vLanId=ip4+ip3+ip2+ip1

        # bridge values
        self.bridgeName=self.vLanId+"br0"

        # Network values
        self.networkName=self.name+"_CMS_"+self.vLanId

    def getName(self):
        return self.name

    def getError(self):
        return self.lastError

    def setError(self,lastError):
        self.lastError=lastError

    def printd(self,msg):
        """ debug logging
        """
        if self.debug:
            print msg

    def makeSubprocessCall(self, command, stdout=None, stderr=None):
        self.printd(".. Calling command \"%s\"" % " ".join(command))
        if stdout and stderr:
            return subprocess.call(command, stdout=stdout, stderr=stderr)
        elif stdout:
            return subprocess.call(command, stdout=stdout)
        elif stderr:
            return subprocess.call(command, stderr=stderr)
        else:
            return subprocess.call(command)

    def destroyInterface(self, hostIp, bridgeNic):
        self.printd('. bring down '+ bridgeNic)
        retval = self.makeSubprocessCall(['virsh',
            # system to connect to
            '-c', 'qemu+ssh://%s/system' % hostIp,
            # command and template domain name
            'iface-destroy', bridgeNic],
            stdout=globals.fnull,stderr=globals.fnull
        )

    def writeInterface(self, hostIp, bridgeNic, serverIP):
        networkData = "DEVICE=%s\nONBOOT=yes\nBOOTPROTO=none\nIPADDR=%s\nNETMASK=%s\nTYPE=Ethernet\nIPV6INIT=no\nUSERCTL=no\nUUID=\"%s\"" % (
            bridgeNic, serverIP, self.netmask, str(uuid.uuid1())
        )
        return self.makeSubprocessCall(['ssh',hostIp,
            'echo "' + networkData + '" > /etc/sysconfig/network-scripts/ifcfg-%s' % bridgeNic]
        )

    def startInterface(self, hostIp, bridgeNic, retries=6):
        if retries:
            retval = self.makeSubprocessCall(['virsh',
                # system to connect to
                '-c', 'qemu+ssh://%s/system' % hostIp,
                # command and template domain name
                'iface-start', bridgeNic]
            )
            if retval:
                self.printd(".. failed to start the interface %s on host %s\n.. Could be slow NIC.  Retrying %s more time(s)" % (
                    bridgeNic, hostIp, retries - 1
                    )
                )
                time.sleep(10)
                return self.startInterface(hostIp, bridgeNic, retries=retries-1)
            else:
                return 0
        else:
            return 1

    def createBridge(self, hostIp, bridgeNic):
        self.printd(". creating bridge " + self.bridgeName)
        return subprocess.call(['virsh',
            # system to connect to
            '-c', 'qemu+ssh://%s/system' % hostIp,
            # command and template domain name
            'iface-bridge', bridgeNic, self.bridgeName,'--no-stp'], stdout=globals.fstdout
        )


    def create(self,server):
        """ create this nework object on this kvm server host.  return True/False with lastError set as suitable.
        """

        self.printd(". create vLan "+self.name+" on server "+server["accessIP"]+". id is "+self.vLanId)

        # confirm we got a serverIP
        serverIP=self.getServerIPForvLan(server)
        
        if serverIP == "":
            # this is a problem. We didn't get a server IP?
            self.setError("Config Error: Did not obtain an IP in NetNicIP for server "+server["accessIP"]+" vLan "+self.name)
            return False

        # how many servers are we installing today?
        if self.bridgedvLan :
            print " Create Bridge for vLan "+self.name+" on KVM server "+server["accessIP"]
            # multiple servers - setup for wired private lan
            
            # does bridge exist?
            retval=subprocess.call(['virsh',
                    # system to connect to
                    '-c', 'qemu+ssh://'+server["accessIP"]+'/system',
                    # command and template domain name
                    'iface-dumpxml', self.bridgeName],
                    stdout=globals.fnull,stderr=globals.fnull)

            if retval != 0:
                # make sure we got a NIC to use.
                bridgeNic=self.getServerBridgeNicForvLan(server)
                if bridgeNic == "":
                    # then we have a problem.
                    self.setError("No server/kvm nic provided for "+server["accessIP"]+" bridged network "+self.name+" and multiple KVM server are being used. Provide a NIC on the kvm host for this vLan to pass through.")
                    return False
               
                # network does not exist.  Create
                self.printd(". creating "+self.name+" bridge "+self.bridgeName+" on "+bridgeNic)
                
                # bring down nic
                self.destroyInterface(server["accessIP"], bridgeNic)

                # configure it as we want for vlan net use.
                retval = self.writeInterface(server["accessIP"], bridgeNic, serverIP)
                if retval != 0:
                    self.SetError("Failed to create network config file for hostnode "+server["accessIP"]+" to create bridge to "+self.name+" network")
                    return False

                self.printd(". hosts vlan "+self.name+" IP is "+serverIP+" on "+bridgeNic)

                # start nic
                self.printd(". start the interface "+bridgeNic)
                retval = self.startInterface(server["accessIP"], bridgeNic) 

                if retval != 0:
                    self.setError("Failed to start NIC "+bridgeNic+" on hostnode "+server["accessIP"]+" for bridge to "+self.name+" network: Error Code:"+str(retval))
                    return False

                # create the bridge
                self.printd(". creating bridge "+self.bridgeName)
                retval = self.createBridge(server["accessIP"], bridgeNic)
                if retval != 0:
                    self.setError("Failed to create bridge "+self.bridgeName+" on nic "+bridgeNic+" on hostnode "+server["accessIP"]+" to network "+self.name+" Error Code:"+str(retval))
                    return False

                # poke sysctl
                subprocess.call(['ssh',server["accessIP"],'sysctl -q -p'])

                self.printd(". bridge "+self.bridgeName+" for vLan "+self.name+" ready.")

            else:
                print("  Skipping creation of "+self.name+" network bridge on server "+server["accessIP"]+" as it already exists")

        else:
            print " Create network for vLan "+self.name+" on KVM server "+server["accessIP"]
            # single server - create the private vLAN
            # does vlan exist?
            retval=subprocess.call(['virsh',
                    # system to connect to
                    '-c', 'qemu+ssh://'+server["accessIP"]+'/system',
                    # command and template domain name
                    'net-info', self.networkName],
                    stdout=globals.fnull,stderr=globals.fnull)
            if retval != 0:
                # network does not exist.  Create
                self.printd(". creating network "+self.networkName)
                netvLANXMLFile="/tmp/networkXML"+self.vLanId+".xml"
        # private network connect string
                self.printd(". serverIP is "+serverIP)
                networkXML='<network><name>'+self.networkName+'</name><forward mode="nat"/><bridge name="vir'+self.vLanId+'br0" stp="off" delay="0"/><ip address="'+serverIP+'" netmask="'+self.netmask+'"></ip></network>'
                self.printd(". network xml is "+networkXML)
                with open(netvLANXMLFile, 'w') as f:
                    f.write(networkXML)
                # create vlan
                retval=subprocess.call(['virsh',
                        # system to connect to
                        '-c', 'qemu+ssh://'+server["accessIP"]+'/system',
                        # command and param
                        'net-define', netvLANXMLFile ],stdout=globals.fstdout)
                if retval != 0:
                    self.setError("Failed to create virtual lan for single kvm "+self.name+" network. Error:"+str(retval))
                    return False

                # set to autostart
                retval=subprocess.call(['virsh',
                        # system to connect to
                        '-c', 'qemu+ssh://'+server["accessIP"]+'/system',
                        # command and param
                        'net-autostart', self.networkName ])
                if retval != 0:
                    self.setError("Failed to set virtual lan "+self.networkName+" autostart for network "+self.name+". Error:"+str(+retval))
                    # try to delete the vlan so we can create it again
                    retval=subprocess.call(['virsh',
                            # system to connect to
                            '-c', 'qemu+ssh://'+server["accessIP"]+'/system',
                            # command and param
                            'net-undefine', self.networkName ])

                    return False

                # start it.
                retval=subprocess.call(['virsh',
                        # system to connect to
                        '-c', 'qemu+ssh://'+server["accessIP"]+'/system',
                        # command and template domain name
                        'net-start', self.networkName])
                if retval != 0:
                    self.setError("Failed to start virtual lan "+self.networkName+" for name "+self.name+". Error:"+str(retval))
                    # try to delete the vlan so we can create it again
                    retval=subprocess.call(['virsh',
                            # system to connect to
                            '-c', 'qemu+ssh://'+server["accessIP"]+'/system',
                            # command and param
                            'net-undefine', self.networkName ])

                    return False

                self.printd(". created "+self.name+" vLAN")
            else:
                print("   Skipping creation of "+self.name+" vLAN on "+server["accessIP"]+" as it already exists")

        return True

    def connectVm(self, server, domainName, VmName):
        """ modify the provided domainName to include an interface into this vLan
            use whatever is the 'next' nic on this system.  Simply add this and order will be good.
        """

        # Attach this vLan based on bridge or network.
        self.printd(". vLanId is "+self.vLanId)
        if self.bridgedvLan:
            # multiple servers - setup for wired private lan via bridge
            self.printd(". bridgeName is "+self.bridgeName)
            if subprocess.call(['virsh',
                    # system to connect to
                    '-c', 'qemu+ssh://'+server["accessIP"]+'/system',
                    # command and domain name
                    'attach-interface',
                    domainName,
                    '--type', 'bridge',
                    '--source', self.bridgeName,
                    '--model','virtio',
                    '--config' ] )!= 0:
                self.setError("Failed to attach "+self.name+" bridge network "+self.bridgeName+" connection for "+VmName+" on server "+server["accessIP"])
                return False
        else:
            # single server - setup with vLAN
            self.printd(". network "+self.name+" is name "+self.networkName)
            if subprocess.call(['virsh',
                    # system to connect to
                    '-c', 'qemu+ssh://'+server["accessIP"]+'/system',
                    # command and domain name
                    'attach-interface',
                    domainName,
                    '--type', 'network',
                    '--source', self.networkName,
                    '--model','virtio',
                    '--config' ] )!= 0:
                self.setError("Failed to attach "+self.name+" vLAN network "+self.networkName+" for "+VmName+" on server "+server["accessIP"])
                return False
    
        return True
        

    def destroy(self,server):
        """ remove this vLan definition from the current KVM server
        """
        self.printd(". vLanID is "+self.vLanId)

        if self.bridgedvLan:
            # take down the bridge.
            self.printd(". Remove private bridge "+self.bridgeName)
            subprocess.call(['virsh',
                # host to connect to
                '-c','qemu+ssh://'+server["accessIP"]+'/system',
                # destory option
                'iface-unbridge',self.bridgeName],stdout=globals.fnull,stderr=globals.fnull)
        else:
            # destory the vLan network
            self.printd(". Destroy network "+self.networkName)
            
            subprocess.call(['virsh',
                    # host to connect to
                    '-c','qemu+ssh://'+server["accessIP"]+'/system',
                    # destory option
                    'net-destroy',self.networkName],stdout=globals.fnull,stderr=globals.fnull)

            # undefine it
            self.printd(". undefine network "+self.networkName)
            subprocess.call(['virsh',
                    # host to connect to
                    '-c','qemu+ssh://'+server["accessIP"]+'/system',
                    # destory option
                    'net-undefine',self.networkName],stdout=globals.fnull,stderr=globals.fnull)

        return True

    def getServerIPForvLan(self,server):
        """ based on the name of this vLan - search the server's vLanAcess attributes for the IP to assign this server
        """
        if "vLanAccess" in server:
            for vLan in server["vLanAccess"]:
                if vLan["name"]==self.name:
                    # found it.
                    return vLan["netNicIP"]

        return ""

    def getServerBridgeNicForvLan(self,server):
        """ based on the name of this vLan - search the server's vLanAcess attributes for the NIC to use bridging this erver
        """
        if "vLanAccess" in server:
            for vLan in server["vLanAccess"]:
                if vLan["name"]==self.name:
                    # found it. - there are valid cases where this value can be empty string
                    return vLan["netNic"]

        return ""
