summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2012-07-11 10:48:44 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2012-07-11 10:48:44 -0400
commitb35d708ea1e56a8d79b2f4e5651c57ebc99e7f8d (patch)
tree6459df3f2e966f3f82ed40f2997737c49be483a6
parent0140c43ad7392725c8ca0e2d59c84b07bc4628bc (diff)
downloadbcfg2-b35d708ea1e56a8d79b2f4e5651c57ebc99e7f8d.tar.gz
bcfg2-b35d708ea1e56a8d79b2f4e5651c57ebc99e7f8d.tar.bz2
bcfg2-b35d708ea1e56a8d79b2f4e5651c57ebc99e7f8d.zip
added feature to allow clients to declare their version to server
-rw-r--r--schemas/clients.xsd1
-rwxr-xr-xsetup.py3
-rw-r--r--src/lib/Bcfg2/Component.py3
-rw-r--r--src/lib/Bcfg2/Server/Core.py89
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Metadata.py35
-rw-r--r--src/lib/Bcfg2/version.py115
-rwxr-xr-xsrc/sbin/bcfg220
7 files changed, 218 insertions, 48 deletions
diff --git a/schemas/clients.xsd b/schemas/clients.xsd
index 56f458a45..d50f3626e 100644
--- a/schemas/clients.xsd
+++ b/schemas/clients.xsd
@@ -29,6 +29,7 @@
<xsd:attribute type='xsd:string' name='secure'/>
<xsd:attribute type='xsd:string' name='pingtime' use='optional'/>
<xsd:attribute type='xsd:string' name='address'/>
+ <xsd:attribute type='xsd:string' name='version'/>
</xsd:complexType>
<xsd:complexType name='ClientsType'>
diff --git a/setup.py b/setup.py
index 13d8dee89..3e5cd1ddc 100755
--- a/setup.py
+++ b/setup.py
@@ -8,6 +8,8 @@ import os
import os.path
import sys
+execfile('src/lib/Bcfg2/version.py')
+
# we only need m2crypto on < python2.6
need_m2crypto = False
version = sys.version_info[:2]
@@ -122,7 +124,6 @@ if need_m2crypto:
setup(cmdclass=cmdclass,
name="Bcfg2",
- version="1.2.2",
description="Bcfg2 Server",
author="Narayan Desai",
author_email="desai@mcs.anl.gov",
diff --git a/src/lib/Bcfg2/Component.py b/src/lib/Bcfg2/Component.py
index b6cea3e22..3ee3a14c8 100644
--- a/src/lib/Bcfg2/Component.py
+++ b/src/lib/Bcfg2/Component.py
@@ -216,7 +216,8 @@ class Component (object):
method_func = self._resolve_exposed_method(method)
except NoExposedMethod:
self.logger.error("Unknown method %s" % (method))
- raise xmlrpclib.Fault(7, "Unknown method %s" % method)
+ raise xmlrpclib.Fault(xmlrpclib.METHOD_NOT_FOUND,
+ "Unknown method %s" % method)
except Exception:
e = sys.exc_info()[1]
if getattr(e, "log", True):
diff --git a/src/lib/Bcfg2/Server/Core.py b/src/lib/Bcfg2/Server/Core.py
index e8ebd511d..6dbab64bd 100644
--- a/src/lib/Bcfg2/Server/Core.py
+++ b/src/lib/Bcfg2/Server/Core.py
@@ -30,7 +30,7 @@ logger = logging.getLogger('Bcfg2.Server.Core')
def critical_error(operation):
"""Log and err, traceback and return an xmlrpc fault to client."""
logger.error(operation, exc_info=1)
- raise xmlrpclib.Fault(7, "Critical unexpected failure: %s" % (operation))
+ raise xmlrpclib.Fault(xmlrpclib.APPLICATION_ERROR, "Critical unexpected failure: %s" % (operation))
try:
import psyco
@@ -413,98 +413,108 @@ class Core(Component):
state.get('state')))
self.client_run_hook("end_statistics", meta)
+ def resolve_client(self, address, cleanup_cache=False, metadata=True):
+ try:
+ client = self.metadata.resolve_client(address,
+ cleanup_cache=cleanup_cache)
+ if metadata:
+ meta = self.build_metadata(client)
+ else:
+ meta = None
+ except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
+ critical_error("Client metadata resolution error for %s; "
+ "check server log" % address[0])
+ except Bcfg2.Server.Plugins.Metadata.MetadataRuntimeError:
+ critical_error('Metadata system runtime failure')
+ return (client, meta)
+
# XMLRPC handlers start here
@exposed
+ def DeclareVersion(self, address, version):
+ """ declare the client version """
+ client, metadata = self.resolve_client(address)
+ try:
+ self.metadata.set_version(client, version)
+ except (Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError,
+ Bcfg2.Server.Plugins.Metadata.MetadataRuntimeError):
+ err = sys.exc_info()[1]
+ critical_error("Unable to set version for %s: %s" %
+ (client, err))
+ return True
+
+ @exposed
def GetProbes(self, address):
"""Fetch probes for a particular client."""
resp = lxml.etree.Element('probes')
+ client, metadata = self.resolve_client(address, cleanup_cache=True)
try:
- name = self.metadata.resolve_client(address, cleanup_cache=True)
- meta = self.build_metadata(name)
-
for plugin in self.plugins_by_type(Bcfg2.Server.Plugin.Probing):
- for probe in plugin.GetProbes(meta):
+ for probe in plugin.GetProbes(metadata):
resp.append(probe)
return lxml.etree.tostring(resp, encoding='UTF-8',
xml_declaration=True)
- except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- warning = 'Client metadata resolution error for %s' % address[0]
- self.logger.warning(warning)
- raise xmlrpclib.Fault(6, warning + "; check server log")
- except Bcfg2.Server.Plugins.Metadata.MetadataRuntimeError:
- err_msg = 'Metadata system runtime failure'
- self.logger.error(err_msg)
- raise xmlrpclib.Fault(6, err_msg)
except:
- critical_error("Error determining client probes")
+ critical_error("Error determining probes for %s" % client)
@exposed
def RecvProbeData(self, address, probedata):
"""Receive probe data from clients."""
- try:
- name = self.metadata.resolve_client(address)
- meta = self.build_metadata(name)
- except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- warning = 'Metadata consistency error'
- self.logger.warning(warning)
- raise xmlrpclib.Fault(6, warning)
+ client, metadata = self.resolve_client(address)
# clear dynamic groups
- self.metadata.cgroups[meta.hostname] = []
+ self.metadata.cgroups[metadata.hostname] = []
try:
xpdata = lxml.etree.XML(probedata.encode('utf-8'),
parser=Bcfg2.Server.XMLParser)
except:
- self.logger.error("Failed to parse probe data from client %s" % \
- (address[0]))
- return False
+ critical_error("Failed to parse probe data from client %s" %
+ client)
sources = []
[sources.append(data.get('source')) for data in xpdata
if data.get('source') not in sources]
for source in sources:
if source not in self.plugins:
- self.logger.warning("Failed to locate plugin %s" % (source))
+ self.logger.warning("Failed to locate plugin %s" % source)
continue
dl = [data for data in xpdata if data.get('source') == source]
try:
- self.plugins[source].ReceiveData(meta, dl)
+ self.plugins[source].ReceiveData(metadata, dl)
except:
- logger.error("Failed to process probe data from client %s" % \
- (address[0]), exc_info=1)
+ critical_error("Failed to process probe data from client %s" %
+ client)
return True
@exposed
def AssertProfile(self, address, profile):
"""Set profile for a client."""
+ client = self.resolve_client(address, metadata=False)[0]
try:
- client = self.metadata.resolve_client(address)
self.metadata.set_profile(client, profile, address)
except (Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError,
Bcfg2.Server.Plugins.Metadata.MetadataRuntimeError):
- warning = 'Metadata consistency error'
- self.logger.warning(warning)
- raise xmlrpclib.Fault(6, warning)
+ err = sys.exc_info()[1]
+ critical_error("Unable to assert profile for %s: %s" %
+ (client, err))
return True
@exposed
def GetConfig(self, address, checksum=False):
"""Build config for a client."""
+ client = self.resolve_client(address)[0]
try:
- client = self.metadata.resolve_client(address)
config = self.BuildConfiguration(client)
return lxml.etree.tostring(config, encoding='UTF-8',
xml_declaration=True)
except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- self.logger.warning("Metadata consistency failure for %s" % (address))
- raise xmlrpclib.Fault(6, "Metadata consistency failure")
+ critical_error("Metadata consistency failure for %s" % client)
@exposed
def RecvStats(self, address, stats):
"""Act on statistics upload."""
+ client = self.resolve_client(address)[0]
sdata = lxml.etree.XML(stats.encode('utf-8'),
parser=Bcfg2.Server.XMLParser)
- client = self.metadata.resolve_client(address)
self.process_statistics(client, sdata)
return "<ok/>"
@@ -519,6 +529,5 @@ class Core(Component):
@exposed
def GetDecisionList(self, address, mode):
"""Get the data of the decision list."""
- client = self.metadata.resolve_client(address)
- meta = self.build_metadata(client)
- return self.GetDecisions(meta, mode)
+ client, metadata = self.resolve_client(address)
+ return self.GetDecisions(metadata, mode)
diff --git a/src/lib/Bcfg2/Server/Plugins/Metadata.py b/src/lib/Bcfg2/Server/Plugins/Metadata.py
index 9786466bc..77e433ab1 100644
--- a/src/lib/Bcfg2/Server/Plugins/Metadata.py
+++ b/src/lib/Bcfg2/Server/Plugins/Metadata.py
@@ -12,7 +12,7 @@ import time
import Bcfg2.Server
import Bcfg2.Server.FileMonitor
import Bcfg2.Server.Plugin
-
+from Bcfg2.version import Bcfg2VersionInfo
def locked(fd):
"""Aquire a lock on a file"""
@@ -171,8 +171,8 @@ class XMLMetadataConfig(Bcfg2.Server.Plugin.XMLFileBacked):
class ClientMetadata(object):
"""This object contains client metadata."""
- def __init__(self, client, profile, groups, bundles,
- aliases, addresses, categories, uuid, password, query):
+ def __init__(self, client, profile, groups, bundles, aliases, addresses,
+ categories, uuid, password, version, query):
self.hostname = client
self.profile = profile
self.bundles = bundles
@@ -183,6 +183,11 @@ class ClientMetadata(object):
self.uuid = uuid
self.password = password
self.connectors = []
+ self.version = version
+ try:
+ self.version_info = Bcfg2VersionInfo(version)
+ except:
+ self.version_info = None
self.query = query
def inGroup(self, group):
@@ -248,6 +253,7 @@ class Metadata(Bcfg2.Server.Plugin.Plugin,
self.aliases = {}
self.groups = {}
self.cgroups = {}
+ self.versions = {}
self.public = []
self.private = []
self.profiles = []
@@ -412,6 +418,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin,
self.floating.append(clname)
if 'password' in client.attrib:
self.passwords[clname] = client.get('password')
+ if 'version' in client.attrib:
+ self.versions[clname] = client.get('version')
self.raliases[clname] = set()
for alias in client.findall('Alias'):
@@ -537,6 +545,20 @@ class Metadata(Bcfg2.Server.Plugin.Plugin,
self.clients[client] = profile
self.clients_xml.write()
+ def set_version(self, client, version):
+ """Set group parameter for provided client."""
+ self.logger.info("Asserting client %s version to %s" % (client, version))
+ if client in self.clients:
+ self.logger.info("Setting version on client %s to %s" %
+ (client, version))
+ self.update_client(client, dict(version=version))
+ else:
+ msg = "Cannot set version on non-existent client %s" % client
+ self.logger.error(msg)
+ raise MetadataConsistencyError(msg)
+ self.versions[client] = version
+ self.clients_xml.write()
+
def resolve_client(self, addresspair, cleanup_cache=False):
"""Lookup address locally or in DNS to get a hostname."""
if addresspair in self.session_cache:
@@ -575,9 +597,9 @@ class Metadata(Bcfg2.Server.Plugin.Plugin,
return self.aliases[cname]
return cname
except socket.herror:
- warning = "address resolution error for %s" % (address)
+ warning = "address resolution error for %s" % address
self.logger.warning(warning)
- raise MetadataConsistencyError
+ raise MetadataConsistencyError(warning)
def get_initial_metadata(self, client):
"""Return the metadata for a given client."""
@@ -599,6 +621,7 @@ class Metadata(Bcfg2.Server.Plugin.Plugin,
[bundles, groups, categories] = self.groups[self.default]
aliases = self.raliases.get(client, set())
addresses = self.raddresses.get(client, set())
+ version = self.versions.get(client, None)
newgroups = set(groups)
newbundles = set(bundles)
newcategories = {}
@@ -622,7 +645,7 @@ class Metadata(Bcfg2.Server.Plugin.Plugin,
[newgroups.add(g) for g in ngroups if g not in newgroups]
newcategories.update(ncategories)
return ClientMetadata(client, profile, newgroups, newbundles, aliases,
- addresses, newcategories, uuid, password,
+ addresses, newcategories, uuid, password, version,
self.query)
def get_all_group_names(self):
diff --git a/src/lib/Bcfg2/version.py b/src/lib/Bcfg2/version.py
new file mode 100644
index 000000000..ac10dac94
--- /dev/null
+++ b/src/lib/Bcfg2/version.py
@@ -0,0 +1,115 @@
+import re
+
+__version__ = "1.3.0"
+
+class Bcfg2VersionInfo(tuple):
+ v_re = re.compile(r'(\d+)(\w+)(\d+)')
+
+ def __new__(cls, vstr):
+ (major, minor, rest) = vstr.split(".")
+ match = cls.v_re.match(rest)
+ if match:
+ micro, releaselevel, serial = match.groups()
+ else:
+ micro = rest
+ releaselevel = 'final'
+ serial = 0
+ return tuple.__new__(cls, [int(major), int(minor), int(micro),
+ releaselevel, int(serial)])
+
+ def __init__(self, vstr):
+ tuple.__init__(self)
+ self.major, self.minor, self.micro, self.releaselevel, self.serial = \
+ tuple(self)
+
+ def __repr__(self):
+ return "(major=%s, minor=%s, micro=%s, releaselevel=%s, serial=%s)" % \
+ tuple(self)
+
+ def _release_cmp(self, r1, r2):
+ if r1 == r2:
+ return 0
+ elif r1 == "final":
+ return -1
+ elif r2 == "final":
+ return 1
+ elif r1 == "rc":
+ return -1
+ elif r2 == "rc":
+ return 1
+ # should never get to anything past this point
+ elif r1 == "pre":
+ return -1
+ elif r2 == "pre":
+ return 1
+ else:
+ # wtf?
+ return 0
+
+ def __gt__(self, version):
+ if version is None:
+ # older bcfg2 clients didn't report their version, so we
+ # handle this case specially and assume that any reported
+ # version is newer than any indeterminate version
+ return True
+ try:
+ for i in range(3):
+ if self[i] > version[i]:
+ return True
+ elif self[i] < version[i]:
+ return False
+ rel = self._release_cmp(self[3], version[3])
+ if rel < 0:
+ return True
+ elif rel > 0:
+ return False
+ if self[4] > version[4]:
+ return True
+ else:
+ return False
+ except TypeError:
+ return self > Bcfg2VersionInfo(version)
+
+ def __lt__(self, version):
+ if version is None:
+ # older bcfg2 clients didn't report their version, so we
+ # handle this case specially and assume that any reported
+ # version is newer than any indeterminate version
+ return False
+ try:
+ for i in range(3):
+ if self[i] < version[i]:
+ return True
+ elif self[i] > version[i]:
+ return False
+ rel = self._release_cmp(self[3], version[3])
+ if rel > 0:
+ return True
+ elif rel < 0:
+ return False
+ if self[4] < version[4]:
+ return True
+ else:
+ return False
+ except TypeError:
+ return self < Bcfg2VersionInfo(version)
+
+ def __eq__(self, version):
+ if version is None:
+ # older bcfg2 clients didn't report their version, so we
+ # handle this case specially and assume that any reported
+ # version is newer than any indeterminate version
+ return False
+ try:
+ rv = True
+ for i in range(len(self)):
+ rv &= self[i] == version[i]
+ return rv
+ except TypeError:
+ return self == Bcfg2VersionInfo(version)
+
+ def __ge__(self, version):
+ return not self < version
+
+ def __le__(self, version):
+ return not self > version
diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2
index 349826676..4fbbef877 100755
--- a/src/sbin/bcfg2
+++ b/src/sbin/bcfg2
@@ -18,6 +18,8 @@ import Bcfg2.Client.Tools
# Compatibility imports
from Bcfg2.Bcfg2Py3k import xmlrpclib
+from Bcfg2.version import __version__
+
import Bcfg2.Proxy
import Bcfg2.Logger
@@ -152,6 +154,24 @@ class Client:
raise SystemExit(1)
try:
+ probe_data = proxy.DeclareVersion(__version__)
+ except xmlrpclib.Fault:
+ err = sys.exc_info()[1]
+ if (err.faultCode == xmlrpclib.METHOD_NOT_FOUND or
+ (err.faultCode == 7 and
+ err.faultString.startswith("Unknown method"))):
+ self.logger.debug("Server does not support declaring "
+ "client version")
+ else:
+ self.logger.error("Failed to declare version: %s" % err)
+ except (Bcfg2.Proxy.ProxyError,
+ Bcfg2.Proxy.CertificateError,
+ socket.gaierror,
+ socket.error):
+ err = sys.exc_info()[1]
+ self.logger.error("Failed to declare version: %s" % err)
+
+ try:
probe_data = proxy.GetProbes()
except (Bcfg2.Proxy.ProxyError,
Bcfg2.Proxy.CertificateError,