summaryrefslogtreecommitdiffstats
path: root/src/lib/Server/Core.py
blob: 8940b798c4341cc2672c2129c1ba54c830aaca54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
'''Bcfg2.Server.Core provides the runtime support for bcfg2 modules'''
__revision__ = '$Revision$'

from os import stat
from stat import ST_MODE, S_ISDIR
from sys import exc_info
from syslog import syslog, LOG_ERR, LOG_INFO
from traceback import extract_tb
from time import time
from ConfigParser import ConfigParser
from elementtree.ElementTree import Element
import _fam

from Bcfg2.Server.Plugin import PluginInitError, PluginExecutionError
from Bcfg2.Server.Metadata import MetadataStore, MetadataConsistencyError
from Bcfg2.Server.Statistics import Statistics

class CoreInitError(Exception):
    '''This error is raised when the core cannot be initialized'''
    pass

class Fam(object):
    '''The fam object is a set of callbacks for file alteration events'''
    
    def __init__(self):
        object.__init__(self)
        self.fm = _fam.open()
        self.users = {}
        self.handles = {}

    def fileno(self):
        '''return fam file handle number'''
        return self.fm.fileno()

    def AddMonitor(self, path, obj=None):
        '''add a monitor to path, installing a callback to obj.HandleEvent'''
        mode = stat(path)[ST_MODE]
        if S_ISDIR(mode):
            handle = self.fm.monitorDirectory(path, None)
            #print "adding callback for directory %s to %s, handle :%s:" % ( path, obj, handle.requestID())
        else:
            handle = self.fm.monitorFile(path, None)
        self.handles[handle.requestID()] = handle
        if obj != None:
            self.users[handle.requestID()] = obj
        return handle.requestID()

    def HandleEvent(self):
        '''Route a fam event to the proper callback'''
        event = self.fm.nextEvent()
        reqid = event.requestID
        if self.users.has_key(reqid):
            #print "dispatching event %s %s to obj %s handle :%s:" % (event.code2str(), event.filename, self.users[reqid], event.requestID)
            self.users[reqid].HandleEvent(event)

class Core(object):
    '''The Core object is the container for all Bcfg2 Server logic, and modules'''
    def __init__(self, setup, configfile):
        object.__init__(self)
        cfile = ConfigParser()
        cfile.read([configfile])
        self.datastore = cfile.get('server','repository')
        try:
            self.fam = Fam()
        except IOError:
            raise CoreInitError, "failed to connect to fam"
        self.pubspace = {}
        self.generators = []
        self.structures = []
        self.cron = {}
        self.setup = setup
        self.plugins = {}
        
        mpath = cfile.get('server','metadata')
        try:
            self.metadata = MetadataStore("%s/metadata.xml" % mpath, self.fam)
        except OSError:
            raise CoreInitError, "metadata path incorrect"
        
        self.stats = Statistics("%s/statistics.xml" % (mpath))

        structures = cfile.get('server', 'structures').split(',')
        generators = cfile.get('server', 'generators').split(',')

        for plugin in structures + generators:
            if not self.plugins.has_key(plugin):
                try:
                    mod = getattr(__import__("Bcfg2.Server.Plugins.%s" %
                                             (plugin)).Server.Plugins, plugin)
                except ImportError:
                    syslog(LOG_ERR, "Failed to load plugin %s" % (plugin))
                    continue
                struct = getattr(mod, plugin)
                try:
                    self.plugins[plugin] = struct(self, self.datastore)
                except PluginInitError:
                    syslog(LOG_ERR, "Failed to instantiate plugin %s" % (plugin))
                except:
                    syslog(LOG_ERR, "Unexpected initiantiation failure for plugin %s" % (plugin))
                    (trace, val, trb)=exc_info()
                    for line in extract_tb(trb):
                        syslog(LOG_ERR, '  File "%s", line %i, in %s\n    %s\n'%line)
                    syslog(LOG_ERR, "%s: %s\n"%(trace, val))
                    del trace, val, trb

        for plugin in structures:
            if self.plugins.has_key(plugin):
                self.structures.append(self.plugins[plugin])
            else:
                syslog(LOG_ERR, "Plugin %s not loaded. Not enabled as a Structure" % (plugin))
        for plugin in generators:
            if self.plugins.has_key(plugin):
                self.generators.append(self.plugins[plugin])
            else:
                syslog(LOG_ERR, "Plugin %s not loaded. Not enabled as a Generator" % (plugin))
                    
    def GetStructures(self, metadata):
        '''Get all structures for client specified by metadata'''
        return reduce(lambda x, y:x+y,
                      [struct.BuildStructures(metadata) for struct in self.structures])

    def BindStructure(self, structure, metadata):
        '''Bind a complete structure'''
        for entry in [child for child in structure.getchildren() if child.tag not in ['SymLink', 'Directory', 'Permissions', 'PostInstall']]:
            try:
                self.Bind(entry, metadata)
            except PluginExecutionError:
                syslog(LOG_ERR, "Failed to bind entry: %s %s" %  (entry.tag, entry.get('name')))

    def Bind(self, entry, metadata):
        '''Bind an entry using the appropriate generator'''
        glist = [gen for gen in self.generators if
                 gen.Entries.get(entry.tag, {}).has_key(entry.get('name'))]
        if len(glist) == 1:
            return glist[0].Entries[entry.tag][entry.get('name')](entry, metadata)
        elif len(glist) > 1:
            generators = ", ".join([gen.__name__ for gen in glist])
            syslog(LOG_ERR, "%s %s served by multiple generators: %s" % (entry.tag,
                                                                         entry.get('name'), generators))
            raise PluginExecutionError, (entry.tag, entry.get('name'))
        else:
            for gen in self.generators:
                if hasattr(gen, "FindHandler"):
                    return gen.FindHandler(entry)(entry, metadata)
            syslog(LOG_ERR, "Failed to find handler for %s:%s" % (entry.tag, entry.get('name')))
            raise PluginExecutionError, (entry.tag, entry.get('name'))
                
    def BuildConfiguration(self, client):
        '''Build Configuration for client'''
        start = time()
        config = Element("Configuration", version='2.0')
        try:
            meta = self.metadata.FetchMetadata(client)
        except MetadataConsistencyError:
            syslog(LOG_ERR, "Metadata consistency error for client %s" % client)
            return Element("error", type='metadata error')

        config.set('toolset', meta.toolset)
        try:
            structures = self.GetStructures(meta)
        except:
            self.LogFailure("GetStructures")
            return Element("error", type='structure error')
        
        for astruct in structures:
            try:
                self.BindStructure(astruct, meta)
                config.append(astruct)
            except:
                self.LogFailure("BindStructure")
        syslog(LOG_INFO, "Generated config for %s in %s seconds"%(client, time() - start))
        return config

    def LogFailure(self, failure):
        '''Log Failures in unexpected cases'''
        (trace, val, trb) = exc_info()
        syslog(LOG_ERR, "Unexpected failure in %s" % (failure))
        for line in extract_tb(trb):
            syslog(LOG_ERR, '  File "%s", line %i, in %s\n    %s\n' % line)
        syslog(LOG_ERR, "%s: %s\n"%(trace, val))
        del trace, val, trb

    def Service(self):
        '''Perform periodic update tasks'''
        while self.fam.fm.pending:
            try:
                self.fam.HandleEvent()
            except:
                self.LogFailure("FamEvent")
        try:
            self.stats.WriteBack()
        except:
            self.LogFailure("Statistics")