#!/usr/bin/python
__copyright__='''
Author: Alan Robertson	<alanr@unix.sh>
Copyright (C) 2007 International Business Machines
Licensed under the GNU GPL version 2 or later
'''

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

#
#	ciblint: Program to audit Linux-HA CIB for potential problems.
#	analogous to 'lint' for 'C' programs
#
#
#	Not everything that 'ciblint' comments on is necessarily a problem
#	but its comments are probably worth spending some effort to understand.
#
#	This program is a work-in-progress, but for many CIBs it's probably
#	useful now.
#
#	A few of the things we try and catch are listed in the usage text below...

from UserDict import UserDict
import os, sys, string, types, re
from xml.dom.ext.reader.Sax2 import FromXmlStream, FromXml

usage_message=\
"usage: " + sys.argv[0] + " [-C] -f cib-file\n" + \
"       " + sys.argv[0] + " [-C] -L\n" +\
"       " + sys.argv[0] + " [-w] (-A|--list-meta_attributes-config-options\n" +\
"       " + sys.argv[0] + " [-w] (-l|--list-crm-config-options)\n" +\
"       " + sys.argv[0] + " [-w] -h --help\n" +'''
  -f cib-filename		analyze CIB from this XML file
  -L --live-cib			analyze live CIB gotten via cibadmin -Q
  -C --ignore-non-defaults	don't print messages for non-default crm_config values
  -w --wiki-format		print usage or crm-config-options in wiki format
  -l --list-crm_config-options	print all valid names for use in <nvpair> sections
                                inside the <crm_config> section
  -A --list-meta_attributes-config-options
                                print all valid names for use in <nvpair> sections
                                inside <meta_attributes> sections
                                 
CIB file can either have a status section or not.  Either is acceptable.
This program is a work-in-progress, but for many CIBs it's probably useful now.

It currently looks for a number of classes of possible errors, including these:
  - Non-unique 'id' strings for the given <tag>
  - 'id' strings (outside the status section) which are not globally unique
  - Incorrect <nvpair> 'name's or 'value's
  - Duplicate <nvpair> 'name's in a list
  - Incorrect XML attribute names or values
  - references to non-existent resources
  - references to non-existent nodes
  - invalid values for the data type (integer, boolean, enum, etc.) involved
  - resources you're not monitoring
  - non-negative values for default-resource-failure-stickiness
  - STONITH not enabled
  - No STONITH resources configured
  - Use of for-testing-only ssh or external/ssh STONITH resource agents
  - validation of <nvpair> names and values in <meta_attributes> sections
  - validation of class and type for <primitive> resources
  - validation of <attributes> names and values in <nvpair>s for <primitive> resources
  - check if clone form resource names are used for non-clone resources
  - ensure that clone form resource names have integral clone numbers
  - check for ids with ":" characters in them in <primitive>, <group>, <clone>, or <master_slave> tags
  - check for resources with non-zero failcounts
  - check for <rule> tags with both score and score_attribute 
  - check for missing values and attributes in expressions
  - make sure attributes mentioned in the CIB are defined somewhere in the cluster
  - give a hint about _ names now being - names
  - warn about start_delay applied to non-monitor operations
  - warn about resources with stop failures
  - warn about resources not running anywhere
  - note what resources are running where
  - Ensure that <clone> and <master_slave> tags have exactly one child resource
  - warn about infinity used as a score in rsc_location statements
  - warn about rsc_colocation without corresponding rsc_order

More documentation can be found online at http://linux-ha.org/ciblint
'''
#
#	Things to do:
#	- validate the CIB against the DTD
#	- Add support for Andrew's new shorthand rsc_location format
#	- look for priority inversions caused by colocation dependencies
#	- look for resources whose LRM entries need cleanup
#	- warn about invalid <meta_attribute> <nvpair> names
#	  depending on the type of resource
#	- Validate clone numbers for resources against clone_max
#	- Check for role dependencies mentioned for non-master-slave
#	  resources
#	- Look for circular start-after dependencies
#	- Look for contradictory colocation constraints
#	- figure out why resource "x" isn't running
#	- make sure attributes mentioned in score_attribute are always ints
#	- make sure attributes mentioned in expressions are always the right type
#	- make special note of prereq=nothing for non-STONITH resources
#	- make special note of prereq!=nothing for STONITH resources
#	- Internal utilities to add:
#		get effective values of meta_attributes for resources
#		get values of node attributes
#		evaluate <rule>s (mostly done)
#
#	To Do for the OCF standards crew:
#	- Add support for enumerations to metadata
#	- add content type alternatives, first recognized is best (?)
#		Some thoughts:
#			score/string
#			positiveinteger/integer
#			negativeinteger/integer
#			ipv4addr/string
#			pathname/string

global CIBADMIN, HA_LIBHBDIR, URLBASE, LRMADMIN
HA_LIBHBDIR = "/usr/lib64/heartbeat"
CIBADMIN="/usr/sbin/cibadmin"
URLBASE="http://linux-ha.org/" + "ciblint/"
LRMADMIN="sudo " + HA_LIBHBDIR + "/lrmadmin"

global note_non_default_values, note_metadata_errors, wikiformat, outlinelen
global stonithresourcecount, allow_global_nonuniques, stonith_remarks
note_non_default_values = 1
note_metadata_errors = 0
wikiformat=0
stonithresourcecount=0
stonithremarks=1
outlinelen=70
allow_global_nonuniques=1
global LRMclasses, LRMresources, GlobalStatus, GlobalAttributeNames, clusterNodes
global ResourceOperationStatuses
ResourceNodeOperationStatuses = {}
LRMclasses = {}
LRMresources = {}
GlobalStatus = None
GlobalAttributeNames={"#uname":1, "#id":1, "#is_dc":1}
clusterNodes = {}

def lintURL(suffix):
  return URLBASE + suffix

def URLinfo(suffix):
  print "INFO: See %s for more information on this topic." % lintURL(suffix)

def crm_config_url(option):
  URLinfo("crm_config#"+option)

def typeToString(node):
  if node.nodeType == node.ELEMENT_NODE:
    return "ELEMENT"
  if node.nodeType == node.TEXT_NODE:
    return "TEXT"
  if node.nodeType == node.CDATA_SECTION_NODE:
    return "CDATA"
  if node.nodeType == node.ENTITY_REFERENCE_NODE:
    return "ENTITYREF"
  if node.nodeType == node.ENTITY_NODE:
    return "ENTITY"
  if node.nodeType == node.PROCESSING_INSTRUCTION_NODE:
    return "INSTRUCTION"
  if node.nodeType == node.COMMENT_NODE:
    return "COMMENT"
  if node.nodeType == node.DOCUMENT_NODE:
    return "DOCUMENT"
  if node.nodeType == node.DOCUMENT_TYPE_NODE:
    return "DOCTYPE"
  if node.nodeType == node.NOTATION_NODE:
    return "NOTATION"
  return "HUH?"


def node2str(node):
  if hasattr(node, "tagName"):
    return "<%s type=%s>" % (node.tagName, typeToString(node))
  return "<NODE TYPE=%s>" % (typeToString(node))

def prchildren(node):
  for child in node.childNodes:
    print "CHILD: " + node2str(child)

def print_wrap(text, linelen, prefix, firstprefix):
    words=text.split()
    line=""
    thisprefix=firstprefix
#    print "prefix=[%s] firstprefix=[%s] thisprefix=[%s]" % (prefix, firstprefix, thisprefix)
    for word in words:
      realprefix=thisprefix.expandtabs()
      llen = linelen - len(realprefix)
      if len(line)+len(word) > llen-1:
        if line != "":
          print thisprefix + line
          thisprefix=prefix
          line=""
      if line == "":
        line=word
      else:
        line=line+ " " + word
    if line != "":
        print thisprefix + line

#
#	A class for a data type as defined by our metadata - with a few extensions...
#	This should probably be a class with a bunch of related classes for the
#	various data types
#
class MetadataValueType:

  # List of supported types
  ValidTypes = {"integer":1, "boolean":1, "time":1, "string":1, "enum":1, "score":1}

  # List of supported time units - probably not correct ;-)
  TimeUnits = {"s":1, "sec":1, "seconds":1,
  	"ms":1, "milliseconds":1, "usec":1, "us":1, "microseconds":1,
  	"min":1, "m":1, "h":1, "hours":1};

  # List of legal boolean strings
  BooleanStrings = {"true":1, "enabled":1, "1":1, "on":1,
  	"false":0, "disabled":0, "0":0, "off":0}

  def __init__(self, typename, validtypevals=None):
    self.typename = string.lower(typename)
    if validtypevals != None and len(validtypevals) > 0:
      self.validtypevals = validtypevals
    else:
      self.validtypevals = None
    if not MetadataValueType.ValidTypes.has_key(typename):
      print "ERROR: Illegal type [%s]" % typename

  def validate(self, value):
    value = string.lower(value)

    if self.validtypevals != None:
      return self.validtypevals.has_key(value)

    if self.typename == "boolean":
      value=string.lower(value)
      return MetadataValueType.BooleanStrings.has_key(value)

    if self.typename == "score":
      if value == "infinity" or value == "-infinity" or value == "+infinity":
        return 1
      i = MetadataValueType("integer")
      return i.validate(value)
      

    if self.typename == "integer":
      if len(value) == 0:
        return None
      trans=string.maketrans("","")
      if value[0] == "+" or value[0] == "-":
        if len(value) < 2:
          return None
        value=value[1:]
      j=0
      max=len(value)
      while j < max:
        # We don't allow negative times...
        if string.find(string.digits, value[j]) < 0:
          return None
        j=j+1
      return 1

    # "time" objects are elapsed times, not time of day, etc
    if self.typename == "time":
       # Split into integer and units
       lastnum=-1
       j=0
       max=len(value)
       while j < max:
         # We don't allow negative times...
         if string.find(string.digits, value[j]) < 0:
           break
         lastnum=j
         j=j+1
       if j <= 0:
         return None
       units=value[j:]
       #print "time: %s: %d [%s]/[%s]" % (value, j, value[0:j], units)
       if len(units) == 0:
         return 1
       return MetadataValueType.TimeUnits.has_key(units)

    if self.typename == "string" or self.typename == "enum":
      return 1
    print "ERROR: invalid data type [%s]" % self.typename
    return None

global StringMetadataType, BooleanMetadataType, IntegerMetadataType
StringMetadataType = MetadataValueType("string")
BooleanMetadataType = MetadataValueType("boolean")
IntegerMetadataType = MetadataValueType("integer")
TimeMetadataType = MetadataValueType("time")
EnumMetadataType = MetadataValueType("enum")
ScoreMetadataType = MetadataValueType("score")
UuidMetadataType = MetadataValueType("string")

#
#	A score class - a lot like longs, except it reserves +infinity and -infinity
#
class Score:
  infinity=long(10000000)
  neginfinity=long(-infinity)

  def __init__(self, weight=0):

    stringweight=string.lower(str(weight))
    if stringweight == "infinity" or weight == "+infinity":
      self.value=Score.infinity
    elif stringweight == "-infinity":
      self.value=Score.neginfinity
    else:
      self.value=string.atol(weight)

    if self.value > Score.infinity:
      raise ValueError, "value > infinity"
    elif self.value < Score.neginfinity:
      raise ValueError, "value < -infinity"

  def __str__(self):
    if self.value == Score.infinity:
      return "infinity"
    if self.value == Score.neginfinity:
      return "-infinity"
    return str(self.value)

  def __int__(self):
    return int(self.value)

  def __long__(self):
    return long(self.value)

  def __add__(self,rhs):
     if (self.value ==    Score.infinity and rhs.value == Score.neginfinity) \
     or (self.value == Score.neginfinity and rhs.value == Score.infinity):
       raise ValueError, "+-infinity added"
     if self.value == Score.infinity or rhs.value == Score.infinity:
       return Score(Score.infinity)
     if self.value == Score.neginfinity or rhs.value == Score.neginfinity:
       return Score(Score.neginfinity)
     return Score(self.weight + rhs.weight)

  def __sub__(self,rhs):
    if hasattr(rhs, "value"):
      return __add__(self, Score(-rhs.value))
    return __add__(self, Score(-rhs))

  def __cmp__(self, rhs):
    if hasattr(rhs, "value"):
      diff=self.value-rhs.value
    else:
      diff=self.value-rhs
    if diff < 0:
      return -1
    if diff > 0:
      return 1
    return 0

