"""Helper routines that help in parsing the xml output that gluster commands generate

This exports:
  run_command_get_xml_output_tree - Runs a xml generating command and get the XML tree from the output
  run_gluster_command - Run a gluster command and get its status details and xml tree root.
  get_subnode_text - wrapper to the findall but returns the text - finds all child elements with a particular tag
  get_op_status - gets the op_status as returned by a gluster command
  _get_bricks - gets all the bricks by parsing the passed tree
  _get_volume_options - gets all the bricks by parsing the passed tree
  get_brick_status - gets the brick status by parsing the passed tree
  get_volume_process_status - gets the status of the volume processes by parsing the passed tree
  get_volume_status_tree_heads - gets the tree head for each volume in the volume status all output
  get_volume_info - gets all the volumes by parsing the passed tree
  get_snapshots - gets all the snapshots by parsing the passed tree
  get_peer_list - gets the peer list by parsing the passed tree
  get_vol_quotas - gets the volume quotas by parsing the passed tree

"""

from xml.etree import ElementTree
import tempfile
import sys
import os
import socket

from integralstor_utils import filesize, command, networking, filesize


def run_command_get_xml_output_tree(cmd):
    """Run a command that is expected to generate an XML output and return the XML tree

    cmd -- The command to run
    """
    tree = None
    try:
        r, err = command.execute(cmd)
        if err:
            raise Exception(err)
        l, err = command.get_output_list(r)
        if err:
            raise Exception(err)
        el, err = command.get_error_list(r)
        if err:
            raise Exception(err)
        if el:
            raise Exception('\n'.join(el))
        # No errors so proceed
        st = '\n'.join(l)
        tree = ElementTree.fromstring(st)
    except Exception, e:
        return None, 'Error obtaining XML tree from command output : %s' % str(e)
    else:
        return tree, None


def get_op_status(root):
    """Get the status and output of a gluster command from the xml output tree and return a dictionary with that info

    root -- The root of the xml tree that needs to be parsed
    """
    d = {}
    try:
        for e in root.getiterator():
            if e.tag == "opRet":
                try:
                    d["op_ret"] = int(e.text)
                except Exception, e:
                    d["op_ret"] = -1
                # print "opRet = %s"%e.text
            elif e.tag == "opErrno":
                try:
                    d["op_errno"] = int(e.text)
                except Exception, e:
                    d["op_errno"] = None
                # print "opErrno = %s"%e.text
            elif e.tag == "opErrstr":
                d["op_errstr"] = e.text
                # print "opErrstr = %s"%e.text
            elif e.tag == "output":
                d["output"] = e.text
                # print "output = %s"%e.text
    except Exception, e:
        return None, 'Error getting gluster command op status from XML output : %s' % str(e)
    else:
        return d, None


def run_gluster_command(cmd):
    """Run a gluster command, parse the xml output and return the opstatus and the xml root

    If the command runs without an error, it returns a dict with the xml output root and the command return codes. Else raises an exception.
    cmd -- The command to run
    """

    d = {}
    try:
        root, err = run_command_get_xml_output_tree(cmd)
        if err:
            raise Exception(err)
        status_dict = None
        if root:
            status_dict, err = get_op_status(root)
            if err:
                raise Exception(err)
            d["op_status"] = status_dict
            # print d['op_status']
            if 'op_ret' in status_dict and status_dict['op_ret'] != 0:
                err_str = 'Gluster command "%s" failed. ' % cmd
                if 'op_errno' in status_dict:
                    err_str += 'Error number : "%d". ' % status_dict['op_errno']
                if 'op_errstr' in status_dict:
                    err_str += 'Error string : "%s". ' % status_dict['op_errstr']
                raise Exception(err_str)
            d["root"] = root
    except Exception, e:
        return d, 'Error running gluster command : %s' % str(e)
    else:
        return d, None


def get_subnode_text(node, subnode):
    """Return the text associated with the first subnode of a given node

    cmd -- The command to run
    """
    ret = None
    try:
        n = node.find(subnode)
        ret = n.text
    except Exception, e:
        return None, 'Error getting XML node text : %s' % str(e)
    else:
        return ret, None


