#!/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
import errno
import datetime
from signal import signal, SIGPIPE, SIG_DFL

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
nounfence = False
schema = None

class StopNodeThread (threading.Thread):
    def __init__ (self, host):
        self.host = host
        threading.Thread.__init__(self)
        self.output = ""
        self.stopped = False

    def isStopped(self):
        return self.stopped

    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
            self.stopped = True

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

    def isStarted(self):
        return self.started

    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
            self.started = True

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
    addaction = removeaction = False
    removeservice = removesubservice = removeresource = False
    listquorum = setquorumd = addheuristic = removeheuristic = False
    settotem = setrm = setcman = setfencedaemon = False
    setuidgid = rmuidgid = 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, nounfence
    global noenable, usealt, orig_cluster_conf, backup

    usealt = False
    backup = False
    signal(SIGPIPE,SIG_DFL)
    noenable = False
#    logging.basicConfig(level=logging.DEBUGRemove
    try:
        opts, args = getopt.gnu_getopt(argv, "dsih:p:f:b", ["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=","lsresourceopts","nounfence","setuidgid","rmuidgid", "noenable",
      "nodisable", "addaction=", "rmaction=","usealt","backup"])
    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 ("--nounfence"): nounfence = 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 ("--addaction"):
            addaction = True
            name = arg
            options = args
        elif opt in ("--rmaction"):
            removeaction = 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") or opt in ("--lsresourceopts"):
            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 ("--setuidgid"): setuidgid = True; options = args
        elif opt in ("--rmuidgid"): rmuidgid = 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
        elif opt in ("--noenable") or opt in ("--nodisable"): noenable = True
        elif opt in ("--usealt"): usealt = True
        elif opt in ("-b","--backup"): backup = True

    if not hostname and not filename:
        hostname = "localhost"

    # hold on w/ possible activation unless "activate" required and "sync" not
    temp_activate, activate = activate, not(sync) and activate

    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 (addaction): add_action(name, options)
    if (removeaction): remove_action(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 (setuidgid): set_uidgid(options)
    if (rmuidgid): rm_uidgid(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 (getclusterschema): get_cluster_schema()

    activate = temp_activate  # activation allowed now (at most once per run)
    if (sync): sync_cluster_conf()

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

      --help            Display this help and exit
  -h, --host host       Cluster node to perform actions on
      --usealt          If primary node name is unavailable, attempt to
                        connect to ricci on the alt interface (only works from
                        a cluster member)
  -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 [--activate]
                        Sync config file to all nodes and optionally activating
                        that configuration on all nodes
  -b, --backup          Backup cluster.conf file before changes in
                        ~/.ccs/backup directory
  -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 [--noenable|--nodisable]
                        Start *AND* enable cluster services on reboot
                        for all nodes (if --noenable or --nodisable is
                        specified cluster services will only be started)
      --stopall [--noenable|--nodisable]
                        Stop *AND* disable cluster services on reboot
                        for all nodes (if --noenable or --nodisable is
                        specified cluster services will only be stopped)
      --start [--noenable|--nodisable]
                        Start *AND* enable cluster services on reboot for
                        host specified with -h or localhost if no host is
                        provided (if --noenable or --nodisable is specified
                        cluster services will only be started)
      --stop [--noenable|--nodisable]
                        Stop *AND* disable cluster services on reboot for
                        host specified with -h or localhost if no host is
                        provided (if --noenable or --nodisable is specified
                        cluster services will only be stopped)

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] [--nounfence]
                        Add fence instance. Fence instance parameters can be
                        found in online documentation in 'Fence Device
                        Parameters'. Using --nounfence prevents ccs from automatically
                        including an unfencing section for agents that require
                        unfencing (ie. fence_scsi, fence_sanlock, etc.)
      --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
      --lsresourceopts [service type]
                        An alias to --lsserviceopts
      --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
      --addaction <resource/name> <action_name> <action_option=val>
                        Add an action to the specified resource.
      --rmaction <resource name> [<action_name> [action options]]
                        Remove all actions from resource, or actions matching
                        action name and options specified
      --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
      --setuidgid uid=<uid> gid=<gid>
                        Set uidgid options
      --rmuidgid uid=<uid> gid=<gid>
                        Remove uidgid entry matching specified uid/gid
      --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):
    if (noenable):
        if not inthread:
            xml = send_ricci_command("cluster", "stop_node", node, ("disable_services", "boolean", "false"))
        else:
            xml = send_ricci_command("cluster", "stop_node", node, ("disable_services", "boolean", "false"), ("cluster_shutdown", "boolean", "true"))
    else:
        if not inthread:
            xml = send_ricci_command("cluster", "stop_node", node)
        else:
            xml = send_ricci_command("cluster", "stop_node", node, ("cluster_shutdown", "boolean", "true"))

    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 = ""
    if (noenable):
      xml = send_ricci_command("cluster", "start_node", node, ("enable_services", "boolean", "false"))
    else:
      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 due 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()

    start_failure = False
    for thread in threads.values():
        thread.join()
        print thread.getoutput()
        if (not thread.isStarted()):
            start_failure = True
    if (start_failure):
        sys.exit(1)

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

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

    stop_failure = False
    for thread in threads.values():
        thread.join()
        print thread.getoutput()
        if (not thread.isStopped()):
            stop_failure = True
    if (stop_failure):
        sys.exit(1)

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, activate
    temp_activate = activate
    activate = False
    xml = get_cluster_conf_xml()
    dom = minidom.parseString(xml).getElementsByTagName('cluster')[0]
    xml = dom.toprettyxml(indent='  ',newl='')
    dom = minidom.parseString(xml)
    nodes = dom.getElementsByTagName('clusternode')
    if len(nodes) > 0:
        for node in nodes[0:-1]:
            nodename = node.getAttribute("name")
            hostname = nodename
            set_cluster_conf(xml)

        # If we're using --activate, only propogate on the last node
        activate = temp_activate
        node = nodes[-1]
        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

    if node.nodeType == minidom.Node.COMMENT_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(True))
    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(dont_return_empty = False):
    global orig_cluster_conf
    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 dont_return_empty and dom.getElementsByTagName('function_response').length > 0:
        fr = dom.getElementsByTagName('function_response')[0]
        for fr_var in fr.getElementsByTagName("var"):
            if fr_var.getAttribute("name") == "success" and fr_var.getAttribute("value") == "false":
                print "Unable to obtain cluster.conf file from remote node"
                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)

        orig_cluster_conf = xml
        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, True)

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())
    unfence=False

    # Verify fence device exists
    for fencedev in dom.getElementsByTagName('fencedevice'):
        if fencedev.getAttribute("name") == name:
            fencedev_found = True
            if need_unfence(fencedev.getAttribute("agent")):
              unfence=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())

    if unfence and not nounfence:
      for o in options[:]:
        if o.startswith("action="):
          options.remove(o)
      options.append("action=on")
      print "Note: Automatically adding unfence action... (use --nounfence to prevent this)"
      add_unfenceinst(name, options[:1] + options[2:])
    
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')
    rms = dom.getElementsByTagName('rm')
    if len(rms) == 0:
        resource = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("rm"))
    if len(resources) == 0:
        resources = dom.getElementsByTagName('rm')[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 or resource.nodeType == minidom.Node.COMMENT_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 or resource.nodeType == minidom.Node.COMMENT_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 return_elements_with_attr(elem, attr_name, attr_val):
    children = elem.childNodes
    ret = []

    if elem.nodeType == minidom.Node.DOCUMENT_NODE:
        for child in elem.getElementsByTagName("cluster"):
            ret = ret + return_elements_with_attr(child,attr_name, attr_val)
        return ret

    if elem.nodeType != minidom.Node.ELEMENT_NODE:
        return []

    if elem.hasAttribute(attr_name) and elem.attributes[attr_name].value == attr_val:
        ret.append(elem)

    for child in children:
        ret = ret + return_elements_with_attr(child, attr_name, attr_val)

    return ret

def add_action(resname, options):
    dom = minidom.parseString(get_cluster_conf_xml())
    if len(options) < 1:
        print "You must specify an action name when adding"
        sys.exit(1)

    action_name = options.pop(0)

    # Verify resource exists
    elems = return_elements_with_attr(dom, "name", resname)
    if len(elems) == 0:
        print "Error: Could not find resource/service with name: %s" % (resname)
        sys.exit(1)

    if len(elems) > 1:
        print "Error: Too many elements in cluster.conf matched name: %s" % (resname)
        sys.exit(1)

    resElem = elems[0]

    for elem in resElem.childNodes:
        if elem.nodeType != minidom.Node.ELEMENT_NODE:
            continue
        if elem.tagName == "action" and elem.attributes["name"].value == action_name:
            print "Error: '%s' action already exists for '%s'" % (action_name, resname)
            sys.exit(1)

    actionElem = dom.createElement("action")
    actionElem.setAttribute("name",action_name)
    resElem.appendChild(actionElem)

    for option in options:
        if option.find('=') == -1:
            print "Error: --addaction options must be of the form option=value"
            sys.exit(1)

        (k,v) = option.split('=',1)
        actionElem.setAttribute(k,v)

    set_cluster_conf(dom.toxml())

def remove_action(resname, options):
    dom = minidom.parseString(get_cluster_conf_xml())

    if len(options) >= 1:
        action_name = options.pop(0)
    else:
        action_name = None

    # Verify resource exists
    elems = return_elements_with_attr(dom, "name", resname)
    if len(elems) == 0:
        print "Error: Could not find resource/service with name: %s" % (resname)
        sys.exit(1)

    if len(elems) > 1:
        print "Error: Too many elements in cluster.conf matched name: %s" % (resname)
        sys.exit(1)

    resElem = elems[0]
    elements_to_remove = []
    for elem in resElem.getElementsByTagName("action"):
        elementMatch = True
        if action_name != None and (not elem.hasAttribute("name") or elem.getAttribute("name") != action_name):
            elementMatch = False
        for option in options:
            if option.find('=') == -1:
                if not elem.hasAttribute("option"):
                    elementMatch = False
            else:
                (k,v) = option.split('=',1)
                if not elem.hasAttribute(k) or elem.getAttribute(k) != v:
                    elementMatch = False
        if elementMatch:
            elements_to_remove.append(elem)

    if len(elements_to_remove) == 0:
        if action_name == None:
            print "Error: no elements found matching resource: %s options: %s" % (resname, " ".join(options))
        else:
            print "Error: no elements found matching resource: %s action name: %s options: %s" % (resname, action_name, " ".join(options))

        sys.exit(1)

    for elem in elements_to_remove:
        elem.parentNode.removeChild(elem)

    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 or vm.nodeType == minidom.Node.COMMENT_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, "altmulticast", "Alt Multicast")
    list_tag(dom, "fence_daemon", "Fence Daemon")
    list_tag(dom, "logging", "Logging")
    list_tag(dom, "logging_daemon", "  Logging Daemons")
    list_tag(dom, "uidgid", "UID/GID")

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_uidgid(options):
    dom = minidom.parseString(get_cluster_conf_xml())

    current_uidgids = dom.getElementsByTagName("uidgid")
    uidgid = dom.getElementsByTagName('cluster')[0].appendChild(dom.createElement("uidgid"))
    attributes_set = False
    for opt in options:
	if opt.find('=') == -1:
	    print "Error: --setuidgid options must be of the form uid=<uid> gid=<gid>"
	    sys.exit(1)
	(k,v) = opt.split('=',1)
	if k != "uid" and k != "gid":
	    print "Error: --setuidgid options must be of the form uid=<uid> gid=<gid>"
	    sys.exit(1)
	uidgid.setAttribute(k,v)
	attributes_set = True
    if not attributes_set:
	print "Error: you must set either gid or uid"
	sys.exit(1)