#
#	A class for a multi-language string - based on the OCF resource agent metadata descriptions
#

class OCFLangString(UserDict):
  def __init__(self, xmlnode, tagname, elemname):
    UserDict.__init__(self)
    self.context = xmlnode.tagName
    self.name = tagname
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      CheckAttributes(tagname)
      if child.tagName != tagname:
        continue
      lang = child.getAttribute("lang")
      if lang == "":
        if note_metadata_errors:
          print "Metadata ERROR: <%s> tag without lang attribute. Context: inside <%s> tag"\
          %	(tagname, self.context)
          print "         (this is not a problem in your CIB)"
        continue
      if self.has_key(lang):
        if note_metadata_errors:
          print "Metadata ERROR: duplicate <%s lang=\"%s\"> tag. Context: inside <%s> tag"\
          %	(tagname, lang, self.context)
          print "         (this is not a problem in your CIB)"
        continue
      if len(child.childNodes) >= 1:
        self[lang]=child.childNodes[0].data
        #print "Snagged %s: %s" % (tagname, child.childNodes[0].data)
      else:
        if note_metadata_errors:
          print "Metadata ERROR: <%s lang=\"%s\"> has no data.  Context:  Inside <%s> tag" \
          %  (tagname, lang, self.context)
          print "         (this is not a problem in your CIB)"
        self[lang]=elemname
      
    if len(self) < 1:
      if note_metadata_errors:
        print "Metadata ERROR: <%s> inside <%s> has no data in any language"\
        %		(tagname, self.context)
      self["en"]=elemname

  def text(self, lang="en"):
    if not self.has_key(lang) and lang != "en":
      if self.has_key("en"):
        return self["en"]
      else:
        for lang in self.keys():
          return self[lang]
        return None
    else:
      return self[lang]

#
#	A class for tracking possible enumeration values based on the OCF RA metadata
#	model.  This is an extension from the curent OCF model, but it should
#	be completely upwards-compatible in the xml.  Here's how it looks:
#
#	<content type="enum" default="xxx">
#	  <enumvals>
#	    <enumval value="red">
#	      <shortdesc lang="en">long description</shortdesc>
#	      <longdesc lang="en">long description</longdesc>
#	    </enumval>
#	  </enumvals>
#	</content>
#	  
class OCFParameterEnumValNodeExtension(UserDict):
  def __init__(self, xmlnode):
    UserDict.__init__(self)
    # xmlnode should point to the <content> tag...
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      CheckAttributes(child)
      if child.tagName == "enumvals":
        for gchild in child.childNodes:
          if gchild.nodeType != gchild.ELEMENT_NODE:
            continue
          CheckAttributes(gchild)
          value = gchild.getAttribute("value")
          if gchild.tagName == "enumval":
            value = gchild.getAttribute("value")
	    self[value] = (OCFLangString(gchild, "shortdesc", value), OCFLangString(gchild, "longdesc", value))
          else:
            print "Metadata ERROR: Don't know how to deal with funky child of <%s>" % child.tagName
            print "Funky child: " + node2str(gchild)
#      elif child.tagName == "enumval":
#        value = child.getAttribute("value")
#	self[value] = (OCFLangString(child, "shortdesc", value), OCFLangString(child, "longdesc", value))
      else:
        print "Metadata ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
    if len(self) < 1:
      self=None


#
# A class for dealing with OCF Parameter metadata
#
class OCFParameterMetadata:
  def __init__(self, xmlnode):
    self.unique = 0
    self.type = StringMetadataType
    self.default = None
    self.name = xmlnode.getAttribute("name")
    self.unique = xmlnode.getAttribute("unique")
    self.typename = "string"
    self.longdescs=OCFLangString(xmlnode,"longdesc", self.name)
    self.shortdescs=OCFLangString(xmlnode,"shortdesc", self.name)
    self.type=MetadataValueType(self.typename)
    if self.unique == "":
      self.unique=0
    else:
      self.unique=string.atoi(self.unique)
    self.required = xmlnode.getAttribute("required")
    if self.required == "":
      self.required=0
    else:
      self.required=string.atoi(self.required)
    children=xmlnode.childNodes
    for child in children:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "shortdesc" or child.tagName == "longdesc":
        continue
      if child.tagName == "content":
        type = child.getAttribute("type")
        # Sneak in and tweak some of our metadata...
        if re.match('^default-resource-.*stickiness$', self.name) and type == "integer":
          # These are really scores, but integer is all the CRM currently knows about
          type="score"
        if type != "":
          self.typename = type
          if type == "enum":
            enuminfo = OCFParameterEnumValNodeExtension(child)
          else:
            enuminfo = None
          self.type=MetadataValueType(type, enuminfo)
        default = child.getAttribute("default")
        if default != "":
          self.default=default
      else:
        print "Metadata ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)

  def shortdesc(self, lang="en"):
    return self.shortdescs.text(lang)

  def longdesc(self, lang="en"):
    return self.longdescs.text(lang)

  def validate(self, value):
    return self.type.validate(value)

  def explainnondefault(self, value, lang="en", exceptions={}):
    if not self.validate(value):
      print "ERROR: Value [%s] is not a valid value of type [%s] in <%s>"  \
	% (value, self.type.typename, self.name)
    if self.default != None and self.default != "" and value != self.default \
    and note_non_default_values and not exceptions.has_key(self.name):
      print "INFO: CIB has non-default value for %s [%s].  Default value is [%s]" \
	%	(self.name, value, self.default)
      print_wrap("Explanation of %s option: %s" % (self.name, self.shortdesc(lang)),\
		outlinelen, "      ", "INFO: ")
      if self.longdesc(lang) != self.shortdesc(lang):
        print_wrap(self.longdesc(lang), outlinelen, "INFO: ", "INFO: ")

#
#	OCF metadata class - descriptions, parameters, types, operations, etc.
#
class OCFMetadata:
  RequiredOps = {"start":1, "stop":1, "monitor":1, "meta-data":1, "validate-all":1}
  def __init__(self, xmlnode):
    self.langfilter = "en"
    self.Operations={}
    self.Parameters={}
    self.name = "none"
    self.longdescs = {}
    self.shortdescs = {}
    children=xmlnode.childNodes
    for topchild in children:
      #print "DEBUG: Metadata top child: " + node2str(topchild)
      if topchild.nodeType != topchild.ELEMENT_NODE:
        continue
      if topchild.tagName != "resource-agent":
         print "ERROR: Unexpected nesting of %s inside <%s>" % (node2str(topchild), xmlnode.tagName)
         continue
      self.name = topchild.getAttribute("name")
      self.longdescs=OCFLangString(topchild,"longdesc", self.name)
      self.shortdescs=OCFLangString(topchild,"shortdesc", self.name)

      for child in topchild.childNodes:
        if child.nodeType != child.ELEMENT_NODE:
          continue

	# Short Description
        if child.tagName == "shortdesc" or child.tagName == "longdesc" \
        or child.tagName == "version":
          continue

	# Parameters
        elif child.tagName == "parameters":
          for gchild in child.childNodes:
            if gchild.nodeType != gchild.ELEMENT_NODE:
              continue
	    # Parameter (singular)
            if gchild.tagName == "parameter":
              p = OCFParameterMetadata(gchild)
              self.Parameters[p.name] = p
            else:
              print "ERROR: Unexpected nesting of %s inside <%s>" % (node2str(gchild), child.tagName)

	# Actions
        elif child.tagName == "actions":
          for gchild in child.childNodes:
            if gchild.nodeType != gchild.ELEMENT_NODE:
              continue
	    # Action (singular)
            if gchild.tagName == "action":
              # Good enough to get us started...
              self.Operations[gchild.getAttribute("name")] = 1
            else:
              print "ERROR: Unexpected nesting of %s inside <%s>" % (node2str(gchild), child.tagName)
        elif child.tagName == "special":
          continue
        else:
          print "ERROR: Unexpected nesting of %s inside <%s>" % (node2str(child), topchild.tagName)
          self=None
          return

  def shortdesc(self, lang="en"):
    return self.shortdescs.text(lang)

  def longdesc(self, lang="en"):
    return self.longdescs.text(lang)

  def has_parameter(self, p):
    return self.Parameters.has_key(p)

  def validate(self, p, value):
    if not self.Parameters.has_key(p):
      print "ERROR: [%s] is not a legal name to [%s]" \
	% (p, self.name)
      return None
    return self.Parameters[p].validate(value)

  def validate_nvpairs(self, nvpairs, context=None):
    ret=1
    if context == None:
      context="inside <%s> tag" % self.name
    for p in nvpairs.keys():
      if not self.Parameters.has_key(p):
        print "ERROR: <nvpair name=\"%s\" ...> \"%s\" is not a legal nvpair name %s" \
	%	(p, p, context)
        ret=0
        continue
      if not self.Parameters[p].validate(nvpairs[p]):
        print "ERROR: <nvpair name=\"%s\" value=\"%s\">: \"%s\" is not a legal value for name %s %s" \
	%	(p, nvpairs[p], nvpairs[p], p, context)
        ret=0
    return ret
 
  def explainnondefault(self, p, value, lang="en", exceptions={}):
    if not self.Parameters.has_key(p):
      print "ERROR: [%s] is not a legal parameter to [%s]" \
	% (p, self.name)
      return
    self.Parameters[p].explainnondefault(value, lang, exceptions=exceptions)

  def explainall(self, lang="en"):
    for key in self.Parameters.keys():
      item = self.Parameters[key]
      if item.default == None:
        deflt=""
      else:
        deflt=": default=%s" % item.default
      
      print_wrap("%s [%s%s]: %s" % (key,item.typename,deflt,	\
	item.shortdesc(lang)), outlinelen, "    ", "")
      print_wrap(item.longdesc(lang), outlinelen, "    ", "    ")
      if item.type.validtypevals != None:
        print_wrap("Legal Values:", outlinelen, "\t    ", "\t")
        for enumval in item.type.validtypevals.keys():
          descs= item.type.validtypevals[enumval]
          shortd=descs[0].text(lang)
          longd=descs[1].text(lang)
          print_wrap("%s: %s" % (enumval, shortd), outlinelen, "\t    ", "\t")
          if (longd != None and long != "" and longd!= shortd):
            print_wrap(longd, outlinelen, "\t    ", "\t    ")

  def explainall_wiki(self, lang="en"):
    for key in self.Parameters.keys():
      item = self.Parameters[key]
      print "== %s ==" % key
      print "[[Anchor(%s)]]" % key
      if item.default == None:
        deflt=""
      else:
        deflt=": default=%s" % item.default
      print "'''%s''' [%s%s]: ''%s''[[BR]]" % (key, item.typename, deflt,\
      	self.Parameters[key].shortdesc(lang))
      print "%s[[BR]]" % (self.Parameters[key].longdesc(lang))
      if item.type.validtypevals != None:
        print "''''Legal Values:''''"
        for enumval in item.type.validtypevals.keys():
          descs=item.type.validtypevals[enumval]
          print " * '''%s:''' %s[[BR]]%s" % (enumval, descs[0].text(lang), descs[1].text(lang))

  def supports_action(self, op):
    if OCFMetadata.RequiredOps.has_key(op):
      return 1
    return self.Operations.has_key(p)

