Statistics
| Branch: | Tag: | Revision:

root / tools / lvmstrap @ 577d45d4

History | View | Annotate | Download (24.8 kB)

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

    
4
# Copyright (C) 2006, 2007, 2011 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

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

    
48
from ganeti.utils import RunCmd, ReadFile
49
from ganeti import constants
50
from ganeti import cli
51
from ganeti import compat
52

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

    
58
verbose_flag = False
59

    
60
#: Supported disk types (as prefixes)
61
SUPPORTED_TYPES = [
62
  "hd",
63
  "sd",
64
  "md",
65
  "ubd",
66
  ]
67

    
68

    
69
class Error(Exception):
70
  """Generic exception"""
71
  pass
72

    
73

    
74
class ProgrammingError(Error):
75
  """Exception denoting invalid assumptions in programming.
76

    
77
  This should catch sysfs tree changes, or otherwise incorrect
78
  assumptions about the contents of the /sys/block/... directories.
79

    
80
  """
81
  pass
82

    
83

    
84
class SysconfigError(Error):
85
  """Exception denoting invalid system configuration.
86

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

    
91
  This should usually mean that the installation of the Xen node
92
  failed in some steps.
93

    
94
  """
95
  pass
96

    
97

    
98
class PrereqError(Error):
99
  """Exception denoting invalid prerequisites.
100

    
101
  If the node does not meet the requirements for cluster membership, this
102
  exception will be raised. Things like wrong kernel version, or no
103
  free disks, etc. belong here.
104

    
105
  This should usually mean that the build steps for the Xen node were
106
  not followed correctly.
107

    
108
  """
109
  pass
110

    
111

    
112
class OperationalError(Error):
113
  """Exception denoting actual errors.
114

    
115
  Errors during the bootstrapping are signaled using this exception.
116

    
117
  """
118
  pass
119

    
120

    
121
class ParameterError(Error):
122
  """Exception denoting invalid input from user.
123

    
124
  Wrong disks given as parameters will be signaled using this
125
  exception.
126

    
127
  """
128
  pass
129

    
130

    
131
def Usage():
132
  """Shows program usage information and exits the program.
133

    
134
  """
135
  print >> sys.stderr, "Usage:"
136
  print >> sys.stderr, USAGE
137
  sys.exit(2)
138

    
139

    
140
def ParseOptions():
141
  """Parses the command line options.
142

    
143
  In case of command line errors, it will show the usage and exit the
144
  program.
145

    
146
  @rtype: tuple
147
  @return: a tuple of (options, args), as returned by
148
      OptionParser.parse_args
149

    
150
  """
151
  global verbose_flag # pylint: disable-msg=W0603
152

    
153
  parser = optparse.OptionParser(usage="\n%s" % USAGE,
154
                                 version="%%prog (ganeti) %s" %
155
                                 constants.RELEASE_VERSION)
156

    
157
  parser.add_option("--alldisks", dest="alldisks",
158
                    help="erase ALL disks", action="store_true",
159
                    default=False)
160
  parser.add_option("-d", "--disks", dest="disks",
161
                    help="Choose disks (e.g. hda,hdg)",
162
                    metavar="DISKLIST")
163
  parser.add_option(cli.VERBOSE_OPT)
164
  parser.add_option("-r", "--allow-removable",
165
                    action="store_true", dest="removable_ok", default=False,
166
                    help="allow and use removable devices too")
167
  parser.add_option("-g", "--vg-name", type="string",
168
                    dest="vgname", default="xenvg", metavar="NAME",
169
                    help="the volume group to be created [default: xenvg]")
170

    
171

    
172
  options, args = parser.parse_args()
173
  if len(args) != 1:
174
    Usage()
175

    
176
  verbose_flag = options.verbose
177

    
178
  return options, args
179

    
180

    
181
def IsPartitioned(disk):
182
  """Returns whether a given disk should be used partitioned or as-is.
183

    
184
  Currently only md devices are used as is.
185

    
186
  """
