Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_kvm.py @ 50cb2e2a

History | View | Annotate | Download (20.6 kB)

1
#
2
#
3

    
4
# Copyright (C) 2008 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""KVM hypervisor
23

24
"""
25

    
26
import os
27
import os.path
28
import re
29
import tempfile
30
import time
31
import logging
32
from cStringIO import StringIO
33

    
34
from ganeti import utils
35
from ganeti import constants
36
from ganeti import errors
37
from ganeti import serializer
38
from ganeti import objects
39
from ganeti.hypervisor import hv_base
40

    
41

    
42
class KVMHypervisor(hv_base.BaseHypervisor):
43
  """KVM hypervisor interface"""
44

    
45
  _ROOT_DIR = constants.RUN_GANETI_DIR + "/kvm-hypervisor"
46
  _PIDS_DIR = _ROOT_DIR + "/pid" # contains live instances pids
47
  _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets
48
  _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data
49
  _DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR, _CONF_DIR]
50

    
51
  PARAMETERS = [
52
    constants.HV_KERNEL_PATH,
53
    constants.HV_INITRD_PATH,
54
    constants.HV_ACPI,
55
    ]
56

    
57
  _MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
58
                                    re.M | re.I)
59

    
60
  def __init__(self):
61
    hv_base.BaseHypervisor.__init__(self)
62
    # Let's make sure the directories we need exist, even if the RUN_DIR lives
63
    # in a tmpfs filesystem or has been otherwise wiped out.
64
    for mydir in self._DIRS:
65
      if not os.path.exists(mydir):
66
        os.mkdir(mydir)
67

    
68
  def _InstancePidAlive(self, instance_name):
69
    """Returns the instance pid and pidfile
70

71
    """
72
    pidfile = "%s/%s" % (self._PIDS_DIR, instance_name)
73
    pid = utils.ReadPidFile(pidfile)
74
    alive = utils.IsProcessAlive(pid)
75

    
76
    return (pidfile, pid, alive)
77

    
78
  def _InstanceMonitor(self, instance_name):
79
    """Returns the instance monitor socket name
80

81
    """
82
    return '%s/%s.monitor' % (self._CTRL_DIR, instance_name)
83

    
84
  def _InstanceSerial(self, instance_name):
85
    """Returns the instance serial socket name
86

87
    """
88
    return '%s/%s.serial' % (self._CTRL_DIR, instance_name)
89

    
90
  def _InstanceKVMRuntime(self, instance_name):
91
    """Returns the instance KVM runtime filename
92

93
    """
94
    return '%s/%s.runtime' % (self._CONF_DIR, instance_name)
95

    
96
  def _WriteNetScript(self, instance, seq, nic):
97
    """Write a script to connect a net interface to the proper bridge.
98

99
    This can be used by any qemu-type hypervisor.
100

101
    @param instance: instance we're acting on
102
    @type instance: instance object
103
    @param seq: nic sequence number
104
    @type seq: int
105
    @param nic: nic we're acting on
106
    @type nic: nic object
107
    @return: netscript file name
108
    @rtype: string
109

110
    """
111
    script = StringIO()
112
    script.write("#!/bin/sh\n")
113
    script.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
114
    script.write("export INSTANCE=%s\n" % instance.name)
115
    script.write("export MAC=%s\n" % nic.mac)
116
    script.write("export IP=%s\n" % nic.ip)
117
    script.write("export BRIDGE=%s\n" % nic.bridge)
118
    script.write("export INTERFACE=$1\n")
119
    # TODO: make this configurable at ./configure time
120
    script.write("if [ -x /etc/ganeti/kvm-vif-bridge ]; then\n")
121
    script.write("  # Execute the user-specific vif file\n")
122
    script.write("  /etc/ganeti/kvm-vif-bridge\n")
123
    script.write("else\n")
124
    script.write("  # Connect the interface to the bridge\n")
125
    script.write("  /sbin/ifconfig $INTERFACE 0.0.0.0 up\n")
126
    script.write("  /usr/sbin/brctl addif $BRIDGE $INTERFACE\n")
127
    script.write("fi\n\n")
128
    # As much as we'd like to put this in our _ROOT_DIR, that will happen to be
129
    # mounted noexec sometimes, so we'll have to find another place.
130
    (tmpfd, tmpfile_name) = tempfile.mkstemp()
131
    tmpfile = os.fdopen(tmpfd, 'w')
132
    tmpfile.write(script.getvalue())
133
    tmpfile.close()
134
    os.chmod(tmpfile_name, 0755)
135
    return tmpfile_name
136

    
137
  def ListInstances(self):
138
    """Get the list of running instances.
