import subprocess
import os
import globals
import time


class awsNas:
    """ an nfs host on the cs hostnodes
    """

    # class properties
    lvmVolume=""        # the LVM volume name this nas sits on
    lvmPath=""          # Path to the LVM volume device
    lvmMountPath=""     # where the LVM volume is mounted in the file system
    name=""             # unique name of this nas. Shared across all hostnodes that support this nas via nfs
                        # VM's will ask for how to mount this NAS
    lvmAvailableFreeSpace=0 # amount of GiB of space free in the local LVM
    lastError="(noError)"   # last error encountered
    debug=False         # are we doing extra logging?


    def __init__(self,name,debug):
        # define the lvm values
        self.lvmVolume="nas_"+name
        self.lvmPath="/dev/lvm_n/"+self.lvmVolume
        self.lvmMountPath=os.path.join('/','mnt','nas',self.lvmVolume)
        self.debug=debug
        self.name=name

    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 initNfsDaemon(self,server,privateNetwork,privateDomain):
        """ set the nfs daemon to start on this node and
            restart on reboot and set the iptables to allow usage
        """
        # service already exists - setup to start on runlevels 345
        retval=subprocess.call(['chkconfig', '--level', '345', 'nfslock', 'on'])
        if retval != 0:
            self.setError("Failed to chkconfgi nfslock. Retval: " + str(retval))
            return False

        retval=subprocess.call(['chkconfig', '--level', '345', 'nfs', 'on'])
        if retval != 0:
            self.setError("Failed to chkconfgi nfs. Retval: " + str(retval))
            return False

        #with open('/etc/exports', 'a+') as ex:
        #    retval = subprocess.call('echo "'+self.lvmMountPath+' '+privateNetwork+'/24(rw,sync,no_root_squash,no_all_squash)"', shell=True, stdout=ex)

        with open('./.idmap.tmp', 'w') as idmap:
            subprocess.call(['grep', '^Domain', '/etc/idmapd.conf'], stdout=idmap)

        domainAdded = False
        for line in open('./.idmap.tmp'):
            # jsut need one line to confirm
            domainAdded = True
            break

        if not domainAdded:
            with open('/etc/idmapd.conf', 'a+') as idmapconf:
                subprocess.call('echo "Domain='+privateDomain+'"', shell=True, stdout=idmapconf)

        retval=subprocess.call(['service', 'nfs', 'start'])
        #retval=subprocess.call(['exportfs', '-v'])

        return True

    def lvmSpaceAvailable(self,server):
        """ how much space is available in this servers LVM?
        """
        freeSpace=0
        # determine amount of free space in LVM, all NASes come from that.
        freeSpaceFile=open('./.lvSpace.tmp','w')
        retval=subprocess.call(['pvs', '--units', 'g', '--noheadings', '--nosuffix'],stdout=freeSpaceFile)
        if retval != 0:
            print "Fatal Error trying to determine amount of freeSpace in LVM partition on hostnode "+server["accessIP"]+" Error:"+str(retval)
            # return zero bytes available will trip an error in the caller
            return 0

        freeSpaceFile.close()
        with open("./.lvSpace.tmp","r") as myfile:
            data=[ line.strip().split() for line in myfile ]
            # free space is the last, 5th, parameter, in g
            freeSpace=float(data[0][5])

        self.printd(".  "+str(freeSpace)+" g space available on "+server["accessIP"]+" lvm")

        if freeSpace==0:
            # No free space? this will likely be a problem.
            print "Warning - no free space on LVM for hostnode "+server["accessIP"]

        return freeSpace

    def amountOfSpaceRequestedinG(self,freeSpace,requestedSpace):
        # translate this request for space from an encoded number to a literal number of GiB
        # requestedSpace is a number in the range of -100..2pow(32)
        # > 0 is simply the number of GiB requested
        # < 0 is a % of the free space requested
        # freeSpace is the number of GiB of free space curently available in the LVM
        if requestedSpace >= 0:
            return requestedSpace
        else:
            # then requested space is a % of available.
            resolvedRequestedSpace = ( requestedSpace * freeSpace ) / 100
            return resolvedRequestedSpace

    def lvmSpaceExistsForAllNases(self,server):
        """ is there enough space in the LVM left over to create the
            requested nases?
        """
        self.lvmAvailableFreeSpace = self.lvmSpaceAvailable(server)
        freeSpace=self.lvmAvailableFreeSpace

        # walk how much is being asked for accros all nases - and make sure we can say yes.
        for nas in server["nasRequests"]:
            nasName=nas["name"]
            requestedSpace=self.amountOfSpaceRequestedinG(freeSpace,nas["size"])
            self.printd(". nasRequest: name:"+nasName+" Size:"+str(requestedSpace)+" Available:"+str(freeSpace))

            if requestedSpace > freeSpace:
                # we have a problem.
                self.setError("Not enough space left over to create requested nas "+nasName+" on host "+server["accessIP"]+" Requested "+str(requestedSpace)+"g, remaining available space after other nases craeted "+str(freeSpace))
                return 1
            else:
                freeSpace -= requestedSpace

        self.printd(". After all space assignments, there will be "+str(freeSpace)+"g left free in the LVM")

        return 0

    def setupLocalLVMMount(self,nasName,server,spaceRequested):
        """ Create the lvm volume, format it and mount
        """

        # assert some things...
        assert self.lvmVolume != ""
        assert self.lvmPath != ""
        assert self.lvmMountPath != ""

        y=open('./.y.tmp','w+')
        y.write('y\n')
        y.seek(0)

        self.printd(". define the lvm volume...")
        retval = subprocess.call(['lvcreate', '-L', str(spaceRequested)+'g', '-n', self.lvmVolume, 'lvm_n'])
        if retval != 0:
                self.setError("Failed to create lv volume for nas "+nasName+" nas on host "+server["accessIP"]+". Error:"+str(retval))
                return False

        self.printd(". format the file system as ext3")
        retval = subprocess.call(['mkfs', '-t', 'ext4', self.lvmPath])
        if retval != 0:
            self.setError("Failed to make new filesystem on "+self.lvmPath+" Error:"+str(retval))
            # try to drop the volume
            subprocess.call(['lvremove', self.lvmMountPath],stdin=y);
            return False;

        # create mount point and mount locally on hostnode
        print "Mounting "+self.name+" NAS Volume"
        self.printd(". create the mount folder for the lvm volume")
        retval=subprocess.call(['mkdir', '-p', self.lvmMountPath])
        if retval != 0:
            # try to drop the volume
            subprocess.call(['lvremove', self.lvmMountPath],stdin=y);
            self.setError("Failed to create remote mount folder on hostnode "+server["accessIP"]+" at "+self.lvmMountPath)
            return False

        self.printd(". create the fstab line to mount the lvm volume")
        mountNASSource=self.lvmPath
        with open('/etc/fstab', 'a+') as fstab:
            retval = subprocess.call('echo "'+mountNASSource+' '+self.lvmMountPath+' ext4 defaults 1 3 "', shell=True, stdout=fstab)
            if retval != 0:
                # try to drop the volume
                subprocess.call(['lvremove', self.lvmMountPath],stdin=y);
                self.setError("Failed to append fstab entry for nas volume "+mountNASSource+" Error"+str(retval))
                return False

        # now mount it now
        self.printd(". mount the lvm volume locally")
        retval = subprocess.call(['mount', self.lvmMountPath])
        if retval != 0:
            # try to drop the volume
            subprocess.call(['lvremove', self.lvmMountPath],stdin=y);
            subprocess.call(['ssh',server["accessIP"],
                'grep -v '+self.lvmVolume+' /etc/fstab > ~/fstab && mv -f ~/fstab /etc/fstab'])

            self.setError("Failed to mount nas volume "+self.lvmVolume)
            return False

        # done.
        return True

    def addHostNode(self,server,size,privateNetwork,privateDomain):
        """ create a NAS of this size, this name on this server
            return boolean success.

            This method should be called once for a nas.

            Additional hostnodes that want to use this same glusterNas should
            call addAdditionalHostNode
        """
        # see if it already exists
        retval=subprocess.call(['lvdisplay', self.lvmPath],stdout=globals.fnull,stderr=globals.fnull)
        if retval==5:
            # does not exist.  Create
            print "Creating "+self.name+" NAS"

            if not self.initNfsDaemon(server,privateNetwork,privateDomain):
                # error is already set...   self.setError("Call to init gluster daemon on host "+server["accessIP"]+" failed.")
                return False

            # resolve how much space we are allocating.
            spaceRequested=self.amountOfSpaceRequestedinG(self.lvmAvailableFreeSpace,float(size))

            # there should be space allocated!
            assert spaceRequested>0

            if not self.setupLocalLVMMount(self.name,server,spaceRequested):
                # error is already set
                return False

            if not self.setupNFSShare(privateNetwork):
                # error is already set
                return False

        else:
            print "Skipping NAS create as it already exists"

        return True

    def setupNFSShare(self, privateNetwork):
        # add this mount to the list of things to share via nfs - and poke the nfs server to share it

        with open('/etc/exports', 'a+') as ex:
            retval = subprocess.call('echo "'+self.lvmMountPath+' '+privateNetwork+'/24(rw,sync,no_root_squash,no_all_squash)"', shell=True, stdout=ex)

        # share 'em
        subprocess.call(['/usr/sbin/exportfs','-a'])

        # print it out
        subprocess.call(['/usr/sbin/exportfs','-v'])

        return True

    def takeDownNFSShare(self,privateNetwork):
        # remove the share that is us.

        # yes could do this in python - but grep -v is so nice.
        with open('/etc/exports.new','w') as e:
            subprocess.call(['/bin/grep','-v',self.lvmMountPath,'/etc/exports'],stdout=e)
        subprocess.call(['/bin/mv','/etc/exports.new','/etc/exports'])
        # unshare 'em
        subprocess.call(['/usr/sbin/exportfs','-u',privateNetwork+':'+self.lvmMountPath])




    def removeHostNode(self,server,privateNetwork):
        """ remove this server from the gluster volume.
            if the last server in the volume - remove volume.
            remove lvm partition for this brick on this hostnode
            remove iptables entries
        """

        self.printd(". removing hostnode from nas "+self.name)

        self.takeDownNFSShare(privateNetwork)

        self.takeDownLVMMount(server)

        self.takeDownIpTables(server)

        return True


    def takeDownLVMMount(self,server):
        """ Undo all the work for this NAS with regard to lvm
        """

        # remove the line from the fs tab to mount the lvm volume
        self.printd(". clean up the fstab for the lvm volume")
        with open('./.fstab','w') as f:
            subprocess.call(['/bin/grep','-v',self.lvmVolume,'/etc/fstab'],stdout=f)

        subprocess.call(['/bin/mv','-f','./.fstab','/etc/fstab'])

        # umount the lvm
        self.printd(". unmount the lvm volume")
        subprocess.call(['umount', self.lvmMountPath])

        self.printd(". remove lvm volume")
        # destroy the lvm volume
        subprocess.call(['lvremove', '-f', self.lvmPath],stdout=globals.fnull,stderr=globals.fnull)

        # remove the mount point folder
        self.printd(". remove lvm mount directory")
        subprocess.call(['rmdir',self.lvmMountPath],stderr=globals.fnull)

    def takeDownIpTables(self,server):
        """ remove work done to setup ip tables to allow gluster traffic
        """

        # cleanup iptables firewall settings