def _get_bricks(volume, type_str, replica_count):
    """Given a volume node of a volume, get the bricks of that volume

    If it is a replicated or a distributed replicate volume, then create a list of each replica set and then create a list of all the lists.
    If it is a distributed volume then just create one list for each brick location and then put all of those in another list just to maintain uniformity.

    volume -- The root of the volume xml node that needs to be parsed
    type_str -- The volume type
    replica_count -- The replica count in case this is a replicate/distributed replicate volume
    """

    bl = []
    try:
        # print type_str, replica_count
        bricks = volume.findall(".//bricks/brick")
        if bricks:
            tl = []
            count = 0
            for brick in bricks:
                # print brick.text
                if "replicate" in type_str.lower():
                    # print count
                    tl.append(brick.text)
                    count += 1
                    # Create lists of replica sets
                    if count == replica_count:
                        # print "appending"
                        bl.append(tl)
                        tl = []
                        count = 0
                else:
                    # Create lists of individual nodes
                    tl = []
                    tl.append(brick.text)
                    bl.append(tl)
        # print bl
    except Exception, e:
        return None, 'Error parsing bricks from XML stream : %s' % str(e)
    else:
        return bl, None


def _get_volume_options(volume):
    """Given an xml volume node, get all the volume options for that volume as a name, value dictionary

    volume -- The root of the volume xml node that needs to be parsed
    """
    options = []
    try:
        n = volume.findall(".//options/option")
        if n:
            for node in n:
                d = {}
                x = node.find("./name")
                if x != None:
                    d["name"] = x.text
                x = node.find(".//value")
                if x != None:
                    d["value"] = x.text
                options.append(d)
    except Exception, e:
        return None, 'Error parsing volume options from XML stream: %s' % str(e)
    else:
        return options, None


def get_brick_status(tree):
    """Given the root of a specific volume tree, record and return the status of all the bricks.

    Returns an info dictionary, the number of bricks up and the number of bricks down. The info dictionary has the keys being the brick names and the values being a dictionary of all the brick status details.

    tree -- The root of the xml tree that needs to be parsed
    """
    bd = {}
    num_up = 0
    num_down = 0
    try:
        nodes = tree.findall('.//node')
        size_total = 0
        size_free = 0
        if nodes:
            for node in nodes:
                # print "----"
                # for a in iter(node):
                #  print a.tag, a.text
                #a = node.find('./node')

                # This code covers some corner case in gluster's xml output,
                # forgot what it is!!!
                if node.find('./node'):
                    continue

                if node.find('./status') == None:
                    continue

                d = {}
                d["status"] = int(node.find('./status').text)
                d["size_total"] = int(node.find('./sizeTotal').text)
                d["size_free"] = int(node.find('./sizeFree').text)
                d["hostname"] = node.find('./hostname').text
                d["path"] = node.find('./path').text
                d["pid"] = node.find('./pid').text
                brick_name = "%s:%s" % (d["hostname"], d["path"])
                bd[brick_name] = d
                if d["status"] == 1:
                    num_up += 1
                else:
                    num_down += 1
    except Exception, e:
        return None, 'Error parsing brick status from XML stream : %s' % str(e)
    else:
        return (bd, num_up, num_down), None


def get_volume_process_status(tree, vol_info, vol_status):
    """Get the status  of each process associated with a volume.

    Returns a dict with a processes_ok indicating if all the processes are ok or not and a brick_status with the brick's nfs and self_heal_deamon status
    vol_info -- A volume info dict  which gives us the vol type and protocols
    vol_status -- A volume status dict which has the brick_status dict
    tree -- The root of the xml tree that needs to be parsed
    """
    return_dict = {}
    try:
        return_dict["processes_ok"] = True
        return_dict['brick_status'] = {}
        nodes = tree.findall('.//node')
        if nodes:
            for node in nodes:
                # This code covers some corner case in gluster's xml output,
                # forgot what it is!!!
                if node.find('./node'):
                    continue

                hostname = node.find('./hostname').text
                # print 'hostname is ', hostname
                if hostname not in ["NFS Server", "Self-heal Daemon"]:
                    continue

                path = node.find('./path').text
                if path == "localhost":
                    path = socket.getfqdn()

                status = int(node.find('./status').text)

                found = False
                # print 'path is ', path
                # print vol_status['brick_status']
                for br in vol_status["brick_status"].keys():
                    # print "splitting %s"%br
                    h, p = br.split(':')
                    # print h, hostname
                    if h == path:
                        # print "Found!"
                        found = True
                        break
                if found:
                    # br now holds the brick for which we will update the nfs
                    # and self heal status
                    if br not in return_dict['brick_status'].keys():
                        return_dict['brick_status'][br] = {}
                    if hostname == "Self-heal Daemon":
                        return_dict["brick_status"][br]["self_heal_deamon_status"] = status
                        if "replicate" in vol_info["type"].lower() and status != 1:
                            return_dict["processes_ok"] = False
                    elif hostname == "NFS Server":
                        # print 'setting nfs server', br
                        return_dict["brick_status"][br]["nfs_status"] = status
                        # print 'return dict', return_dict
                        if vol_info["protocols"]["nfs"] and status != 1:
                            return_dict["processes_ok"] = False
    except Exception, e:
        return return_dict, 'Error parsing volume process status from XML stream : %s' % str(e)
    else:
        # print 'final', return_dict
        return return_dict, None


