#!/usr/bin/python

import getopt, sys
import socket, ssl
from xml.dom import minidom
import logging
import os
import os.path
import re
import threading
import time, random
from Queue import Queue
import getpass
import subprocess
import tempfile

RICCI_PORT = 11111
CLUSTERRNG = "/usr/share/ccs/cluster.rng"
EMPTYCLUSTERCONF = "/usr/share/ccs/empty_cluster.conf"

password = None
debug = False
sync = False
usefile = False
hostname = False
filename = False
activate = False
verifyconf = True
schema = None

class StopNodeThread (threading.Thread):
    def __init__ (self, host):
        self.host = host
        threading.Thread.__init__(self)

    def getoutput(self):
        return self.output

    def run (self):
        self.output = stop_node(self.host,True)
        if self.output == 0:
            self.output = "Stopped %s" % self.host

class StartNodeThread (threading.Thread):
    def __init__ (self, host):
        self.host = host
        threading.Thread.__init__(self)
        self.output = ""

    def getoutput(self):
        return self.output

    def run (self):
        self.output = start_node(self.host,True)
        if self.output == 0:
            self.output = "Started %s" % self.host

def main(argv):
    getconf = sendconf = False
    getclusterschema = False
    status = start = stop = startall = stopall = False
    listnodes = listservices = listdomains = False
    addnode = removenode = getversion = False
    incrementversion = setversion = False
    addmethod = removemethod = createcluster = False
    addfencedev = removefencedev = False
    addfenceinst = removefenceinst = False
    addunfenceinst = removeunfenceinst = False
    addalt = rmalt = False
    lsfencedev = lsfenceinst = False
    addfailoverdomain = removefailoverdomain = False
    lsfailoverdomain = addfailoverdomainnode = removefailoverdomainnode = False
    addservice = addsubservice = addresource = False
    removeservice = removesubservice = removeresource = False
    listquorum = setquorumd = addheuristic = removeheuristic = False
    settotem = setrm = setcman = setfencedaemon = False
    setlogging = addlogging = removelogging = False
    addvm = removevm = False
    listmisc = False
    setdlm = setmulticast = setaltmulticast = False
    passwordset = False
    nodeid = votes = False
    checkconf = False
    exp = exprm = False
    lsfenceopts = lsserviceopts = False

    global password, debug, sync, hostname, usefile, filename, activate
    global verifyconf, schema
#    logging.basicConfig(level=logging.DEBUGRemove
    try:
        opts, args = getopt.getopt(argv, "dsih:p:f:", ["help","host=","getconf","status","start","stop","lsnodes",
      "lsservices", "listdomains", "addnode=", "rmnode=", "getversion","setversion=","incversion",
      "createcluster=", "password=", "addmethod=", "rmmethod=", "addfencedev=", "rmfencedev=",
      "addfenceinst=", "rmfenceinst=", "lsfencedev", "lsfenceinst", "sync", "addfailoverdomain=",
      "rmfailoverdomain=", "addservice=", "rmservice=", "addsubservice=", "rmsubservice=", "addresource=",
      "rmresource=", "setquorumd","addheuristic","settotem", "setrm", "setcman", "setfencedaemon",
      "setlogging", "addlogging","rmlogging","setmulticast","sync","addfailoverdomainnode=", "file=", "nodeid=",
      "votes=", "rmfailoverdomainnode=", "setdlm", "rmheuristic", "startall", "stopall", "activate",
      "setconf", "lsquorum", "lsfailoverdomain", "lsmisc", "checkconf", "exp=", "exprm=", "addunfenceinst=",
      "rmunfenceinst=","addvm=","rmvm=", "lsfenceopts", "lsserviceopts", "getschema", "ignore", "debug",
      "setaltmulticast","addalt=", "rmalt="])
    except getopt.GetoptError:
        usage()
        sys.exit(2)
    for opt, arg in opts:
        if arg == "--help": usage() ; sys.exit()
        if opt == ("--help"): usage() ; sys.exit()
        elif opt in ("--host","-h"):
            hostname = arg
            logging.debug("Hostname = %s" % hostname)
        elif opt in ("--file","-f"):
            usefile = True
            filename = arg
            logging.debug("Filename = %s" % filename)
        elif opt in ("--ignore","-i"):
            verifyconf = False
        elif opt in ("--password", "-p"): passwordset = True ; password = arg
        elif opt in ("--getconf"): getconf = True
        elif opt in ("--checkconf"): checkconf = True
        elif opt in ("--setconf"): sendconf = True
        elif opt in ("--status"): status = True
        elif opt in ("--start"): start = True
        elif opt in ("--stop"): stop = True
        elif opt in ("--startall"): startall = True
        elif opt in ("--stopall"): stopall = True
        elif opt in ("--lsnodes"): listnodes = True
        elif opt in ("--lsservices"): listservices = True
        elif opt in ("--listdomains"): listdomains = True
        elif opt in ("--addnode"): addnode = True ; node = arg 
        elif opt in ("--rmnode"): removenode = True ; node = arg 
        elif opt in ("--getversion"): getversion = True
        elif opt in ("--setversion"): setversion = True ; version = arg 
        elif opt in ("--incversion"): incrementversion = True
        elif opt in ("--sync"): sync = True
        elif opt in ("--activate"): activate = True
        elif opt in ("--createcluster"):
            createcluster = True
            clustername = arg
        elif opt in ("--addmethod"):
            addmethod = True
            method = arg
            options = args
        elif opt in ("--rmmethod"):
            removemethod = True
            method = arg
            options = args
        elif opt in ("--addfencedev"):
            addfencedev = True
            name = arg
            options = args
        elif opt in ("--rmfencedev"): removefencedev = True; name = arg
        elif opt in ("--addfenceinst"):
            addfenceinst = True
            name = arg
            options = args
        elif opt in ("--rmfenceinst"):
            removefenceinst = True
            name = arg
            options = args
        elif opt in ("--addunfenceinst"):
            addunfenceinst = True
            name = arg
            options = args
        elif opt in ("--rmunfenceinst"):
            removeunfenceinst = True
            name = arg
            options = args
        elif opt in ("--addalt"): addalt = True; name = arg; options = args
        elif opt in ("--rmalt"): rmalt = True; name = arg; options = args
        elif opt in ("--lsfencedev"): lsfencedev = True;
        elif opt in ("--lsfenceinst"): lsfenceinst = True; options = args
        elif opt in ("--lsfailoverdomain"): lsfailoverdomain = True
        elif opt in ("--addfailoverdomain"):
            addfailoverdomain = True
            name = arg
            options = args
        elif opt in ("--rmfailoverdomain"):
            removefailoverdomain = True
            name = arg
        elif opt in ("--addfailoverdomainnode"):
            addfailoverdomainnode = True
            name = arg
            options = args
        elif opt in ("--rmfailoverdomainnode"):
            removefailoverdomainnode = True
            name = arg
            options = args
        elif opt in ("--addservice"):
            addservice = True
            name = arg
            options = args
        elif opt in ("--rmservice"):
            removeservice = True
            name = arg
        elif opt in ("--addsubservice"):
            addsubservice = True
            name = arg
            options = args
        elif opt in ("--rmsubservice"):
            removesubservice = True
            name = arg
            options = args
        elif opt in ("--addresource"):
            addresource = True
            name = arg
            options = args
        elif opt in ("--rmresource"):
            removeresource = True
            name = arg
            options = args
        elif opt in ("--addvm"):
            addvm = True
            name = arg
            options = args
        elif opt in ("--rmvm"):
            removevm = True
            name = arg
        elif opt in ("--lsserviceopts"):
            lsserviceopts = True
            options = args
        elif opt in ("--lsfenceopts"):
            lsfenceopts = True
            options = args
        elif opt in ("--lsquorumd"): listquorum = True
        elif opt in ("--setquorumd"): setquorumd = True; options = args
        elif opt in ("--addheuristic"): addheuristic = True; options = args
        elif opt in ("--rmheuristic"): removeheuristic = True; options = args
        elif opt in ("--lsmisc"): listmisc = True
        elif opt in ("--settotem"): settotem = True; options = args
        elif opt in ("--setrm"): setrm = True; options = args
        elif opt in ("--setcman"): setcman = True; options = args
        elif opt in ("--setdlm"): setdlm = True; options = args
        elif opt in ("--setfencedaemon"): setfencedaemon = True; options = args
        elif opt in ("--setlogging"): setlogging = True; options = args
        elif opt in ("--addlogging"): addlogging = True; options = args
        elif opt in ("--rmlogging"): removelogging = True; options = args
        elif opt in ("--setmulticast"): setmulticast = True; options = args
        elif opt in ("--setaltmulticast"): setaltmulticast = True; options = args
        elif opt in ("--nodeid"): nodeid = arg
        elif opt in ("--votes"): votes = arg
        elif opt in ("-d","--debug"): debug = True
        elif opt in ("--exp"): exp = True; tag = arg; options = args
        elif opt in ("--exprm"): exprm = True; location = arg
        elif opt in ("--getschema"): getclusterschema = True;

    if not hostname and not filename:
        print "Must specify a hostname or filename."
        sys.exit(2)

    if (getconf): get_cluster_conf()
    if (sendconf): send_cluster_conf()
    if (checkconf): check_cluster_conf()
    if (status): get_cluster_status()
    if (stop): stop_node(hostname) 
    if (start): start_node(hostname)
    if (startall): start_all()
    if (stopall): stop_all()
    if (listnodes): list_nodes()
    if (listservices): list_services()
    if (listdomains): list_domains()
    if (addnode): add_node(node, nodeid, votes)
    if (removenode): remove_node(node)
    if (getversion): print get_version()
    if (setversion): set_version(version)
    if (incrementversion): increment_version()
    if (createcluster): create_cluster(clustername)
    if (addmethod): add_method(method, options)
    if (removemethod): remove_method(method, options)
    if (addfencedev): add_fencedev(name, options)
    if (removefencedev): remove_fencedev(name)
    if (addfenceinst): add_fenceinst(name, options)
    if (removefenceinst): remove_fenceinst(name, options)
    if (addunfenceinst): add_unfenceinst(name, options)
    if (removeunfenceinst): remove_unfenceinst(name, options)
    if (addalt): add_alt(False, name, options)
    if (rmalt): add_alt(True, name, options)
    if (lsfencedev): list_fencedev()
    if (lsfenceinst): list_fenceinst(options)
    if (lsfailoverdomain): list_failoverdomain()
    if (addfailoverdomain): add_failoverdomain(name, options)
    if (removefailoverdomain): remove_failoverdomain(name)
    if (addfailoverdomainnode): add_failoverdomainnode(name, options)
    if (removefailoverdomainnode): remove_failoverdomainnode(name, options)
    if (addservice): add_service(name, options)
    if (removeservice): remove_service(name)
    if (addsubservice): add_subservice(name, options)
    if (removesubservice): remove_subservice(name, options)
    if (addresource): add_resource(name, options)
    if (removeresource): remove_resource(name, options)
    if (addvm): add_vm(name,options)
    if (removevm): remove_vm(name)
    if (lsserviceopts): list_serviceopts(options)
    if (lsfenceopts): list_fenceopts(options)
    if (listquorum): list_quorum()
    if (setquorumd): set_quorumd(options)
    if (addheuristic): add_heuristic(options)
    if (removeheuristic): remove_heuristic(options)
    if (listmisc): list_misc()
    if (settotem): set_totem(options)
    if (setrm): set_rm(options)
    if (setcman): set_cman(options)
    if (setdlm): set_dlm(options)
    if (setfencedaemon): set_fencedaemon(options)
    if (setlogging): set_logging(options)
    if (addlogging): add_logging_daemon(options)
    if (removelogging): remove_logging_daemon(options)
    if (setmulticast): set_multicast(False, options)
    if (setaltmulticast): set_multicast(True, options)
    if (exp): expert_mode(tag, options)
    if (exprm): expert_mode_remove(location)
    if (sync): sync_cluster_conf()
    if (getclusterschema): get_cluster_schema()

