~ubuntu-branches/ubuntu/hardy/bcfg2/hardy-updates

« back to all changes in this revision

Viewing changes to src/lib/Client/Tools/__init__.py

  • Committer: Bazaar Package Importer
  • Author(s): Sami Haahtinen
  • Date: 2006-11-16 22:39:16 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061116223916-8dtn3t86cz58vg2x
Tags: 0.8.6.1-1
* New Upstream Release
* Replaced faulty if clause in bcfg2.postrm (Closes: #398772)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
'''This contains all Bcfg2 Tool modules'''
 
2
__revision__ = '$Revision: 2435 $'
 
3
 
 
4
__all__ = ["APT", "Blast", "Chkconfig", "DebInit", "Encap", "PostInstall",
 
5
           "POSIX", "RPM", "SMF", "SYSV"]
 
6
 
 
7
import os, popen2, stat, sys, Bcfg2.Client.XML
 
8
 
 
9
class toolInstantiationError(Exception):
 
10
    '''This error is called if the toolset cannot be instantiated'''
 
11
    pass
 
12
 
 
13
class readonlypipe(popen2.Popen4):
 
14
    '''This pipe sets up stdin --> /dev/null'''
 
15
    def __init__(self, cmd, bufsize=-1):
 
16
        popen2._cleanup()
 
17
        c2pread, c2pwrite = os.pipe()
 
18
        null = open('/dev/null', 'w+')
 
19
        self.pid = os.fork()
 
20
        if self.pid == 0:
 
21
            # Child
 
22
            os.dup2(null.fileno(), sys.__stdin__.fileno())
 
23
            #os.dup2(p2cread, 0)
 
24
            os.dup2(c2pwrite, 1)
 
25
            os.dup2(c2pwrite, 2)
 
26
            self._run_child(cmd)
 
27
        os.close(c2pwrite)
 
28
        self.fromchild = os.fdopen(c2pread, 'r', bufsize)
 
29
        popen2._active.append(self)
 
30
 
 
31
class executor:
 
32
    '''this class runs stuff for us'''
 
33
    def __init__(self, logger):
 
34
        self.logger = logger
 
35
        
 
36
    def run(self, command):
 
37
        '''Run a command in a pipe dealing with stdout buffer overloads'''
 
38
        self.logger.debug('> %s' % command)
 
39
 
 
40
        runpipe = readonlypipe(command, bufsize=16384)
 
41
        output = ''
 
42
        cmdstat = -1
 
43
        while cmdstat == -1:
 
44
            runpipe.fromchild.flush()
 
45
            moreOutput = runpipe.fromchild.read()
 
46
            if len(moreOutput) > 0:                
 
47
                self.logger.debug('< %s' % moreOutput)
 
48
            output += moreOutput
 
49
            cmdstat = runpipe.poll()
 
50
 
 
51
        return (cmdstat, [line for line in output.split('\n') if line])
 
52
 
 
53
class Tool:
 
54
    '''All tools subclass this. It defines all interfaces that need to be defined'''
 
55
    __name__ = 'Tool'
 
56
    __execs__ = []
 
57
    __handles__ = []
 
58
    __req__ = {}
 
59
    __important__ = []
 
60
    
 
61
    def __init__(self, logger, setup, config, states):
 
62
        self.setup = setup
 
63
        self.logger = logger
 
64
        if not hasattr(self, '__ireq__'):
 
65
            self.__ireq__ = self.__req__
 
66
        self.config = config
 
67
        self.cmd = executor(logger)
 
68
        self.states = states
 
69
        self.modified = []
 
70
        self.extra = []
 
71
        self.handled = [entry for struct in self.config for entry in struct \
 
72
                        if self.handlesEntry(entry)]
 
73
        for filename in self.__execs__:
 
74
            try:
 
75
                mode = stat.S_IMODE(os.stat(filename)[stat.ST_MODE])
 
76
                if mode & stat.S_IEXEC != stat.S_IEXEC:
 
77
                    self.logger.debug("%s: %s not executable" % \
 
78
                                      (self.__name__, filename))
 
79
                    raise toolInstantiationError
 
80
            except OSError:
 
81
                raise toolInstantiationError
 
82
            except:
 
83
                self.logger.debug("%s failed" % filename, exc_info=1)
 
84
                raise toolInstantiationError
 
85
 
 
86
    def BundleUpdated(self, _):
 
87
        '''This callback is used when bundle updates occur'''
 
88
        pass
 
89
 
 
90
    def Inventory(self, structures=[]):
 
91
        '''Dispatch verify calls to underlying methods'''
 
92
        if not structures:
 
93
            structures = self.config.getchildren()
 
94
        for (struct, entry) in [(struct, entry) for struct in structures \
 
95
                                for entry in struct.getchildren() \
 
96
                                if self.canVerify(entry)]:
 
97
            try:
 
98
                func = getattr(self, "Verify%s" % (entry.tag))
 
99
                self.states[entry] = func(entry, self.buildModlist(entry, struct))
 
100
            except:
 
101
                self.logger.error(
 
102
                    "Unexpected failure of verification method for entry type %s" \
 
103
                    % (entry.tag), exc_info=1)
 
104
        self.extra = self.FindExtra()
 
105
 
 
106
    def Install(self, entries):
 
107
        '''Install all entries in sublist'''
 
108
        for entry in entries:
 
109
            try:
 
110
                func = getattr(self, "Install%s" % (entry.tag))
 
111
                self.states[entry] = func(entry)
 
112
                self.modified.append(entry)
 
113
            except:
 
114
                self.logger.error("Unexpected failure of install method for entry type %s" \
 
115
                                  % (entry.tag), exc_info=1)
 
116
 
 
117
    def Remove(self, entries):
 
118
        '''Remove specified extra entries'''
 
119
        pass
 
120
 
 
121
    def getSupportedEntries(self):
 
122
        '''return a list of supported entries'''
 
123
        return [entry for struct in self.config.getchildren() for entry in struct.getchildren() \
 
124
                if self.handlesEntry(entry)]
 
125
    
 
126
    def handlesEntry(self, entry):
 
127
        '''return if entry is handled by this Tool'''
 
128
        return (entry.tag, entry.get('type')) in self.__handles__
 
129
 
 
130
    def buildModlist(self, entry, struct):
 
131
        '''Build a list of potentially modified POSIX paths for this entry'''
 
132
        if entry.tag != 'Package' or struct.tag != 'Bundle':
 
133
            return []
 
134
        return [sentry.get('name') for sentry in struct if sentry.tag in \
 
135
                ['ConfigFile', 'SymLink', 'Directory']]
 
136
 
 
137
    def canVerify(self, entry):
 
138
        '''test if entry has enough information to be verified'''
 
139
        if not self.handlesEntry(entry):
 
140
            return False
 
141
        
 
142
        if [attr for attr in self.__req__[entry.tag] if attr not in entry.attrib]:
 
143
            self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
 
144
                              % (entry.tag, entry.get('name')))
 