139

140
    We can do this by listing our live instances directory and
141
    checking whether the associated kvm process is still alive.
142

143
    """
144
    result = []
145
    for name in os.listdir(self._PIDS_DIR):
146
      filename = "%s/%s" % (self._PIDS_DIR, name)
147
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
148
        result.append(name)
149
    return result
150

    
151
  def GetInstanceInfo(self, instance_name):
152
    """Get instance properties.
153

154
    @param instance_name: the instance name
155

156
    @return: tuple (name, id, memory, vcpus, stat, times)
157

158
    """
159
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
160
    if not alive:
161
      return None
162

    
163
    cmdline_file = "/proc/%s/cmdline" % pid
164
    try:
165
      fh = open(cmdline_file, 'r')
166
      try:
167
        cmdline = fh.read()
168
      finally:
169
        fh.close()
170
    except EnvironmentError, err:
171
      raise errors.HypervisorError("Failed to list instance %s: %s" %
172
                                   (instance_name, err))
173

    
174
    memory = 0
175
    vcpus = 0
176
    stat = "---b-"
177
    times = "0"
178

    
179
    arg_list = cmdline.split('\x00')
180
    while arg_list:
181
      arg =  arg_list.pop(0)
182
      if arg == '-m':
183
        memory = arg_list.pop(0)
184
      elif arg == '-smp':
185
        vcpus = arg_list.pop(0)
186

    
187
    return (instance_name, pid, memory, vcpus, stat, times)
188

    
189
  def GetAllInstancesInfo(self):
190
    """Get properties of all instances.
191

192
    @return: list of tuples (name, id, memory, vcpus, stat, times)
193

194
    """
195
    data = []
196
    for name in os.listdir(self._PIDS_DIR):
197
      filename = "%s/%s" % (self._PIDS_DIR, name)
198
      if utils.IsProcessAlive(utils.ReadPidFile(filename)):
199
        data.append(self.GetInstanceInfo(name))
200

    
201
    return data
202

    
203
  def _GenerateKVMRuntime(self, instance, block_devices, extra_args):
204
    """Generate KVM information to start an instance.
205

