summaryrefslogtreecommitdiffstats
path: root/pyanaconda/dispatch.py
blob: ef42f145b3216a96efe76074e1a7c5f306ca5bbd (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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
#
# dispatch.py: install/upgrade master flow control
#
# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006  Red Hat, Inc.
# All rights reserved.
#
# 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, see <http://www.gnu.org/licenses/>.
#
# Author(s): Erik Troan <ewt@redhat.com>
#

import string
from types import *

import indexed_dict
import errors
from constants import *
from packages import writeKSConfiguration, turnOnFilesystems
from packages import doPostAction
from packages import copyAnacondaLogs
from packages import firstbootConfiguration
from packages import betaNagScreen
from packages import setupTimezone
from packages import setFileCons
from storage import storageInitialize
from storage import storageComplete
from storage.partitioning import doAutoPartition
from bootloader import writeBootloader
from flags import flags
from upgrade import upgradeMountFilesystems
from upgrade import restoreTime
from upgrade import upgradeSwapSuggestion, upgradeMigrateFind
from upgrade import findRootParts, queryUpgradeContinue
from installmethod import doMethodComplete
from kickstart import doKickstart, runPostScripts
from sshd import doSshd
from rescue import doRescue

from backend import doPostSelection, doBackendSetup, doBasePackageSelect
from backend import doPreInstall, doPostInstall, doInstall
from backend import writeConfiguration

from packages import doReIPL

import logging
log = logging.getLogger("anaconda")

class Step(object):
    SCHED_UNSCHEDULED = 0
    SCHED_SCHEDULED = 1 # will execute if not explicitly skipped
    SCHED_SKIPPED = 2   # is never going to execute
    SCHED_REQUESTED = 3 # will execute and can not be skipped
    SCHED_DONE = 4      # done is a final state

    sched_state_machine = [
        # Table of allowed state changes, rows are the current state, columns
        # are what we want to transition into. False when such transition is not
        # allowed.
        # unsch sched  skip   req    done
        [True , True , True , True , True ], # unscheduled
        [False, True , True , True , True ], # scheduled
        [False, False, True , False, False], # skipped
        [False, False, False, True , True ], # requested
        [False, False, False, False, True ]] # done

    def __init__(self, name, target = None):
        """ Dispatcher step object.

            Target is a callable that performs the step (direct step). It
            accepts a sole argument: the anaconda object. If target is None,
            user interface object is told to handle the step (i.e. indirect
            step).
        """
        self.name = name
        self.target = target # None for dynamic target (e.g. gui view)
        self._sched = self.SCHED_UNSCHEDULED

    def _reschedule(self, to_sched):
        new_sched = self.sched_state_machine[self._sched][to_sched]
        if new_sched is False:
            raise errors.DispatchError(
                "Can not reschedule step '%s' from '%s' to '%s'" %
                (self.name,
                 self.namesched(self._sched),
                 self.namesched(to_sched)))
        self._sched = to_sched

    @property
    def direct(self):
        return self.target is not None

    def done(self):
        self._reschedule(self.SCHED_DONE)

    def request(self):
        self._reschedule(self.SCHED_REQUESTED)

    def namesched(self, sched):
        return {
            self.SCHED_UNSCHEDULED : "unscheduled",
            self.SCHED_SCHEDULED   : "scheduled",
            self.SCHED_SKIPPED     : "skipped",
            self.SCHED_REQUESTED   : "requested",
            self.SCHED_DONE        : "done"
            }[sched]

    @property
    def sched(self):
        return self._sched

    def schedule(self):
        self._reschedule(self.SCHED_SCHEDULED)

    def skip(self):
        self._reschedule(self.SCHED_SKIPPED)


class Dispatcher(object):

    def __init__(self, anaconda):
        self.anaconda = anaconda
        self.anaconda.dir = DISPATCH_FORWARD
        self.step = None # name of the current step
        # step dictionary mapping step names to step objects
        self.steps = indexed_dict.IndexedDict()
        # Note that not only a subset of the steps is executed for a particular
        # run, depending on the kind of installation, user selection, kickstart
        # commands, used installclass and used user interface.
        self._add_step("sshd", doSshd)
        self._add_step("rescue", doRescue)
        self._add_step("kickstart", doKickstart)
        self._add_step("language")
        self._add_step("keyboard")
        self._add_step("betanag", betaNagScreen)
        self._add_step("filtertype")
        self._add_step("filter")
        self._add_step("storageinit", storageInitialize)
        self._add_step("findrootparts", findRootParts)
        self._add_step("findinstall")
        self._add_step("network")
        self._add_step("timezone")
        self._add_step("accounts")
        self._add_step("setuptime", setupTimezone)
        self._add_step("parttype")
        self._add_step("cleardiskssel")
        self._add_step("autopartitionexecute", doAutoPartition)
        self._add_step("partition")
        self._add_step("upgrademount", upgradeMountFilesystems)
        self._add_step("restoretime", restoreTime)
        self._add_step("upgradecontinue", queryUpgradeContinue)
        self._add_step("upgradeswapsuggestion", upgradeSwapSuggestion)
        self._add_step("addswap")
        self._add_step("upgrademigfind", upgradeMigrateFind)
        self._add_step("upgrademigratefs")
        self._add_step("storagedone", storageComplete)
        self._add_step("enablefilesystems", turnOnFilesystems)
        self._add_step("upgbootloader")
        self._add_step("bootloader")
        self._add_step("reposetup", doBackendSetup)
        self._add_step("tasksel")
        self._add_step("basepkgsel", doBasePackageSelect)
        self._add_step("group-selection")
        self._add_step("postselection", doPostSelection)
        self._add_step("install")
        self._add_step("preinstallconfig", doPreInstall)
        self._add_step("installpackages", doInstall)
        self._add_step("postinstallconfig", doPostInstall)
        self._add_step("writeconfig", writeConfiguration)
        self._add_step("firstboot", firstbootConfiguration)
        self._add_step("instbootloader", writeBootloader)
        self._add_step("reipl", doReIPL)
        self._add_step("writeksconfig", writeKSConfiguration)
        self._add_step("setfilecon", setFileCons)
        self._add_step("copylogs", copyAnacondaLogs)
        self._add_step("methodcomplete", doMethodComplete)
        self._add_step("postscripts", runPostScripts)
        self._add_step("dopostaction", doPostAction)
        self._add_step("complete")

    def _add_step(self, name, target = None):
        self.steps[name] = Step(name, target)

    def _advance_step(self):
        i = self._step_index()
        self.step = self.steps[i + 1].name

    def _step_index(self):
        return self.steps.index(self.step)

    def done_steps(self, *steps):
        map(lambda s: self.steps[s].done(), steps)

    def go_back(self):
        """
        The caller should make sure canGoBack() is True before calling this
        method.
        """
        self._setDir(DISPATCH_BACK)
        self.dispatch()

    def go_forward(self):
        self._setDir(DISPATCH_FORWARD)
        self.dispatch()

    def can_go_back(self):
        # Begin with the step before this one. If all steps are skipped,
        # we can not go backwards from this one.
        i = self._step_index() - 1
        while i >= 0:
            sname = self.steps[i].name
            if not self.step_is_direct(sname) and self.step_enabled(sname):
                return True
            i -= 1
        return False

    def request_step(self, *steps):
        map(lambda s: self.steps[s].request(), steps)

    def run(self):
        self.anaconda.intf.run(self.anaconda)
        log.info("dispatch: finished.")

    def schedule_steps(self, *steps):
        map(lambda s: self.steps[s].schedule(), steps)

    def step_disabled(self, step):
        """ True if step is not yet scheduled to be run or will never be run
            (i.e. is skipped).
        """
        return not self.step_enabled(step)

    def step_enabled(self, step):
        """ True if step is scheduled to be run or have been run already. """
        return self.steps[step].sched in [Step.SCHED_SCHEDULED,
                                          Step.SCHED_REQUESTED,
                                          Step.SCHED_DONE]

    def skipStep(self, *steps):
        map(lambda s: self.steps[s].skip(), steps)

    def step_is_direct(self, step):
        return self.steps[step].direct

    def dispatch(self):
        total_steps = len(self.steps)
        if self.step == None:
            log.info("dispatch: resetting to the first step.")
            self.step = self.steps[0].name
        else:
            log.info("dispatch: leaving (%d) step %s" %
                     (self.dir, self.step))
            self.done_steps(self.step)
            self._advance_step()

        while True:
            if self._step_index() >= total_steps:
                # installation has proceeded beyond the last step: finished
                self.anaconda.intf.shutdown()
                return
            if self.step_disabled(self.step):
                self._advance_step()
                continue
            log.info("dispatch: moving (%d) to step %s" %
                     (self.dir, self.step))
            if self.stepIsDirect(self.step):
                # handle a direct step by just calling the function
                log.debug("dispatch: %s is a direct step" % self.step)
                self.dir = self.steps[self.step].target(self.anaconda)
            else:
                # handle an indirect step (IOW the user interface has a screen
                # to display to the user):
                rc = self.anaconda.intf.display_step(self.step)
                if rc == DISPATCH_WAITING:
                    # a new screen has been set up and we are waiting for the
                    # user input now (this only ever happens with the GTK UI and
                    # is because we need to get back to gtk.main())
                    return
                elif rc == DISPATCH_DEFAULT:
                    log.debug("dispatch: the interface chose "
                              "not to display step %s." % self.step)
                else:
                    self.dir = rc
            log.info("dispatch: leaving (%d) step %s" %
                     (self.dir, self.step))
            self.done_steps(self.step)
            self._advance_step()

    def _getDir(self):
        return self.anaconda.dir

    def _setDir(self, dir):
        if dir not in [DISPATCH_BACK, DISPATCH_FORWARD, DISPATCH_DEFAULT]:
            raise RuntimeError("dispatch: wrong direction code")
        if dir in [DISPATCH_BACK, DISPATCH_FORWARD]:
            self.anaconda.dir = dir

    dir = property(_getDir,_setDir)