Statistics
| Branch: | Tag: | Revision:

root / tools / lvmstrap @ f4ad2ef0

History | View | Annotate | Download (23.7 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 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
"""Program which configures LVM on the Ganeti nodes.
23

    
24
This program wipes disks and creates a volume group on top of them. It
25
can also show disk information to help you decide which disks you want
26
to wipe.
27

    
28
The error handling is done by raising our own exceptions from most of
29
the functions; these exceptions then handled globally in the main()
30
function. The exceptions that each function can raise are not
31
documented individually, since almost every error path ends in a
32
raise.
33

    
34
Another two exceptions that are handled globally are IOError and
35
OSError. The idea behind this is, since we run as root, we should
36
usually not get these errors, but if we do it's most probably a system
37
error, so they should be handled and the user instructed to report
38
them.
39
"""
40

    
41

    
42
import os
43
import sys
44
import optparse
45
import time
46

    
47
from ganeti.utils import RunCmd, ReadFile
48
from ganeti import constants
49
from ganeti import cli
50

    
51
USAGE = ("\tlvmstrap diskinfo\n"
52
         "\tlvmstrap [--vgname=NAME] [--allow-removable]"
53
         " { --alldisks | --disks DISKLIST }"
54
         " create")
55

    
56
verbose_flag = False
57

    
58

    
59
class Error(Exception):
60
  """Generic exception"""
61
  pass
62

    
63

    
64
class ProgrammingError(Error):
65
  """Exception denoting invalid assumptions in programming.
66

    
67
  This should catch sysfs tree changes, or otherwise incorrect
68
  assumptions about the contents of the /sys/block/... directories.
69
  """
70
  pass
71

    
72

    
73
class SysconfigError(Error):
74
  """Exception denoting invalid system configuration.
75

    
76
  If the system configuration is somehow wrong (e.g. /dev files
77
  missing, or having mismatched major/minor numbers relative to
78
  /sys/block devices), this exception will be raised.
79

    
80
  This should usually mean that the installation of the Xen node
81
  failed in some steps.
82
  """
83
  pass
84

    
85

    
86
class PrereqError(Error):
87
  """Exception denoting invalid prerequisites.
88

    
89
  If the node does not meet the requirements for cluster membership, this
90
  exception will be raised. Things like wrong kernel version, or no
91
  free disks, etc. belong here.
92

    
93
  This should usually mean that the build steps for the Xen node were
94
  not followed correctly.
95
  """
96
  pass
97

    
98

    
99
class OperationalError(Error):
100
  """Exception denoting actual errors.
101

    
102
  Errors during the bootstrapping are signaled using this exception.
103
  """
104
  pass
105

    
106

    
107
class ParameterError(Error):
108
  """Exception denoting invalid input from user.
109

    
110
  Wrong disks given as parameters will be signaled using this
111
  exception.
112
  """
113
  pass
114

    
115

    
116
def Usage():
117
  """Shows program usage information and exits the program."""
118

    
119
  print >> sys.stderr, "Usage:"
120
  print >> sys.stderr, USAGE
121
  sys.exit(2)
122

    
123

    
124
def ParseOptions():
125
  """Parses the command line options.
126

    
127
  In case of command line errors, it will show the usage and exit the
128
  program.
129

    
130
  Returns:
131
    (options, args), as returned by OptionParser.parse_args
132
  """
133
  global verbose_flag # pylint: disable-msg=W0603
134

    
135
  parser = optparse.OptionParser(usage="\n%s" % USAGE,
136
                                 version="%%prog (ganeti) %s" %
137
                                 constants.RELEASE_VERSION)
138

    
139
  parser.add_option("--alldisks", dest="alldisks",
140
                    help="erase ALL disks", action="store_true",
141
                    default=False)
142
  parser.add_option("-d", "--disks", dest="disks",
143
                    help="Choose disks (e.g. hda,hdg)",
144
                    metavar="DISKLIST")
145
  parser.add_option(cli.VERBOSE_OPT)
146
  parser.add_option("-r", "--allow-removable",
147
                    action="store_true", dest="removable_ok", default=False,
148
                    help="allow and use removable devices too")
149
  parser.add_option("-g", "--vg-name", type="string",
150
                    dest="vgname", default="xenvg", metavar="NAME",
151
                    help="the volume group to be created [default: xenvg]")
152

    
153

    
154
  options, args = parser.parse_args()
155
  if len(args) != 1:
156
    Usage()
157

    
158
  verbose_flag = options.verbose
159

    
160
  return options, args
161

    
162

    
163
def ExecCommand(command):
164
  """Executes a command.
165

    
166
  This is just a wrapper around commands.getstatusoutput, with the
167
  difference that if the command line argument -v has been given, it
168
  will print the command line and the command output on stdout.
169

    
170
  Args:
171
    the command line
172
  Returns:
173
    (status, output) where status is the exit status and output the
174
      stdout and stderr of the command together
175
  """
176

    
177
  if verbose_flag:
178
    print command
179
  result = RunCmd(command)
180
  if verbose_flag:
181
    print result.output
182
  return result
183

    
184

    
185
def CheckPrereq():
186
  """Check the prerequisites of this program.
187

    
188
  It check that it runs on Linux 2.6, and that /sys is mounted and the
189
  fact that /sys/block is a directory.
190
  """
191

    
192
  if os.getuid() != 0:
193
    raise PrereqError("This tool runs as root only. Really.")
194

    
195
  osname, _, release, _, _ = os.uname()
196
  if osname != 'Linux':
197
    raise PrereqError("This tool only runs on Linux"
198
                      " (detected OS: %s)." % osname)
199

    
200
  if not release.startswith("2.6."):
201
    raise PrereqError("Wrong major kernel version (detected %s, needs"
202
                      " 2.6.*)" % release)
203

    
204
  if not os.path.ismount("/sys"):
205
    raise PrereqError("Can't find a filesystem mounted at /sys."
206
                      " Please mount /sys.")
207

    
208
  if not os.path.isdir("/sys/block"):
209
    raise SysconfigError("Can't find /sys/block directory. Has the"
210
                         " layout of /sys changed?")
211

    
212
  if not os.path.ismount("/proc"):
213
    raise PrereqError("Can't find a filesystem mounted at /proc."
214
                      " Please mount /proc.")
215

    
216
  if not os.path.exists("/proc/mounts"):
217
    raise SysconfigError("Can't find /proc/mounts")
218

    
219

    
220
def CheckVGExists(vgname):
221
  """Checks to see if a volume group exists.
222

    
223
  Args:
224
    vgname: the volume group name
225

    
226
  Returns:
227
    a four-tuple (exists, lv_count, vg_size, vg_free), where:
228
      exists: True if the volume exists, otherwise False; if False,
229
        all other members of the tuple are None
230
      lv_count: The number of logical volumes in the volume group
231
      vg_size: The total size of the volume group (in gibibytes)
232
      vg_free: The available space in the volume group
233
  """
234

    
235
  result = ExecCommand("vgs --nohead -o lv_count,vg_size,vg_free"
236
                       " --nosuffix --units g"
237
                       " --ignorelockingfailure %s" % vgname)
238
  if not result.failed:
239
    try:
240
      lv_count, vg_size, vg_free = result.stdout.strip().split()
241
    except ValueError:
242
      # This means the output of vgdisplay can't be parsed
243
      raise PrereqError("cannot parse output of vgs (%s)" % result.stdout)
244
  else:
245
    lv_count = vg_size = vg_free = None
246

    
247
  return not result.failed, lv_count, vg_size, vg_free
248

    
249

    
250
def CheckSysDev(name, devnum):
251
  """Checks consistency between /sys and /dev trees.
252

    
253
  In /sys/block/<name>/dev and /sys/block/<name>/<part>/dev are the
254
  kernel-known device numbers. The /dev/<name> block/char devices are
255
  created by userspace and thus could differ from the kernel
256
  view. This function checks the consistency between the device number
257
  read from /sys and the actual device number in /dev.
258

    
259
  Note that since the system could be using udev which removes and
260
  recreates the device nodes on partition table rescan, we need to do
261
  some retries here. Since we only do a stat, we can afford to do many
262
  short retries.
263

    
264
  Args:
265
   name: the device name, e.g. 'sda'
266
   devnum: the device number, e.g. 0x803 (2051 in decimal) for sda3
267

    
268
  Returns:
269
    None; failure of the check is signaled by raising a
270
      SysconfigError exception
271
  """
272

    
273
  path = "/dev/%s" % name
274
  for _ in range(40):
275
    if os.path.exists(path):
276
      break
277
    time.sleep(0.250)
278
  else:
279
    raise SysconfigError("the device file %s does not exist, but the block"
280
                         " device exists in the /sys/block tree" % path)
281
  rdev = os.stat(path).st_rdev
282
  if devnum != rdev:
283
    raise SysconfigError("For device %s, the major:minor in /dev is %04x"
284
                         " while the major:minor in sysfs is %s" %
285
                         (path, rdev, devnum))
286

    
287

    
288
def ReadDev(syspath):
289
  """Reads the device number from a sysfs path.
290

    
291
  The device number is given in sysfs under a block device directory
292
  in a file named 'dev' which contains major:minor (in ASCII). This
293
  function reads that file and converts the major:minor pair to a dev
294
  number.
295

    
296
  Args:
297
    syspath: the path to a block device dir in sysfs, e.g. /sys/block/sda
298

    
299
  Returns:
300
    the device number
301
  """
302

    
303
  if not os.path.exists("%s/dev" % syspath):
304
    raise ProgrammingError("Invalid path passed to ReadDev: %s" % syspath)
305
  f = open("%s/dev" % syspath)
306
  data = f.read().strip()
307
  f.close()
308
  major, minor = data.split(":", 1)
309
  major = int(major)
310
  minor = int(minor)
311
  dev = os.makedev(major, minor)
312
  return dev
313

    
314

    
315
def ReadSize(syspath):
316
  """Reads the size from a sysfs path.
317

    
318
  The size is given in sysfs under a block device directory in a file
319
  named 'size' which contains the number of sectors (in ASCII). This
320
  function reads that file and converts the number in sectors to the
321
  size in bytes.
322

    
323
  Args:
324
    syspath: the path to a block device dir in sysfs, e.g. /sys/block/sda
325

    
326
  Returns:
327
    the device size in bytes
328
  """
329

    
330
  if not os.path.exists("%s/size" % syspath):
331
    raise ProgrammingError("Invalid path passed to ReadSize: %s" % syspath)
332
  f = open("%s/size" % syspath)
333
  data = f.read().strip()
334
  f.close()
335
  size = 512L * int(data)
336
  return size
337

    
338

    
339
def ReadPV(name):
340
  """Reads physical volume information.
341

    
342
  This function tries to see if a block device is a physical volume.
343

    
344
  Args:
345
    dev: the device name (e.g. sda)
346
  Returns:
347
    The name of the volume group to which this PV belongs, or
348
    "" if this PV is not in use, or
349
    None if this is not a PV
350
  """
351

    
352
  result = ExecCommand("pvdisplay -c /dev/%s" % name)
353
  if result.failed:
354
    return None
355
  vgname = result.stdout.strip().split(":")[1]
356
  return vgname
357

    
358

    
359
def GetDiskList(opts):
360
  """Computes the block device list for this system.
361

    
362
  This function examines the /sys/block tree and using information
363
  therein, computes the status of the block device.
364

    
365
  Returns:
366
    [(name, size, dev, partitions, inuse), ...]
367
  where:
368
    name is the block device name (e.g. sda)
369
    size the size in bytes
370
    dev  the device number (e.g. 8704 for hdg)
371
    partitions is [(name, size, dev), ...] mirroring the disk list data
372
    inuse is a boolean showing the in-use status of the disk, computed as the
373
      possibility of re-reading the partition table (the meaning of the
374
      operation varies with the kernel version, but is usually accurate;
375
      a mounted disk/partition or swap-area or PV with active LVs on it
376
      is busy)
377
  """
378

    
379
  dlist = []
380
  for name in os.listdir("/sys/block"):
381
    if (not name.startswith("hd") and
382
        not name.startswith("sd") and
383
        not name.startswith("ubd")):
384
      continue
385

    
386
    size = ReadSize("/sys/block/%s" % name)
387

    
388
    f = open("/sys/block/%s/removable" % name)
389
    removable = int(f.read().strip())
390
    f.close()
391

    
392
    if removable and not opts.removable_ok:
393
      continue
394

    
395
    dev = ReadDev("/sys/block/%s" % name)
396
    CheckSysDev(name, dev)
397
    inuse = not CheckReread(name)
398
    # Enumerate partitions of the block device
399
    partitions = []
400
    for partname in os.listdir("/sys/block/%s" % name):
401
      if not partname.startswith(name):
402
        continue
403
      partdev = ReadDev("/sys/block/%s/%s" % (name, partname))
404
      partsize = ReadSize("/sys/block/%s/%s" % (name, partname))
405
      CheckSysDev(partname, partdev)
406
      partitions.append((partname, partsize, partdev))
407
    partitions.sort()
408
    dlist.append((name, size, dev, partitions, inuse))
409
  dlist.sort()
410
  return dlist
411

    
412

    
413
def GetMountInfo():
414
  """Reads /proc/mounts and computes the mountpoint-devnum mapping.
415

    
416
  This function reads /proc/mounts, finds the mounted filesystems
417
  (excepting a hard-coded blacklist of network and virtual
418
  filesystems) and does a stat on these mountpoints. The st_dev number
419
  of the results is memorised for later matching against the
420
  /sys/block devices.
421

    
422
  Returns:
423
   a mountpoint: device number dictionary
424
  """
425

    
426
  mountlines = ReadFile("/proc/mounts").splitlines()
427
  mounts = {}
428
  for line in mountlines:
429
    _, mountpoint, fstype, _ = line.split(None, 3)
430
    # fs type blacklist
431
    if fstype in ["nfs", "nfs4", "autofs", "tmpfs", "proc", "sysfs"]:
432
      continue
433
    try:
434
      dev = os.stat(mountpoint).st_dev
435
    except OSError, err:
436
      # this should be a fairly rare error, since we are blacklisting
437
      # network filesystems; with this in mind, we'll ignore it,
438
      # since the rereadpt check catches in-use filesystems,
439
      # and this is used for disk information only
440
      print >> sys.stderr, ("Can't stat mountpoint '%s': %s" %
441
                            (mountpoint, err))
442
      print >> sys.stderr, "Ignoring."
443
      continue
444
    mounts[dev] = mountpoint
445
  return mounts
446

    
447

    
448
def DevInfo(name, dev, mountinfo):
449
  """Computes miscellaneous information about a block device.
450

    
451
  Args:
452
    name: the device name, e.g. sda
453

    
454
  Returns:
455
    (mpath, whatvg, fileinfo), where
456
    mpath is the mount path where this device is mounted or None
457
    whatvg is the result of the ReadPV function
458
    fileinfo is the output of file -bs on the device
459
  """
460

    
461
  if dev in mountinfo:
462
    mpath = mountinfo[dev]
463
  else:
464
    mpath = None
465

    
466
  whatvg = ReadPV(name)
467

    
468
  result = ExecCommand("file -bs /dev/%s" % name)
469
  if result.failed:
470
    fileinfo = "<error: %s>" % result.stderr
471
  fileinfo = result.stdout[:45]
472
  return mpath, whatvg, fileinfo
473

    
474

    
475
def ShowDiskInfo(opts):
476
  """Shows a nicely formatted block device list for this system.
477

    
478
  This function shows the user a table with the information gathered
479
  by the other functions defined, in order to help the user make a
480
  choice about which disks should be allocated to our volume group.
481

    
482
  """
483
  mounts = GetMountInfo()
484
  dlist = GetDiskList(opts)
485

    
486
  print "------- Disk information -------"
487
  headers = {
488
      "name": "Name",
489
      "size": "Size[M]",
490
      "used": "Used",
491
      "mount": "Mount",
492
      "lvm": "LVM?",
493
      "info": "Info"
494
      }
495
  fields = ["name", "size", "used", "mount", "lvm", "info"]
496

    
497
  flatlist = []
498
  # Flatten the [(disk, [partition,...]), ...] list
499
  for name, size, dev, parts, inuse in dlist:
500
    if inuse:
501
      str_inuse = "yes"
502
    else:
503
      str_inuse = "no"
504
    flatlist.append((name, size, dev, str_inuse))
505
    for partname, partsize, partdev in parts:
506
      flatlist.append((partname, partsize, partdev, ""))
507

    
508
  strlist = []
509
  for name, size, dev, in_use in flatlist:
510
    mp, vgname, fileinfo = DevInfo(name, dev, mounts)
511
    if mp is None:
512
      mp = "-"
513
    if vgname is None:
514
      lvminfo = "-"
515
    elif vgname == "":
516
      lvminfo = "yes,free"
517
    else:
518
      lvminfo = "in %s" % vgname
519

    
520
    if len(name) > 3:
521
      # Indent partitions
522
      name = " %s" % name
523

    
524
    strlist.append([name, "%.2f" % (float(size) / 1024 / 1024),
525
                    in_use, mp, lvminfo, fileinfo])
526

    
527
  data = cli.GenerateTable(headers, fields, None,
528
                           strlist, numfields=["size"])
529

    
530
  for line in data:
531
    print line
532

    
533

    
534
def CheckReread(name):
535
  """Check to see if a block device is in use.
536

    
537
  Uses blockdev to reread the partition table of a block device, and
538
  thus compute the in-use status. See the discussion in GetDiskList
539
  about the meaning of 'in use'.
540

    
541
  Returns:
542
    boolean, the in-use status of the device
543
  """
544

    
545
  for _ in range(3):
546
    result = ExecCommand("blockdev --rereadpt /dev/%s" % name)
547
    if not result.failed:
548
      break
549
    time.sleep(2)
550

    
551
  return not result.failed
552

    
553

    
554
def WipeDisk(name):
555
  """Wipes a block device.
556

    
557
  This function wipes a block device, by clearing and re-reading the
558
  partition table. If not successful, it writes back the old partition
559
  data, and leaves the cleanup to the user.
560

    
561
  Args:
562
    the device name (e.g. sda)
563
  """
564

    
565
  if not CheckReread(name):
566
    raise OperationalError("CRITICAL: disk %s you selected seems to be in"
567
                           " use. ABORTING!" % name)
568

    
569
  fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
570
  olddata = os.read(fd, 512)
571
  if len(olddata) != 512:
572
    raise OperationalError("CRITICAL: Can't read partition table information"
573
                           " from /dev/%s (needed 512 bytes, got %d" %
574
                           (name, len(olddata)))
575
  newdata = "\0" * 512
576
  os.lseek(fd, 0, 0)
577
  bytes_written = os.write(fd, newdata)
578
  os.close(fd)
579
  if bytes_written != 512:
580
    raise OperationalError("CRITICAL: Can't write partition table information"
581
                           " to /dev/%s (tried to write 512 bytes, written"
582
                           " %d. I don't know how to cleanup. Sorry." %
583
                           (name, bytes_written))
584

    
585
  if not CheckReread(name):
586
    fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
587
    os.write(fd, olddata)
588
    os.close(fd)
589
    raise OperationalError("CRITICAL: disk %s which I have just wiped cannot"
590
                           " reread partition table. Most likely, it is"
591
                           " in use. You have to clean after this yourself."
592
                           " I tried to restore the old partition table,"
593
                           " but I cannot guarantee nothing has broken." %
594
                           name)
595

    
596

    
597
def PartitionDisk(name):
598
  """Partitions a disk.
599

    
600
  This function creates a single partition spanning the entire disk,
601
  by means of fdisk.
602

    
603
  Args:
604
    the device name, e.g. sda
605
  """
606
  result = ExecCommand(
607
    'echo ,,8e, | sfdisk /dev/%s' % name)
608
  if result.failed:
609
    raise OperationalError("CRITICAL: disk %s which I have just partitioned"
610
                           " cannot reread its partition table, or there"
611
                           " is some other sfdisk error. Likely, it is in"
612
                           " use. You have to clean this yourself. Error"
613
                           " message from sfdisk: %s" %
614
                           (name, result.output))
615

    
616

    
617
def CreatePVOnDisk(name):
618
  """Creates a physical volume on a block device.
619

    
620
  This function creates a physical volume on a block device, overriding
621
  all warnings. So it can wipe existing PVs and PVs which are in a VG.
622

    
623
  Args:
624
    the device name, e.g. sda
625

    
626
  """
627
  result = ExecCommand("pvcreate -yff /dev/%s1 " % name)
628
  if result.failed:
629
    raise OperationalError("I cannot create a physical volume on"
630
                           " partition /dev/%s1. Error message: %s."
631
                           " Please clean up yourself." %
632
                           (name, result.output))
633

    
634

    
635
def CreateVG(vgname, disks):
636
  """Creates the volume group.
637

    
638
  This function creates a volume group named `vgname` on the disks
639
  given as parameters. The physical extent size is set to 64MB.
640

    
641
  Args:
642
    disks: a list of disk names, e.g. ['sda','sdb']
643

    
644
  """
645
  pnames = ["'/dev/%s1'" % disk for disk in disks]
646
  result = ExecCommand("vgcreate -s 64MB '%s' %s" % (vgname, " ".join(pnames)))
647
  if result.failed:
648
    raise OperationalError("I cannot create the volume group %s from"
649
                           " disks %s. Error message: %s. Please clean up"
650
                           " yourself." %
651
                           (vgname, " ".join(disks), result.output))
652

    
653

    
654
def ValidateDiskList(options):
655
  """Validates or computes the disk list for create.
656

    
657
  This function either computes the available disk list (if the user
658
  gave --alldisks option), or validates the user-given disk list (by
659
  using the --disks option) such that all given disks are present and
660
  not in use.
661

    
662
  Args:
663
    the options returned from OptParser.parse_options
664

    
665
  Returns:
666
    a list of disk names, e.g. ['sda', 'sdb']
667
  """
668

    
669
  sysdisks = GetDiskList(options)
670
  if not sysdisks:
671
    raise PrereqError("no disks found (I looked for"
672
                      " non-removable block devices).")
673
  sysd_free = []
674
  sysd_used = []
675
  for name, _, _, _, used in sysdisks:
676
    if used:
677
      sysd_used.append(name)
678
    else:
679
      sysd_free.append(name)
680

    
681
  if not sysd_free:
682
    raise PrereqError("no free disks found! (%d in-use disks)" %
683
                      len(sysd_used))
684
  if options.alldisks:
685
    disklist = sysd_free
686
  elif options.disks:
687
    disklist = options.disks.split(",")
688
    for name in disklist:
689
      if name in sysd_used:
690
        raise ParameterError("disk %s is in use, cannot wipe!" % name)
691
      if name not in sysd_free:
692
        raise ParameterError("cannot find disk %s!" % name)
693
  else:
694
    raise ParameterError("Please use either --alldisks or --disks!")
695

    
696
  return disklist
697

    
698

    
699
def BootStrap():
700
  """Actual main routine."""
701

    
702
  CheckPrereq()
703

    
704
  options, args = ParseOptions()
705
  vgname = options.vgname
706
  command = args.pop(0)
707
  if command == "diskinfo":
708
    ShowDiskInfo(options)
709
    return
710
  if command != "create":
711
    Usage()
712

    
713
  exists, lv_count, vg_size, vg_free = CheckVGExists(vgname)
714
  if exists:
715
    raise PrereqError("It seems volume group '%s' already exists:\n"
716
                      "  LV count: %s, size: %s, free: %s." %
717
                      (vgname, lv_count, vg_size, vg_free))
718

    
719

    
720
  disklist = ValidateDiskList(options)
721

    
722
  for disk in disklist:
723
    WipeDisk(disk)
724
    PartitionDisk(disk)
725
  for disk in disklist:
726
    CreatePVOnDisk(disk)
727
  CreateVG(vgname, disklist)
728

    
729
  status, lv_count, size, _ = CheckVGExists(vgname)
730
  if status:
731
    print "Done! %s: size %s GiB, disks: %s" % (vgname, size,
732
                                              ",".join(disklist))
733
  else:
734
    raise OperationalError("Although everything seemed ok, the volume"
735
                           " group did not get created.")
736

    
737

    
738
def main():
739
  """application entry point.
740

    
741
  This is just a wrapper over BootStrap, to handle our own exceptions.
742
  """
743

    
744
  try:
745
    BootStrap()
746
  except PrereqError, err:
747
    print >> sys.stderr, "The prerequisites for running this tool are not met."
748
    print >> sys.stderr, ("Please make sure you followed all the steps in"
749
                          " the build document.")
750
    print >> sys.stderr, "Description: %s" % str(err)
751
    sys.exit(1)
752
  except SysconfigError, err:
753
    print >> sys.stderr, ("This system's configuration seems wrong, at"
754
                          " least is not what I expect.")
755
    print >> sys.stderr, ("Please check that the installation didn't fail"
756
                          " at some step.")
757
    print >> sys.stderr, "Description: %s" % str(err)
758
    sys.exit(1)
759
  except ParameterError, err:
760
    print >> sys.stderr, ("Some parameters you gave to the program or the"
761
                          " invocation is wrong. ")
762
    print >> sys.stderr, "Description: %s" % str(err)
763
    Usage()
764
  except OperationalError, err:
765
    print >> sys.stderr, ("A serious error has happened while modifying"
766
                          " the system's configuration.")
767
    print >> sys.stderr, ("Please review the error message below and make"
768
                          " sure you clean up yourself.")
769
    print >> sys.stderr, ("It is most likely that the system configuration"
770
                          " has been partially altered.")
771
    print >> sys.stderr, str(err)
772
    sys.exit(1)
773
  except ProgrammingError, err:
774
    print >> sys.stderr, ("Internal application error. Please signal this"
775
                          " to xencluster-team.")
776
    print >> sys.stderr, "Error description: %s" % str(err)
777
    sys.exit(1)
778
  except Error, err:
779
    print >> sys.stderr, "Unhandled application error: %s" % err
780
    sys.exit(1)
781
  except (IOError, OSError), err:
782
    print >> sys.stderr, "I/O error detected, please report."
783
    print >> sys.stderr, "Description: %s" % str(err)
784
    sys.exit(1)
785

    
786

    
787
if __name__ == "__main__":
788
  main()