206
    """
207
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
208
    kvm = constants.KVM_PATH
209
    kvm_cmd = [kvm]
210
    kvm_cmd.extend(['-m', instance.beparams[constants.BE_MEMORY]])
211
    kvm_cmd.extend(['-smp', instance.beparams[constants.BE_VCPUS]])
212
    kvm_cmd.extend(['-pidfile', pidfile])
213
    # used just by the vnc server, if enabled
214
    kvm_cmd.extend(['-name', instance.name])
215
    kvm_cmd.extend(['-daemonize'])
216
    if not instance.hvparams[constants.HV_ACPI]:
217
      kvm_cmd.extend(['-no-acpi'])
218

    
219
    boot_drive = True
220
    for cfdev, dev_path in block_devices:
221
      # TODO: handle FD_LOOP and FD_BLKTAP (?)
222
      if boot_drive:
223
        boot_val = ',boot=on'
224
        boot_drive = False
225
      else:
226
        boot_val = ''
227

    
228
      # TODO: handle different if= types
229
      if_val = ',if=virtio'
230

    
231
      drive_val = 'file=%s,format=raw%s%s' % (dev_path, if_val, boot_val)
232
      kvm_cmd.extend(['-drive', drive_val])
233

    
234
    kvm_cmd.extend(['-kernel', instance.hvparams[constants.HV_KERNEL_PATH]])
235

    
236
    initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
237
    if initrd_path:
238
      kvm_cmd.extend(['-initrd', initrd_path])
239

    
240
    kvm_cmd.extend(['-append', 'console=ttyS0,38400 root=/dev/vda'])
241

    
242
    #"hvm_boot_order",
243
    #"hvm_cdrom_image_path",
244

    
245
    kvm_cmd.extend(['-nographic'])
246
    # FIXME: handle vnc, if needed
247
    # How do we decide whether to have it or not?? :(
248
    #"vnc_bind_address",
249
    #"network_port"
250
    monitor_dev = 'unix:%s,server,nowait' % \
251
      self._InstanceMonitor(instance.name)
252
    kvm_cmd.extend(['-monitor', monitor_dev])
253
    serial_dev = 'unix:%s,server,nowait' % self._InstanceSerial(instance.name)
254
    kvm_cmd.extend(['-serial', serial_dev])
255

    
256
    # Save the current instance nics, but defer their expansion as parameters,
257
    # as we'll need to generate executable temp files for them.
258
    kvm_nics = instance.nics
259

    
260
    return (kvm_cmd, kvm_nics)
261

    
262
  def _WriteKVMRuntime(self, instance_name, data):
263
    """Write an instance's KVM runtime
264

265
    """
266
    try:
267
      utils.WriteFile(self._InstanceKVMRuntime(instance_name),
268
                      data=data)
269
    except EnvironmentError, err:
270
      raise errors.HypervisorError("Failed to save KVM runtime file: %s" % err)
271

    
272
  def _ReadKVMRuntime(self, instance_name):
273
    """Read an instance's KVM runtime
274

275
    """
276
    try:
277
      file_content = utils.ReadFile(self._InstanceKVMRuntime(instance_name))
278
    except EnvironmentError, err:
279
      raise errors.HypervisorError("Failed to load KVM runtime file: %s" % err)
280
    return file_content
281

    
282
  def _SaveKVMRuntime(self, instance, kvm_runtime):
283
    """Save an instance's KVM runtime
284

285
    """
286
    kvm_cmd, kvm_nics = kvm_runtime
287
    serialized_nics = [nic.ToDict() for nic in kvm_nics]
288
    serialized_form = serializer.Dump((kvm_cmd, serialized_nics))
289
    self._WriteKVMRuntime(instance.name, serialized_form)
290

    
291
  def _LoadKVMRuntime(self, instance, serialized_runtime=None):
292
    """Load an instance's KVM runtime
293

294
    """
295
    if not serialized_runtime:
296
      serialized_runtime = self._ReadKVMRuntime(instance.name)
297
    loaded_runtime = serializer.Load(serialized_runtime)
298
    kvm_cmd, serialized_nics = loaded_runtime
299
    kvm_nics = [objects.NIC.FromDict(snic) for snic in serialized_nics]
300
    return (kvm_cmd, kvm_nics)
301

    
302
  def _ExecuteKVMRuntime(self, instance, kvm_runtime, incoming=None):
303
    """Execute a KVM cmd, after completing it with some last minute data
304

305
    @type incoming: tuple of strings
306
    @param incoming: (target_host_ip, port)
307

