summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNarayan Desai <desai@mcs.anl.gov>2007-01-24 02:19:43 +0000
committerNarayan Desai <desai@mcs.anl.gov>2007-01-24 02:19:43 +0000
commitb238887ad8172c6a613b7d68bb551dac1aac57bb (patch)
tree1a0ce284f2c9b2b95447f4ea768b02e68f04797b
parent9182bcf5871ee041e789bb48d057323b6bc6b4b1 (diff)
downloadbcfg2-b238887ad8172c6a613b7d68bb551dac1aac57bb.tar.gz
bcfg2-b238887ad8172c6a613b7d68bb551dac1aac57bb.tar.bz2
bcfg2-b238887ad8172c6a613b7d68bb551dac1aac57bb.zip
Implement Action support
- Schema addition for bundles/rules - Tool driver for actions - Frame support for pre-actions and post-actions - Frame blacklist support (to preclude blocked entries from getting installed on the post-clobber pass) git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@2713 ce84e21b-d406-0410-9b95-82705330c041
-rw-r--r--schemas/bundle.xsd4
-rw-r--r--schemas/rules.xsd12
-rw-r--r--schemas/types.xsd26
-rw-r--r--src/lib/Client/Frame.py77
-rw-r--r--src/lib/Client/Tools/Action.py52
-rw-r--r--src/lib/Client/Tools/__init__.py4
6 files changed, 142 insertions, 33 deletions
diff --git a/schemas/bundle.xsd b/schemas/bundle.xsd
index 1890108c8..cdbc5f900 100644
--- a/schemas/bundle.xsd
+++ b/schemas/bundle.xsd
@@ -18,6 +18,7 @@
<xsd:element name='SymLink' type='StructureEntry'/>
<xsd:element name='Permissions' type='StructureEntry'/>
<xsd:element name='PostInstall' type='StructureEntry'/>
+ <xsd:element name='Action' type='StructureEntry'/>
<xsd:element name='Group' type='GroupType'/>
</xsd:choice>
<xsd:attribute type='xsd:string' name='name' use='required'/>
@@ -34,6 +35,7 @@
<xsd:element name='SymLink' type='StructureEntry'/>
<xsd:element name='Permission' type='StructureEntry'/>
<xsd:element name='PostInstall' type='StructureEntry'/>
+ <xsd:element name='Action' type='StructureEntry'/>
<xsd:element name='Group' type='GroupType'/>
</xsd:choice>
<xsd:attribute type='xsd:string' name='name'/>
@@ -42,4 +44,4 @@
<xsd:attribute type='xsd:string' name='revision'/>
</xsd:complexType>
</xsd:element>
-</xsd:schema> \ No newline at end of file
+</xsd:schema>
diff --git a/schemas/rules.xsd b/schemas/rules.xsd
index 270b08828..82ed4aaa0 100644
--- a/schemas/rules.xsd
+++ b/schemas/rules.xsd
@@ -29,6 +29,14 @@
<xsd:attribute type='xsd:string' name='group'/>
</xsd:complexType>
+ <xsd:complexType name='ActionType'>
+ <xsd:attribute type='ActionTimingEnum' name='timing' use='required'/>
+ <xsd:attribute type='ActionWhenEnum' name='when' use='required'/>
+ <xsd:attribute type='ActionStatusEnum' name='status' use='required'/>
+ <xsd:attribute type='xsd:string' name='name' use='required'/>
+ <xsd:attribute type='xsd:string' name='command' use='required'/>
+ </xsd:complexType>
+
<xsd:complexType name='SymLinkType'>
<xsd:attribute type='xsd:string' name='name' use='required'/>
<xsd:attribute type='xsd:string' name='to' use='required'/>
@@ -48,6 +56,7 @@
<xsd:element name='SymLink' type='SymLinkType'/>
<xsd:element name='Package' type='PackageType'/>
<xsd:element name='Permissions' type='PermissionsType'/>
+ <xsd:element name='Action' type='ActionType'/>
<xsd:element name='Group' type='RContainerType'/>
<xsd:element name='Client' type='RContainerType'/>
</xsd:choice>
@@ -64,10 +73,11 @@
<xsd:element name='SymLink' type='SymLinkType'/>
<xsd:element name='Package' type='PackageType'/>
<xsd:element name='Permissions' type='PermissionsType'/>
+ <xsd:element name='Action' type='ActionType'/>
<xsd:element name='Group' type='RContainerType'/>
<xsd:element name='Client' type='RContainerType'/>
</xsd:choice>
<xsd:attribute name='priority' type='xsd:integer' use='required'/>
</xsd:complexType>
</xsd:element>
-</xsd:schema> \ No newline at end of file
+</xsd:schema>
diff --git a/schemas/types.xsd b/schemas/types.xsd
index b9d7131dd..f722309f8 100644
--- a/schemas/types.xsd
+++ b/schemas/types.xsd
@@ -8,7 +8,7 @@
</xsd:documentation>
</xsd:annotation>
- <xsd:simpleType name='PackageTypeEnum'>
+ <xsd:simpleType name='PackageTypeEnum'>
<xsd:restriction base='xsd:string'>
<xsd:enumeration value='deb' />
<xsd:enumeration value='rpm' />
@@ -25,4 +25,26 @@
</xsd:restriction>
</xsd:simpleType>
-</xsd:schema> \ No newline at end of file
+ <xsd:simpleType name='ActionTimingEnum'>
+ <xsd:restriction base='xsd:string'>
+ <xsd:enumeration value='both'/>
+ <xsd:enumeration value='pre'/>
+ <xsd:enumeration value='post'/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name='ActionWhenEnum'>
+ <xsd:restriction base='xsd:string'>
+ <xsd:enumeration value='modified'/>
+ <xsd:enumeration value='always'/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name='ActionStatusEnum'>
+ <xsd:restriction base='xsd:string'>
+ <xsd:enumeration value='ignore'/>
+ <xsd:enumeration value='check'/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+</xsd:schema>
diff --git a/src/lib/Client/Frame.py b/src/lib/Client/Frame.py
index 38b65f8e8..9b3d482c4 100644
--- a/src/lib/Client/Frame.py
+++ b/src/lib/Client/Frame.py
@@ -43,6 +43,7 @@ class Frame:
self.tools = []
self.states = {}
self.whitelist = []
+ self.blacklist = []
self.removal = []
self.logger = logging.getLogger("Bcfg2.Client.Frame")
if self.setup['drivers']:
@@ -137,6 +138,7 @@ class Frame:
elif self.setup['remove'] == 'packages':
self.removal = [entry for entry in self.extra if entry.tag == 'Package']
+ candidates = [entry for entry in self.states if not self.states[entry]]
if self.setup['dryrun']:
updated = [entry for entry in self.states if not self.states[entry]]
if updated:
@@ -150,21 +152,41 @@ class Frame:
self.removal = []
return
elif self.setup['interactive']:
- self.whitelist = promptFilter(prompt, [entry for entry in self.states \
- if not self.states[entry]])
+ self.whitelist = promptFilter(prompt, candidates)
+ self.blacklist = [c for c in candidates if c not in self.whitelist]
self.removal = promptFilter(rprompt, self.removal)
- elif self.setup['bundle']:
- # only install entries in specified bundle
- mbs = [bund for bund in self.config.findall('./Bundle') \
- if bund.get('name') == self.setup['bundle']]
- if not mbs:
- self.logger.error("Could not find bundle %s" % (self.setup['bundle']))
- return
- self.whitelist = [entry for entry in self.states if not self.states[entry] \
- and entry in mbs[0].getchildren()]
else:
- # all systems are go
- self.whitelist = [entry for entry in self.states if not self.states[entry]]
+ # need to do bundle and pre-action checks
+ if self.setup['bundle']:
+ bundles = [b for b in self.config.findall('./Bundle') \
+ if b.get('name') == self.setup['bundle']]
+ else:
+ bundles = self.config.findall('./Bundle')
+ gbundles = []
+ for bundle in bundles:
+ actions = [a for a in bundle.findall('./Action') \
+ if a.get('timing') != 'post']
+ # run all actions if modified or always
+ for action in actions:
+ if action.get('when') == 'always':
+ self.DispatchInstallCalls([action])
+ else:
+ # when == modified
+ # check if bundle should be modified
+ if [c for c in candidates if c in bundle]:
+ self.DispatchInstallCalls([action])
+ if False in [self.states[a] for a in actions]:
+ self.logger.info("Bundle %s failed prerequisite action" % \
+ (bundle.get('name')))
+ continue
+ else:
+ gbundles.append(bundle)
+
+ for entry in candidates:
+ if [bundle for bundle in gbundles if entry in bundle]:
+ self.whitelist.append(entry)
+ else:
+ self.blacklist.append(entry)
def DispatchInstallCalls(self, entries):
'''Dispatch install calls to underlying tools'''
@@ -180,11 +202,12 @@ class Frame:
def Install(self):
'''Install all entries'''
self.DispatchInstallCalls(self.whitelist)
+ mods = self.modified
+ mbundles = [struct for struct in self.config.findall('Bundle') if \
+ [mod for mod in mods if mod in struct]]
+
if self.modified:
# Handle Bundle interdeps
- mods = self.modified
- mbundles = [struct for struct in self.config.findall('Bundle') if \
- [mod for mod in mods if mod in struct]]
if mbundles:
self.logger.info("The Following Bundles have been modifed:")
self.logger.info([mbun.get('name') for mbun in mbundles])
@@ -196,7 +219,7 @@ class Frame:
except:
self.logger.error("%s.Inventory() call failed:" % tool.__name__, exc_info=1)
clobbered = [entry for bundle in mbundles for entry in bundle \
- if not self.states[entry]]
+ if not self.states[entry] and entry not in self.blacklist]
if clobbered:
self.logger.debug("Found clobbered entries:")
self.logger.debug(["%s:%s" % (entry.tag, entry.get('name')) \
@@ -204,16 +227,16 @@ class Frame:
if not self.setup['interactive']:
self.DispatchInstallCalls(clobbered)
- for bundle in self.config.findall('.//Bundle'):
- for tool in self.tools:
- try:
- if bundle in mbundles:
- tool.BundleUpdated(bundle)
- else:
- tool.BundleNotUpdated(bundle)
- except:
- self.logger.error("%s.BundleNotUpdated() call failed:" % \
- (tool.__name__), exc_info=1)
+ for bundle in self.config.findall('.//Bundle'):
+ for tool in self.tools:
+ try:
+ if bundle in mbundles:
+ tool.BundleUpdated(bundle)
+ else:
+ tool.BundleNotUpdated(bundle)
+ except:
+ self.logger.error("%s.BundleNotUpdated() call failed:" % \
+ (tool.__name__), exc_info=1)
def Remove(self):
'''Remove extra entries'''
diff --git a/src/lib/Client/Tools/Action.py b/src/lib/Client/Tools/Action.py
new file mode 100644
index 000000000..ddd15a264
--- /dev/null
+++ b/src/lib/Client/Tools/Action.py
@@ -0,0 +1,52 @@
+'''Action driver'''
+__revision__ = '$Revision$'
+
+import Bcfg2.Client.Tools
+
+# <Action timing='pre|post|both' name='name' command='cmd text' when='always|modified'
+# status='ignore|check'/>
+# <PostInstall name='foo'/>
+# => <Action timing='post' when='modified' name='n' command='foo' status='ignore'/>
+
+class Action(Bcfg2.Client.Tools.Tool):
+ '''Implement Actions'''
+ __name__ = 'Action'
+ __handles__ = [('PostInstall', None), ('Action', None)]
+ __req__ = {'PostInstall': ['name'],
+ 'Action':['name', 'timing', 'when', 'command', 'status']}
+
+ def RunAction(self, entry):
+ '''This method handles command execution and status return'''
+ self.logger.debug("Running Action %s" % (entry.get('name')))
+ rc = self.cmd.run(entry.get('command'))[0]
+ self.logger.debug("Action: %s got rc %s" % (entry.get('command'), rc))
+ if entry.get('status', 'check') == 'ignore':
+ return True
+ else:
+ return rc == 0
+
+ def VerifyAction(self, dummy, _):
+ '''Actions always verify true'''
+ return True
+
+ def InstallAction(self, entry):
+ '''Run actions as pre-checks for bundle installation'''
+ if entry.get('timing') != 'post':
+ self.states[entry] = self.RunAction(entry)
+ return self.states[entry]
+ return True
+
+ def BundleUpdated(self, bundle):
+ '''Run postinstalls when bundles have been updated'''
+ for postinst in bundle.findall("PostInstall"):
+ self.cmd.run(postinst.get('name'))
+ for action in bundle.findall("Action"):
+ if action.get('timing') in ['post', 'both']:
+ self.states[action] = self.RunAction(action)
+
+ def BundleNotUpdated(self, bundle):
+ '''Run Actions when bundles have not been updated'''
+ for action in bundle.findall("Action"):
+ if action.get('timing') in ['post', 'both'] and \
+ action.get('when') != 'modified':
+ self.states[action] = self.RunAction(action)
diff --git a/src/lib/Client/Tools/__init__.py b/src/lib/Client/Tools/__init__.py
index 142084792..90d3cedbc 100644
--- a/src/lib/Client/Tools/__init__.py
+++ b/src/lib/Client/Tools/__init__.py
@@ -1,8 +1,8 @@
'''This contains all Bcfg2 Tool modules'''
__revision__ = '$Revision$'
-__all__ = ["APT", "Blast", "Chkconfig", "DebInit", "Encap", "Portage", "Yum",
- "PostInstall", "POSIX", "RPM", "RcUpdate", "SMF", "SYSV", "launchd"]
+__all__ = ["Action", "APT", "Blast", "Chkconfig", "DebInit", "Encap", "launchd",
+ "Portage", "POSIX", "RPM", "RcUpdate", "SMF", "SYSV", "Yum"]
import os, popen2, stat, sys, Bcfg2.Client.XML