#
# A class for collecting OCF-style metadata from the CRM and the pengine
# and a little extra hand-created metadata...
#
class CRMMetadata:
  def __init__(self, hblibdir=HA_LIBHBDIR):
    ExtraMetadata =\
'''<resource-agent name="crm_config">
  <version>1.0</version>
  <longdesc lang="en">Extra Options that can be configured for crm_config section.</longdesc>
  <shortdesc lang="en">crm_config extra metadata</shortdesc>
  <parameters>
    <parameter name="last-lrm-refresh" unique="0">
      <shortdesc lang="en">last LRM refresh</shortdesc>
      <longdesc lang="en">you can't set this yourself</longdesc>
      <content type="integer"/>
    </parameter>
    <parameter name="dc-version" unique="0">
      <shortdesc lang="en">Version of Heartbeat the DC is running</shortdesc>
      <longdesc lang="en">you can't set this yourself</longdesc>
      <content type="string"/>
    </parameter>
    <parameter name="shutdown-escalation" unique="0">
      <shortdesc lang="en">time before shutdown escalation</shortdesc>
      <longdesc lang="en">time before normal shutdown is replaced by ungraceful escalated shutdown</longdesc>
      <content type="string"/>
    </parameter>
  </parameters>
</resource-agent>
'''
    self.metadatalist = []
    for file in ["crmd", "pengine"]:
      command="%s/%s metadata" % (hblibdir, file)
      f = os.popen(command)
      f.readline()  # work around bad first line in metadata
      xml = FromXmlStream(f)
      self.metadatalist.append(OCFMetadata(xml))
      f.close()
      xml = None
    self.metadatalist.append(OCFMetadata(FromXml(ExtraMetadata)))

  def searchfor(self, key):
    for mdlist in self.metadatalist:
       if mdlist.has_parameter(key):
         return mdlist
    return None

  def validate(self, nvpairs):
    for key in nvpairs.keys():
      mdlist=self.searchfor(key)
      if not mdlist:
        print ("ERROR: <nvpair name=\"%s\"...>: [%s] is not a legal name for" \
        +	" the <crm_config> section") % (key, key)
        if string.find(key, "_") >= 0:
          otherkey=re.sub("_", "-", key, 100)
          mdlist2 = self.searchfor(otherkey)
          if mdlist2:
            print ("INFO: [%s] IS a legal name." \
            +	" Name [%s] is obsolescent") % (otherkey, key)
            mdlist2.validate(otherkey, nvpairs[key])
      else:
        mdlist.validate(key, nvpairs[key])

  def defaultvalue(self, key):
      for mdlist in self.metadatalist:
         if mdlist.has_parameter(key):
           entry=mdlist.Parameters[key].default
           return entry
      return None
           
  def explainnondefault(self, nvpairs, exceptions={}):
    self.validate(nvpairs)
    for key in nvpairs.keys():
      found=None
      for mdlist in self.metadatalist:
         if mdlist.has_parameter(key):
           found=1
           mdlist.explainnondefault(key, nvpairs[key], exceptions=exceptions)

  def explainall(self):
    if wikiformat:
      print "= crm_config options ="
    for mdlist in self.metadatalist:
      if wikiformat:
        mdlist.explainall_wiki()
      else:
        mdlist.explainall()
    if wikiformat:
      print "----"
      print "''This page the output from ciblint -w -l''"

  def __getitem__(self, key):
    for mdlist in self.metadatalist:
      if mdlist.Parameters.has_key(key):
        return mdlist.Parameters[key]
    raise KeyError, "Key %s not found" % key

#
#	Get <crm_config> metadata from pengine and crmd
#
class MetaAttrMetadata:
  def __init__(self, hblibdir=HA_LIBHBDIR):
    self.metadatalist = []
    for file in ["crmd", "pengine"]:
      command="%s/%s metadata" % (hblibdir, file)
      f = os.popen(command)
      f.readline()  # work around bad first line in metadata
      xml = FromXmlStream(f)
      self.metadatalist.append(OCFMetadata(xml))
      f.close()
      xml = None

#
#	Obtain and cache metadata from resource agents
#	This involves talking to the LRM, which requires that we run
#	as root.
#
class MetadataCache(UserDict):
 def __is_lrm_working__(self):
   cmd=LRMADMIN + " -C 2>/dev/null"
   f = os.popen(cmd)
   global LRMclasses, LRMresources
   localclasses = f.readlines()
   localclasses = localclasses[1:]
   j=0
   while j < len(localclasses):
     LRMclasses[localclasses[j].strip()] = 1
     j=j+1
   f.close()
   for rclass in LRMclasses:
     LRMresources[rclass] = {}
     cmd=LRMADMIN + " -T %s 2>/dev/null" % rclass
     f = os.popen(cmd)
     types = f.readlines()
     types = types[1:]
     f.close()
     for type in types:
       type=type.strip()
       # Not quite right, because it ignores provider...
       LRMresources[rclass][type]=1
   return len(LRMclasses) >= 1

 def __init__(self):
    UserDict.__init__(self)
    # Test if LRM connections work...
    self.uselrm=MetadataCache.__is_lrm_working__(self)

 def __resource_key__(self, rclass, type, provider):
   if rclass == "ocf":
     return "Resource/"+rclass+"/"+provider+"/"+type
   return "Resource/"+rclass+"/"+type

 def __stash_lrm__(self, key, command):
   #print "COMMAND=%s" % command
   f = os.popen(command)
   #print "READ1:", f.readline()  # ignore line
   #print "READ2:", f.readline()  # ignore line
   #print "READ3:", f.readline()  # ignore line
   #print "READ4:", f.readline()  # ignore line
   try:
     self[key]=OCFMetadata(FromXmlStream(f))
   except:
     if note_metadata_errors:
       print "Metadata ERROR: Could not get resource metadata for %s" % key
       print "INFO: Resource parameters for this resource type will not be checked"
     self[key] = None
   else:
     #print "HOORAY!  PARSING WORKED for %s!" % key
     #print self[key]
     None
   return f.close()

 def __add_resource__(self, rclass, type, provider=None):
   if not self.uselrm:
      return 0
   if not LRMclasses.has_key(rclass):
     print "ERROR: %s is not a valid resource class" % rclass
     return 0
   if not LRMresources[rclass].has_key(type):
     print "ERROR: [%s] is not a valid type for resource class [%s]" % (type, rclass)
     return 0
   key=self.__resource_key__(rclass, type, provider)
   if (provider == None or provider == ""):
     provider="NULL"
   lrmcommand="%s --metadata %s %s %s 2>/dev/null | grep -v '^<!DOCTYPE'|tee foo.bar" % (LRMADMIN, rclass, type, provider)
   return self.__stash_lrm__(key, lrmcommand)

 def RAmetadata(self, rclass, type, provider=None):
   key=self.__resource_key__(rclass, type, provider)

   if self.has_key(key):
     return self[key]
   else:
     self.__add_resource__(rclass, type, provider)
   if self.has_key(key):
     return self[key]
   return None

global crmMetadata, crm_config_section, metaDataCache
global MetaAttributeMetadata, MetaAttributeMetadata_string
crmMetadata = None
MetaAttributeMetadata = None
metaDataCache = MetadataCache()

#
#	Metadata describing the <meta_attribute> <nvpair> names and values
#	This should probably eventually come from the CRM/pengine.
#