# Check to make sure we're not duplicating an already existing item
    for ug in current_uidgids:
	if ug.getAttribute("uid") == uidgid.getAttribute("uid") and ug.getAttribute("gid") == uidgid.getAttribute("gid"):
	    print "Error: uidgid entry already exists with uid=%s, gid=%s" % (ug.getAttribute("uid"),ug.getAttribute("gid"))
	    sys.exit(1)

    set_cluster_conf(dom.toxml())

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

    uidgids = dom.getElementsByTagName('uidgid')
    if len(uidgids) == 0:
      print "Error: can't remove uidgid, no uidgid section exists in cluster.conf"
      sys.exit(1)

    uid = ""
    gid = ""

    for opt in options:
	if opt.find('=') == -1:
	    print "Error: --rmuidgid options must be of the form uid=<uid> gid=<gid>"
	    sys.exit(1)
	(k,v) = opt.split('=',1)
	if k != "uid" and k != "gid":
	    print "Error: --rmuidgid options must be of the form uid=<uid> gid=<gid>"
	    sys.exit(1)
	if k == "uid":
	    uid = v
	if k == "gid":
	    gid = v

    removed = False
    for ug in uidgids:
	if ug.getAttribute("uid") == uid and ug.getAttribute("gid") == gid:
	    ug.parentNode.removeChild(ug)
	    print "Removing uid=%s, gid=%s" % (uid,gid)
	    removed = True
	    break

    if removed:
	set_cluster_conf(dom.toxml())
    else:
	print "Error: unable to find uidgid with uid=%s, gid=%s" % (uid,gid)
    	sys.exit(1)

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 or logger.nodeType == minidom.Node.COMMENT_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, False)

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