def usage():
    print """Usage: ccs [OPTION]...
Cluster configuration system.

      --help            Display this help and exit
  -h, --host host       Cluster node to perform actions on
  -f, --file file       File to perform actions on
  -i, --ignore          Ignore validation errors in cluster.conf file
  -p, --password        Ricci user password for node running ricci
      --getconf         Print current cluster.conf file
      --setconf         Use the file specified by '-f' to send to the host
                        specified with '-h'
      --checkconf       If file is specified, verify that all the nodes in the
                        file have the same cluster.conf as the file.  If a
                        host is specified then verify that all nodes in the
                        host's cluster.conf file have the identical
                        cluster.conf file
                        same cluster.conf
      --getschema       Print current cluster schema file (if using -h use
      			schema from network, if using -f use local schema)
      --sync            Sync config file to all nodes
      --activate        Activate config on node (use this option with --sync
                        to activate config on all nodes)
  -d, --debug           Display debugging information to help troubleshoot
                        connection issues with ricci
      --exp tag [location] [options]
                        Expert mode to add elements not currently defined in
                        ccs (see man page for more information)
      --exprm location
                        Expert mode to remove elements not currently defined in
                        ccs (see man page for more information)

Cluster Operations:
      --createcluster <cluster>
                        Create a new cluster.conf (removing old one if it
                                                   exists)
      --getversion      Get the current cluster.conf version
      --setversion <n>  Set the cluster.conf version
      --incversion      Increment the cluster.conf version by 1
      --startall        Start *AND* enable cluster services on reboot
                        for all nodes
      --stopall         Stop *AND* disable cluster services on reboot
                        for all nodes
      --start           Start *AND* enable cluster services on reboot for
                        host specified with -h
      --stop            Stop *AND* disable cluster services on reboot for
                        host specified with -h

Node Operations:
      --lsnodes         List all nodes in the cluster
      --addnode <node>  Add node <node> to the cluster
      --rmnode <node>
                        Remove a node from the cluster
      --nodeid <nodeid> Specify nodeid when adding a node
      --votes <votes>   Specify number of votes when adding a node
      --addalt <node name> <alt name> [alt options]
      			Add an altname to a node for RRP
      --rmalt <node name>
      			Remove an altname from a node for RRP

Fencing Operations:
      --lsfenceopts [fence type]
                        List available fence devices.  If a fence type is
                        specified, then list options for the specified
                        fence type
      --lsfencedev      List all of the fence devices configured
      --lsfenceinst [<node>]
                        List all of the fence methods and instances on the
                        specified node or all nodes if no node is specified
      --addmethod <method> <node>
                        Add a fence method to a specific node
      --rmmethod <method> <node>
                        Remove a fence method from a specific node
      --addfencedev <device name> [fence device options]
                        Add fence device. Fence devices and parameters can be
                        found in online documentation in 'Fence Device
                        Parameters'
      --rmfencedev <fence device name>
                        Remove fence device
      --addfenceinst <fence device name> <node> <method> [options]
                        Add fence instance. Fence instance parameters can be
                        found in online documentation in 'Fence Device
                        Parameters'
      --rmfenceinst <fence device name> <node> <method>
                        Remove all instances of the fence device listed from
                        the given method and node
      --addunfenceinst <fence device name> <node> [options]
                        Add an unfence instance
      --rmunfenceinst <fence device name> <node>
                        Remove all instances of the fence device listed from
                        the unfence section of the node

Failover Domain Operations:
      --lsfailoverdomain
                        Lists all of the failover domains and failover domain
                        nodes configured in the cluster
      --addfailoverdomain <name> [restricted] [ordered] [nofailback]
                        Add failover domain
      --rmfailoverdomain <name>
                        Remove failover domain
      --addfailoverdomainnode <failover domain> <node> [priority]
                        Add node to given failover domain
      --rmfailoverdomainnode <failover domain> <node>
                        Remove node from failover domain

Service Operations:
      --lsserviceopts [service type]
                        List available services.  If a service type is
                        specified, then list options for the specified
                        service type
      --lsservices      List currently configured services and resources in
                        the cluster
      --addresource <resource type> [resource options] ...
                        Add global cluster resources to the cluster
                        Resource types and variables can be found in the
                        online documentation under 'HA Resource Parameters'
      --rmresource <resource type> [resource options]
                        Remove specified resource with resource options
      --addservice <servicename> [service options] ...
                        Add service to cluster
      --rmservice <servicename>
                        Removes a service and all of its subservices
      --addvm <virtual machine name> [vm options] ...
                        Add a virtual machine to the cluster
      --rmvm <virtual machine name>
                        Removes named virtual machine from the cluster
      --addsubservice <servicename> <subservice> [service options] ...
                        Add individual subservices, if adding child services,
                        use ':' to separate parent and child subservices
                        and brackets to identify subservices of the same type

                        Subservice types and variables can be found in the
                        online documentation in 'HA Resource Parameters'

                        To add a nfsclient subservice as a child of the 2nd
                        nfsclient subservice in the 'service_a' service use
                        the following example: --addsubservice service_a \\
                                               nfsclient[1]:nfsclient \\
                                               ref=/test
      --rmsubservice <servicename> <subservice>
                        Removes a specific subservice specified by the
                        subservice, using ':' to separate elements and
                        brackets to identify between subservices of the
                        same type.
                        To remove the 1st nfsclient child subservice
                        of the 2nd nfsclient subservice in the 'service_a'
                        service, use the following example:
                                            --rmsubservice service_a \\
                                            nfsclient[1]:nfsclient

Quorum Operations:
      --lsquorum        List quorum options and heuristics
      --setquorumd [quorumd options] ...
                        Add quorumd options
      --addheuristic [heuristic options] ...
                        Add heuristics to quorumd
      --rmheuristic [heuristic options] ...
                        Remove heuristic specified by heurstic options

Misc Options
      --lsmisc          List all of the misc options
      --settotem [totem options]
                        Set totem options
      --setdlm [dlm options]
                        Set dlm options
      --setrm [resource manager options]
                        Set resource manager options
      --setcman [cman options]
                        Set cman options
      --setmulticast [multicast address] [multicast options]
                        Sets the multicast address to use (or removes it
                        if no multicast address is given)
      --setaltmulticast [alt multicast address] [alt multicast options]
                        Sets the alt multicast address to use (or removes it
                        if no alt multicast address is given)
      --setfencedaemon [fence daemon options]
                        Set fence daemon options
      --setlogging [logging options]
                        Set logging options
      --addlogging [logging_daemon options]
                        Add a logging daemon (see cluster.conf for options)
      --rmlogging [logging_daemon options]
                        Remove the logging daemon with specified options
  """