def get_volume_status_tree_heads(tree):
    """Given a tree head for info on status of all the volumes, return the individual tree heads for each volume

    tree -- The root of the xml tree that needs to be parsed
    """
    try:
        found_list = tree.findall('.//volumes/volume')
    except Exception, e:
        return None, 'Error parsing volume status tree heads from XML stream : %s' % str(e)
    else:
        return found_list, None


def get_volume_info(tree, admin_vol_name):
    """Given the tree head from the xml output of the gluster volume info all command, return the list of dicts with some volume information but exclude the passed in admin_vol_name.

    tree -- The root of the xml tree that needs to be parsed
    admin_vol_name - the name of the integralview admin volume
    """

    vl = []
    try:
        found_list = tree.findall('.//volumes/volume')
        if found_list:
            for volume in found_list:
                _vol_name, err = get_subnode_text(volume, "name")
                if err:
                    raise Exception(err)
                # print _vol_name

                if admin_vol_name:
                    if _vol_name == admin_vol_name:
                        continue

                v = {}
                v["name"] = _vol_name
                val, err = get_subnode_text(volume, "typeStr")
                if err:
                    raise Exception(err)
                v["type"] = val

                val, err = get_subnode_text(volume, "status")
                if err:
                    raise Exception(err)
                v["status"] = int(val)

                val, err = get_subnode_text(volume, "statusStr")
                if err:
                    raise Exception(err)
                v["status_str"] = val

                val, err = get_subnode_text(volume, "brickCount")
                if err:
                    raise Exception(err)
                v["brick_count"] = int(val)

                val, err = get_subnode_text(volume, "distCount")
                if err:
                    raise Exception(err)
                v["dist_count"] = int(val)

                val, err = get_subnode_text(volume, "stripeCount")
                if err:
                    raise Exception(err)
                v["stripe_count"] = int(val)

                val, err = get_subnode_text(volume, "replicaCount")
                if err:
                    raise Exception(err)
                v["replica_count"] = int(val)

                val, err = get_subnode_text(volume, "optCount")
                if err:
                    raise Exception(err)
                v["opt_count"] = val

                val, err = _get_bricks(volume, v["type"], v["replica_count"])
                # print val, err
                if err:
                    raise Exception(err)
                v["bricks"] = val

                val, err = _get_volume_options(volume)
                if err:
                    raise Exception(err)
                v["options"] = val
                # print val

                protocols = {}
                # Set enabled unless turned off with options
                protocols["cifs"] = True
                protocols["nfs"] = True
                for option in v["options"]:
                    if option["name"] == "user.cifs":
                        if option["value"] in ['disable', 'off', 'false']:
                            protocols["cifs"] = False
                        if option["name"] == "nfs.disable":
                            if option["value"] in ['on', 'true']:
                                protocols["nfs"] = False
                v["protocols"] = protocols

                vl.append(v)
    except Exception, e:
        return None, 'Error parsing volume list from XML stream : %s' % str(e)
    else:
        return vl, None


