mirror of https://github.com/sipwise/heartbeat.git
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.
2476 lines
90 KiB
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)
|