308
    """
309
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
310
    if alive:
311
      raise errors.HypervisorError("Failed to start instance %s: %s" %
312
                                   (instance.name, "already running"))
313

    
314
    temp_files = []
315

    
316
    kvm_cmd, kvm_nics = kvm_runtime
317

    
318
    if not kvm_nics:
319
      kvm_cmd.extend(['-net', 'none'])
320
    else:
321
      for nic_seq, nic in enumerate(kvm_nics):
322
        nic_val = "nic,macaddr=%s,model=virtio" % nic.mac
323
        script = self._WriteNetScript(instance, nic_seq, nic)
324
        kvm_cmd.extend(['-net', nic_val])
325
        kvm_cmd.extend(['-net', 'tap,script=%s' % script])
326
        temp_files.append(script)
327

    
328
    if incoming:
329
      target, port = incoming
330
      kvm_cmd.extend(['-incoming', 'tcp:%s:%s' % (target, port)])
331

    
332
    result = utils.RunCmd(kvm_cmd)
333
    if result.failed:
334
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
335
                                   (instance.name, result.fail_reason,
336
                                    result.output))
337

    
338
    if not utils.IsProcessAlive(utils.ReadPidFile(pidfile)):
339
      raise errors.HypervisorError("Failed to start instance %s: %s" %
340
                                   (instance.name))
341

    
342
    for filename in temp_files:
343
      utils.RemoveFile(filename)
344

    
345
  def StartInstance(self, instance, block_devices, extra_args):
346
    """Start an instance.
347

348
    """
349
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
350
    if alive:
351
      raise errors.HypervisorError("Failed to start instance %s: %s" %
352
                                   (instance.name, "already running"))
353

    
354
    kvm_runtime = self._GenerateKVMRuntime(instance, block_devices, extra_args)
355
    self._SaveKVMRuntime(instance, kvm_runtime)
356
    self._ExecuteKVMRuntime(instance, kvm_runtime)
357

    
358
  def _CallMonitorCommand(self, instance_name, command):
359
    """Invoke a command on the instance monitor.
360

361
    """
362
    socat = ("echo %s | %s STDIO UNIX-CONNECT:%s" %
363
             (utils.ShellQuote(command),
364
              constants.SOCAT_PATH,
365
              utils.ShellQuote(self._InstanceMonitor(instance_name))))
366
    result = utils.RunCmd(socat)
367
    if result.failed:
368
      msg = ("Failed to send command '%s' to instance %s."
369
             " output: %s, error: %s, fail_reason: %s" %
370
             (instance.name, result.stdout, result.stderr, result.fail_reason))
371
      raise errors.HypervisorError(msg)
372

    
373
    return result
374

    
375
  def _RetryInstancePowerdown(self, instance, pid, timeout=30):
376
    """Wait for an instance  to power down.
377

378
    """
379
    # Wait up to $timeout seconds
380
    end = time.time() + timeout
381
    wait = 1
382
    while time.time() < end and utils.IsProcessAlive(pid):
383
      self._CallMonitorCommand(instance.name, 'system_powerdown')
384
      time.sleep(wait)
385
      # Make wait time longer for next try
386
      if wait < 5:
387
        wait *= 1.3
388

    
389
  def StopInstance(self, instance, force=False):
390
    """Stop an instance.
391

392
    """
393
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
394
    if pid > 0 and alive:
395
      if force or not instance.hvparams[constants.HV_ACPI]:
396
        utils.KillProcess(pid)
397
      else:
398
        self._RetryInstancePowerdown(instance, pid)
399

    
400
    if not utils.IsProcessAlive(pid):
401
      utils.RemoveFile(pidfile)
402
      utils.RemoveFile(self._InstanceMonitor(instance.name))
403
      utils.RemoveFile(self._InstanceSerial(instance.name))
404
      utils.RemoveFile(self._InstanceKVMRuntime(instance.name))
405
      return True
406
    else:
407
      return False
408

    
409
  def RebootInstance(self, instance):
410
    """Reboot an instance.
411

412
    """
413
    # For some reason if we do a 'send-key ctrl-alt-delete' to the control
414
    # socket the instance will stop, but now power up again. So we'll resort
415
    # to shutdown and restart.
416
    pidfile, pid, alive = self._InstancePidAlive(instance.name)
417
    if not alive:
418
      raise errors.HypervisorError("Failed to reboot instance %s: not running" %
419
                                             (instance.name))
420
    # StopInstance will delete the saved KVM runtime so:
421
    # ...first load it...
422
    kvm_runtime = self._LoadKVMRuntime(instance)
423
    # ...now we can safely call StopInstance...
424
    if not self.StopInstance(instance):
425
      self.StopInstance(instance, force=True)
426
    # ...and finally we can save it again, and execute it...
427
    self._SaveKVMRuntime(instance, kvm_runtime)
428
    self._ExecuteKVMRuntime(instance, kvm_runtime)
429

    
430
  def MigrationInfo(self, instance):
431
    """Get instance information to perform a migration.