187
  return not disk.startswith('md')
188

    
189

    
190
def DeviceName(disk):
191
  """Returns the appropriate device name for a disk.
192

    
193
  For non-partitioned devices, it returns the name as is, otherwise it
194
  returns the first partition.
195

    
196
  """
197
  if IsPartitioned(disk):
198
    device = '/dev/%s1' % disk
199
  else:
200
    device = '/dev/%s' % disk
201
  return device
202

    
203

    
204
def ExecCommand(command):
205
  """Executes a command.
206

    
207
  This is just a wrapper around commands.getstatusoutput, with the
208
  difference that if the command line argument -v has been given, it
209
  will print the command line and the command output on stdout.
210

    
211
  @param command: the command line to be executed
212
  @rtype: tuple
213
  @return: a tuple of (status, output) where status is the exit status
214
      and output the stdout and stderr of the command together
215

    
216
  """
217
  if verbose_flag:
218
    print command
219
  result = RunCmd(command)
220
  if verbose_flag:
221
    print result.output
222
  return result
223

    
224

    
225
def CheckPrereq():
226
  """Check the prerequisites of this program.
227

    
228
  It check that it runs on Linux 2.6, and that /sys is mounted and the
229
  fact that /sys/block is a directory.
230

    
231
  """
232
  if os.getuid() != 0:
233
    raise PrereqError("This tool runs as root only. Really.")
234

    
235
  osname, _, release, _, _ = os.uname()
236
  if osname != 'Linux':
237
    raise PrereqError("This tool only runs on Linux"
238
                      " (detected OS: %s)." % osname)
239

    
240
  if not release.startswith("2.6."):
241
    raise PrereqError("Wrong major kernel version (detected %s, needs"
242
                      " 2.6.*)" % release)
243

    
244
  if not os.path.ismount("/sys"):
245
    raise PrereqError("Can't find a filesystem mounted at /sys."
246
                      " Please mount /sys.")
247

    
248
  if not os.path.isdir("/sys/block"):
249
    raise SysconfigError("Can't find /sys/block directory. Has the"
250
                         " layout of /sys changed?")
251

    
252
  if not os.path.ismount("/proc"):
253
    raise PrereqError("Can't find a filesystem mounted at /proc."
254
                      " Please mount /proc.")
255

    
256
  if not os.path.exists("/proc/mounts"):
257
    raise SysconfigError("Can't find /proc/mounts")
258

    
259

    
260
def CheckVGExists(vgname):
261
  """Checks to see if a volume group exists.
262

    
263
  @param vgname: the volume group name
264

    
265
  @return: a four-tuple (exists, lv_count, vg_size, vg_free), where:
266
      - exists: True if the volume exists, otherwise False; if False,
267
        all other members of the tuple are None
268
      - lv_count: The number of logical volumes in the volume group
269
      - vg_size: The total size of the volume group (in gibibytes)
270
      - vg_free: The available space in the volume group
271

    
272
  """
273
  result = ExecCommand("vgs --nohead -o lv_count,vg_size,vg_free"
274
                       " --nosuffix --units g"
275
                       " --ignorelockingfailure %s" % vgname)
276
  if not result.failed:
277
    try:
278
      lv_count, vg_size, vg_free = result.stdout.strip().split()
279
    except ValueError:
280
      # This means the output of vgdisplay can't be parsed
281
      raise PrereqError("cannot parse output of vgs (%s)" % result.stdout)
282
  else:
283
    lv_count = vg_size = vg_free = None
284

    
285
  return not result.failed, lv_count, vg_size, vg_free