def stop_node(node, inthread=False):
    xml = send_ricci_command("cluster", "stop_node", node)

    dom = minidom.parseString(xml)
    vars = dom.getElementsByTagName('var')
    for var in vars:
        if var.getAttribute("name") != "success":
            continue
        if not var.hasAttribute("value"):
            myerr = "Unable to get valid response from ricci for %s." % node
            if inthread:
                return myerr
            else:
                print myerr
                sys.exit(1)
        if var.getAttribute("value") == "false":
            myerr = "Unable to stop %s." % node
            if inthread:
                return myerr
            else:
                print myerr
                sys.exit(1)

        # If it succeeds print nothing and return 0
        if var.getAttribute("value") == "true":
            return 0

    myerr = "Unable to get valid response from ricci for %s." % node
    if inthread:
        return myerr
    else:
        print myerr
        sys.exit(1)
    
def start_node(node, inthread=False):
    error_description = ""
    xml = send_ricci_command("cluster", "start_node", node)

    dom = minidom.parseString(xml)
    vars = dom.getElementsByTagName('var')
    for var in vars:
        if var.getAttribute("name") == "error_description":
            error_description = var.getAttribute("value")
            break

    for var in vars:
        if var.getAttribute("name") != "success":
            continue

        if not var.hasAttribute("value"):
            myerr = "Unable to get valid response from ricci for %s." % node
            if inthread:
                return myerr
            else:
                print myerr
                sys.exit(1)

        if var.getAttribute("value") == "false":
            myerr = "Unable to start %s, possibly do to lack of quorum, try --startall\n" % node
            myerr += "Error: " +error_description
            if inthread:
                return myerr
            else:
                print myerr
                sys.exit(1)

        # If it succeeds print nothing and return 0
        if var.getAttribute("value") == "true":
            return 0

    myerr = "Unable to get valid response from ricci for %s." % node
    if inthread:
        return myerr
    else:
        print myerr
        sys.exit(1)

def start_all(nodelist = None):
    if nodelist == None:
        nodelist = get_nodelist()
    threads = {}

    for node in nodelist:
        threads[node] = StartNodeThread(node)
        threads[node].start()

    for thread in threads.values():
        thread.join()
        print thread.getoutput()

def stop_all(nodelist = None):
    if nodelist == None:
        nodelist = get_nodelist()
    threads = {}

    for node in nodelist:
        threads[node] = StopNodeThread(node)
        threads[node].start()

    for thread in threads.values():
        thread.join()
        print thread.getoutput()

def get_nodelist():
    xml = get_cluster_conf_xml()
    dom = minidom.parseString(xml)
    nodelist = []
    for node in dom.getElementsByTagName('clusternode'):
        nodelist.append(node.getAttribute("name"))
    return nodelist

def get_cluster_conf():
    xml = get_cluster_conf_xml()
    xml = minidom.parseString(xml).getElementsByTagName('cluster')[0].toprettyxml(indent='  ',newl='')
    print xml

def get_cluster_status():
    if usefile:
        print "Error: Can't get status on a local file"
        sys.exit(1)

    xml = send_ricci_command("cluster","status")
    xml = minidom.parseString(xml)

    found_error = False
    if (len(xml.getElementsByTagName('cluster')) < 1):
        for var in xml.getElementsByTagName('var'):
            if var.getAttribute("name") == "error_description":
                print "Error: %s" % var.getAttribute("value")
                found_error = True
        if not found_error:        
            print "Error: Did not receive valid status back"
        sys.exit(1)
    xml = xml.getElementsByTagName('cluster')[0].toprettyxml(indent='  ',newl='')
    print xml

def sync_cluster_conf():
    global hostname
    xml = get_cluster_conf_xml()
    dom = minidom.parseString(xml).getElementsByTagName('cluster')[0]
    xml = dom.toprettyxml(indent='  ',newl='')
    dom = minidom.parseString(xml)
    for node in dom.getElementsByTagName('clusternode'):
        nodename = node.getAttribute("name")
        hostname = nodename
        set_cluster_conf(xml)

def list_nodes():
    xml = get_cluster_conf_xml()
    dom = minidom.parseString(xml)
    for node in dom.getElementsByTagName('clusternode'):
        print node.getAttribute("name") + ": " + getNodeAttr(node, "name")

def list_services():
    xml = get_cluster_conf_xml()
    dom = minidom.parseString(xml)
    vm_already_printed = False
    for node in dom.getElementsByTagName('service'):
        print_services_map(node,0)
    for node in dom.getElementsByTagName('resources'):
        print_services_map(node,0)
    for node in dom.getElementsByTagName('vm'):
        if node.parentNode.tagName != 'resources':
            if vm_already_printed == False:
                print "virtual machines:"
                vm_already_printed = True
            print_services_map(node,2)

def print_services_map(node,level):
    num_spaces = level * 2
    prefix = ""
    
    if node.nodeType == minidom.Node.TEXT_NODE:
        return
    for i in range(num_spaces):
        prefix = " " + prefix

    nodeattr = getNodeAttr(node)

    print prefix + node.tagName + ": " + nodeattr
    for cn in node.childNodes:
        print_services_map(cn, level + 1)

def getNodeAttr(node, ignore = []):
    nodeattr = ""
    nodeattrlist = []
    if node.attributes != None:
        length = node.attributes.length
        for i in range(length):
            if node.attributes.item(i).name not in ignore:
                if node.attributes.item(i).name == "name":
                    nodeattrlist.insert(0,node.attributes.item(i))
                else:
                    nodeattrlist.append(node.attributes.item(i))
        for item in nodeattrlist:
            nodeattr = nodeattr + item.name + "=" + item.value + ", "

    nodeattr = re.sub(r", $","", nodeattr)
    return nodeattr

def list_domains():
    xml = get_cluster_conf_xml()
    dom = minidom.parseString(xml)
    for node in dom.getElementsByTagName('failoverdomain'):
        print node.getAttribute("name")

# Add a node to the cluster.conf
#   Before adding a node we need to verify another node
#   with the same name doesn't already exist
def add_node(node_to_add,nodeid,votes):
    nodeid_list = set()

    if nodeid and nodeid.isdigit(): nodeid = int(nodeid)
    else: nodeid = False

    if votes and votes.isdigit(): votes = int(votes)
    else: votes = False

    dom = minidom.parseString(get_cluster_conf_xml())
    for node in dom.getElementsByTagName('clusternode'):
        if (node.getAttribute("name") == node_to_add):
            print "Node '%s' already exists in cluster.conf" % node_to_add
            sys.exit(1)
        if nodeid and (node.getAttribute("nodeid") == str(nodeid)):
            print "Nodeid '%d' already exists in cluster.conf" % nodeid
            sys.exit(1)
        nodeid_list.add(node.getAttribute("nodeid"))
    node = dom.createElement("clusternode")
    node.setAttribute("name",node_to_add)

    # Use the first nodeid above 0 that isn't already used
    if nodeid == False:
        nodeid = 0
        while (True):
            nodeid = nodeid + 1
            if (str(nodeid) not in nodeid_list): break

    node.setAttribute("nodeid",str(nodeid))
    if votes: node.setAttribute("votes",str(votes))
    dom.getElementsByTagName("clusternodes")[0].appendChild(node)
    set_cluster_conf(dom.toxml())
    print "Node %s added." % (node_to_add)

def remove_node(node_to_remove):
    nodeFound = False

    dom = minidom.parseString(get_cluster_conf_xml())
    for node in dom.getElementsByTagName('clusternode'):
        if (node.getAttribute("name") == node_to_remove):
            node.parentNode.removeChild(node)
            nodeFound = True

    if (nodeFound == False):
        print "Unable to find node %s" % node_to_remove
        sys.exit(1)

    set_cluster_conf(dom.toxml())

def get_version():
    dom = minidom.parseString(get_cluster_conf_xml())
    return dom.getElementsByTagName('cluster')[0].getAttribute("config_version")

def set_version(version):
    dom = minidom.parseString(get_cluster_conf_xml())
    dom.getElementsByTagName('cluster')[0].setAttribute("config_version",version)
    set_cluster_conf(dom.toxml(), False)

def increment_version():
    new_version = int(get_version()) + 1
    set_version(str(new_version))
    print new_version

# If display is true, we print out the output, otherwise we just return it
def get_cluster_schema(display=True):
    if usefile:
        out = get_cluster_schema_file()
        if out == None:
            print "Unable to open cluster schema: %s" % CLUSTERRNG
            sys.exit(1)
    else:
        xml = send_ricci_command("cluster","get_cluster_schema")
        dom = minidom.parseString(xml)
        te = dom.getElementsByTagName("grammar")
        if len(te) > 0:
            out = te[0].toxml()
        else:
            print "Unable to get valid schema file from ricci falling back to local schema"
            out = get_cluster_schema_file()
            if out == None:
                print "Unable to open cluster schema: %s" % CLUSTERRNG
                sys.exit(1)

    if display:
        print out
    else:
        return out