432

433
    @type instance: L{objects.Instance}
434
    @param instance: instance to be migrated
435
    @rtype: string
436
    @return: content of the KVM runtime file
437

438
    """
439
    return self._ReadKVMRuntime(instance.name)
440

    
441
  def AcceptInstance(self, instance, info, target):
442
    """Prepare to accept an instance.
443

444
    @type instance: L{objects.Instance}
445
    @param instance: instance to be accepted
446
    @type info: string
447
    @param info: content of the KVM runtime file on the source node
448
    @type target: string
449
    @param target: target host (usually ip), on this node
450

451
    """
452
    kvm_runtime = self._LoadKVMRuntime(instance, serialized_runtime=info)
453
    incoming_address = (target, constants.KVM_MIGRATION_PORT)
454
    self._ExecuteKVMRuntime(instance, kvm_runtime, incoming=incoming_address)
455

    
456
  def FinalizeMigration(self, instance, info, success):
457
    """Finalize an instance migration.
458

459
    Stop the incoming mode KVM.
460

461
    @type instance: L{objects.Instance}
462
    @param instance: instance whose migration is being aborted
463

464
    """
465
    if success:
466
      self._WriteKVMRuntime(instance.name, info)
467
    else:
468
      self.StopInstance(instance, force=True)
469

    
470
  def MigrateInstance(self, instance_name, target, live):
471
    """Migrate an instance to a target node.
472

473
    The migration will not be attempted if the instance is not
474
    currently running.
475

476
    @type instance_name: string
477
    @param instance_name: name of the instance to be migrated
478
    @type target: string
479
    @param target: ip address of the target node
480
    @type live: boolean
481
    @param live: perform a live migration
482

483
    """
484
    pidfile, pid, alive = self._InstancePidAlive(instance_name)
485
    if not alive:
486
      raise errors.HypervisorError("Instance not running, cannot migrate")
487

    
488
    if not live:
489
      self._CallMonitorCommand(instance_name, 'stop')
490

    
491
    migrate_command = ('migrate -d tcp:%s:%s' %
492
                       (target, constants.KVM_MIGRATION_PORT))
493
    self._CallMonitorCommand(instance_name, migrate_command)
494

    
495
    info_command = 'info migrate'
496
    done = False
497
    while not done:
498
      result = self._CallMonitorCommand(instance_name, info_command)
499
      match = self._MIGRATION_STATUS_RE.search(result.stdout)
500
      if not match:
501
        raise errors.HypervisorError("Unknown 'info migrate' result: %s" %
502
                                     result.stdout)
503
      else:
504
        status = match.group(1)
505
        if status == 'completed':
506
          done = True
507
        elif status == 'active':
508
          time.sleep(2)
509
        elif status == 'failed' or status == 'cancelled':
510
          if not live:
511
            self._CallMonitorCommand(instance_name, 'cont')
512
          raise errors.HypervisorError("Migration %s at the kvm level" %
513
                                       status)
514
        else:
515
          logging.info("KVM: unknown migration status '%s'" % status)
516
          time.sleep(2)
517

    
518
    utils.KillProcess(pid)
519
    utils.RemoveFile(pidfile)
520
    utils.RemoveFile(self._InstanceMonitor(instance_name))
521
    utils.RemoveFile(self._InstanceSerial(instance_name))
522
    utils.RemoveFile(self._InstanceKVMRuntime(instance_name))
523

    
524
  def GetNodeInfo(self):
525
    """Return information about the node.
526

527
    @return: a dict with the following keys (values in MiB):