286

    
287

    
288
def CheckSysDev(name, devnum):
289
  """Checks consistency between /sys and /dev trees.
290

    
291
  In /sys/block/<name>/dev and /sys/block/<name>/<part>/dev are the
292
  kernel-known device numbers. The /dev/<name> block/char devices are
293
  created by userspace and thus could differ from the kernel
294
  view. This function checks the consistency between the device number
295
  read from /sys and the actual device number in /dev.
296

    
297
  Note that since the system could be using udev which removes and
298
  recreates the device nodes on partition table rescan, we need to do
299
  some retries here. Since we only do a stat, we can afford to do many
300
  short retries.
301

    
302
  @param name: the device name, e.g. 'sda'
303
  @param devnum: the device number, e.g. 0x803 (2051 in decimal) for sda3
304
  @raises L{SysconfigError}: in case of failure of the check
305

    
306
  """
307
  path = "/dev/%s" % name
308
  for _ in range(40):
309
    if os.path.exists(path):
310
      break
311
    time.sleep(0.250)
312
  else:
313
    raise SysconfigError("the device file %s does not exist, but the block"
314
                         " device exists in the /sys/block tree" % path)
315
  rdev = os.stat(path).st_rdev
316
  if devnum != rdev:
317
    raise SysconfigError("For device %s, the major:minor in /dev is %04x"
318
                         " while the major:minor in sysfs is %s" %
319
                         (path, rdev, devnum))
320

    
321

    
322
def ReadDev(syspath):
323
  """Reads the device number from a sysfs path.
324

    
325
  The device number is given in sysfs under a block device directory
326
  in a file named 'dev' which contains major:minor (in ASCII). This
327
  function reads that file and converts the major:minor pair to a dev
328
  number.
329

    
330
  @type syspath: string
331
  @param syspath: the path to a block device dir in sysfs,
332
      e.g. C{/sys/block/sda}
333

    
334
  @return: the device number
335

    
336
  """
337
  if not os.path.exists("%s/dev" % syspath):
338
    raise ProgrammingError("Invalid path passed to ReadDev: %s" % syspath)
339
  f = open("%s/dev" % syspath)
340
  data = f.read().strip()
341
  f.close()
342
  major, minor = data.split(":", 1)
343
  major = int(major)
344
  minor = int(minor)
345
  dev = os.makedev(major, minor)
346
  return dev
347

    
348

    
349
def ReadSize(syspath):
350
  """Reads the size from a sysfs path.
351

    
352
  The size is given in sysfs under a block device directory in a file
353
  named 'size' which contains the number of sectors (in ASCII). This
354
  function reads that file and converts the number in sectors to the
355
  size in bytes.
356

    
357
  @type syspath: string
358
  @param syspath: the path to a block device dir in sysfs,
359
      e.g. C{/sys/block/sda}
360

    
361
  @rtype: int
362
  @return: the device size in bytes
363

    
364
  """
365

    
366
  if not os.path.exists("%s/size" % syspath):
367
    raise ProgrammingError("Invalid path passed to ReadSize: %s" % syspath)
368
  f = open("%s/size" % syspath)
369
  data = f.read().strip()
370
  f.close()
371
  size = 512L * int(data)
372
  return size
373

    
374

    
375
def ReadPV(name):
376
  """Reads physical volume information.
377

    
378
  This function tries to see if a block device is a physical volume.
379

    
380
  @type name: string
381
  @param name: the device name (e.g. sda)
382

    
383
  @return: the name of the volume group to which this PV belongs, or
384
      "" if this PV is not in use, or None if this is not a PV
385

    
386
  """
387
  result = ExecCommand("pvdisplay -c /dev/%s" % name)
388
  if result.failed:
389
    return None
390
  vgname = result.stdout.strip().split(":")[1]
391
  return vgname