# Returns the contents of the local cluster schema file, or 'None' if there
# is some error reading it
def get_cluster_schema_file():
    try:
        rng = open(CLUSTERRNG, 'r')
        out = rng.read()
        rng.close()
    except IOError:
        return None
    return out

def get_cluster_conf_xml():
    if usefile:
        try:
            f = open(filename, 'r')
            xml = f.read()
            f.close()
        except IOError:
            print "Unable to open file: %s" % filename
            sys.exit(1)
    else:
        xml = send_ricci_command("cluster", "get_cluster.conf")

    try:
        dom = minidom.parseString(xml)
    except minidom.xml.parsers.expat.ExpatError:
        print "Cluster configuration file specified is not in a valid xml format."
        sys.exit(1)

    if dom.getElementsByTagName('cluster').length > 0:
        xml =  dom.getElementsByTagName('cluster')[0].toxml()
        if verifyconf and verify_cluster_conf(xml) != 0:
            print "Cluster.conf file specified is not a valid cluster.conf file (use -i to ignore this error)"
            sys.exit(1)
        return xml
    else:
        return empty_cluster_conf()

# Create a minimal cluster.conf file similiar to the one
# created by system-config-cluster
def empty_cluster_conf(name="cluster"):
    impl = minidom.getDOMImplementation()
    newdoc = impl.createDocument(None, "cluster", None)

    top = newdoc.documentElement
    top.setAttribute('config_version','1')
    top.setAttribute('name',name)
    fence_daemon = newdoc.createElement("fence_daemon")
    clusternodes = newdoc.createElement("clusternodes")
    cman = newdoc.createElement("cman")
    fencedevices = newdoc.createElement("fencedevices")
    rm = newdoc.createElement("rm")
    failoverdomains = newdoc.createElement("failoverdomains")
    resources = newdoc.createElement("resources")
    
    top.appendChild(fence_daemon)
    top.appendChild(clusternodes)
    top.appendChild(cman)
    top.appendChild(fencedevices)
    rm.appendChild(failoverdomains)
    rm.appendChild(resources)
    top.appendChild(rm)

    return newdoc.toprettyxml()

def create_cluster(clustername):
    xml = empty_cluster_conf(clustername)

    f = open(EMPTYCLUSTERCONF, 'r')

    # Verify that a config doesn't exist, and if it does warn the user
    if verifyconf:
        if usefile:
            if os.path.exists(filename):
                print "%s already exists, use '-i' to override." % filename
                sys.exit(1)
        elif get_cluster_conf_xml() != f.read():
                print "cluster.conf already exists, use '-i' to override."
                sys.exit(1)

    # No need to increment on cluster.conf creation
    set_cluster_conf(xml, False)

def add_method(method, options):
    method_found = False
    node_found = False

    if len(options) != 1:
        usage()
        sys.exit(2)

    nodename = options[0]

    dom = minidom.parseString(get_cluster_conf_xml())
    for node in dom.getElementsByTagName('clusternode'):
        if (node.getAttribute("name") == nodename):
            node_found = True
            for methodnode in node.getElementsByTagName("method"):
                if methodnode.getAttribute("name") == method:
                    method_found = True
                    break
            break

    if node_found == False:
        print "Node '%s' does not currently exist in cluster.conf." % (nodename)
        sys.exit(1)

    if method_found == True:
        print "Method '%s' already exists in cluster.conf." % (method)
        sys.exit(1)

    fencenodes = node.getElementsByTagName("fence")
    if len(fencenodes) == 0:
        fencenode = dom.createElement("fence")
    else:
        fencenode = fencenodes[0]

    methodnode = dom.createElement("method")
    methodnode.setAttribute("name", method)
    fencenode.appendChild(methodnode)
    node.appendChild(fencenode)
    set_cluster_conf(dom.toxml())
    print "Method %s added to %s." % (method, nodename)

def remove_method(method, options):
    method_found = False
    node_found = False
    
    if len(options) != 1:
        usage()
        sys.exit(2)

    nodename = options[0]
    dom = minidom.parseString(get_cluster_conf_xml())
    for node in dom.getElementsByTagName('clusternode'):
        if (node.getAttribute("name") == nodename):
            node_found = True
            for methodnode in node.getElementsByTagName("method"):
                if methodnode.getAttribute("name") == method:
                    method_found = True
                    break
            break

    if node_found == False:
        print "Node %s does not exist in cluster.conf." % (nodename)
        sys.exit(1)
    if method_found == False:
        print "Method %s is not present for %s." % (method, nodename)
        sys.exit(1)

    methodnode.parentNode.removeChild(methodnode)
    set_cluster_conf(dom.toxml())
    print "Method %s removed from %s." % (method, nodename)

def add_fencedev(name, options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # Verify fencedevices section exists in cluster.conf
    fencedevices = dom.getElementsByTagName('fencedevices')
    if len(fencedevices) == 0:
        dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("fencedevices"))
    elif len(fencedevices) > 1:
        print "Error: Too many fencedevices elements in cluster.conf"
        sys.exit(1)
    
    # Verify fence device with same name does not already exist
    for fencedev in dom.getElementsByTagName('fencedevice'):
        if fencedev.getAttribute("name") == name:
            print "Fence device '%s' already exists in cluster.conf." % name
            sys.exit(1)

    newfencedev = dom.createElement("fencedevice")
    newfencedev = set_element_attributes(newfencedev, options)

    newfencedev.setAttribute("name", name)
    fencedevelem = dom.getElementsByTagName('fencedevices')[0]
    fencedevelem.appendChild(newfencedev)
    set_cluster_conf(dom.toxml())

def remove_fencedev(name):
    fencedev_found = False
    dom = minidom.parseString(get_cluster_conf_xml())

    # Verify fence device exists before attempting to remove
    for fencedev in dom.getElementsByTagName('fencedevice'):
        if fencedev.getAttribute("name") == name:
            fencedev.parentNode.removeChild(fencedev)
            fencedev_found = True

    if fencedev_found == False:
        print "Fence device '%s' does not exist in cluster.conf." % name
        sys.exit(1)

    set_cluster_conf(dom.toxml())

def add_fenceinst(name, options):
    fencedev_found = method_found = False

    if len(options) < 2:
        usage()
        sys.exit(2)
        
    nodename = options[0]
    methodname = options[1]
    dom = minidom.parseString(get_cluster_conf_xml())

    # Verify fence device exists
    for fencedev in dom.getElementsByTagName('fencedevice'):
        if fencedev.getAttribute("name") == name:
            fencedev_found = True
            break
    
    # Verify method exists for specified node
    for node in dom.getElementsByTagName('clusternode'):
        if node.getAttribute("name") == nodename:
            for method in node.getElementsByTagName('method'):
                if method.getAttribute("name") == methodname:
                    method_found = True
                    break
            break 

    if fencedev_found == False:
        print "Fence device '%s' not found." % name
        sys.exit(1)

    if method_found == False:
        print "Method '%s' not found in node '%s'." % (methodname, nodename)
        sys.exit(1)

    newfenceinst = dom.createElement("device")
    newfenceinst = set_element_attributes(newfenceinst, options[2:])

    newfenceinst.setAttribute("name", name)
    method.appendChild(newfenceinst)
    set_cluster_conf(dom.toxml())
    
def remove_fenceinst(name, options):
    fenceinst_found = False

    if len(options) < 2:
        usage()
        sys.exit(2)
        
    nodename = options[0]
    methodname = options[1]
    dom = minidom.parseString(get_cluster_conf_xml())

    # Verify fence instance exists before attempting to remove
    for node in dom.getElementsByTagName('clusternode'):
        if node.getAttribute("name") == nodename:
            for method in node.getElementsByTagName('method'):
                if method.getAttribute("name") == methodname:
                    for instance in method.getElementsByTagName('device'):
                        if instance.getAttribute("name") == name:
                            instance.parentNode.removeChild(instance)
                            fenceinst_found = True


    if fenceinst_found == False:
        print "Fence instance '%s' for node '%s' in method '%s' does not exist in cluster.conf." % (name, nodename, methodname)
        sys.exit(1)

    set_cluster_conf(dom.toxml())

def add_unfenceinst(name, options):
    dom = minidom.parseString(get_cluster_conf_xml())
    node_found = False
    if len(options) < 1:
        usage()
        sys.exit(2)

    nodename = options[0]

    # Verify node exists
    for node in dom.getElementsByTagName('clusternode'):
        if node.getAttribute("name") == nodename:
            node_found = True
            break

    if node_found == False:
        print "Node '%s' does not currently exist in cluster.conf." % (nodename)
        sys.exit(1)

    if len(node.getElementsByTagName("unfence")) == 0:
        unfence = dom.createElement("unfence")
        node.appendChild(unfence)
    else:
        unfence = node.getElementsByTagName("unfence")[0]

    newunfenceinst = dom.createElement("device")
    newunfenceinst = set_element_attributes(newunfenceinst, options[1:])

    newunfenceinst.setAttribute("name", name)
    unfence.appendChild(newunfenceinst)
    set_cluster_conf(dom.toxml())