528
          - memory_total: the total memory size on the node
529
          - memory_free: the available memory on the node for instances
530
          - memory_dom0: the memory used by the node itself, if available
531

532
    """
533
    # global ram usage from the xm info command
534
    # memory                 : 3583
535
    # free_memory            : 747
536
    # note: in xen 3, memory has changed to total_memory
537
    try:
538
      fh = file("/proc/meminfo")
539
      try:
540
        data = fh.readlines()
541
      finally:
542
        fh.close()
543
    except EnvironmentError, err:
544
      raise errors.HypervisorError("Failed to list node info: %s" % err)
545

    
546
    result = {}
547
    sum_free = 0
548
    for line in data:
549
      splitfields = line.split(":", 1)
550

    
551
      if len(splitfields) > 1:
552
        key = splitfields[0].strip()
553
        val = splitfields[1].strip()
554
        if key == 'MemTotal':
555
          result['memory_total'] = int(val.split()[0])/1024
556
        elif key in ('MemFree', 'Buffers', 'Cached'):
557
          sum_free += int(val.split()[0])/1024
558
        elif key == 'Active':
559
          result['memory_dom0'] = int(val.split()[0])/1024
560
    result['memory_free'] = sum_free
561

    
562
    cpu_total = 0
563
    try:
564
      fh = open("/proc/cpuinfo")
565
      try:
566
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
567
                                   fh.read()))
568
      finally:
569
        fh.close()
570
    except EnvironmentError, err:
571
      raise errors.HypervisorError("Failed to list node info: %s" % err)
572
    result['cpu_total'] = cpu_total
573

    
574
    return result
575

    
576
  @staticmethod
577
  def GetShellCommandForConsole(instance):
578
    """Return a command for connecting to the console of an instance.
579

580
    """
581
    # TODO: we can either try the serial socket or suggest vnc
582
    return "echo Console not available for the kvm hypervisor yet"
583

    
584
  def Verify(self):
585
    """Verify the hypervisor.
586

587
    Check that the binary exists.
588

589
    """
590
    if not os.path.exists(constants.KVM_PATH):
591
      return "The kvm binary ('%s') does not exist." % constants.KVM_PATH
592
    if not os.path.exists(constants.SOCAT_PATH):
593
      return "The socat binary ('%s') does not exist." % constants.SOCAT_PATH
594

    
595

    
596
  @classmethod
597
  def CheckParameterSyntax(cls, hvparams):
598
    """Check the given parameters for validity.
599

600
    For the KVM hypervisor, this only check the existence of the
601
    kernel.
602

603
    @type hvparams:  dict
604
    @param hvparams: dictionary with parameter names/value
605
    @raise errors.HypervisorError: when a parameter is not valid
606

607
    """
608
    super(KVMHypervisor, cls).CheckParameterSyntax(hvparams)
609

    
610
    if not hvparams[constants.HV_KERNEL_PATH]:
611
      raise errors.HypervisorError("Need a kernel for the instance")
612

    
613
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
614
      raise errors.HypervisorError("The kernel path must be an absolute path")
615

    
616
    if hvparams[constants.HV_INITRD_PATH]:
617
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
618
        raise errors.HypervisorError("The initrd path must be an absolute path"
619
                                     ", if defined")
620

    
621
  def ValidateParameters(self, hvparams):
622
    """Check the given parameters for validity.
623

624
    For the KVM hypervisor, this checks the existence of the
625
    kernel.
626

627
    """
628
    super(KVMHypervisor, self).ValidateParameters(hvparams)
629

    
630
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
631
    if not os.path.isfile(kernel_path):
632
      raise errors.HypervisorError("Instance kernel '%s' not found or"
633
                                   " not a file" % kernel_path)
634
    initrd_path = hvparams[constants.HV_INITRD_PATH]
635
    if initrd_path and not os.path.isfile(initrd_path):
636
      raise errors.HypervisorError("Instance initrd '%s' not found or"
637
                                   " not a file" % initrd_path)