def get_snapshots(root):
    """Get information of each snapshot, given the xml tree of the appropriate command

    Returns a list of dicts, each one containing info about a snapshot.

    root -- The root of the xml tree that needs to be parsed
    """
    l = []
    try:
        t = root.findall('.//snapshots/snapshot')
        if t:
            for node in t:
                d = {}
                val, err = get_subnode_text(node, "name")
                if err:
                    raise Exception(err)
                d["name"] = val
                val, err = get_subnode_text(node, "createTime")
                if err:
                    raise Exception(err)
                d["create_time"] = val
                x = node.find('./snapVolume')
                if x:
                    val, err = get_subnode_text(x, "status")
                    if err:
                        raise Exception(err)
                    d["status"] = val
                l.append(d)
    except Exception, e:
        return None, 'Error parsing snapshots from XML stream : %s' % str(e)
    else:
        return l, None


def get_peer_list(root):
    """Get the list of gluster peers along with their status - also include the status of the local host

    Returns a list of dicts, each one containing the info and status of each peer

    root -- The root of the xml tree that needs to be parsed
    """

    peerlist = []
    try:
        t = root.findall('.//peer')
        if t:
            for peer in t:
                d = {}
                val, err = get_subnode_text(peer, "hostname")
                if err:
                    raise Exception(err)
                d["hostname"] = val
                val, err = get_subnode_text(peer, "connected")
                if err:
                    raise Exception(err)
                d["status"] = val
                val, err = get_subnode_text(peer, "stateStr")
                if err:
                    raise Exception(err)
                d["status_str"] = val
                #peerlist.append(get_subnode_text(peer, "hostname"))
                peerlist.append(d)
        tree = None
        if peerlist:
            d = {}
            #d["hostname"] = os.uname()[1]
            d["hostname"] = socket.getfqdn()
            can_connect, err = networking.can_connect("localhost", 24007)
            if err:
                raise Exception(err)
            if can_connect:
                d["status"] = '1'
            else:
                d["status"] = '0'
            d["status_str"] = "Peer in Cluster"
            peerlist.append(d)
    except Exception, e:
        return None, 'Error parsing peer list from XML stream : %s' % str(e)
    else:
        return peerlist, None


def get_vol_quotas(root):
    """Get information about quotas for a given volume

    Return a dict of all quotas with keys being the dir and the value being the quota information for that path.
    root -- The root of the xml tree that needs to be parsed
    """
    return_dict = {}
    try:
        t = root.findall(".//volQuota/limit")
        if t:
            for q in t:
                d = {}
                val, err = get_subnode_text(q, "hard_limit")
                if err:
                    raise Exception(err)
                d["hard_limit"] = val
                d['hard_limit_human_readable'] = filesize.get_naturalsize(
                    val, binary=True)

                val, err = get_subnode_text(q, "soft_limit_percent")
                if err:
                    raise Exception(err)
                d["soft_limit_percent"] = val

                val, err = get_subnode_text(q, "soft_limit_value")
                if err:
                    raise Exception(err)
                d["soft_limit_value"] = val

                val, err = get_subnode_text(q, "used_space")
                if err:
                    raise Exception(err)
                d["used_space"] = val
                d['used_space_human_readable'] = filesize.get_naturalsize(
                    val, binary=True)

                val, err = get_subnode_text(q, "avail_space")
                if err:
                    raise Exception(err)
                d["avail_space"] = val
                d['avail_space_human_readable'] = filesize.get_naturalsize(
                    val, binary=True)

                val, err = get_subnode_text(q, "sl_exceeded")
                if err:
                    raise Exception(err)
                d["sl_exceeded"] = val

                val, err = get_subnode_text(q, "hl_exceeded")
                if err:
                    raise Exception(err)
                d["hl_exceeded"] = val

                val, err = get_subnode_text(q, "path")
                if err:
                    raise Exception(err)
                return_dict[val] = d
    except Exception, e:
        return None, 'Error parsing volume quotas from XML stream : %s' % str(e)
    else:
        return return_dict, None


def main():
    pass


if __name__ == "__main__":
    main()