#'''<?xml version="1.0"?>
MetaAttributeMetadata_string =\
'''<resource-agent name="meta_attributes">
  <version>1.0</version>
  <longdesc lang="en">Options that can be configured for resource meta_attributes.</longdesc>
  <shortdesc lang="en">Resource meta_attributes Options</shortdesc>

  <parameters>

    <parameter name="is_managed" unique="0">
      <content type="boolean"/>
      <shortdesc lang="en">"true" if this resource is managed by Heartbeat</shortdesc>
      <longdesc lang="en">If set to "false", Heartbeat will not monitor it, nor restart it on failure of resource or node.  Commonly set to "false" to allow for maintenance of the resource.  It defaults to the currently active value of "is-managed-default" from the crm_config section, or can be inherited from a parent (containing) resource.  If "false", the resource will not started if stopped, stopped if started nor have any recurring actions scheduled.  The resource may still be referenced in colocation constraints and ordering constraints (though obviously if no actions are performed on it then it will prevent the action on the other resource too)</longdesc>
    </parameter>

    <parameter name="restart_type" unique="0">
      <shortdesc lang="en">how to restart dependents</shortdesc>
      <longdesc lang="en">how to restart dependents when this resource restarts</longdesc>
      <content type="enum" default="ignore">
        <enumvals>
	  <enumval value="ignore">
            <shortdesc lang="en">ignore restarts of resources we depend on</shortdesc>
            <longdesc lang="en">Restarts of resources which this resource has order-dependencies on will not cause restarts of this resource</longdesc>
	  </enumval>
	  <enumval value="restart">
            <shortdesc lang="en">Restart this resource when resources we depend on restart.</shortdesc>
            <longdesc lang="en">Restart this resource whenever any other resource restarts on which this resource has order-dependencies. If this resource has to start after resource foo, then whenever foo restarts, we will restart this resource.  For example, you can Use this to have a restart of your database also trigger a restart of your web-server.</longdesc>
	  </enumval>
        </enumvals>
      </content>
    </parameter>

    <parameter name="multiple_active" unique="0">
      <shortdesc lang="en">action to take when multiple instances are active</shortdesc>
      <longdesc lang="en">When an instance of this resource is found to be active mutliple times, this parameter specifies what action to take.</longdesc>
      <content type="enum" default="start_stop">
	<enumvals>
	  <enumval value="stop_start">
            <shortdesc lang="en">stop all and restart one</shortdesc>
            <longdesc lang="en">Stop all active instances of this resource, then restart one.  This works fine if the resource won't be irreparably damaged by having multiple active instances.</longdesc>
          </enumval>
	  <enumval value="stop_only">
            <shortdesc lang="en">stop all instances</shortdesc>
            <longdesc lang="en">Stop all instances of this resource and wait for the administrator to recover the resources manually.  This is a good choice if this resource (like most filesystems) will be damaged by multiple running copies, and get more damaged the longer they're multiply active, and require manual repair in order to be usable again.</longdesc>
	  </enumval>
	  <enumval value="block">
            <shortdesc lang="en">do nothing</shortdesc>
            <longdesc lang="en">Don't do anything - wait for the administrator to recover the resources.</longdesc>
	  </enumval>
	</enumvals>
      </content>
    </parameter>

    <parameter name="resource_stickiness" unique="0">
      <shortdesc lang="en">Stay where we are or move to "better" place?</shortdesc>
      <longdesc lang="en">0: resources will be placed "optimally" without regard for current location.
positive value: Node currently running resource is has a score of "resource_stickiness" added to its other scores.  Negative values aren't typically very sensible.</longdesc>
      <content type="score" default="0"/>
    </parameter>

    <parameter name="ordered" unique="0">
      <content type="boolean" default="true"/>
      <shortdesc lang="en">Is start-after ordering implied by the group?</shortdesc>
      <longdesc lang="en">If true, then each element of the group has a start-after ordering dependency implied between it and its immediate predecessor in the group (if any).</longdesc>
    </parameter>


    <parameter name="collocated" unique="0">
      <content type="boolean" default="true"/>
      <shortdesc lang="en">Is start-after ordering implied by the group?</shortdesc>
      <longdesc lang="en">If true, then each element of the group has a colocation dependency implied between it and its immediate predecessor in the group (if any).</longdesc>
    </parameter>

    <parameter name="interleave" unique="0">
      <content type="boolean" default="0"/>
      <shortdesc lang="en">affects clones with colocation constraints</shortdesc>
      <longdesc lang="en">If a colocation constraint is created between two clone resources and interleaved is true, then clone N from one resource will be assigned the same location as clone N from the other resource.  If the number of runnable clones differs, then the leftovers can be located anywhere.  Using a cloned group is a much better way of achieving the same result.
      </longdesc>
    </parameter>

    <parameter name="notify" unique="0">
      <content type="boolean" default="false"/>
      <shortdesc lang="en">notify clone resource agent of changes in state</shortdesc>
      <longdesc lang="en">Issue pre and post operation notifications to entire clone set for every operation.  This introduces shell-level barriers for the resource agent actions    If true, inform peers before and after any clone is stopped or started.  If an action failed, you will (currently) not recieve a post-notification.  Instead you can next expect to see a pre-notification for a stop.  If a stop fails, and you have fencing you will get a post-notification for the stop after the fencing operation has completed.  In order to use the notification service ALL resources in the clone MUST support the notify action.  Currently this action is not permitted to fail, though depending on your configuration, can block almost indefinitely.  Behavior in response to a failed action or notification is likely to be improved in future releases.
      </longdesc>
    </parameter>

    <parameter name="globally_unique" unique="0">
      <shortdesc lang="en">"true" means this clone is unique within the cluster</shortdesc>
      <longdesc lang="en">A globally unique clone can distinguish which clone number it is within the cluster - typically implying a true cluster-aware application.  A non-unique clone is typically a non-cluster-aware application which can only tell if a clone of the application is running on this machine, but not which clone number it is.</longdesc>
      <content type="boolean" default="false"/>
    </parameter>


    <parameter name="clone_max" unique="0">
      <content type="integer" default="0"/>
      <shortdesc lang="en">Max number of clones</shortdesc>
      <longdesc lang="en">Total maximum number of clones of this resource permitted. Value defaults to the number of nodes in the system.</longdesc>
    </parameter>

    <parameter name="clone_node_max" unique="0">
      <content type="integer" default="1"/>
      <shortdesc lang="en">Max number of clones/node</shortdesc>
      <longdesc lang="en">Total maximum number of clones allowed to run on one node at a time</longdesc>
    </parameter>

    <parameter name="master_max" unique="0">
      <content type="integer" default="1"/>
      <shortdesc lang="en">Max number of masters</shortdesc>
      <longdesc lang="en">Maximum number of instances of this resource which are allowed to be master at one time. Must be less than or equal to "clone_max"</longdesc>
    </parameter>

    <parameter name="master_node_max" unique="0">
      <content type="integer" default="1"/>
      <shortdesc lang="en">Max number of masters/node</shortdesc>
      <longdesc lang="en">Maximum number of instances of this resource which are allowed to be master on a node at one time. Must be less than or equal to "clone_node_max"</longdesc>
    </parameter>


    <parameter name="target_role" unique="0">
      <shortdesc lang="en">Desired state of the resource</shortdesc>
      <longdesc lang="en">Heartbeat will attempt keep it in the specfied state.  It defaults to "#default".  If set to some other value, it will ignore the value of is_managed or is_managed_default. Target_role can also be inherited from a parent (containing) resource.</longdesc>
      <content type="enum">
	<enumvals>
	  <enumval value="#default">
            <shortdesc lang="en">allow cluster to determine its state</shortdesc>
            <longdesc lang="en">explicitly inherit target_role from parent (containing) resource or allow Heartbeat to control its state if there is no parent target_role.</longdesc>
	  </enumval>
	  <enumval value="started">
            <shortdesc lang="en">Keep resource started</shortdesc>
            <longdesc lang="en">Ignore any value of is_managed, or is-managed-default and keep the resource started</longdesc>
	  </enumval>
	  <enumval value="stopped">
            <shortdesc lang="en">Keep resource stopped</shortdesc>
            <longdesc lang="en">Ignore any value of is_managed, or is-managed-default and keep the resource stopped.  Resources which depend on this resource will also be stopped.</longdesc>
	  </enumval>
	  <enumval value="master">
            <shortdesc lang="en">keep resource in master mode</shortdesc>
            <longdesc lang="en">Ignore any value of is_managed, or is-managed-default and other promotion preferences, and keep (all instances of) the resource in master state.</longdesc>
	  </enumval>
	  <enumval value="slave">
            <shortdesc lang="en">keep resource in slave mode</shortdesc>
            <longdesc lang="en">keep resource in slave mode. This will force all instances to stay in slave mode</longdesc>
            <longdesc lang="en">Ignore any value of is_managed, or is-managed-default and other promotion preferences, and keep (all instances of) the resource in slave state.</longdesc>
	  </enumval>
	</enumvals>
      </content>
    </parameter>

    <parameter name="allow_migrate" unique="0">
      <shortdesc lang="en">"true" means this resource migratable</shortdesc>
      <longdesc lang="en">see short description.</longdesc>
      <content type="boolean" default="false"/>
    </parameter>


    <parameter name="resource_failure_stickiness" unique="0">
      <content type="score" default="0"/>
      <shortdesc lang="en">resource_failure_stickiness FIXME</shortdesc>
      <longdesc lang="en">resource_failure_stickiness FIXME</longdesc>
    </parameter>


    <parameter name="start_prereq" unique="0">
      <content type="enum" default="0"/>
      <shortdesc lang="en">obsolete?</shortdesc>
      <longdesc lang="en">Is this obsolete - replaced by the prereq attribute of the op tag?</longdesc>
    </parameter>

    <parameter name="priority" unique="0">
      <content type="integer" default="0"/>
      <shortdesc lang="en">The priority of this resource.  Higher numbers are higher priorities.</shortdesc>
      <longdesc lang="en">Higher priority resources are scheduled first, with lower priority resources scheduled after high priority resources have been placed.</longdesc>
    </parameter>

  </parameters>
</resource-agent>
'''

GlobalIDSet = {}
GlobalTagIDSet = {}
crm_config_section=None

#
# Check the 'id' attributes of the current node to make sure it has an id and that it's
# unique for the tag and (preferably) also globally unique.
#

def CheckXMLNodeUniqueness(xmlnode,globallyunique=1):
    CheckAttributes(xmlnode)
    if hasattr(xmlnode, "tagName"):
      id = xmlnode.getAttribute("id")
      if id == "":
        return None
      return CheckIdUniqueness(xmlnode.tagName, id, globallyunique=globallyunique)
    return 1

def CheckIdUniqueness(tag, id,globallyunique=1):
    global GlobalIdSet
    global GlobalTagIDSet
    global allow_global_nonuniques
    ret = 1
    if not GlobalTagIDSet.has_key(tag):
      GlobalTagIDSet[tag] = {}
    if GlobalTagIDSet[tag].has_key(id):
      print "ERROR: <%s> id '%s' not unique among all <%s> tags" \
      % (tag, id, tag)
      ret = None
    else:
      GlobalTagIDSet[tag][id] = id
    if not allow_global_nonuniques and globallyunique and GlobalIDSet.has_key(id):
      print "NOTE: '%s' not globally unique among all 'id's" % id
    else:
      GlobalIDSet[id] = id
    return ret

#
# A few types for us to use internally...
# Some should probably be implemented more directly as noted below.
#

RoleMetadataType = MetadataValueType("enum", {"started":1, "stopped":1,"master":1,"slave":1})
NodeMetadataType = MetadataValueType("enum", {"normal":1})
OrderMetadataType = MetadataValueType("enum", {"before":1, "after":1})
StopStartMetadataType = MetadataValueType("enum", {"stop_start":1, "stop_only":1, "block":1})
OnFailMetadataType = MetadataValueType("enum", {"ignore":1, "block":1, "stop":1, "restart":1, "fence":1})
RestartMetadataType = MetadataValueType("enum", {"ignore":1, "restart":1})
OpMetadataType = MetadataValueType("enum", {"lt":1, "lte":1,"gt":1,"gte":1,
				"eq":1, "neq":1, "defined":1, "not_defined":1})
DateOpMetadataType =   MetadataValueType("enum", {"in_range":1, "date_spec":1,"gt":1,"lt":1})
BoolDefaultMetadatatype = MetadataValueType("enum", {"true":1, "false":0,"1":1,"0":0, "#default":1,"on":1, "off":0})
CrmStatusMetadataType = MetadataValueType("enum", {"online":1, "offline":0})
MemberStatusMetadataType = MetadataValueType("enum", {"member":1, "pending":0, "down":0})
HaStatusMetadataType = MetadataValueType("enum", {"active":1, "init":1, "dead":0})
LrmOpMetadataType = MetadataValueType("enum", {"start":1, "stop":1,"reload":1,"monitor":1, "migrate":1, "promote":1, "demote":1})
LrmClassMetadataType = MetadataValueType("enum", {"stonith":1, "ocf":1,"heartbeat":1,"lsb":1})
BooleanOpMetadataType = MetadataValueType("enum", {"and":1, "or":1})
DatatypeMetadatatype = MetadataValueType("enum", {"number":1, "version":1, "string":1})
PrereqMetadataType = MetadataValueType("enum", {"fence":1, "quorum":1, "nothing":1})
			
# Could eventually do something fancier for these...
ResourceMetadataType = StringMetadataType
GroupMetadataType = StringMetadataType

idonly = {"id": (StringMetadataType,1)}

