Revision 0db17fcf

b/image_creator/os_type/windows.py
44 44
import os
45 45
import time
46 46
import random
47
import string
47 48
import subprocess
48 49

  
49 50
kvm = get_command('kvm')
50 51

  
52
BOOT_TIMEOUT = 300
53

  
51 54

  
52 55
class Windows(OSBase):
53 56
    """OS class for Windows"""
......
69 72
        if print_header:
70 73
            self.out.output("Disabling IPv6 privacy extensions")
71 74

  
72
        out, err, rc = self._guest_exec(
73
            'netsh interface ipv6 set global randomizeidentifiers=disabled '
74
            'store=persistent')
75

  
76
        if rc != 0:
77
            raise FatalError("Unable to disable IPv6 privacy extensions: %s" %
78
                             err)
75
        self._guest_exec('netsh interface ipv6 set global '
76
                         'randomizeidentifiers=disabled store=persistent')
79 77

  
80 78
    @sysprep(enabled=True)
81 79
    def microsoft_sysprep(self, print_header=True):
82
        """Run the Micorsoft System Preparation Tool on the Image. After this
83
        runs, no other task may run.
80
        """Run the Microsoft System Preparation Tool on the Image. This will
81
        remove system-specific data and will make the image ready to be
82
        deployed. After this no other task may run.
84 83
        """
85 84

  
86 85
        if print_header:
87 86
            self.out.output("Executing sysprep on the image (may take more "
88 87
                            "than 10 minutes)")
89 88

  
90
        out, err, rc = self._guest_exec(r'C:\windows\system32\sysprep\sysprep '
91
                                        r'/quiet /generalize /oobe /shutdown')
89
        self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
90
                         r'/quiet /generalize /oobe /shutdown')
92 91
        self.syspreped = True
93
        if rc != 0:
94
            raise FatalError("Unable to perform sysprep: %s" % err)
95 92

  
96 93
    def do_sysprep(self):
97 94
        """Prepare system for image creation."""
......
107 104
        self.mount(readonly=False)
108 105
        try:
109 106
            disabled_uac = self._update_uac_remote_setting(1)
107
            token = self._enable_os_monitor()
110 108
        finally:
111 109
            self.umount()
112 110

  
......
120 118
            ret = self.g.kill_subprocess()
121 119

  
122 120
        self.out.success('done')
121

  
122
        vm = None
123
        monitor = None
123 124
        try:
124 125
            self.out.output("Starting windows VM ...", False)
125

  
126
            def random_mac():
127
                mac = [0x00, 0x16, 0x3e,
128
                       random.randint(0x00, 0x7f),
129
                       random.randint(0x00, 0xff),
130
                       random.randint(0x00, 0xff)]
131
                return ':'.join(map(lambda x: "%02x" % x, mac))
132

  
133
            vm = kvm('-smp', '1', '-m', '1024', '-drive',
134
                     'file=%s,format=raw,cache=none,if=virtio' %
135
                     self.image.device,
136
                     '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
137
                     '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' %
138
                     random_mac(), '-vnc', ':0', _bg=True)
139
            time.sleep(60)
126
            monitorfd, monitor = tempfile.mkstemp()
127
            os.close(monitorfd)
128
            vm, display = self._create_vm(monitor)
129
            self.out.success("started (console on vnc display: %d)." % display)
130

  
131
            self.out.output("Waiting for OS to boot ...", False)
132
            if not self._wait_on_file(monitor, token):
133
                raise FatalError("Windows booting timed out.")
134
            else:
135
                self.out.success('done')
136

  
137
            self.out.output("Disabling automatic logon ...", False)
138
            self._disable_autologon()
140 139
            self.out.success('done')
141 140

  
141
            self.out.output('Preparing system from image creation:')
142

  
142 143
            tasks = self.list_syspreps()
143 144
            enabled = filter(lambda x: x.enabled, tasks)
144 145

  
......
160 161
                task()
161 162
                setattr(task.im_func, 'executed', True)
162 163

  
164
            self.out.output("Shutting down windows VM ...", False)
163 165
            if not ms_sysprep_enabled:
164 166
                self._shutdown()
167
            self.out.success("done")
165 168

  
166 169
            vm.wait()
167 170
        finally:
168
            if vm.process.alive:
169
                vm.terminate()
171
            if monitor is not None:
172
                os.unlink(monitor)
173

  
174
            if vm is not None:
175
                self._destroy_vm(vm)
170 176

  
171 177
            self.out.output("Relaunching helper VM (may take a while) ...",
172 178
                            False)
......
176 182
        if disabled_uac:
177 183
            self._update_uac_remote_setting(0)
178 184

  
185
    def _create_vm(self, monitor):
186
        """Create a VM with the image attached as the disk
187

  
188
            monitor: a file to be used to monitor when the OS is up