392

    
393

    
394
def GetDiskList(opts):
395
  """Computes the block device list for this system.
396

    
397
  This function examines the /sys/block tree and using information
398
  therein, computes the status of the block device.
399

    
400
  @return: a list like [(name, size, dev, partitions, inuse), ...], where:
401
      - name is the block device name (e.g. sda)
402
      - size the size in bytes
403
      - dev is the device number (e.g. 8704 for hdg)
404
      - partitions is [(name, size, dev), ...] mirroring the disk list
405
        data inuse is a boolean showing the in-use status of the disk,
406
        computed as the possibility of re-reading the partition table
407
        (the meaning of the operation varies with the kernel version,
408
        but is usually accurate; a mounted disk/partition or swap-area
409
        or PV with active LVs on it is busy)
410

    
411
  """
412
  dlist = []
413
  for name in os.listdir("/sys/block"):
414
    if not compat.any([name.startswith(pfx) for pfx in SUPPORTED_TYPES]):
415
      continue
416

    
417
    size = ReadSize("/sys/block/%s" % name)
418

    
419
    f = open("/sys/block/%s/removable" % name)
420
    removable = int(f.read().strip())
421
    f.close()
422

    
423
    if removable and not opts.removable_ok:
424
      continue
425

    
426
    dev = ReadDev("/sys/block/%s" % name)
427
    CheckSysDev(name, dev)
428
    inuse = not CheckReread(name)
429
    # Enumerate partitions of the block device
430
    partitions = []
431
    for partname in os.listdir("/sys/block/%s" % name):
432
      if not partname.startswith(name):
433
        continue
434
      partdev = ReadDev("/sys/block/%s/%s" % (name, partname))
435
      partsize = ReadSize("/sys/block/%s/%s" % (name, partname))
436
      CheckSysDev(partname, partdev)
437
      partitions.append((partname, partsize, partdev))
438
    partitions.sort()
439
    dlist.append((name, size, dev, partitions, inuse))
440
  dlist.sort()
441
  return dlist
442

    
443

    
444
def GetMountInfo():
445
  """Reads /proc/mounts and computes the mountpoint-devnum mapping.
446

    
447
  This function reads /proc/mounts, finds the mounted filesystems
448
  (excepting a hard-coded blacklist of network and virtual
449
  filesystems) and does a stat on these mountpoints. The st_dev number
450
  of the results is memorised for later matching against the
451
  /sys/block devices.
452

    
453
  @rtype: dict
454
  @return: a {mountpoint: device number} dictionary
455

    
456
  """
457
  mountlines = ReadFile("/proc/mounts").splitlines()
458
  mounts = {}
459
  for line in mountlines:
460
    _, mountpoint, fstype, _ = line.split(None, 3)
461
    # fs type blacklist
462
    if fstype in ["nfs", "nfs4", "autofs", "tmpfs", "proc", "sysfs"]:
463
      continue
464
    try:
465
      dev = os.stat(mountpoint).st_dev
466
    except OSError, err:
467
      # this should be a fairly rare error, since we are blacklisting
468
      # network filesystems; with this in mind, we'll ignore it,
469
      # since the rereadpt check catches in-use filesystems,
470
      # and this is used for disk information only
471
      print >> sys.stderr, ("Can't stat mountpoint '%s': %s" %
472
                            (mountpoint, err))
473
      print >> sys.stderr, "Ignoring."
474
      continue
475
    mounts[dev] = mountpoint
476
  return mounts
477

    
478

    
479
def DevInfo(name, dev, mountinfo):
480
  """Computes miscellaneous information about a block device.
481

    
482
  @type name: string
483
  @param name: the device name, e.g. sda
484

    
485
  @return: a tuple (mpath, whatvg, fileinfo), where:
486
      - mpath is the mount path where this device is mounted or None
487
      - whatvg is the result of the ReadPV function
488
      - fileinfo is the output of file -bs on the device
489

    
490
  """
491
  if dev in mountinfo:
492
    mpath = mountinfo[dev]
493
  else:
494
    mpath = None
495

    
496
  whatvg = ReadPV(name)
497

    
498
  result = ExecCommand("file -bs /dev/%s" % name)
499
  if result.failed:
500
    fileinfo = "<error: %s>" % result.stderr