NVPairValidKeys = {}
#
# Set of valid attribute keys and value types for various tags
#
AttributeKeys = {
	"op": {		"id": (StringMetadataType,1),
			"disabled": (BooleanMetadataType,0),
			"interval": (TimeMetadataType,0),
                        "name": (StringMetadataType,1),
			"on_fail": (OnFailMetadataType,0),
			"prereq": (PrereqMetadataType,0),
                        "role": (RoleMetadataType,0),
                        "start_delay": (TimeMetadataType, 0),
	                "timeout": (TimeMetadataType,0)},

	"primitive":{	"id": (StringMetadataType,1),
			"class": (StringMetadataType,1),
			"description": (StringMetadataType,0),
			"provider": (StringMetadataType,0),
			"type": (StringMetadataType,1)},
	"group": {	"id": (StringMetadataType,1),
			"description": (StringMetadataType,0),
			"ordered": (BooleanMetadataType,0),
			"colocated":(BooleanMetadataType,0)},
	"clone": {	"id": (StringMetadataType,1),
			"description": (StringMetadataType,0)},

	"enum":	{},
	"enumvals":	{},
	"enumval":	{"value":(StringMetadataType,1)},

	"master_slave": {"id": (StringMetadataType,1),
			"description": (StringMetadataType,0)},

	"nvpair": {	"id": (StringMetadataType,1),
			"name": (StringMetadataType,1),
			"value": (StringMetadataType,1)},

	"cluster_property_set": idonly,

	"cib": {	"admin_epoch":(IntegerMetadataType,1),
			"have_quorum":(BooleanMetadataType,1),
			"num_peers": (IntegerMetadataType, 1),
			"cib_feature_revision":(StringMetadataType,1),
			"crm_feature_set":(StringMetadataType,0),
			"generated":(BooleanMetadataType,1),
			"ignore_dtd":(BooleanMetadataType,1),
			"epoch":(IntegerMetadataType,1),
			"ccm_transition":(IntegerMetadataType,1),
			"dc_uuid":(UuidMetadataType,1),
			"cib-last-written":(StringMetadataType,1),
			"num_updates":(IntegerMetadataType,1)},

	"node": {	"id": (UuidMetadataType,1),
			"description": (StringMetadataType,0),
			"uname": (StringMetadataType,1),
			"type": (NodeMetadataType,1)},
	"instance_attributes": idonly,

	"meta_attributes": idonly,

	"attributes": {},

	"shortdesc":{	"lang":(StringMetadataType,1)},

	"longdesc": {	"lang":(StringMetadataType,1)},

	"rsc_order": {	"id": (StringMetadataType,1),
                        "type": (OrderMetadataType,1),
			"from": (ResourceMetadataType,1),
                        "to": (ResourceMetadataType,1),
			"action":(LrmOpMetadataType,0),
                        "to_action":(LrmOpMetadataType,0)},

	"rsc_location":	{"id": (StringMetadataType,1),
			"rsc": (ResourceMetadataType,1),
			"description":(StringMetadataType,0),
			"node":(StringMetadataType,0),
			"score":(ScoreMetadataType,0)},

	"rsc_colocation":{"id": (StringMetadataType,1),
			"from": (ResourceMetadataType,1),
			"to": (ResourceMetadataType,1),
			"score": (ScoreMetadataType,0),
      			"symmetrical":(BooleanMetadataType,0),
			"from_role":(RoleMetadataType,0),
			"to_role":(RoleMetadataType,0),
      			"node_attribute":(StringMetadataType,0)},

	"rule": {	"id": (StringMetadataType,1),
			"role":(RoleMetadataType,0),
			"score": (ScoreMetadataType,0),
			"score_attribute": (StringMetadataType,0),
			"boolean_op":(BooleanOpMetadataType,0)}, # defaults to 'and'

	"expression":	{	"id": (StringMetadataType,1),
				"attribute": (StringMetadataType,1),
				"operation": (OpMetadataType,17),
				"value": (StringMetadataType,0),
				"type": (DatatypeMetadatatype,0)},	#defaults to 'string'

	"date_expression": {	"id": (StringMetadataType,1),
				"operation": (DateOpMetadataType,1),
				"start": (StringMetadataType,0),
				"end": (StringMetadataType,0)},

	"date_spec": {	"id": (StringMetadataType,1),
			"hours": (StringMetadataType,0),
			"monthdays": (StringMetadataType,0),
			"weekdays": (StringMetadataType,0),
			"yeardays": (StringMetadataType,0),
			"months": (StringMetadataType,0),
			"weeks": (StringMetadataType,0),
			"weekyears": (StringMetadataType,0),
			"years": (StringMetadataType,0),
			"moon": (StringMetadataType,0)},

	"duration": {	"id": (StringMetadataType,1),
			"hours": (StringMetadataType,0),
			"monthdays": (StringMetadataType,0),
			"weekdays": (StringMetadataType,0),
			"yeardays": (StringMetadataType,0),
			"months": (StringMetadataType,0),
			"weeks": (StringMetadataType,0),
			"years": (StringMetadataType,0)},

	"operations":	{},
	"crm_config":	{},
	"configuration":	{},
	"nodes":	{},
	"resources":	{},
	"constraints":	{},

	"status":	{},
	"node_state":	{ "id": (UuidMetadataType,1),
			"uname": (StringMetadataType,1),
			"ha": (HaStatusMetadataType,1),
			"crmd": (CrmStatusMetadataType,1),
			"join": (MemberStatusMetadataType,1),
			"expected": (MemberStatusMetadataType,1),
			"in_ccm": (BooleanMetadataType,1),
			"crm-debug-origin":  (StringMetadataType,0),
			"shutdown":(IntegerMetadataType,1),
			"clear_shutdown":(IntegerMetadataType,0)},
	"transient_attributes":	idonly,
	"lrm":	idonly,
	"lrm_resources":{},
	"lrm_resource":{"id": (UuidMetadataType,1),
			"class":(LrmClassMetadataType,1),
			"type": (StringMetadataType,1),
			"provider": (StringMetadataType,1)},

	"lrm_rsc_op":{
			"id": (StringMetadataType,1),
			"operation": (LrmOpMetadataType,1),
			"op_status": (IntegerMetadataType,1),
			"rc_code": (IntegerMetadataType,1),
			"call_id": (IntegerMetadataType,1),
			"crm_feature_set": (StringMetadataType,1),
			"crm-debug-origin": (StringMetadataType,1),
			"transition_key": (StringMetadataType,1),
			"op_digest": (StringMetadataType,1),
			"op_restart_digest": (StringMetadataType,1),
			"op_force_restart": (StringMetadataType,1),
			"interval": (TimeMetadataType,1),
			"transition_magic": (StringMetadataType,1)},
};

#
# Validate the attributes of this tag...
#
def CheckAttributes(xmlnode):
   global AttributeKeys
   result=1
   if not hasattr(xmlnode, "attributes") or xmlnode.attributes == None or xmlnode.nodeType != xmlnode.ELEMENT_NODE:
      return 1
   tagname=xmlnode.tagName
   if not AttributeKeys.has_key(tagname):
     print "ERROR: Unrecognized tag <%s/>" % tagname
     return 0
   table=AttributeKeys[tagname]
   for attribtup in xmlnode.attributes.keys():
      attrib=attribtup[1]
      if not table.has_key(attrib):
         print "ERROR: attribute [%s] not legal/obsolescent with tag <%s/>" % (attrib, tagname)
         continue
      (type,mandatory)=table[attrib]
      value=xmlnode.getAttribute(attrib)
      if not type.validate(value):
         print "ERROR: <%s %s=\"%s\"...>: [%s] is not a legal value for attribute %s"\
	 %	(tagname, attrib, value, value, attrib)

   for tattrib in table.keys():
     found=0
     mandatory=0
     tinfo=table[tattrib]
     type=tinfo[0]
     mandatory=tinfo[1]
     if not mandatory:
       continue
     for attribtup in xmlnode.attributes.keys():
        myattrib=attribtup[1]
        if myattrib == tattrib:
          found=1
          break
     if not found:
         print "ERROR: <%s ...>: [%s] is a required attribute for tag %s" \
	 %	(tagname, tattrib, tagname)
   

#
#	A class representing a collection of name/value pairs
#
class NVpairs(UserDict):
  def __init__(self):
    UserDict.__init__(self)
    self.name2idmap = {}
    self.id2namemap = {}

  def addnvpair(self, node):
    CheckXMLNodeUniqueness(node)
    name=node.getAttribute("name")
    id = node.getAttribute("id")
    value=node.getAttribute("value")
    if name == "":
      print "ERROR: <nvpair> without 'name'"
    if id == "":
      print "ERROR: <nvpair> without 'id'"
    if self.has_key(name):
      print "ERROR: name '%s' multiply defined in <nvpair> list - values are [%s] and [%s]"\
      %		(name, self[name], value)
    if self.id2namemap.has_key(id):
      print "ERROR: <nvpair> id '"+id+"' multiply defined in <nvpair> list "

    self[name] = value
    self.name2idmap[name] = id
    self.id2namemap[id] = name

  def name2id(self, name):
    return self.name2idmap[name]

  def id2name(self, id):
    return self.id2namemap[id]

global ComplainedAboutNoNodes
ComplainedAboutNoNodes=0

def NodeUnameIsValid(uname):
  global ComplainedAboutNoNodes
  if len(clusterNodes) < 1 and not ComplainedAboutNoNodes:
    ComplainedAboutNoNodes=1
    print "WARNING: No <nodes> listed, cannot validate node names"
    
  return len(clusterNodes) < 1 or clusterNodes.has_key(uname)

def NodeUUIDIsValid(uuid):
  global ComplainedAboutNoNodes
  if len(clusterNodes.uuid2unamemap) < 1 and not ComplainedAboutNoNodes:
    ComplainedAboutNoNodes=1
    print "WARNING: No <nodes> listed, cannot validate node names"

  return len(clusterNodes) < 1 or clusterNodes.uuid2unamemap.has_key(uuid)

#
# A class for collecting information about cluster nodes
#
class ClusterNodes(UserDict):
  def __init__(self):
    UserDict.__init__(self)
    self.uuid2unamemap = {}
    self.instance_attribute_sets = {}

  def addnode(self, xmlnode):
    global GlobalAttributeNames
    CheckXMLNodeUniqueness(xmlnode)
    uname=xmlnode.getAttribute("uname")
    id = xmlnode.getAttribute("id")
    type = xmlnode.getAttribute("type")
    if uname == "":
      print "ERROR: <node> without 'uname'"
    if id == "":
      print "ERROR: <node> without 'id'"
    if type != "normal":
      print "ERROR: <node> %s type is [%s]" % (uname, type)
    if self.has_key(uname):
      print "ERROR: name '"+uname+"' multiply defined in current <node> list"
    if self.uuid2unamemap.has_key(id):
      print "ERROR: <node> id '"+id+"' multiply defined in current node list "
    self[uname] = id
    self.uuid2unamemap[id] = uname
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "instance_attributes":
         inst_attrs=InstanceAttributesTag(child)
         self.instance_attribute_sets[uname] = inst_attrs.attributes.nvpairs
         for key in inst_attrs.attributes.nvpairs.keys():
           GlobalAttributeNames[key]=1
      else:
        print "ERROR: Don't know how to deal with funky child of <%s>" % child.tagName
        print "Funky child: " + node2str(child)
        self=None
        return

  def uuid2uname(self, uuid):
    return self.uuid2unamemap[id]

#
# A class for processing the <attributes> tag
#
class AttributesTag:
  def __init__(self, xmlnode):
    CheckAttributes(xmlnode)
    self.nvpairs = NVpairs()
    for childnode in xmlnode.childNodes:
      if childnode.nodeType == childnode.ELEMENT_NODE and \
      childnode.tagName == "nvpair":
        self.nvpairs.addnvpair(childnode)

#
# A base class for things with <attributes>  and <rules> inside..
#
class CommonAttrs:
  def __init__(self, xmlnode, tagname="attributes"):
    CheckXMLNodeUniqueness(xmlnode)
    self.attributes = None
    self.rules = []
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == tagname:
        self.attributes = AttributesTag(child)
      elif child.tagName == "rule":
        self.rules.append(RuleTag(child))
      else:
        print "ERROR: Unexpected nesting of %s inside <%s>"\
        %	(node2str(child), xmlnode.tagName)

  def keys(self, clusternode=None):
    print "ERROR: CommonAttrs.keys() Not implemented yet."
    return None

  def has_key(self, clusternode=None):
    print "ERROR: CommonAttrs.has_key() Not implemented yet."
    return None

  def __getitem__(self, key, clusternode=None):
    print "CommonAttrs.__getitem__() Not implemented yet."
    return None

  def getitem(key, clusternode=None):
    return __getitem__(key, clusternode=clusternode)


#
# A class for processing the <instance_attributes> tag
#
class InstanceAttributesTag(CommonAttrs):
  def __init__(self, xmlnode):
    CommonAttrs.__init__(self, xmlnode, tagname="attributes")

#
# A class for processing the <meta_attributes> tag
#
class MetaAttributesTag(CommonAttrs):
  def __init__(self, xmlnode):
    CommonAttrs.__init__(self, xmlnode, tagname="attributes")
    # Validate <meta_attributes> nvpairs against our metadata for meta_attributes
    MetaAttributeMetadata.validate_nvpairs(self.attributes.nvpairs)

#
# A class for processing the <transient_attributes> tag
#
class TransientAttributesTag(CommonAttrs):
  def __init__(self, xmlnode):
    inityet=0
    CheckXMLNodeUniqueness(xmlnode)
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "instance_attributes":
        if inityet:
          print "ERROR: TransientAttributesTag: Multiple instance_attributes children."
        else:
          CommonAttrs.__init__(self, child, tagname="attributes")
        inityet=1
      else:
        print "ERROR: Unexpected nesting of %s inside <%s>"\
        %	(node2str(child), xmlnode.tagName)
        self=None
        return
    if not inityet:
       print "ERROR: TransientAttributesTag: NO instance_attributes children."
       self=None
       return
    self.nvpairs=self.attributes.nvpairs
    

#
# A class for processing the <cluster_property_set> tag
#
class ClusterPropertySetTag(CommonAttrs):
  def __init__(self, xmlnode):
    global crmMetadata
    CommonAttrs.__init__(self, xmlnode, tagname="attributes")
    crmMetadata.explainnondefault(self.attributes.nvpairs, exceptions={"stonith-enabled":1})
    fattr="default-resource-failure-stickiness"
    if self.attributes.nvpairs.has_key(fattr):
      if Score(self.attributes.nvpairs[fattr]) > 0:
        print "ERROR: %s should normally be negative: value is [%s]" \
	%	(fattr, Score(self.attributes.nvpairs[fattr]))
    