189
        """
190

  
191
        def random_mac():
192
            mac = [0x00, 0x16, 0x3e,
193
                   random.randint(0x00, 0x7f),
194
                   random.randint(0x00, 0xff),
195
                   random.randint(0x00, 0xff)]
196

  
197
            return ':'.join(map(lambda x: "%02x" % x, mac))
198

  
199
        # Use ganeti's VNC port range for a random vnc port
200
        vnc_port = random.randint(11000, 14999)
201
        display = vnc_port - 5900
202

  
203
        vm = kvm('-smp', '1', '-m', '1024', '-drive',
204
                 'file=%s,format=raw,cache=none,if=virtio' % self.image.device,
205
                 '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
206
                 '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' %
207
                 random_mac(), '-vnc', ':%d' % display, '-serial',
208
                 'file:%s' % monitor, _bg=True)
209

  
210
        return vm, display
211

  
212
    def _destroy_vm(self, vm):
213
        """Destroy a VM previously created by _create_vm"""
214
        if vm.process.alive:
215
            vm.terminate()
216

  
179 217
    def _shutdown(self):
180 218
        """Shuts down the windows VM"""
219
        self._guest_exec(r'shutdown /s /t 5')
181 220

  
182
        self.out.output("Shutting down windows VM ...", False)
183
        out, err, rc = self._guest_exec(r'shutdown /s /t 5')
221
    def _wait_on_file(self, fname, msg):
222
        """Wait until a message appears on a file"""
184 223

  
185
        if rc != 0:
186
            raise FatalError("Unable to perform shutdown: %s" % err)
224
        for i in range(BOOT_TIMEOUT):
225
            time.sleep(1)
226
            with open(fname) as f:
227
                for line in f:
228
                    if line.startswith(msg):
229
                        return True
230
        return False
187 231

  
188
        self.out.success('done')
232
    def _disable_autologon(self):
233
        """Disable automatic logon on the windows image"""
234

  
235
        winlogon = \
236
            r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
237

  
238
        self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
239
        self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
240
        self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
189 241

  
190 242
    def _registry_file_path(self, regfile):
191 243
        """Retrieves the case sensitive path to a registry file"""
......
199 251
                             % (regfile, str(e)))
200 252
        return path
201 253

  
254
    def _enable_os_monitor(self):
255
        """Add a script in the registry that will send a random string to the
256
        first serial port when the windows image finishes booting.
257
        """
258

  
259
        token = "".join(random.choice(string.ascii_letters) for x in range(16))
260

  
261
        path = self._registry_file_path('SOFTWARE')
262
        softwarefd, software = tempfile.mkstemp()
263
        try:
264
            os.close(softwarefd)
265
            self.g.download(path, software)
266

  
267
            h = hivex.Hivex(software, write=True)
268

  
269
            # Enable automatic logon.
270
            # This is needed because we need to execute a script that we add in
271
            # the RunOnce registry entry and those programs only get executed
272
            # when a user logs on. There is a RunServicesOnce registry entry
273
            # whose keys get executed in the background when the logon dialog
274
            # box first appears, but they seem to only work with services and
275
            # not arbitrary command line expressions :-(
276
            #
277
            # Instructions on how to turn on automatic logon in Windows can be
278
            # found here: http://support.microsoft.com/kb/324737
279
            #
280
            # Warning: Registry change will not work if the “Logon Banner” is
281
            # defined on the server either by a Group Policy object (GPO) or by
282
            # a local policy.
283

  
284
            winlogon = h.root()
285
            for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
286
                          'Winlogon'):
287
                winlogon = h.node_get_child(winlogon, child)
288

  
289
            h.node_set_value(
290
                winlogon,
291
                {'key': 'DefaultUserName', 't': 1,
292
                 'value': "Administrator".encode('utf-16le')})
293
            h.node_set_value(
294
                winlogon,
295
                {'key': 'DefaultPassword', 't': 1,
296
                 'value':  self.sysprep_params['password'].encode('utf-16le')})
297
            h.node_set_value(
298
                winlogon,
299
                {'key': 'AutoAdminLogon', 't': 1,
300
                 'value': "1".encode('utf-16le')})
301

  
302
            key = h.root()
303
            for child in ('Microsoft', 'Windows', 'CurrentVersion'):
304
                key = h.node_get_child(key, child)
305

  
306
            runonce = h.node_get_child(key, "RunOnce")
307
            if runonce is None:
308
                runonce = h.node_add_child(key, "RunOnce")
309

  
310
            value = (
311
                r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
312
                r'-ExecutionPolicy RemoteSigned '
313
                r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
314
                r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
315
                r'$port.Close()}"').encode('utf-16le')
316

  
317
            h.node_set_value(runonce,
318
                             {'key': "BootMonitor", 't': 1, 'value': value})
319

  
320
            h.commit(None)
321

  
322
            self.g.upload(software, path)
323
        finally:
324
            os.unlink(software)
325

  
326
        return token
327

  
202 328
    def _update_uac_remote_setting(self, value):
203 329
        """Updates the registry key value:
204 330
        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
......
284 410
        # Filter out the guest account
285 411
        return filter(lambda x: x != "Guest", users)
286 412

  
287
    def _guest_exec(self, command):
413
    def _guest_exec(self, command, fatal=True):
414
        """Execute a command on a windows VM"""
415

  
288 416
        user = "Administrator%" + self.sysprep_params['password']
289 417
        addr = 'localhost'
290 418
        runas = '--runas=%s' % user
......
292 420
            ['winexe', '-U', user, "//%s" % addr, runas, command],
293 421
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
294 422

  
295
        result = winexe.communicate()
423
        stdout, stderr = winexe.communicate()
296 424
        rc = winexe.poll()
297 425

  
298
        return (result[0], result[1], rc)
426
        if rc != 0 and fatal:
427
            reason = stderr if len(stderr) else stdout
428
            raise FatalError("Command: `%s' failed. Reason: %s" %
429
                             (command, reason))
430

  
431
        return (stdout, stderr, rc)
299 432

  
300 433
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :

Also available in: Unified diff