501
  fileinfo = result.stdout[:45]
502
  return mpath, whatvg, fileinfo
503

    
504

    
505
def ShowDiskInfo(opts):
506
  """Shows a nicely formatted block device list for this system.
507

    
508
  This function shows the user a table with the information gathered
509
  by the other functions defined, in order to help the user make a
510
  choice about which disks should be allocated to our volume group.
511

    
512
  """
513
  mounts = GetMountInfo()
514
  dlist = GetDiskList(opts)
515

    
516
  print "------- Disk information -------"
517
  headers = {
518
      "name": "Name",
519
      "size": "Size[M]",
520
      "used": "Used",
521
      "mount": "Mount",
522
      "lvm": "LVM?",
523
      "info": "Info"
524
      }
525
  fields = ["name", "size", "used", "mount", "lvm", "info"]
526

    
527
  flatlist = []
528
  # Flatten the [(disk, [partition,...]), ...] list
529
  for name, size, dev, parts, inuse in dlist:
530
    if inuse:
531
      str_inuse = "yes"
532
    else:
533
      str_inuse = "no"
534
    flatlist.append((name, size, dev, str_inuse))
535
    for partname, partsize, partdev in parts:
536
      flatlist.append((partname, partsize, partdev, ""))
537

    
538
  strlist = []
539
  for name, size, dev, in_use in flatlist:
540
    mp, vgname, fileinfo = DevInfo(name, dev, mounts)
541
    if mp is None:
542
      mp = "-"
543
    if vgname is None:
544
      lvminfo = "-"
545
    elif vgname == "":
546
      lvminfo = "yes,free"
547
    else:
548
      lvminfo = "in %s" % vgname
549

    
550
    if len(name) > 3:
551
      # Indent partitions
552
      name = " %s" % name
553

    
554
    strlist.append([name, "%.2f" % (float(size) / 1024 / 1024),
555
                    in_use, mp, lvminfo, fileinfo])
556

    
557
  data = cli.GenerateTable(headers, fields, None,
558
                           strlist, numfields=["size"])
559

    
560
  for line in data:
561
    print line
562

    
563

    
564
def CheckReread(name):
565
  """Check to see if a block device is in use.
566

    
567
  Uses blockdev to reread the partition table of a block device (or
568
  fuser if the device is not partitionable), and thus compute the
569
  in-use status.  See the discussion in GetDiskList about the meaning
570
  of 'in use'.
571

    
572
  @rtype: boolean
573
  @return: the in-use status of the device
574

    
575
  """
576
  use_blockdev = IsPartitioned(name)
577
  if use_blockdev:
578
    cmd = "blockdev --rereadpt /dev/%s" % name
579
  else:
580
    cmd = "fuser -vam /dev/%s" % name
581

    
582
  for _ in range(3):
583
    result = ExecCommand(cmd)
584
    if not use_blockdev and result.failed:
585
      break
586
    elif not result.failed:
587
      break
588
    time.sleep(2)
589

    
590
  if use_blockdev:
591
    return not result.failed
592
  else:
593
    return result.failed
594

    
595

    
596
def WipeDisk(name):
597
  """Wipes a block device.
598

    
599
  This function wipes a block device, by clearing and re-reading the
600
  partition table. If not successful, it writes back the old partition
601
  data, and leaves the cleanup to the user.
602

    
603
  @param name: the device name (e.g. sda)
604

    
605
  """
606

    
607
  if not CheckReread(name):
608
    raise OperationalError("CRITICAL: disk %s you selected seems to be in"
609
                           " use. ABORTING!" % name)
610

    
611
  fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
612
  olddata = os.read(fd, 512)
613
  if len(olddata) != 512:
614
    raise OperationalError("CRITICAL: Can't read partition table information"
615
                           " from /dev/%s (needed 512 bytes, got %d" %
616
                           (name, len(olddata)))
