Statistics
| Branch: | Tag: | Revision:

root / tools / lvmstrap @ c118d1f4

History | View | Annotate | Download (23.9 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
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
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("-v", "--verbose",
146
                    action="store_true", dest="verbose", default=False,
147
                    help="print command execution messages to stdout")
148
  parser.add_option("-r", "--allow-removable",
149
                    action="store_true", dest="removable_ok", default=False,
150
                    help="allow and use removable devices too")
151
  parser.add_option("-g", "--vg-name", type="string",
152
                    dest="vgname", default="xenvg", metavar="NAME",
153
                    help="the volume group to be created [default: xenvg]")
154

    
155

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

    
160
  verbose_flag = options.verbose
161

    
162
  return options, args
163

    
164

    
165
def ExecCommand(command):
166
  """Executes a command.
167

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

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

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

    
186

    
187
def CheckPrereq():
188
  """Check the prerequisites of this program.
189

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

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

    
197
  osname, nodename, release, version, arch = os.uname()
198
  if osname != 'Linux':
199
    raise PrereqError("This tool only runs on Linux"
200
                      " (detected OS: %s)." % osname)
201

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

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

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

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

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

    
221

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

    
225
  Args:
226
    vgname: the volume group name
227

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

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

    
249
  return not result.failed, lv_count, vg_size, vg_free
250

    
251

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

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

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

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

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

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

    
289

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

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

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

    
301
  Returns:
302
    the device number
303
  """
304

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

    
316

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

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

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

    
328
  Returns:
329
    the device size in bytes
330
  """
331

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

    
340

    
341
def ReadPV(name):
342
  """Reads physical volume information.
343

    
344
  This function tries to see if a block device is a physical volume.
345

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

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

    
360

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

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

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

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

    
388
    size = ReadSize("/sys/block/%s" % name)
389

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

    
394
    if removable and not opts.removable_ok:
395
      continue
396

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

    
414

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

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

    
424
  Returns:
425
   a mountpoint: device number dictionary
426
  """
427

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

    
451

    
452
def DevInfo(name, dev, mountinfo):
453
  """Computes miscellaneous information about a block device.
454

    
455
  Args:
456
    name: the device name, e.g. sda
457

    
458
  Returns:
459
    (mpath, whatvg, fileinfo), where
460
    mpath is the mount path where this device is mounted or None
461
    whatvg is the result of the ReadPV function
462
    fileinfo is the output of file -bs on the device
463
  """
464

    
465
  if dev in mountinfo:
466
    mpath = mountinfo[dev]
467
  else:
468
    mpath = None
469

    
470
  whatvg = ReadPV(name)
471

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

    
478

    
479
def ShowDiskInfo(opts):
480
  """Shows a nicely formatted block device list for this system.
481

    
482
  This function shows the user a table with the information gathered
483
  by the other functions defined, in order to help the user make a
484
  choice about which disks should be allocated to our volume group.
485

    
486
  """
487
  mounts = GetMountInfo()
488
  dlist = GetDiskList(opts)
489

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

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

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

    
524
    if len(name) > 3:
525
      # Indent partitions
526
      name = " %s" % name
527

    
528
    strlist.append([name, "%.2f" % (float(size) / 1024 / 1024),
529
                    in_use, mp, lvminfo, fileinfo])
530

    
531
  data = cli.GenerateTable(headers, fields, None,
532
                           strlist, numfields=["size"])
533

    
534
  for line in data:
535
    print line
536

    
537

    
538
def CheckReread(name):
539
  """Check to see if a block device is in use.
540

    
541
  Uses blockdev to reread the partition table of a block device, and
542
  thus compute the in-use status. See the discussion in GetDiskList
543
  about the meaning of 'in use'.
544

    
545
  Returns:
546
    boolean, the in-use status of the device
547
  """
548

    
549
  for retries in range(3):
550
    result = ExecCommand("blockdev --rereadpt /dev/%s" % name)
551
    if not result.failed:
552
      break
553
    time.sleep(2)
554

    
555
  return not result.failed
556

    
557

    
558
def WipeDisk(name):
559
  """Wipes a block device.
560

    
561
  This function wipes a block device, by clearing and re-reading the
562
  partition table. If not successful, it writes back the old partition
563
  data, and leaves the cleanup to the user.
564

    
565
  Args:
566
    the device name (e.g. sda)
567
  """
568

    
569
  if not CheckReread(name):
570
    raise OperationalError("CRITICAL: disk %s you selected seems to be in"
571
                           " use. ABORTING!" % name)
572

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

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

    
600

    
601
def PartitionDisk(name):
602
  """Partitions a disk.
603

    
604
  This function creates a single partition spanning the entire disk,
605
  by means of fdisk.
606

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

    
620

    
621
def CreatePVOnDisk(name):
622
  """Creates a physical volume on a block device.
623

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

    
627
  Args:
628
    the device name, e.g. sda
629

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

    
638

    
639
def CreateVG(vgname, disks):
640
  """Creates the volume group.
641

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

    
645
  Args:
646
    disks: a list of disk names, e.g. ['sda','sdb']
647

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

    
657

    
658
def ValidateDiskList(options):
659
  """Validates or computes the disk list for create.
660

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

    
666
  Args:
667
    the options returned from OptParser.parse_options
668

    
669
  Returns:
670
    a list of disk names, e.g. ['sda', 'sdb']
671
  """
672

    
673
  sysdisks = GetDiskList(options)
674
  if not sysdisks:
675
    raise PrereqError("no disks found (I looked for"
676
                      " non-removable block devices).")
677
  sysd_free = []
678
  sysd_used = []
679
  for name, size, dev, part, used in sysdisks:
680
    if used:
681
      sysd_used.append(name)
682
    else:
683
      sysd_free.append(name)
684

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

    
700
  return disklist
701

    
702

    
703
def BootStrap():
704
  """Actual main routine."""
705

    
706
  CheckPrereq()
707

    
708
  options, args = ParseOptions()
709
  vgname = options.vgname
710
  command = args.pop(0)
711
  if command == "diskinfo":
712
    ShowDiskInfo(options)
713
    return
714
  if command != "create":
715
    Usage()
716

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

    
723

    
724
  disklist = ValidateDiskList(options)
725

    
726
  for disk in disklist:
727
    WipeDisk(disk)
728
    PartitionDisk(disk)
729
  for disk in disklist:
730
    CreatePVOnDisk(disk)
731
  CreateVG(vgname, disklist)
732

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

    
741

    
742
def main():
743
  """application entry point.
744

    
745
  This is just a wrapper over BootStrap, to handle our own exceptions.
746
  """
747

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

    
790

    
791
if __name__ == "__main__":
792
  main()