# If the hostname is not set, the we try localhost
    if hostname == False and host_not_required == False:
        print "Error: you must specify a filename and host when using --setconf"
        sys.exit(1)

    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 backup:
        path = os.path.expanduser("~/.ccs/backup/")
        try:
            os.makedirs(path)
        except OSError as exc:
            if exc.errno == errno.EEXIST and os.path.isdir(path):
                pass
            else:
                raise
        filetime = datetime.datetime.now().strftime("%Y%m%d-%H%M%S-%f")
        with open("%scluster.conf.%s" % (path,filetime),"w") as backup_file:
                backup_file.write(orig_cluster_conf)

    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 (errnum, 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('w+b', -1, ".ccs")
    schemaFile.write(schema)
    schemaFile.flush()

    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."

# If success is "10" (RRC_AUTH_FAIL) then we have already been
# authenticated, but we used a bad password
def verify_authentication_password(dom):
    ricci =  dom.getElementsByTagName("ricci")
    if len(ricci) > 0:
        if ricci[0].getAttribute("success") != "0":
            return False
        else:
            return True
    print "Error: no ricci tag in ricci response"
    sys.exit(1)

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 get_alt_name(host):
    try:
        with open('/etc/cluster/cluster.conf') as f:
            data = f.read()
    except IOError:
        return None
    dom = minidom.parseString(data)
    node_found = False

    for node in dom.getElementsByTagName('clusternode'):
        if node.getAttribute("name") == host:
            for altname in node.getElementsByTagName('altname'):
                return altname.getAttribute("name")

    return None

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)

    if (not verify_authentication_password(dom)):
        print "This node is already authorized, but a bad password was provided. (try not using the '-p' option)"
        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:
        if usealt:
            ss.settimeout(10)
        ss.connect((host, RICCI_PORT))
    except socket.error:
        if not usealt:
            alt_name = None
        else:
            alt_name = get_alt_name(host)
        if alt_name != None:
            try:
                ss = ssl.wrap_socket(socket.socket(socket.AF_INET), key, cert)
                ss.settimeout(10)
                ss.connect((alt_name, RICCI_PORT))
            except socket.error:
                print "Unable to connect to %s or %s, make sure the ricci server is started." % (host,alt_name)
                sys.exit(1)
        else:
            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

# This function returns true if the agent requires an unfence section
def need_unfence(agent):
    if agent == "fence_scsi" or agent == "fence_sanlock":
	return True
    else:
      	return False

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

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