You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
heartbeat/tools/ciblint

2476 lines
90 KiB

#!/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
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/lib/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})
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),
"name": (StringMetadataType,1),
"timeout": (TimeMetadataType,0),
"start_delay": (TimeMetadataType, 0),
"interval": (TimeMetadataType,0),
"disabled": (BooleanMetadataType,0),
"role": (RoleMetadataType,0),
"prereq": (PrereqMetadataType,0)},
"primitive":{ "id": (StringMetadataType,1),
"class": (StringMetadataType,1),
"provider": (StringMetadataType,0),
"type": (StringMetadataType,1)},
"group": { "id": (StringMetadataType,1),
"ordered": (BooleanMetadataType,0),
"colocated":(BooleanMetadataType,0)},
"clone": { "id": (StringMetadataType,1)},
"enum": {},
"enumvals": {},
"enumval": {"value":(StringMetadataType,1)},
"master_slave": idonly,
"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),
"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),
"score":(ScoreMetadataType,0),
"symmetrical":(BooleanOpMetadataType,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":
CheckAttributes(child)
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"))
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
global AllResourceLocations, AllResourceCoLocations
AllPrimitives = {}
AllGroups = {}
AllMasterSlave = {}
AllClones = {}
AllResources = {}
AllResourceLocations = []
AllResourceCoLocations = []
#
# 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)
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_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.rsc, 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 == "neq":
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 = 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
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)
#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_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:
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)
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)