def remove_unfenceinst(name, options):
    dom = minidom.parseString(get_cluster_conf_xml())
    unfenceinst_found = False

    if len(options) < 1:
        usage()
        sys.exit(2)

    nodename = options[0]

    # Verify unfence instance exists before attempting to remove
    for node in dom.getElementsByTagName('clusternode'):
        if node.getAttribute("name") == nodename:
            for unfence in node.getElementsByTagName('unfence'):
                for instance in unfence.getElementsByTagName('device'):
                    if instance.getAttribute("name") == name:
                        instance.parentNode.removeChild(instance)
                        unfenceinst_found = True


    if unfenceinst_found == False:
        print "Unfence instance '%s' for node '%s' does not exist in cluster.conf." % (name, nodename)
        sys.exit(1)

    set_cluster_conf(dom.toxml())

def add_alt(remove, node_name, options):
    dom = minidom.parseString(get_cluster_conf_xml())
    node_found = False

    if (not remove) and len(options) < 1:
        usage()
        sys.exit(2)

    for node in dom.getElementsByTagName('clusternode'):
        if node.getAttribute("name") == node_name:
            node_found = True
            break

    if node_found == False:
        print "Node '%s' does not currently exist in cluster.conf." % (node_name)
        sys.exit(1)

    # Clear out old altnames
    for altname_node in node.getElementsByTagName('altname'):
        node.removeChild(altname_node)

    if not remove:
        new_altname = dom.createElement("altname")
        new_altname = set_element_attributes(new_altname, options[1:])
        new_altname.setAttribute("name", options[0])
        node.appendChild(new_altname)

    set_cluster_conf(dom.toxml())

def list_failoverdomain():
    dom = minidom.parseString(get_cluster_conf_xml())
    fds = dom.getElementsByTagName("failoverdomain")
    if len(fds) == 0:
        sys.exit(0)

    for fd in fds:
        print fd.getAttribute("name") + ": " + getNodeAttr(fd,"name")
        for fdn in fd.getElementsByTagName("failoverdomainnode"):
            print "  " + fdn.getAttribute("name") + ": " + getNodeAttr(fdn,"name")

def add_failoverdomain(name, options):
    dom = minidom.parseString(get_cluster_conf_xml())

    failoverdomains_array = dom.getElementsByTagName("failoverdomains")

    # Verify failoverdomains and rm exist in cluster.conf file
    if len(failoverdomains_array) > 0:
        failoverdomains = failoverdomains_array[0]
    else:
        rm_array = dom.getElementsByTagName("rm")
        if len(rm_array) == 0:
            rm = dom.getElementsByTagName("cluster")[0].appendChild(dom.createElement("rm"));
        else:
            rm = rm_array[0]
        failoverdomains = rm.appendChild(dom.createElement("failoverdomains"))

    # Verify that there already isn't a failover domain with the same name
    failoverdomain_found = False
    for failoverdomain in failoverdomains.getElementsByTagName("failoverdomain"):
        if failoverdomain.getAttribute("name") == name:
            failoverdomain_found = True
            break
    
    if failoverdomain_found:
        print "Failover domain '%s' already exists." % (name)
        sys.exit(1)
    
    failoverdomain = failoverdomains.appendChild(dom.createElement("failoverdomain"))
    failoverdomain.setAttribute("name",name)

    if "restricted" in options: failoverdomain.setAttribute("restricted", "1")
    else: failoverdomain.setAttribute("restricted", "0")
    if "ordered" in options: failoverdomain.setAttribute("ordered", "1")
    else: failoverdomain.setAttribute("ordered", "0")
    if "nofailback" in options: failoverdomain.setAttribute("nofailback", "1")
    else: failoverdomain.setAttribute("nofailback", "0")

    set_cluster_conf(dom.toxml())

def remove_failoverdomain(name):
    dom = minidom.parseString(get_cluster_conf_xml())

    failoverdomains = dom.getElementsByTagName("failoverdomains")
    if len(failoverdomains) > 0:
        for failoverdomain in failoverdomains[0].getElementsByTagName("failoverdomain"):
            if failoverdomain.getAttribute("name") == name:
                failoverdomains[0].removeChild(failoverdomain)
                set_cluster_conf(dom.toxml())
                return
    else:
        print "No failoverdomains section found in cluster.conf"
        sys.exit(1)

    print "Unable to find failover domain '%s' in cluster.conf file." % (name)
    sys.exit(1)
    
# Add's a failoverdomainnode entry under a failoverdomain with 'name' and
# a node in options[0] if a failoverdomainnode already exists it is overwritten
def add_failoverdomainnode(name, options):
    if len(options) == 0:
        usage()
        sys.exit(2)

    node = options[0]
    if len(options) >= 2:
        if not options[1].isdigit():
            print "Priority must be an integer between 1 and 100"
            sys.exit(2)

        priority = int(options[1])
        if priority < 1 or priority > 100:
            print "Priority must be between 1 and 100"
            sys.exit(2)
    else:
        priority = -1 

    dom = minidom.parseString(get_cluster_conf_xml())

    
    failoverdomains_array = dom.getElementsByTagName("failoverdomains")

    if len(failoverdomains_array) > 0:
        failoverdomains = failoverdomains_array[0]
    else:
        print "No failoverdomains section is present, add a failover domain first."
        sys.exit(1)

    for failoverdomain in failoverdomains.getElementsByTagName("failoverdomain"):
        if failoverdomain.getAttribute("name") == name:
            for failoverdomainnode in failoverdomain.getElementsByTagName("failoverdomainnode"):
                if failoverdomainnode.getAttribute("name") == node:
                    failoverdomain.removeChild(failoverdomainnode)
            failoverdomain = failoverdomain.appendChild(dom.createElement("failoverdomainnode"))
            failoverdomain.setAttribute("name",node)
            if priority != -1:
                failoverdomain.setAttribute("priority",str(priority))
            set_cluster_conf(dom.toxml())
            return

    print "Unable to find failoverdomain '%s'" % (name)
    sys.exit(1)



def remove_failoverdomainnode(name, options):
    dom = minidom.parseString(get_cluster_conf_xml())

    if len(options) != 1:
        usage()
        sys.exit(2)

    node = options[0]

    failoverdomains = dom.getElementsByTagName("failoverdomains")
    if len(failoverdomains) > 0:
        for failoverdomain in failoverdomains[0].getElementsByTagName("failoverdomain"):
            if failoverdomain.getAttribute("name") == name:
                for failoverdomainnode in failoverdomain.getElementsByTagName("failoverdomainnode"):
                    if failoverdomainnode.getAttribute("name") == node:
                        failoverdomain.removeChild(failoverdomainnode)
                        set_cluster_conf(dom.toxml())
                        return
    else:
        print "No failoverdomains section found in cluster.conf"
        sys.exit(1)

    print "Unable to find node '%s' in failover domain '%s'." % (node, name)
    sys.exit(1)


def list_fencedev():
    dom = minidom.parseString(get_cluster_conf_xml())
    fds = dom.getElementsByTagName('fencedevices')

    # If no fencedevice section, we don't print anything
    if len(fds) == 0:
        sys.exit(0)

    for fd in fds[0].getElementsByTagName('fencedevice'):
        print fd.getAttribute("name") + ": " + getNodeAttr(fd,"name")

def list_fenceinst(options):
    if len(options) == 0:
        node = None
    else:
        node = options[0]

    dom = minidom.parseString(get_cluster_conf_xml())

    nodes = dom.getElementsByTagName("clusternode")

    if node != None:
        for n in nodes:
            if n.getAttribute("name") != node:
                nodes.remove(n)
                break;

    for n in nodes:
        print n.getAttribute("name")
        for method in n.getElementsByTagName('method'):
            print "  " + method.getAttribute('name')
            for inst in method.getElementsByTagName('device'):
                print "    " + inst.getAttribute("name") + ": " + getNodeAttr(inst, "name")
    return