145
            return False
 
146
        return True
 
147
 
 
148
 
 
149
    def FindExtra(self):
 
150
        '''Return a list of extra entries'''
 
151
        return []
 
152
 
 
153
    def canInstall(self, entry):
 
154
        '''test if entry has enough information to be installed'''
 
155
        if not self.handlesEntry(entry):
 
156
            return False
 
157
        if [attr for attr in self.__ireq__[entry.tag] if attr not in entry.attrib]:
 
158
            self.logger.error("Incomplete information for entry %s:%s; cannot install" \
 
159
                              % (entry.tag, entry.get('name')))
 
160
            return False
 
161
        return True
 
162
    
 
163
class PkgTool(Tool):
 
164
    '''PkgTool provides a one-pass install with fallback for use with packaging systems'''
 
165
    pkgtool = ('echo %s', ('%s', ['name']))
 
166
    pkgtype = 'echo'
 
167
    __name__ = 'PkgTool'
 
168
 
 
169
    def __init__(self, logger, setup, config, states):
 
170
        Tool.__init__(self, logger, setup, config, states)
 
171
        self.installed = {}
 
172
        self.Remove = self.RemovePackages
 
173
        self.FindExtra = self.FindExtraPackages
 
174
        self.RefreshPackages()
 
175
 
 
176
    def VerifyPackage(self, dummy, _):
 