#
# A class for processing the <crm_config> tag
#
class CRMConfigTag:
  def __init__(self, xmlnode):
    global crm_config_section
    self.propertysets = []
    CheckAttributes(xmlnode)
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "cluster_property_set":
        self.propertysets.append(ClusterPropertySetTag(child))
      else:
        print "ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
        self=None
        return
    crm_config_section=self

  def configoption(self, configname):
    global crmMetadata
    for pset in self.propertysets:
      # FIXME:  We are ignoring any <rule>s that might be here...
      if pset.attributes.nvpairs.has_key(configname):
        return pset.attributes.nvpairs[configname]
    return crmMetadata.defaultvalue(configname)

#
# A class for processing the <nodes> tag
#
class NodesTag:
  global clusterNodes
  clusterNodes = ClusterNodes()

  def __init__(self, xmlnode):
    global clusterNodes
    CheckAttributes(xmlnode)
    children = xmlnode.childNodes
    for child in children:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName != "node":
        print "ERROR: Don't know how to deal with funky child of <nodes>"
        print "Funky child: " + node2str(child)
        self=None
        return
      uname=child.getAttribute("uname")
      CheckAttributes(child)
      clusterNodes.addnode(child)
     
    self = clusterNodes

#
# A class for processing the <operations> tag
#
class OperationsTag(UserDict):
  def __init__(self, xmlnode):
    UserDict.__init__(self)
    self.operations = None
    self.instance_attributes = None
    CheckAttributes(xmlnode)
    children = xmlnode.childNodes
    for child in children:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "op":
        CheckXMLNodeUniqueness(child)
        name = child.getAttribute("name")
        if name == "":
          print "ERROR: <op/> tag is missing name attribute"
        self[name] = child.getAttribute("id")
        if child.getAttribute("interval") != "" and name != "monitor":
          print "ERROR: repeat interval [%s] specified for operation [%s]" \
          %	(child.getAttribute("interval"), name)
        if child.getAttribute("interval") == "" and name == "monitor":
          print "WARNING: monitor operation specified without interval=\"...\""
        if child.getAttribute("start_delay") != "" and name != "monitor":
          print ("WARNING: <op name=\"%s\" start_delay=\"%s\"...>: start_delay" \
	  +   " only appropriate for op \"monitor\".") % (name, child.getAttribute("start_delay"))
        if child.getAttribute("on_fail") != "" and name != "stop":
          print ("WARNING: <op name=\"%s\" on_fail=\"%s\"...>: on_fail" \
	  +   " only appropriate for op \"stop\".") % (name, child.getAttribute("on_fail"))
      else:
        print "ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
        self=None
        return

global AllPrimitives, AllGroups, AllMasterSlave, AllClones, AllRscOrders, AllRscFromOrders, AllRscToOrders
global AllResourceLocations, AllResourceCoLocations
AllPrimitives = {}
AllGroups = {}
AllMasterSlave = {}
AllClones = {}
AllResources = {}
AllResourceLocations = []
AllResourceCoLocations = []
AllResourceRscOrders = []
AllResourceRscFromOrders = {}
AllResourceRscToOrders = {}

#
# Base class for all of our Resource Types (PrimitiveTag, GroupTag, etc.)
#

class ResourceType:
  rtypelist={"group":AllGroups, "master_slave":AllMasterSlave, "clone":AllClones, "primitive":AllPrimitives}
  crm_config_meta_map={"resource_failure_stickiness":"default-resource-failure-stickiness",
  	"resource_stickiness":"default-resource-stickiness", "is-managed":"is-managed-default"}

  def __init__(self, xmlnode, inclone=0, ingroup=0, parent=None):
    global AllPrimitives, AllGroups, AllMasterSlave, AllClones
    self.name = xmlnode.getAttribute("id")
    self.rsctype=xmlnode.tagName
    self.inclone = inclone
    self.ingroup = ingroup
    self.parent = parent
    self.childrscs = None
    self.xmlattributes = xmlnode.attributes
    self.instance_attributes = []
    self.meta_attributes = []
    self.MetaAttrs = {}
    CheckXMLNodeUniqueness(xmlnode)
    if self.name.find(":") >= 0:
      print "ERROR: Cannot name a resource %s: <%s id=\"%s\">" \
      %		(self.name, xmlnode.tagName, self.name)


    id=xmlnode.getAttribute("id")

    for rtype in ResourceType.rtypelist.keys():
      if ResourceType.rtypelist[rtype].has_key(id):
        print "ERROR: A <%s> exists with the same id as this <%s id=\"%s\">"\
        %	(rtype, id, xmlnode.tagName)
    ResourceType.rtypelist[xmlnode.tagName][id] = self
    AllResources[id] = self

  def _process_meta_attributes(self, metachild):
    mattr=MetaAttributesTag(metachild)
    self.meta_attributes.append(mattr)
    table=mattr.attributes.nvpairs
    for key in table.keys():
      self.MetaAttrs[key] = table[key]

  def meta_attr_value(self, metaname):
    if self.MetaAttrs.has_key(metaname):
      return self.MetaAttrs[metaname]
    elif self.parent != None:
      return self.parent.meta_attr_value(metaname)
    elif ResourceType.crm_config_meta_map.has_key(metaname):
      keyname=ResourceType.crm_config_meta_map[metaname]
      return crm_config_section.configoption(keyname)
    return None

#
# Validate a reference to a resource
#

def ValidateResource(rscname):
    result=1
    rsplit=rscname.split(":",1)
    rname=rsplit[0]
    if not AllResources.has_key(rname):
      print "Cant find [%s] of [%s]" % (rname, rscname)
      return None
    if len(rsplit) > 1:
      if not AllResources[rname].inclone:
        print "ERROR: Resource [%s] referenced as [%s] is not a clone resource" \
        %		(rname, rscname)
        return None
      elif not IntegerMetadataType.validate(rsplit[1]):
        print "ERROR: Clone number [%s] in resource name [%s] is not an integer." \
        %		(rsplit[1], rscname)
        return None
      else:
         # FIXME - validate clone number against clone_max for clone resource
         None
    return AllResources[rname]

 
#
# A class for processing the <primitive> tag
#
class PrimitiveTag(ResourceType):
  def __init__(self, xmlnode, inclone=0, ingroup=0, parent=None):
    ResourceType.__init__(self, xmlnode, inclone=inclone, ingroup=ingroup, parent=parent)
    self.operations = None
    self.instance_attributes = []
    self.meta_attributes = []
    self.operations = []
    self.rscclass = xmlnode.getAttribute("class")
    self.provider = xmlnode.getAttribute("provider")
    self.type = xmlnode.getAttribute("type")
    if self.rscclass == "stonith":
      global stonithresourcecount
      stonithresourcecount=stonithresourcecount+1
      if stonithremarks and (self.type=="ssh" or self.type == "external/ssh"):
        print "WARNING: %s STONITH resource NOT approved for production" % (self.type)
    children = xmlnode.childNodes
    for child in children:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "instance_attributes":
        iattr = InstanceAttributesTag(child)
        self.instance_attributes.append(iattr)
        metadata = metaDataCache.RAmetadata(self.rscclass, self.type, self.provider)
        if metadata != None:
          #print "Validating nvpairs for resource %s" % (self.name)
          metadata.validate_nvpairs(iattr.attributes.nvpairs, context="for resource <primitive type=\"%s\" class=\"%s\">" % (self.type, self.rscclass))
      elif child.tagName == "meta_attributes":
        self._process_meta_attributes(child)
      elif child.tagName == "operations":
        self.operations.append(OperationsTag(child))
        if not self.operations[len(self.operations)-1].has_key("monitor"):
          print "INFO: monitoring not requested for resource <primitive id=\"%s\"...>" \
          %	self.name
      else:
        print "ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
        self=None
        return


#
# A class for processing the <group> tag
#
class GroupTag(ResourceType):
  def __init__(self, xmlnode, inclone=0,ingroup=0, parent=None):
    ResourceType.__init__(self, xmlnode, inclone=inclone, ingroup=ingroup, parent=parent)
    self.childrscs = []
    children = xmlnode.childNodes
    for child in children:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "primitive":
        self.childrscs.append(PrimitiveTag(child, inclone=0, parent=self, ingroup=1))
      elif child.tagName == "meta_attributes":
        self._process_meta_attributes(child)
      elif child.tagName == "instance_attributes":
        print "WARNING: <group> has <instance_attributes> (should be <meta_attributes>)"
        self._process_meta_attributes(child)
      else:
        print "ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
        self=None
        return

#
# A class for processing the <clone> tag
#
class CloneTag(ResourceType):
  def __init__(self, xmlnode, inclone=0,ingroup=0, parent=None):
    ResourceType.__init__(self, xmlnode, inclone=inclone, ingroup=ingroup, parent=parent)
    self.childrscs = []
    self.instance_attributes = []
    if inclone:
      print "ERROR: Cannot nest <clone> inside either <clone> or <master_slave> tags"
    children = xmlnode.childNodes
    for child in children:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "primitive":
        self.childrscs.append(PrimitiveTag(child, inclone=1, ingroup=ingroup, parent=self))
      elif child.tagName == "group":
        self.childrscs.append(GroupTag(child, inclone=1, ingroup=ingroup, parent=self))
      elif child.tagName == "instance_attributes":
        iattr = InstanceAttributesTag(child)
        self.instance_attributes.append(iattr)
      elif child.tagName == "meta_attributes":
        self._process_meta_attributes(child)
      else:
        print "ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
        self=None
        return
    if len(self.childrscs) != 1:
      print ("ERROR: <clone id=\"%s\"> tag must have exactly one "\
      +		"child resource instead of %d.")\
      %		(self.name, len(self.childrscs))

#
# A class for processing the <master_slave> tag
#
class MasterSlaveTag(ResourceType):
  def __init__(self, xmlnode, inclone=0,ingroup=0, parent=None):
    ResourceType.__init__(self, xmlnode, inclone=inclone, ingroup=ingroup, parent=parent)
    self.childrscs = []
    self.instance_attributes = []
    if inclone:
      print "ERROR: Cannot nest <master_slave> inside either <clone> or <master_slave> tags"
    children = xmlnode.childNodes
    for child in children:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "primitive":
        self.childrscs.append(PrimitiveTag(child, inclone=1, ingroup=ingroup, parent=self))
      elif child.tagName == "group":
        self.childrscs.append(GroupTag(child, inclone=1, ingroup=ingroup, parent=self))
      elif child.tagName == "instance_attributes":
        iattr = InstanceAttributesTag(child)
        self.instance_attributes.append(iattr)
      else:
        print "ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
        self=None
        return
    if len(self.childrscs) != 1:
      print ("ERROR: <master_slave id=\"%s\"> tag must have exactly one "\
      +		"child resource instead of %d.")\
      %		(self.name, len(self.childrscs))

#
# A class for processing the <rsc_colocation> tag
#
class ResourceCoLocationTag:
  def __init__(self, xmlnode):
    CheckXMLNodeUniqueness(xmlnode)
    self.name = xmlnode.getAttribute("id")
    self.fromid = xmlnode.getAttribute("from")
    self.toid = xmlnode.getAttribute("to")
    self.score = xmlnode.getAttribute("score")
    self.bidirectional = xmlnode.getAttribute("symmetrical")
    if self.fromid == "":
      print "ERROR: <rsc_colocation> tag requires 'from' attribute"
      self=None
      return
    if self.toid == "":
      print "ERROR: <rsc_colocation> tag requires 'to' attribute"
      self=None
      return
    if self.score == "":
      print "ERROR: <rsc_colocation> tag requires 'score' attribute"
      self=None
      return
    # Deal with clone references...

    if not ValidateResource(self.fromid):
      print "ERROR: <rsc_colocation from=\"%s\"...>: no such resource [%s]" \
      %		(self.fromid, fromid)
    if not ValidateResource(self.toid):
      print "ERROR: <rsc_colocation to=\"%s\"...>: no such resource [%s]" \
      %		(self.toid, toid)

    for child in xmlnode.childNodes:
      if child.nodeType == child.ELEMENT_NODE:
        print "ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
        self=None
        return
    AllResourceCoLocations.append(self)