'''
def get_volume_info():

  production = True
  try:
    production =  settings.PRODUCTION
  except Exception, e:
    production = False

  if not production:
    #fn = "/home/bkrram/Documents/software/Django-1.4.3/code/gluster_admin/gluster_admin/utils/b.xml"
    fn = "%s/b.xml"%settings.BASE_FILE_PATH
    with open(fn, 'rt') as f:
      tree = ElementTree.parse(f)
  else:
    temp = tempfile.TemporaryFile()
    try:
      cmd = "/usr/local/sbin/gluster volume info all --xml"
      r = command.execute(cmd)
      if r:
        #print "cmd out is "
        l = command.get_output_list(r)
        #print l
        #print "cmd err is "
        #l = command.get_error_list(r)
        #print l
        for line in l:
          temp.write(line)
        temp.seek(0)
        tree = ElementTree.parse(temp)
    finally:
      temp.close()


  vl = []
  for volume in tree.findall('.//volumes/volume'):
    _vol_name = get_subnode_text(volume, "name")
    if settings and settings.ADMIN_VOL_NAME and _vol_name == settings.ADMIN_VOL_NAME:
      continue
    v = {}
    v["name"] = _vol_name
    v["type"] = get_subnode_text(volume, "typeStr")
    v["status"] = int(get_subnode_text(volume, "status"))
    v["status_str"] = get_subnode_text(volume, "statusStr")
    v["brick_count"] = int(get_subnode_text(volume, "brickCount"))
    v["dist_count"] = int(get_subnode_text(volume, "distCount"))
    v["stripe_count"] = int(get_subnode_text(volume, "stripeCount"))
    v["replica_count"] = int(get_subnode_text(volume, "replicaCount"))
    v["opt_count"] = get_subnode_text(volume, "optCount")
    v["bricks"] = _get_bricks(volume, v["type"], v["replica_count"])
    v["options"] = _get_volume_options(volume)

    protocols = {}
    # Set enabled unless turned off with options
    protocols["cifs"] = True
    protocols["nfs"] = True
    for option in v["options"]:
      if option["name"] == "user.cifs":
        if option["value"] in ['disable','off','false']:
          protocols["cifs"] = False 
        if option["name"] == "nfs.disable":
          if option["value"] in ['on','true']:
            protocols["nfs"] = False 
    v["protocols"] = protocols
    vl.append(v)

  for vol in vl:
    if vol["status"] != 1:
      continue      
    if not production:
      fn = "%s/volume_status_detail.xml"%settings.BASE_FILE_PATH
      with open(fn, 'rt') as f:
        tree = ElementTree.parse(f)
    else:
      temp = tempfile.TemporaryFile()
      try:
        cmd = "/usr/local/sbin/gluster volume status %s detail --xml"%vol["name"]
        print cmd
        r = command.execute(cmd)
        if r:
          #print "cmd out is "
          l = command.get_output_list(r)
          #print l
          #print "cmd err is "
          #l = command.get_error_list(r)
          #print l
          for line in l:
            temp.write(line)
          temp.seek(0)
          tree = ElementTree.parse(temp)
      finally:
        temp.close()

    root = tree.getroot()
    nodes = root.findall('.//node')
    size_total = 0
    size_free = 0
    bd = {}
    num_up = 0
    num_down = 0
    for node in nodes:
      #print "----"
      #for a in iter(node):
      #  print a.tag, a.text
      #a = node.find('./node')
      if node.find('./node'):
        print "continuing"
        continue
      #a = node.find('./status')
      if node.find('./status') == None:
        print "continuing"
        continue
      #print "ok"
      d = {}
      d["status"] = int(node.find('./status').text)
      d["size_total"] = int(node.find('./sizeTotal').text)
      d["size_free"] = int(node.find('./sizeFree').text)
      d["hostname"] = node.find('./hostname').text
      d["path"] = node.find('./path').text
      d["pid"] = node.find('./pid').text
      brick_name = "%s:%s"%(d["hostname"], d["path"])
      bd[brick_name] = d
      if d["status"] == 1:
        num_up +=1
      else:
        num_down += 1 
    #assert False
    vol["brick_status"] = bd

    if vol["replica_count"] > 1:
      replica_set_status = []
      for br in vol["bricks"] :
        counted = False
        num_down = 0
        num_up = 0
        for  b in br:
          if b not in bd:
            #Could happen if a brick is down
            return None
          if bd[b]["status"] == 1:
            num_up += 1
            if not counted:
              #Found one up replica so only consider size info for this. If all down then it does not count
              size_free += bd[b]["size_free"]
              size_total += bd[b]["size_total"]
              counter = True
          else:
            num_down += 1
          replica_set_status.append(num_down)
      #print replica_set_status
      if num_up == 0:
        vol["data_access_status"] = "Volume down. No data accessible!"
      else:
        if max(replica_set_status) == vol["replica_count"]:
          vol["data_access_status"] = "Some data inaccessible"
          vol["data_access_status_code"] = -1
        elif max(replica_set_status) > 0:
          num_more = vol["replica_count"] - max(replica_set_status)
          vol["data_access_status"] = "Data accessible but vulnerable. Loss of %d more data locations will cause data loss"%num_more
          vol["data_access_status_code"] = 1
        else:
          vol["data_access_status"] = "Healthy"
          vol["data_access_status_code"] = 0
    else:
      #Distributed so count em all
      num_down = 0
      for b in bd.keys():
        if bd[b]["status"] == 1:
          num_up += 1
          size_free += bd[b]["size_free"]
          size_total += bd[b]["size_total"]
        else:
          num_down += 1
      if num_down > 0:
        vol["data_access_status"] = "Some data inaccessible"
        vol["data_access_status_code"] = 1
      else:
        vol["data_access_status"] = "Healthy"
        vol["data_access_status_code"] = 0
    vol["size_total"] = filesize.get_naturalsize(size_total, binary=True)
    vol["size_used"] = filesize.get_naturalsize(size_total-size_free, binary=True)
    vol["size_free"] = filesize.get_naturalsize(size_free, binary=True)
    #print size_total-size_free
    #print (size_total-size_free)/float(size_total)
    vol["size_used_percent"] = int(((size_total-size_free)/float(size_total)) * 100)
    #print vol["size_used_percent"]


    # Now get the status of the self heal and NFS servers for each node
    if not production:
      fn = "%s/volume_status.xml"%settings.BASE_FILE_PATH
      with open(fn, 'rt') as f:
        tree = ElementTree.parse(f)
    else:
      temp = tempfile.TemporaryFile()
      try:
        cmd = "/usr/local/sbin/gluster volume status %s --xml"%vol["name"]
        r = command.execute(cmd)
        if r:
          #print "cmd out is "
          l = command.get_output_list(r)
          #print l
          #print "cmd err is "
          #l = command.get_error_list(r)
          #print l
          for line in l:
            temp.write(line)
          temp.seek(0)
          tree = ElementTree.parse(temp)
      finally:
        temp.close()

    root = tree.getroot()
    nodes = root.findall('.//node')

    vol["processes_ok"] = True
    for node in nodes:
      if node.find('./node'):
        print "continuing"
        continue
      hostname = node.find('./hostname').text
      if hostname not in ["NFS Server", "Self-heal Daemon"]:
        continue
      path = node.find('./path').text
      if path == "localhost":
        path = os.uname()[1]
      status = int(node.find('./status').text)
      found = False
      for br in vol["brick_status"].keys():
        #print "splitting %s"%br
        h, p = br.split(':')
        #print h, hostname
        if h == path:
          #print "Found!"
          found = True
          break
      if found:
        #br now holds the brick for which we will update the nfs and self heal status
        if hostname == "Self-heal Daemon":
          vol["brick_status"][br]["self_heal_deamon_status"] = status
          if "replicate" in vol["type"].lower() and status != 1:
            vol["processes_ok"] = False
        elif hostname == "NFS Server":
          vol["brick_status"][br]["nfs_status"] = status
          if vol["protocols"]["nfs"] and status != 1:
            vol["processes_ok"] = False
      
      
  #print vl
  #assert False
  return vl
'''
'''
    #If production, run the command else read from file_name, return the xml tree
    el = []
    tree = None
    if not use_cmd:
      with open(file_name, 'rt') as f:
        tree = ElementTree.parse(f)
    else:
      temp = tempfile.TemporaryFile()
      r, err = command.execute(cmd)
      if err:
        raise Exception(err)
      if r:
        l, err = command.get_output_list(r)
        if err:
          raise Exception(err)
        el, err = command.get_error_list(r)
        if err:
          raise Exception(err)
        if l:
          for line in l:
            temp.write(line)
      temp.seek(0)
      tree = ElementTree.parse(temp)
      temp.close()
    if tree:
      d["tree"] = tree
    if el:
      d["error_list"] = el
  except Exception, e:
    return None, 'Error obtaining XML tree from command output : %s'%str(e)
  else:
    return d, None
'''

# vim: tabstop=8 softtabstop=0 expandtab ai shiftwidth=4 smarttab