177
        '''Dummy verification method'''
 
178
        return False
 
179
    
 
180
    def Install(self, packages):
 
181
        '''Run a one-pass install, followed by single pkg installs in case of failure'''
 
182
        self.logger.info("Trying single pass package install for pkgtype %s" % \
 
183
                         self.pkgtype)
 
184
 
 
185
        data = [tuple([pkg.get(field) for field in self.pkgtool[1][1]]) for pkg in packages]
 
186
        pkgargs = " ".join([self.pkgtool[1][0] % datum for datum in data])
 
187
 
 
188
        self.logger.debug("Installing packages: :%s:" % pkgargs)
 
189
        self.logger.debug("Running command ::%s::" % (self.pkgtool[0] % pkgargs))
 
190
 
 
191
        cmdrc = self.cmd.run(self.pkgtool[0] % pkgargs)[0]
 
192
        if cmdrc == 0:
 
193
            self.logger.info("Single Pass Succeded")
 
194
            # set all package states to true and flush workqueues
 
195
            pkgnames = [pkg.get('name') for pkg in packages]
 
196
            for entry in [entry for entry in self.states.keys()
 
197
                          if entry.tag == 'Package' and entry.get('type') == self.pkgtype
 
198
                          and entry.get('name') in pkgnames]:
 
199
                self.logger.debug('Setting state to true for pkg %s' % (entry.get('name')))
 
200
                self.states[entry] = True
 
201
            self.RefreshPackages()
 
202
        else:
 
203
            self.logger.error("Single Pass Failed")
 
204
            # do single pass installs
 
205
            self.RefreshPackages()
 
206
            for pkg in packages:
 
207
                # handle state tracking updates
 
208
                if self.VerifyPackage(pkg, []):
 
209
                    self.logger.info("Forcing state to true for pkg %s" % (pkg.get('name')))
 
210
                    self.states[pkg] = True
 
211
                else:
 
212
                    self.logger.info("Installing pkg %s version %s" %
 
213
                                     (pkg.get('name'), pkg.get('version')))
 
214
                    cmdrc = self.cmd.run(self.pkgtool[0] %
 
215
                                         (self.pkgtool[1][0] %
 
216
                                          tuple([pkg.get(field) for field in self.pkgtool[1][1]])))
 
217
                    if cmdrc[0] == 0:
 
218
                        self.states[pkg] = True
 
219
                    else:
 
220
                        self.logger.error("Failed to install package %s" % (pkg.get('name')))
 
221
        for entry in [ent for ent in packages if self.states[ent]]:
 
222
            self.modified.append(entry)
 
223
 
 
224
    def RefreshPackages(self):
 
225
        '''Dummy state refresh method'''
 
226
        pass
 
227
 
 
228
    def RemovePackages(self, packages):
 
229
        '''Dummy implementation of package removal method'''
 
230
        pass
 
231
 
 
232
    def FindExtraPackages(self):
 
233
        '''Find extra packages'''
 
234
        packages = [entry.get('name') for entry in self.getSupportedEntries()]
 
235
        extras = [key for key in self.installed if key not in packages]
 
236
        return [Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype) \
 
237
                for name in extras]
 
238
 
 
239
class SvcTool(Tool):
 
240
    '''This class defines basic Service behavior'''
 
241
    __name__ = 'SvcTool'
 
242
 
 
243
    def BundleUpdated(self, bundle):
 
244
        '''The Bundle has been updated'''
 
245
        for entry in bundle:
 
246
            if self.handlesEntry(entry):
 
247
                if entry.get('status') == 'on':
 
248
                    self.logger.debug('Restarting service %s' % entry.get('name'))
 
249
                    self.cmd.run('/etc/init.d/%s %s' % \
 
250
                                 (entry.get('name'), entry.get('reload', 'reload')))
 
251
                else:
 
252
                    self.logger.debug('Stopping service %s' % entry.get('name'))
 
253
                    self.cmd.run('/etc/init.d/%s stop' %  (entry.get('name')))