#
# A class for processing the <rsc_order> tag
#
class ResourceOrderTag:
  def __init__(self, xmlnode):
    CheckXMLNodeUniqueness(xmlnode)
    self.rules = []
    self.name = xmlnode.getAttribute("id")
    self.fromid = xmlnode.getAttribute("from")
    self.toid = xmlnode.getAttribute("to")
    self.action = xmlnode.getAttribute("action")
    self.to_action = xmlnode.getAttribute("to_action")
    if not AllResources.has_key(self.fromid):
      print "ERROR: <rsc_order id=\"%s\"...>: no such resource [%s]" \
      %		(self.name, self.fromid)
    elif self.action != "" and not AllMasterSlave.has_key(self.fromid):
      print ("ERROR: Resource %s must be a master slave" + \
             " to have specified action %s") % (self.fromid, self.action)
    if not AllResources.has_key(self.toid):
      print "ERROR: <rsc_order id=\"%s\"...>: no such resource [%s]" \
      %		(self.name, self.toid)
    elif self.to_action != "" and not AllMasterSlave.has_key(self.fromid):
      print ("ERROR: Resource %s must be a master slave" + \
             " to have specified action %s") % (self.toid, self.to_action)
    if self.action != "" and self.to_action != "":
      print "ERROR: cannot specify both action and to_action in the same <rsc_order> tag"
    AllResourceRscOrders.append(self)
    if not AllResourceRscFromOrders.has_key(self.fromid):
      AllResourceRscFromOrders[self.fromid] = []
    AllResourceRscFromOrders[self.fromid].append(self)
    if not AllResourceRscFromOrders.has_key(self.toid):
      AllResourceRscFromOrders[self.toid] = []
    AllResourceRscFromOrders[self.toid].append(self)
#
# A class for processing the <rsc_location> tag
#
class ResourceLocationTag:
  def __init__(self, xmlnode):
    CheckXMLNodeUniqueness(xmlnode)
    self.rules = []
    self.name = xmlnode.getAttribute("id")
    self.rsc = xmlnode.getAttribute("rsc")
    if not AllResources.has_key(self.rsc):
      print "ERROR: <rsc_location id=\"%s\"...>: no such resource [%s]" \
      %		(self.name, self.rsc)

    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "rule":
        self.rules.append(RuleTag(child))
      else:
        print "ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
        self=None
        return
    AllResourceLocations.append(self)

#
# An abstract class for evaluatable expressions
#
class EvalExpress:
  def __init__(self, xmlnode):
    CheckXMLNodeUniqueness(xmlnode)

#
# A class for processing the <expression> tag
#
class ExpressionTag(EvalExpress):
  def __init__(self, xmlnode):
    global clusterNodes
    EvalExpress.__init__(self, xmlnode)

    self.attribute = xmlnode.getAttribute("attribute")
    self.value=xmlnode.getAttribute("value")
    self.operation=xmlnode.getAttribute("operation")
    self.type=xmlnode.getAttribute("type")
    if self.operation != "defined" and self.operation != "not_defined"\
    and self.value == "":
      print "ERROR: <expression operation=\"%s\"...> requires a value operand"\
      %		(self.operation)
    if self.attribute == "":
      print "ERROR: <expression operation=\"%s\"...> requires an attribute operand"\
      %		(self.operation)

    if self.attribute == "#uname":
      uname=xmlnode.getAttribute("value")
      if not NodeUnameIsValid(uname):
        print "ERROR: <expression attribute=\"#uname\" value=\"%s\"...> references unknown cluster node [%s]" \
	%	(uname,uname)
    elif self.attribute  == "#id":
      value=xmlnode.getAttribute("value")
      if not NodeUUIDIsValid(value):
        print "ERROR: <expression attribute=\"#id\" value=\"%s\"...>: unknown uuid [%s]" \
	%	(value, value)
    elif self.attribute != "" and not GlobalAttributeNames.has_key(self.attribute):
      print "WARNING: node attribute [%s] does not seem to be defined anywhere in the cluster."\
      %		self.attribute
    for child in xmlnode.childNodes:
      if child.nodeType == child.ELEMENT_NODE:
        print "ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
        self=None
        return

  def eval(self, context):
    if self.operation == "defined":
      return context.has_key(self.attribute)
    if self.operation == "not_defined":
      return not context.has_key(self.attribute)
    if self.type == "":
      self.type="string"
    if self.attribute == "":
      print "ERROR: Missing attribute from expression"
      return 0
    if not context.has_key(self.attribute):
      attval=""
    else:
      attval=context[self.attribute]
    if self.type == "number":
      lhs=int(self.value)
      rhs=int(attval)
    elif self.type == "string":
      lhs=self.value + ""
      rhs=attval + ""
    else:
      print "ERROR: unimplemented expression type [%s] - treated as string"\
      %		self.type
      lhs=self.value + ""
      rhs=attval + ""
    if self.operation == "lt":
      return lhs < rhs
    elif self.operation == "lte":
      return lhs <= rhs
    if self.operation == "gt":
      return lhs > rhs
    elif self.operation == "gte":
      return lhs >= rhs
    elif self.operation == "eq":
      return lhs == rhs
    elif self.operation == "ne":
      return lhs != rhs
    print "ERROR: unimplemented expression operator [%s] - returning false"\
    %		self.operation
    return None

#
# A class for processing the <rule> tag
#
class RuleTag(EvalExpress):
  def __init__(self, xmlnode):
    EvalExpress.__init__(self, xmlnode)
    self.expressions= []
    self.score = string.lower(xmlnode.getAttribute("score"))
    self.score_attribute = xmlnode.getAttribute("score_attribute")
    self.boolean_op = xmlnode.getAttribute("boolean_op")
    if self.score != "" and self.score_attribute != "":
      print "ERROR: <rule> tag allows only one of 'score' or 'score_attribute'"
    if self.score_attribute != "" and not GlobalAttributeNames.has_key(self.score_attribute):
      print "WARNING: score_attribute [%s] does not seem to be defined anywhere in the cluster."\
      %		self.score_attribute
    if self.score == "infinity" or self.score == "+infinity":
        print "WARNING: <rule> score of \"%s\" probably not what you want because of common side effects."\
	%	self.score
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "expression":
        self.expressions.append(ExpressionTag(child))
      elif child.tagName == "rule":
        self.expressions.append(RuleTag(child))
      else:
        print "ERROR: Don't know how to deal with funky child of <%s>" % xmlnode.tagName
        print "Funky child: " + node2str(child)
        self=None
        return
    if self.boolean_op == "":
      self.boolean_op="and"
      if len(self.expressions) > 1:
        print "WARNING: <rule> boolean_op unspecified - defaulted to \"and\"."

  def eval(self, context):
    if len(self.expressions) < 1:
      return 1
    if self.boolean_op == "and":
      for expr in self.expressions:
        if not expr.eval(context):
          return 0
      return 1
    elif self.boolean_op == "or":
      for expr in self.expressions:
        if expr.eval(context):
          return 1
      return 0
    print "ERROR: Invalid boolean_op: [%s]" % self.boolean_op
    return None

  def score(self, context):
    retscore=Score(0)
    if self.score != "":
      retscore=retscore+Score(self.score)
    if self.score_attribute != "":
      if context.has_key(self.score_attribute):
        retscore=retscore+Score(self.score)
      else:
        retscore=Score("-infinity")
    return retscore

  def evalscore(self, context):
     if self.eval(context):
       return self.score(context)
     return Score(0)


class OpStatus:
  def __init__(self, xmlnode, rsc):
	self.rsc = rsc
	self.operation = xmlnode.getAttribute("operation")
	self.call_id = int(xmlnode.getAttribute("call_id"))
	self.rc_code = int(xmlnode.getAttribute("rc_code"))
	self.op_status = int(xmlnode.getAttribute("op_status"))
	self.interval = xmlnode.getAttribute("interval")

  def __repr__(self):
    return "(%s: %s,rc=%d,status=%d,callid=%d)"\
    %	(self.rsc.name, self.operation, self.rc_code, self.op_status, self.call_id)

  def __str__(self):
    return "(%s: %s,rc=%d,status=%d,callid=%d)"\
    %	(self.rsc.name, self.operation, self.rc_code, self.op_status, self.call_id)

def save_resource_node_op_status(resource, clusternode, opstatus):
  global ResourceNodeOperationStatuses
  if not ResourceNodeOperationStatuses.has_key(resource):
    ResourceNodeOperationStatuses[resource] = {}
  if not ResourceNodeOperationStatuses[resource].has_key(clusternode):
    ResourceNodeOperationStatuses[resource][clusternode] = {}
  if ResourceNodeOperationStatuses[resource][clusternode].has_key(opstatus.call_id):
    print "CIB <status> error: Duplicate (resource,node,call_id): (%s, %s, %d)"\
    %	(resource, clusternode, opstatus.call_id)
  else:
    ResourceNodeOperationStatuses[resource][clusternode][opstatus.call_id] = opstatus
#
# A class for processing the <lrm_resource> tag
#
class LrmResourceTag:
  def __init__(self, xmlnode, clusternode):
    #CheckXMLNodeUniqueness(xmlnode, globallyunique=0)
    # Although this has an 'id' tag, it's the id if the resource this points to
    # and is not unique among all <lrm_resource> tags
    self.rscid = xmlnode.getAttribute("id")
    #print "GOT <%s id=\"%s\">" % (xmlnode.tagName, self.rscid)
    self.resource = ValidateResource(self.rscid)
    if self.resource == None:
      print "ERROR: <lrm_resource id=\"%s\"> tag without corresponding resource" % self.rscid
      self=None
      return
    if self.resource.rsctype != "primitive":
      print "ERROR: <lrm_resource id=\"%s\"> tag refers to <%s> resource" % (id, self.resource.rsctype)
      self=None
      return
      
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "lrm_rsc_op":
        pieces=self.rscid.split(":")
        if len(pieces) > 1 and not self.resource.inclone:
          print "ERROR: name <%s> indicates a clone resource but <%s> is not a clone"\
          %	(self.rscid, self.resource.name)
          self=None
          return
        if self.resource.inclone and len(pieces) < 2:
          print "ERROR: <%s> is a clone resource but <%s> is not a clone reference"\
          %	(self.resource.name, self.rscid)
          self=None
          return

        opstatus = OpStatus(child, self.resource)
	save_resource_node_op_status(self.rscid, clusternode, opstatus)
        if not self.resource.inclone:
          # Non-clone case
          if not hasattr(self.resource,"LRMresources"):
            self.resource.LRMresources = {}
          if not self.resource.LRMresources.has_key(clusternode):
            self.resource.LRMresources[clusternode] = []
          self.resource.LRMresources[clusternode].append(opstatus)
        else:
          # Clone case
          cloneno = int(pieces[1])
          if not hasattr(self.resource, "LRMclones"):
            self.resource.LRMclones = {}
          if not self.resource.LRMclones.has_key(cloneno):
            self.resource.LRMclones[cloneno] = {}
          if not self.resource.LRMclones[cloneno].has_key(clusternode):
            self.resource.LRMclones[cloneno][clusternode] = []
          self.resource.LRMclones[cloneno][clusternode].append(opstatus)
          # What about master-slave resources?
      else:
        print "ERROR: Unexpected nesting of %s inside <%s>" % (node2str(child), xmlnode.tagName)
  # End of __init__() 
     