617
  newdata = "\0" * 512
618
  os.lseek(fd, 0, 0)
619
  bytes_written = os.write(fd, newdata)
620
  os.close(fd)
621
  if bytes_written != 512:
622
    raise OperationalError("CRITICAL: Can't write partition table information"
623
                           " to /dev/%s (tried to write 512 bytes, written"
624
                           " %d. I don't know how to cleanup. Sorry." %
625
                           (name, bytes_written))
626

    
627
  if not CheckReread(name):
628
    fd = os.open("/dev/%s" % name, os.O_RDWR | os.O_SYNC)
629
    os.write(fd, olddata)
630
    os.close(fd)
631
    raise OperationalError("CRITICAL: disk %s which I have just wiped cannot"
632
                           " reread partition table. Most likely, it is"
633
                           " in use. You have to clean after this yourself."
634
                           " I tried to restore the old partition table,"
635
                           " but I cannot guarantee nothing has broken." %
636
                           name)
637

    
638

    
639
def PartitionDisk(name):
640
  """Partitions a disk.
641

    
642
  This function creates a single partition spanning the entire disk,
643
  by means of fdisk.
644

    
645
  @param name: the device name, e.g. sda
646

    
647
  """
648
  result = ExecCommand(
649
    'echo ,,8e, | sfdisk /dev/%s' % name)
650
  if result.failed:
651
    raise OperationalError("CRITICAL: disk %s which I have just partitioned"
652
                           " cannot reread its partition table, or there"
653
                           " is some other sfdisk error. Likely, it is in"
654
                           " use. You have to clean this yourself. Error"
655
                           " message from sfdisk: %s" %
656
                           (name, result.output))
657

    
658

    
659
def CreatePVOnDisk(name):
660
  """Creates a physical volume on a block device.
661

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

    
665
  @param name: the device name, e.g. sda
666

    
667
  """
668
  device = DeviceName(name)
669
  result = ExecCommand("pvcreate -yff %s" % device)
670
  if result.failed:
671
    raise OperationalError("I cannot create a physical volume on"
672
                           " %s. Error message: %s."
673
                           " Please clean up yourself." %
674
                           (device, result.output))
675

    
676

    
677
def CreateVG(vgname, disks):
678
  """Creates the volume group.
679

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

    
683
  @param disks: a list of disk names, e.g. ['sda','sdb']
684

    
685
  """
686
  pnames = [DeviceName(d) for d in disks]
687
  result = ExecCommand("vgcreate -s 64MB '%s' %s" % (vgname, " ".join(pnames)))
688
  if result.failed:
689
    raise OperationalError("I cannot create the volume group %s from"
690
                           " disks %s. Error message: %s. Please clean up"
691
                           " yourself." %
692
                           (vgname, " ".join(disks), result.output))
693

    
694

    
695
def ValidateDiskList(options):
696
  """Validates or computes the disk list for create.
697

    
698
  This function either computes the available disk list (if the user
699
  gave --alldisks option), or validates the user-given disk list (by
700
  using the --disks option) such that all given disks are present and
701
  not in use.
702

    
703
  @param options: the options returned from OptParser.parse_options
704

    
705
  @return: a list of disk names, e.g. ['sda', 'sdb']
706

    
707
  """
708
  sysdisks = GetDiskList(options)
709
  if not sysdisks:
710
    raise PrereqError("no disks found (I looked for"
711
                      " non-removable block devices).")
712
  sysd_free = []
713
  sysd_used = []
714
  for name, _, _, _, used in sysdisks:
715
    if used:
716
      sysd_used.append(name)
717
    else:
718
      sysd_free.append(name)
719

    
720
  if not sysd_free:
721
    raise PrereqError("no free disks found! (%d in-use disks)" %
722
                      len(sysd_used))
723
  if options.alldisks:
724
    disklist = sysd_free
725
  elif options.disks:
726
    disklist = options.disks.split(",")