def add_service(name, options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # Verify rm section exists in cluster.conf
    rm_array = dom.getElementsByTagName("rm")
    if len(rm_array) == 0:
        rm = dom.getElementsByTagName("cluster")[0].appendChild(dom.createElement("rm"));
    else:
        rm = rm_array[0]

    # Verify service doesn't exist with the same name
    for service in dom.getElementsByTagName('service'):
        if service.getAttribute("name") == name:
            print "Service '%s' already exists in cluster.conf." % name
            sys.exit(1)

    service = dom.getElementsByTagName('rm')[0].appendChild(dom.createElement("service"))
    service = set_element_attributes(service, options)

    service.setAttribute("name", name)
    set_cluster_conf(dom.toxml())

def remove_service(name):
    serviceRemoved = False

    dom = minidom.parseString(get_cluster_conf_xml())

    rm = dom.getElementsByTagName("rm")
    if len(rm) > 0:
        rm = rm[0]
        services = rm.getElementsByTagName("service")
        for service in services:
            if service.getAttribute("name") == name:
                rm.removeChild(service)
                serviceRemoved = True
    else:
        print "No <rm> section in cluster.conf"
        sys.exit(1)

    if not serviceRemoved:
        print "Unable to find service: %s" % name
        sys.exit(1)

    set_cluster_conf(dom.toxml())

# Add a subservice checking for slashes and brackets
def add_subservice(name, options):
    dom = minidom.parseString(get_cluster_conf_xml())
    serviceFound = False

    location = options.pop(0).split(':')
    subservice_type = location.pop(-1)
    location = ":".join(location)

    # Verify top level service exists
    for service in dom.getElementsByTagName("service"):
        if service.getAttribute("name") == name:
            serviceFound = True
            break;

    if serviceFound == False:
        print "Unable to find service: %s" % name
        sys.exit(1)


    service = getLocation(service, location)
    if service == None:
        print "Unable to find service %s" % location
        sys.exit(1)

    subservice = service.appendChild(dom.createElement(subservice_type))
    subservice = set_element_attributes(subservice, options)

    set_cluster_conf(dom.toxml())

def remove_subservice(name, options):
    dom = minidom.parseString(get_cluster_conf_xml())
    serviceFound = False

    if len(options) != 1:
        usage() 
        sys.exit(1)

    location = options.pop(0)

    # Verify that the service exists
    for service in dom.getElementsByTagName("service"):
        if service.getAttribute("name") == name:
            serviceFound = True
            break;

    if serviceFound == False:
        print "Unable to find service: %s" % name
        sys.exit(1)

    service = getLocation(service, location)
    if service == None:
        print "Unable to find service %s" % location
        sys.exit(1)

    service.parentNode.removeChild(service)
    set_cluster_conf(dom.toxml())

def expert_mode(tag, options):
    dom = minidom.parseString(get_cluster_conf_xml())

    parent = dom.getElementsByTagName('cluster')[0]
    if len(options) != 0:
        location = options.pop(0)

        parent = getLocation(parent,location) 
        if parent == None:
            print "Unable to find %s" % location
            sys.exit(1)

    newelement = parent.appendChild(dom.createElement(tag))
    newelement = set_element_attributes(newelement, options)
    set_cluster_conf(dom.toxml())

def expert_mode_remove(location):
    dom = minidom.parseString(get_cluster_conf_xml())

    parent = dom.getElementsByTagName('cluster')[0]
    parent = getLocation(parent,location) 
    if parent == None:
        print "Unable to find %s" % location
        sys.exit(1)

    parent.parentNode.removeChild(parent)
    set_cluster_conf(dom.toxml())

# Return the element specified by the location in dom
# Elements separated by ':' and elements of the same name referenced by [n]
def getLocation(dom, location):
    if location == "":
        return dom

    elems = location.split(':')

    for elem in elems:
        element_number = 0
        r = re.search(r"\[(\d+)\]$", elem)
        if r:
            if r.group(1):
                element_number = int(r.group(1))
        elem = re.sub(r"\[\d+\]$", "",  elem)

        count = 0
        element_found = False
        for node in dom.childNodes:
            if node.nodeType != node.ELEMENT_NODE:
                continue
            if node.tagName == elem:
                if count == element_number:
                    dom = node
                    element_found = True
                    break
                else: count = count + 1
        if element_found == False:
            return None

    return dom

def add_resource(type, options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # Verify resources section exists in cluster.conf
    resources = dom.getElementsByTagName('resources')
    if len(resources) == 0:
        resources = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("resources"))
    elif len(resources) > 1:
        print "Error: Too many resources elements in cluster.conf"
        sys.exit(1)
    else:
        resources = resources[0]

    # Verify name is unique, if no name then we don't search for duplicates
    resname = False
    for option in options:
        (attr, sep, val) = option.partition('=')
        if attr == "name":
            resname = val
            break

    if resname != False:
        for resource in resources.childNodes:
            if resource.nodeType == minidom.Node.TEXT_NODE:
                continue
            if resource.getAttribute("name") == resname:
                print "Duplicate name: %s" % resname
                sys.exit(1)

    newresource = dom.createElement(type)
    newresource = set_element_attributes(newresource, options)

    resources = dom.getElementsByTagName('resources')[0]
    resources.appendChild(newresource)
    set_cluster_conf(dom.toxml())

# Removes the first resource that matches all the options
def remove_resource(type, options):
    dom = minidom.parseString(get_cluster_conf_xml())
    resourceMatch = False

    # Verify resources section exists in cluster.conf
    resources = dom.getElementsByTagName('resources')
    if len(resources) != 1:
        print "Error: resources section not present or improperly formatted"
        sys.exit(1)
    else:
        resources = resources[0]

    resource_options = {}
    for option in options:
        (attr, sep, val) = option.partition('=')
        if (sep == ""):
            print "Invalid option: %s" % option
            sys.exit(1)
        resource_options[attr] = val

    for resource in resources.childNodes:
        if resource.nodeType == minidom.Node.TEXT_NODE:
            continue
        if resource.tagName == type:
            resourceMatch = False
            for option,val in resource_options.iteritems():
                if resource.getAttribute(option) != val:
                    resourceMatch = False
                    break
                else:
                    resourceMatch = True
            if resourceMatch == True:
                break

    if resourceMatch != True:
        print "Unable to find matching resource: %s, %s" % (type,options)
        sys.exit(1)

    resources.removeChild(resource)
            
    set_cluster_conf(dom.toxml())

def add_vm(vmname, options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # Verify rm section exists in cluster.conf
    rm = dom.getElementsByTagName('rm')
    if len(rm) == 0:
        rm = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("rm"))
    elif len(rm) > 1:
        print "Error: Too many rm elements in cluster.conf"
        sys.exit(1)
    else:
        rm = rm[0]

    # Verify name is unique, if no name then we don't search for duplicates
    if rm != False:
        for vm in rm.childNodes:
            if vm.nodeType == minidom.Node.TEXT_NODE:
                continue
            if vm.getAttribute("name") == vmname:
                print "Duplicate name: %s" % vmname
                sys.exit(1)

    newvm = dom.createElement("vm")
    newvm = set_element_attributes(newvm, options)
    newvm.setAttribute("name", vmname)

    rm = dom.getElementsByTagName('rm')[0]
    rm.appendChild(newvm)
    set_cluster_conf(dom.toxml())

def remove_vm(name):
    vmRemoved = False

    dom = minidom.parseString(get_cluster_conf_xml())

    rm = dom.getElementsByTagName("rm")
    if len(rm) > 0:
        rm = rm[0]
        vms = rm.getElementsByTagName("vm")
        for vm in vms:
            if vm.getAttribute("name") == name:
                rm.removeChild(vm)
                vmRemoved = True
    else:
        print "No <rm> section in cluster.conf"
        sys.exit(1)

    if not vmRemoved:
        print "Unable to find virtual machine: %s" % name
        sys.exit(1)

    set_cluster_conf(dom.toxml())

# Print out all the available service options from the cluster.rng file
def list_serviceopts(options):
    if len(options) == 0:
        servicename = None
    else:
        servicename = options[0]

    # We don't print out info about the 'action' service
    if servicename == "action":
        return

    rng = open(CLUSTERRNG)
    dom = minidom.parseString(rng.read())

    for elem in dom.getElementsByTagName("define"):
        if elem.getAttribute("name") == "CHILDREN":
            for elem2 in elem.getElementsByTagName("ref"):
                # We don't print out info about the 'action' service
                if elem2.getAttribute("name") == "RESOURCEACTION":
                    continue
                if servicename:
                    if elem2.getAttribute("name").upper() == servicename.upper():
                        (servname, servopts) = getServiceOptions(dom, elem2)
                        print servname
                        print servopts
                else:
                    (servname, servopts) = getServiceOptions(dom, elem2)
                    print servname

def list_fenceopts(options):
    if len(options) == 0:
        fencename = None
    else:
        fencename = options[0]

    rng = open(CLUSTERRNG)
    dom = minidom.parseString(rng.read())

    for elem in dom.getElementsByTagName("define"):
        if elem.getAttribute("name") == "FENCEDEVICEOPTIONS":
            for elem2 in elem.getElementsByTagName("group"):
                if fencename:
                    if elem2.getAttribute("rha:name") == fencename:
                        (fencelongname,fenceopts) = getFenceOptions(dom, elem2)
                        print fencelongname
                        if fenceopts == "":
                            print "No description"
                        else:
                            print fenceopts
                else:
                    (fencelongname,fenceopts) = getFenceOptions(dom, elem2)
                    if fencelongname: print fencelongname

                    
def getServiceOptions(dom, elem):
    name = elem.getAttribute("name")
    for elem in dom.getElementsByTagName("define"):
        if elem.getAttribute("name") == name:
            elem2 = elem.getElementsByTagName("element")[0]
            description =  elem2.getAttribute("rha:description")
            if not description:
                description = "No description available"
            longname = elem2.getAttribute("name") + " - " + description
            options = getOptions(elem2)
            return (longname, options)
    return (False, False)

def getFenceOptions(dom, elem):
    if not elem.getAttribute("rha:name"):
        return (False, False)
    description =  elem.getAttribute("rha:description")
    if not description:
        description = "No description available"
    longname = elem.getAttribute("rha:name") + " - " + description
    options = getOptions(elem)
    return (longname, options)
        

def getOptions(elem):
    optionalOptions = []
    requiredOptions = []
    retval = ""
    for elem2 in elem.getElementsByTagName("attribute"):
        if elem2.parentNode.nodeName != "optional":
            if elem2.getAttribute("name") != "ref":
                requiredOptions.append(elem2)
        else:
            optionalOptions.append(elem2)
    retval += "  Required Options:\n"
    for elem2 in requiredOptions:
        description = elem2.getAttribute("rha:description")
        # Remove excess whitespace some attributes have
        description = re.sub("\s\s+", " ", description)
        name =  elem2.getAttribute("name")
        if not description:
            description = "No description available"
        retval += "    " + name + ": " + description + "\n"
    retval += "  Optional Options:\n"
    for elem2 in optionalOptions:
        description = elem2.getAttribute("rha:description")
        # Remove excess whitespace some attributes have
        description = re.sub("\s\s+", " ", description)
        name =  elem2.getAttribute("name")
        if not description:
            description = "No description available"
        retval += "    " + name + ": " + description + "\n"
    return retval

def list_quorum():
    xml = get_cluster_conf_xml()
    dom = minidom.parseString(xml)
    
    list_tag(dom,"quorumd", "Quorumd")
    list_tag(dom, "heuristic", "  heuristic")

def set_quorumd(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # If quorumd section exists we rewrite it, else we create a new one
    quorumd = dom.getElementsByTagName('quorumd')
    if len(quorumd) == 0:
        quorumd = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("quorumd"))
    elif len(quorumd) > 1:
        print "Error: Too many quorumd elements in cluster.conf"
        sys.exit(1)
    else:
        quorumd = quorumd[0]

    while(quorumd.attributes.length != 0):
        quorumd.removeAttribute(quorumd.attributes.item(0).name)
    quorumd = set_element_attributes(quorumd, options)

    set_cluster_conf(dom.toxml())

def add_heuristic(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    if len(options) == 0:
        print "You must specify at least one option when adding a heuristic."
        sys.exit(1)

    # If quorumd section is present we use it, else we create an empty one
    quorumd = dom.getElementsByTagName('quorumd')
    if len(quorumd) == 0:
        quorumd = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("quorumd"))
    elif len(quorumd) > 1:
        print "Error: Too many quorumd elements in cluster.conf"
        sys.exit(1)
    else:
        quorumd = quorumd[0]

    heuristic = dom.createElement("heuristic")
    heuristic = set_element_attributes(heuristic, options)

    quorumd.appendChild(heuristic)
    set_cluster_conf(dom.toxml())

def remove_heuristic(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # If quorumd section is present we use it, else we create an empty one
    quorumd = dom.getElementsByTagName('quorumd')
    if len(quorumd) == 0:
        print "Error: no quorumd section in cluster.conf"
        sys.exit(1)
    elif len(quorumd) > 1:
        print "Error: Too many quorumd elements in cluster.conf"
        sys.exit(1)
    else:
        quorumd = quorumd[0]

    heuristic_options = {}
    for option in options:
        (attr, sep, val) = option.partition('=')
        if (sep == ""):
            print "Invalid option: %s" % option
            sys.exit(1)
        heuristic_options[attr] = val

    heuristicMatch = False
    for heuristic in dom.getElementsByTagName('heuristic'):
        if options == [] and heuristic.attributes.length == 0:
            heuristicMatch = True
            break
        for option,val in heuristic_options.iteritems():
            if heuristic.getAttribute(option) != val:
                heuristicMatch = False
                break
            else:
                heuristicMatch = True
        if heuristicMatch == True:
            break

    if heuristicMatch != True:
        print "Unable to find matching heuristic: %s" % (options)
        sys.exit(1)

    quorumd.removeChild(heuristic)
            
    set_cluster_conf(dom.toxml())

def list_misc():
    xml = get_cluster_conf_xml()
    dom = minidom.parseString(xml)
    
    list_tag(dom,"totem", "Totem")
    list_tag(dom, "dlm", "DLM")
    list_tag(dom, "rm", "Resource Manager")
    list_tag(dom, "cman", "CMAN")
    list_tag(dom, "multicast", "Multicast")
    list_tag(dom, "fencedaemon", "Fence Daemon")
    list_tag(dom, "logging", "Logging")
    list_tag(dom, "logging_daemon", "  Logging Daemons")

def set_totem(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # If totem section is present we use it, else we create it
    totem = dom.getElementsByTagName('totem')
    if len(totem) == 0:
        totem = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("totem"))
    elif len(totem) > 1:
        print "Error: Too many totem elements in cluster.conf"
        sys.exit(1)
    else:
        totem = totem[0]

    while(totem.attributes.length != 0):
        totem.removeAttribute(totem.attributes.item(0).name)

    totem = set_element_attributes(totem, options)
    set_cluster_conf(dom.toxml())

def set_rm(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # If rm section is present we use it, else we create it
    rm = dom.getElementsByTagName('rm')
    if len(rm) == 0:
        rm = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("rm"))
    elif len(rm) > 1:
        print "Error: Too many rm elements in cluster.conf"
        sys.exit(1)
    else:
        rm = rm[0]

    while(rm.attributes.length != 0):
        rm.removeAttribute(rm.attributes.item(0).name)
    rm = set_element_attributes(rm, options)
    set_cluster_conf(dom.toxml())

def set_cman(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # If cman section is present we use it, else we create it
    cman = dom.getElementsByTagName('cman')
    if len(cman) == 0:
        cman = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("cman"))
    elif len(cman) > 1:
        print "Error: Too many cman elements in cluster.conf"
        sys.exit(1)
    else:
        cman = cman[0]

    while(cman.attributes.length != 0):
        cman.removeAttribute(cman.attributes.item(0).name)
    cman = set_element_attributes(cman, options)
    set_cluster_conf(dom.toxml())

def set_dlm(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # If dlm section is present we use it, else we create it
    dlm = dom.getElementsByTagName('dlm')
    if len(dlm) == 0:
        dlm = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("dlm"))
    elif len(dlm) > 1:
        print "Error: Too many dlm elements in cluster.conf"
        sys.exit(1)
    else:
        dlm = dlm[0]

    while(dlm.attributes.length != 0):
        dlm.removeAttribute(dlm.attributes.item(0).name)
    dlm = set_element_attributes(dlm, options)
    set_cluster_conf(dom.toxml())

def set_fencedaemon(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # If fence_daemon section is present we use it, else we create it
    fence_daemon = dom.getElementsByTagName('fence_daemon')
    if len(fence_daemon) == 0:
        fence_daemon = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("fence_daemon"))
    elif len(fence_daemon) > 1:
        print "Error: Too many fence_daemon elements in cluster.conf"
        sys.exit(1)
    else:
        fence_daemon = fence_daemon[0]

    while(fence_daemon.attributes.length != 0):
        fence_daemon.removeAttribute(fence_daemon.attributes.item(0).name)
    fence_daemon = set_element_attributes(fence_daemon, options)
    set_cluster_conf(dom.toxml())

def set_logging(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    # If logging section is present we use it, else we create it
    logging = dom.getElementsByTagName('logging')
    if len(logging) == 0:
        logging = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("logging"))
    elif len(logging) > 1:
        print "Error: Too many logging elements in cluster.conf"
        sys.exit(1)
    else:
        logging = logging[0]

    while(logging.attributes.length != 0):
        logging.removeAttribute(logging.attributes.item(0).name)
    logging = set_element_attributes(logging, options)
    set_cluster_conf(dom.toxml())

def add_logging_daemon(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    logging = dom.getElementsByTagName('logging')
    if len(logging) == 0:
        logging = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("logging"))
    elif len(logging) > 1:
        print "Error: Too many logging elements in cluster.conf"
        sys.exit(1)
    else:
        logging = logging[0]

    logger = dom.createElement("logging_daemon")
    logger = set_element_attributes(logger, options)

    logging.appendChild(logger)
    set_cluster_conf(dom.toxml())

def remove_logging_daemon(options):
    dom = minidom.parseString(get_cluster_conf_xml())
    loggerMatch = False

    # Verify logging section exists in cluster.conf
    logging = dom.getElementsByTagName('logging')
    if len(logging) != 1:
        print "Error: logging section not present or improperly formatted"
        sys.exit(1)
    else:
        logging = logging[0]

    logger_options = {}
    for option in options:
        (attr, sep, val) = option.partition('=')
        if (sep == ""):
            print "Invalid option: %s" % option
            sys.exit(1)
        logger_options[attr] = val

    for logger in logging.childNodes:
        if logger.nodeType == minidom.Node.TEXT_NODE:
            continue
        loggerMatch = False
        for option,val in logger_options.iteritems():
            if logger.getAttribute(option) != val:
                loggerMatch = False
                break
            else:
                loggerMatch = True
        if loggerMatch == True:
            break

    if loggerMatch != True:
        print "Unable to find matching logger: %s" % (options)
        sys.exit(1)

    logging.removeChild(logger)
            
    set_cluster_conf(dom.toxml())

def set_multicast(alt,options):
    dom = minidom.parseString(get_cluster_conf_xml())


    cman = dom.getElementsByTagName("cman")
    if len(cman) == 0:
        cman = dom.createElement("cman")
        dom.getElementsByTagName("cluster")[0].appendChild(cman)
    elif len(cman) == 1:
        cman = cman[0]
    else:
        print "Error: more than one cman section is present."
        sys.exit(1)

    if alt:
      tagName = "altmulticast"
    else:
      tagName = "multicast"

    mc = cman.getElementsByTagName(tagName)

    for i in mc:
        cman.removeChild(i)

    if len(options) >= 1:
        addr = options.pop(0)
        mc = dom.createElement(tagName)
        mc.setAttribute("addr",addr)
        set_element_attributes(mc, options)
        cman.appendChild(mc)

    set_cluster_conf(dom.toxml())

# Lists options for a specific tag
def list_tag(dom, tag, prettyname):
    elements = dom.getElementsByTagName(tag)

    # If no 'tag' section, we don't print anything
    if len(elements) == 0:
        return

    for element in elements:
        print prettyname + ": " + getNodeAttr(element)

# Sets the attributes of a specific xml element where options
# is an array of strings containing attribute=value
# returns the modified element
def set_element_attributes(element, options):
    for option in options:
        (attr, sep, val) = option.partition('=')
        if (sep == ""):
            print "Invalid option: %s" % option
            sys.exit(1)
        element.setAttribute(attr,val)
    return element

# Send the cluster.conf file specified by -f to the host specified by -h
def send_cluster_conf():
    global usefile, filename
    if not usefile or not filename:
        print "Error: must specify a filename and host when using --setconf"
        sys.exit(2)

    # Will default to the file
    xml = get_cluster_conf_xml()
    # After reading file, zero out usefile and filename to send to host
    usefile = filename = False
    set_cluster_conf(xml, False)

# Set the cluster.conf file and increment the version
def set_cluster_conf(xml, increment = True):
    dom = minidom.parseString(xml)

    if increment:
      version = dom.getElementsByTagName("cluster")[0].getAttribute("config_version")
      version = int(version) + 1
      dom.getElementsByTagName("cluster")[0].setAttribute("config_version",str(version))
      xml = dom.toxml()

    xml = xml.replace('<?xml version="1.0" ?>','')
    log_msg (xml)

    if verifyconf:
        if verify_cluster_conf(xml) != 0:
            print "Validation Failure, unable to modify configuration file (use -i to ignore this error)."
            sys.exit(1)

    if usefile:
        xml = dom.toprettyxml("  ","\n")
        xml = xml.replace('<?xml version="1.0" ?>','')
        xml = re.sub(r"(?m)^\s*\n","", xml)
	try:
	    f = open(filename, 'w')
	    f.write(xml)
	    f.close()
	except IOError as (errno, strerror):
	    print "Error: Unable to write file: '%s', %s" % (filename, strerror)
	    exit(1)
    else:
        if activate:
            log_msg (send_ricci_command("cluster", "set_cluster.conf", hostname, ("propagate", "boolean", "true"),("cluster.conf","xml","",xml)))
        else:
            log_msg (send_ricci_command("cluster", "set_cluster.conf", hostname, ("cluster.conf","xml","",xml)))

# Returns 0 if verification succeeded, an error code if it didn't
def verify_cluster_conf(xml):
    if (not os.path.isfile(CLUSTERRNG)):
        print "%s is missing, unable to validate (use -i to ignore this error)" % CLUSTERRNG
        sys.exit(1)

    global schema

    if (schema == None): 
        schema = get_cluster_schema(False)

    schema = schema
    schemaFile = tempfile.NamedTemporaryFile(delete=False)
    schemaFile.write(schema)
    schemaFile.close()

    lint = subprocess.Popen(["/usr/bin/xmllint","--noout","--relaxng",
        schemaFile.name, "-"], stdin=subprocess.PIPE,
        stderr=subprocess.STDOUT, stdout = subprocess.PIPE)
    (stdout, stderr) = lint.communicate(xml)
    ret = lint.wait()
    schemaFile.close()
    return ret

def check_cluster_conf():
    global hostname, usefile
    xml = get_cluster_conf_xml()
    dom = minidom.parseString(xml)
    
    if usefile == True:
      xml = xml.replace("\t","")
    usefile = False
    bad_nodes = []
    orig_hostname = hostname
    for node in dom.getElementsByTagName("clusternode"):
        hostname = node.getAttribute("name")
        if hostname == orig_hostname:
            continue
        xml2 = get_cluster_conf_xml()
        if xml2 != xml:
            print "Node: %s does not match" % hostname
            bad_nodes.append(hostname)

    hostname = orig_hostname
    if len(bad_nodes) > 0:
        sys.exit(1)
    else:
        print "All nodes in sync."

def verify_authentication(dom):
    ricci =  dom.getElementsByTagName("ricci")
    if len(ricci) > 0:
        if ricci[0].getAttribute("authenticated") != "true":
            return False
        else:
            return True
    print "Error: no ricci tag in ricci response"
    sys.exit(1)

def check_authentication(host):
    msg = '<ricci function="authenticate" password="%s" version="1.0"/>' % password
    res = send_to_ricci(msg, host)     
    dom = minidom.parseString(res[1])
    if (not verify_authentication(dom)):
        print "Unable to authenticate with ricci node, please check password."
        sys.exit(1)

def send_ricci_command(module, command, host = None, *vars):
    global password
    variables = ""

    if host == None:
        host = hostname

    for value in vars:
        variables = variables + '<var mutable="false" name="%s" type="%s" value="%s">' % (value[0],value[1],value[2])
        if (len(value) > 3):
            variables = variables + value[3]
        variables = variables + "</var>"

    # If a password is set, then we authenticate
    if password != None:
        check_authentication(host)
    
    msg = '<ricci function="process_batch" async="false" version="1.0"><batch><module name="%s"><request API_version="1.0"><function_call name="%s">%s</function_call></request></module></batch></ricci>' % (module,command,variables)
    res = send_to_ricci(msg, host)
    dom = minidom.parseString(res[1].replace('\t',''))
    if (not verify_authentication(dom)):
        password = getpass.getpass(host + " password: ");
        check_authentication(host)
        res = send_to_ricci(msg, host)
        dom = minidom.parseString(res[1].replace('\t',''))
        if (not verify_authentication(dom)):
            print "Error: Not yet authenticated with %s (try -p option)" % host
            sys.exit(1)
    function_response = dom.getElementsByTagName('function_response')
    if (len(function_response) != 0):
        xml = function_response[0].toxml()
    else:
      	print "Error: Unable to retrieve information from ricci (is modcluster installed?)"
	sys.exit(1)
    return xml 

def send_to_ricci(msg, host=hostname):
    cert = os.path.expanduser("~/.ccs/cacert.pem")
    key = os.path.expanduser("~/.ccs/privkey.pem")
    config = os.path.expanduser("~/.ccs/cacert.config")
    try:
        (family, socktype, proto, canonname, sockaddr) = socket.getaddrinfo(host, RICCI_PORT)[0]
    except socket.gaierror:
        print "Unable to resolve %s, does that host exist?" % host
        sys.exit(1)
    
    s = socket.socket(family, socktype, proto)

    # Make sure we have a client certificate and private key
    # If not we need to autogenerate them (including creating an
    # openssl configuration file
    if (os.path.isfile(cert) == False or os.path.isfile(key) == False):
        log_msg("Autogenerating private key and certificate.")
        if not os.path.exists(os.path.expanduser("~/.ccs")):
            os.mkdir(os.path.expanduser("~/.ccs"),0700);
        f = open (config, 'w')
        f.write("""
[ req ]
distinguished_name     = req_distinguished_name
attributes             = req_attributes
prompt                 = no

[ req_distinguished_name ]
C                      = US
ST                     = State or Province
L                      = Locality
O                      = Organization Name
OU                     = Organizational Unit Name
CN                     = Common Name
emailAddress           = root@localhost

[ req_attributes ]
""")
        f.close()
        if (debug):
            redirect = ""
        else:
            redirect = " > /dev/null 2&>1"

        os.system ("/usr/bin/openssl genrsa -out %s 2048 %s" % (key,redirect))
        os.system ("/usr/bin/openssl req -new -x509 -key %s -out %s -days 1825 -config ~/.ccs/cacert.config" % (key,cert))

    ss = ssl.wrap_socket(s, key, cert)
    try:
        ss.connect((host, RICCI_PORT))
    except socket.error:
        print "Unable to connect to %s, make sure the ricci server is started." % host
        sys.exit(1)

    log_msg ("***Sending to ricci server:")
    log_msg (msg)
    log_msg ("***Sending End")
    logging.debug("Connected...")
    res1 = ss.read(1024)
    logging.debug("Writing...")
    logging.debug(msg)
    ss.write(msg)
    logging.debug("Writen...")
    res2 = ''
    while True:
        logging.debug("Waiting to read...")
        buff = ss.read(10485760)
        logging.debug(buff)
        logging.debug("Read...")
        if buff == '':
            break
        res2 += buff
        try:
            minidom.parseString(res2)
            break
        except:
            pass
    log_msg ("***Received from ricci server")
    log_msg (res2)
    log_msg ("***Receive End")
    return res1, res2

def log_msg(message):
    if debug == True:
      print message

if __name__ == "__main__":
    main(sys.argv[1:])