class LrmResourcesTag:
  def __init__(self, xmlnode, clusternode):
    #print "GOT <%s>" % (xmlnode.tagName)
    CheckAttributes(xmlnode)
    self.resources=[]
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "lrm_resource":
        self.resources.append(LrmResourceTag(child, clusternode))
      else:
        print "ERROR: Unexpected nesting of %s inside <%s>" % (node2str(child), xmlnode.tagName)

class LrmTag:
  def __init__(self, xmlnode, clusternode):
    self.id = xmlnode.getAttribute("id")
    CheckXMLNodeUniqueness(xmlnode, globallyunique=0)
    #print "GOT <%s id=\"%s\">" % (xmlnode.tagName, self.id)
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "lrm_resources":
        self.resources = LrmResourcesTag(child, clusternode)
      else:
        print "ERROR: Unexpected nesting of %s inside <%s>" % (node2str(child), xmlnode.tagName)

class NodeStateTag:
  def __init__(self, xmlnode):
    CheckXMLNodeUniqueness(xmlnode, globallyunique=0)
    self.id = xmlnode.getAttribute("id")
    #print "GOT <%s id=\"%s\">" % (xmlnode.tagName, self.id)
    self.uname = xmlnode.getAttribute("uname")
    self.attributes = xmlnode.attributes
    self.id = xmlnode.getAttribute("id")
    self.join = xmlnode.getAttribute("join")
    self.ha = xmlnode.getAttribute("ha")
    self.transients = None
    self.lrmstate = None
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "lrm":
        self.lrmstate = LrmTag(child, self.uname)
      elif child.tagName == "transient_attributes":
        self.transients=TransientAttributesTag(child)
      else:
        print "ERROR: Unexpected nesting of %s inside <%s>" % (node2str(child), xmlnode.tagName)
    if self.lrmstate == None:
      print "WARNING: Tag <%s> expects nested <lrm_state/> section - none found."\
      %		(xmlnode.tagName)
    if self.join == "member":
      if self.transients == None:
        print "WARNING: Tag <%s> expects nested <transient_attributes/> section for active members - none found."\
        %		(xmlnode.tagName)
    else:
      print "WARNING: Node [%s] is down." % (self.uname)
    

class StatusTag:
  def __init__(self, xmlnode):
    global GlobalStatus
    #print "GOT <%s>" % (xmlnode.tagName)
    CheckAttributes(xmlnode)
    self.nodestates = []
    for child in xmlnode.childNodes:
      if child.nodeType != child.ELEMENT_NODE:
        continue
      if child.tagName == "node_state":
        self.nodestates.append(NodeStateTag(child))
      else:
        print "ERROR: Unexpected nesting of %s inside <%s>" % (node2str(child), xmlnode.tagName)
    GlobalStatus=self


def descend(xmlnode):
  if xmlnode.nodeType == xmlnode.ELEMENT_NODE:
    CheckAttributes(xmlnode)
    if xmlnode.tagName == "crm_config":
      return CRMConfigTag(xmlnode)
    if xmlnode.tagName == "status":
      return StatusTag(xmlnode)
    if xmlnode.tagName == "cluster_property_set":
      return ClusterPropertySetTag(xmlnode)
    if xmlnode.tagName == "meta_attributes":
      return MetaAttributesTag(xmlnode)
    if xmlnode.tagName == "instance_attributes":
      return InstanceAttributesTag(xmlnode)
    if xmlnode.tagName == "attributes":
      return AttributesTag(xmlnode)
    if xmlnode.tagName == "nodes":
      return NodesTag(xmlnode)
    if xmlnode.tagName == "primitive":
      return PrimitiveTag(xmlnode)
    if xmlnode.tagName == "group":
      return GroupTag(xmlnode)
    if xmlnode.tagName == "master_slave":
      return MasterSlaveTag(xmlnode)
    if xmlnode.tagName == "clone":
      return CloneTag(xmlnode)
    if xmlnode.tagName == "rsc_location":
      return ResourceLocationTag(xmlnode)
    if xmlnode.tagName == "rsc_colocation":
      return ResourceCoLocationTag(xmlnode)
    if xmlnode.tagName == "rsc_order":
      return ResourceOrderTag(xmlnode)
    #print "Element: " + xmlnode.tagName
    #mymap = xmlnode.attributes
    #if mymap != None:
    #  for key in mymap.keys():
    #    print "  xmlAttribute:", key[1]+ "="+ xmlnode.getAttribute(key)
  for child in xmlnode.childNodes:
    descend(child)

def failcount_name_to_rscname(name):
  fc="fail-count-"
  if (name[0:(len(fc))]) == fc:
     return name[len(fc):]
  return None
   
def look_for_failcounts():
  if GlobalStatus == None:  return
  for node in GlobalStatus.nodestates:
    if node.transients == None: continue
    for name in node.transients.nvpairs.keys():
      rscname = failcount_name_to_rscname(name)
      if rscname != None:
        if not ValidateResource(rscname):
          print "ERROR: transient attributes reference invalid resource [%s]"\
	  %	rscname
        failcount=int(node.transients.nvpairs[name])
        if failcount != 0:
          print "WARNING: resource %s has failcount %d on node %s"	\
          %	(rscname, failcount, node.uname)

def revcompare(lhs, rhs):
  return rhs.__cmp__(lhs)

def analyze_op_status():
  for r in ResourceNodeOperationStatuses.keys():
    runningon=None
    for cn in ResourceNodeOperationStatuses[r].keys():
      callids=ResourceNodeOperationStatuses[r][cn].keys()
      callids.sort(revcompare)
      lastid=callids[0]
      lastop=ResourceNodeOperationStatuses[r][cn][lastid]
      #print "Last operation on [%s,%s]: %s"\
      #%		(r, cn, lastop)
      if lastop.op_status != 0:
        print "ERROR: Resource %s \"%s\" operation failed on node %s (rc=%d)."\
        %	(r, lastop.operation, cn, lastop.rc_code)
        if lastop.operation == "stop":
          print "ERROR: Stop failures are very serious!"
        continue
      isrunning=0
      # FIXME:  Doesn't account for master/slave
      if lastop.operation=="start" and lastop.op_status == 0:
        isrunning=1
      if lastop.operation=="monitor" \
      and 	lastop.op_status == 0 and lastop.rc_code == 0:
        isrunning=1
      if isrunning:
        print "INFO: Resource %s running on node %s" %	(r, cn)
        if runningon != None and runningon != cn:
          print "ERROR: Resource %s running more than one place (%s, %s)!"\
          %	(r, runningon, cn)
        runningon=cn
    if not runningon:
      print "WARNING: Resource %s not running anywhere." % r

      #callids.sort()
      #print "SECOND CALLIDS:", callids
      #for id in callids:
      #  opstat=ResourceNodeOperationStatuses[r][cn][id]
      #  print "node: %s Op Status: %s" % (cn, opstat)

def analyze_colocations():
  for colo in AllResourceCoLocations:
    foundit=0
    if AllResourceRscFromOrders.has_key(colo.fromid):
      frlist= AllResourceRscFromOrders[colo.fromid]
      for order in frlist:
        if order.toid == colo.toid:
          foundit=1
          break
    if not foundit and AllResourceRscFromOrders.has_key(colo.toid):
      tolist= AllResourceRscFromOrders[colo.toid]
      for order in tolist:
        if order.fromid == colo.toid:
          foundit=1
          break
    if not foundit:
      print ("WARNING: Specifying <rsc_colocation> between \"%s\" and \"%s\"" \
	+ " without specifying corresponding <rsc_order> is not common.") \
	% (colo.fromid, colo.toid)
     
AllResourceRscFromOrders = {}
AllResourceRscToOrders = {}

def analyze_CIB(doc):
  global crmMetadata
  global MetaAttributeMetadata
  global MetaAttributeMetadata_string
  crmMetadata = CRMMetadata()
  
  MetaAttributeMetadata = OCFMetadata(FromXml(MetaAttributeMetadata_string))

  global crm_config_section, stonithresourcecount
  descend(doc)
  stonithwarn=0

  stonithtag="stonith-enabled"
  enableval = crmMetadata[stonithtag].default
  stonith_enabled = MetadataValueType.BooleanStrings[enableval]

  for pset in crm_config_section.propertysets:
    if pset.attributes.nvpairs.has_key(stonithtag):
      enableval=pset.attributes.nvpairs[stonithtag]
      stonith_enabled = MetadataValueType.BooleanStrings[enableval]
  if stonith_enabled and stonithremarks:
    if stonithresourcecount == 0:
      stonithwarn=1
      print "ERROR:  STONITH enabled, but No STONITH resources configured.  STONITH is NOT available."
  elif stonithremarks and len(clusterNodes) >= 2:
    stonithwarn=1
    print "WARNING: STONITH disabled <nvpair name=\"%s\" value=\"%s\">.  STONITH is STRONGLY recommended."\
    %	(stonithtag, enableval)
    if stonithresourcecount == 0:
      print "WARNING: No STONITH resources configured.  STONITH is not available."
  if stonithwarn:
    URLinfo("stonith")
    crm_config_url(stonithtag)
  analyze_colocations()
  look_for_failcounts()
  analyze_op_status()

#
# "lint" out a CIB found in a file (pathname)
#
def examine_cib_path(cibfile):
  file = open(cibfile, "r")
  doc = FromXmlStream(file, validate=0)
  analyze_CIB(doc)
  doc=None

#
# "lint" out the live CIB
#
def examine_cib_live():
  file = os.popen(CIBADMIN + " -Q")
  doc = FromXmlStream(file, validate=0)
  file.close()
  analyze_CIB(doc)
  doc=None

#
# Explain all the CIB options in the <crm_config> section
#
def explain_crm_config():
  global crmMetadata
  crmMetadata = CRMMetadata()
  crmMetadata.explainall()

def explain_meta_attributes_config():
  global MetaAttributeMetadata, MetaAttributeMetadata_string
  MetaAttributeMetadata = OCFMetadata(FromXml(MetaAttributeMetadata_string))
  if wikiformat:
    MetaAttributeMetadata.explainall_wiki()
  else:
    MetaAttributeMetadata.explainall()

wiki_usage_message = "{{{" + usage_message + "}}}\n"


def usage():
  if wikiformat:
  	sys.stderr.write(wiki_usage_message)
  else:
  	sys.stderr.write(usage_message)


#
#	Main program starts here -- process arguments, etc.
#

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

livecib=0
cibfile=0
listcrmoptions=0
listmetaattrsoptions=0
skipnext=0
usageerror=0
askforusage=0
count=0
for arg in sys.argv[1:]:
  count=count+1
  if skipnext:
    skipnext=0
    continue
  if arg == "-h" or arg == "--help" or arg == "-?":
    askforusage = 1
  elif arg == "-w" or arg == "--wiki-format":
    wikiformat = 1
  elif arg == "-C" or arg == "--ignore-non-defaults":
    note_non_default_values = 0
  elif arg == "-L" or arg == "--live-cib":
    livecib = 1
  elif arg == '-l' or arg == "--list-crm-config-options":
    listcrmoptions = 1
  elif arg == '-A' or arg == "--list-meta_attributes-config-options":
    listmetaattrsoptions=1
  elif arg == "-f":
    if count >= (len(sys.argv)-1):
      usageerror=1
    else:
      cibfile=1
      cibfilename=sys.argv[count+1]
      skipnext=1
  else:
    sys.stderr.write("invalid option: " + arg + "\n")
    usageerror=1

total=livecib+cibfile+listcrmoptions+askforusage+listmetaattrsoptions

if total > 1:
  print "ERROR: Contradictory options specified."
if usageerror or total != 1:
  usage()
  sys.exit(1)

if listcrmoptions:
  explain_crm_config()
if listmetaattrsoptions:
  explain_meta_attributes_config()
if askforusage:
  usage()
if livecib:
  examine_cib_live()
if cibfile:
  examine_cib_path(cibfilename)
sys.exit(0)