727
    for name in disklist:
728
      if name in sysd_used:
729
        raise ParameterError("disk %s is in use, cannot wipe!" % name)
730
      if name not in sysd_free:
731
        raise ParameterError("cannot find disk %s!" % name)
732
  else:
733
    raise ParameterError("Please use either --alldisks or --disks!")
734

    
735
  return disklist
736

    
737

    
738
def BootStrap():
739
  """Actual main routine.
740

    
741
  """
742
  CheckPrereq()
743

    
744
  options, args = ParseOptions()
745
  vgname = options.vgname
746
  command = args.pop(0)
747
  if command == "diskinfo":
748
    ShowDiskInfo(options)
749
    return
750
  if command != "create":
751
    Usage()
752

    
753
  exists, lv_count, vg_size, vg_free = CheckVGExists(vgname)
754
  if exists:
755
    raise PrereqError("It seems volume group '%s' already exists:\n"
756
                      "  LV count: %s, size: %s, free: %s." %
757
                      (vgname, lv_count, vg_size, vg_free))
758

    
759

    
760
  disklist = ValidateDiskList(options)
761

    
762
  for disk in disklist:
763
    WipeDisk(disk)
764
    if IsPartitioned(disk):
765
      PartitionDisk(disk)
766
  for disk in disklist:
767
    CreatePVOnDisk(disk)
768
  CreateVG(vgname, disklist)
769

    
770
  status, lv_count, size, _ = CheckVGExists(vgname)
771
  if status:
772
    print "Done! %s: size %s GiB, disks: %s" % (vgname, size,
773
                                              ",".join(disklist))
774
  else:
775
    raise OperationalError("Although everything seemed ok, the volume"
776
                           " group did not get created.")
777

    
778

    
779
def main():
780
  """Application entry point.
781

    
782
  This is just a wrapper over BootStrap, to handle our own exceptions.
783

    
784
  """
785
  try:
786
    BootStrap()
787
  except PrereqError, err:
788
    print >> sys.stderr, "The prerequisites for running this tool are not met."
789
    print >> sys.stderr, ("Please make sure you followed all the steps in"
790
                          " the build document.")
791
    print >> sys.stderr, "Description: %s" % str(err)
792
    sys.exit(1)
793
  except SysconfigError, err:
794
    print >> sys.stderr, ("This system's configuration seems wrong, at"
795
                          " least is not what I expect.")
796
    print >> sys.stderr, ("Please check that the installation didn't fail"
797
                          " at some step.")
798
    print >> sys.stderr, "Description: %s" % str(err)
799
    sys.exit(1)
800
  except ParameterError, err:
801
    print >> sys.stderr, ("Some parameters you gave to the program or the"
802
                          " invocation is wrong. ")
803
    print >> sys.stderr, "Description: %s" % str(err)
804
    Usage()
805
  except OperationalError, err:
806
    print >> sys.stderr, ("A serious error has happened while modifying"
807
                          " the system's configuration.")
808
    print >> sys.stderr, ("Please review the error message below and make"
809
                          " sure you clean up yourself.")
810
    print >> sys.stderr, ("It is most likely that the system configuration"
811
                          " has been partially altered.")
812
    print >> sys.stderr, str(err)
813
    sys.exit(1)
814
  except ProgrammingError, err:
815
    print >> sys.stderr, ("Internal application error. Please signal this"
816
                          " to xencluster-team.")
817
    print >> sys.stderr, "Error description: %s" % str(err)
818
    sys.exit(1)
819
  except Error, err:
820
    print >> sys.stderr, "Unhandled application error: %s" % err
821
    sys.exit(1)
822
  except (IOError, OSError), err:
823
    print >> sys.stderr, "I/O error detected, please report."
824
    print >> sys.stderr, "Description: %s" % str(err)
825
    sys.exit(1)
826

    
827

    
828
if __name__ == "__main__":
829
  main()